452 lines
17 KiB
C
452 lines
17 KiB
C
#define _MICE_FILES
|
|
#include "files.h"
|
|
|
|
HANDLE open_hook(file_hook_t* file_hook) {
|
|
open_hook_t* opened = (open_hook_t*)malloc(sizeof(open_hook_t));
|
|
ZeroMemory(opened, sizeof *opened);
|
|
|
|
opened->hook = file_hook;
|
|
|
|
HANDLE handle = GetDummyHandle();
|
|
opened->ctx.m_Handle = handle;
|
|
opened->ctx.m_HookData = file_hook->hook_data;
|
|
|
|
SetDataForHandle(handle, HDATA_FILE, opened, TRUE);
|
|
|
|
return handle;
|
|
}
|
|
|
|
file_hook_t* file_hook_list = NULL;
|
|
file_hook_t* new_file_hook(LPCWSTR filename) {
|
|
file_hook_t* hook = (file_hook_t*)malloc(sizeof(file_hook_t));
|
|
memset(hook, 0, sizeof *hook);
|
|
|
|
hook->filename = filename;
|
|
|
|
return hook;
|
|
}
|
|
void hook_file(file_hook_t* hook) {
|
|
hook->next = NULL;
|
|
if (file_hook_list == NULL) {
|
|
file_hook_list = hook;
|
|
return;
|
|
}
|
|
|
|
file_hook_t* hl = file_hook_list;
|
|
while (hl->next != NULL) hl = hl->next;
|
|
hl->next = hook;
|
|
};
|
|
|
|
struct buffer_file {
|
|
LPBYTE buffer;
|
|
DWORD nBytes;
|
|
DWORD access;
|
|
};
|
|
BOOL bf_ReadFile(file_context_t* ctx, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
|
|
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) {
|
|
struct buffer_file* pBF = (struct buffer_file*)ctx->m_HookData;
|
|
if (!(pBF->access & GENERIC_READ)) {
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return FALSE;
|
|
}
|
|
|
|
if (ctx->m_Pointer.QuadPart > pBF->nBytes) {
|
|
*lpNumberOfBytesRead = 0;
|
|
return TRUE;
|
|
}
|
|
if (ctx->m_Pointer.QuadPart + nNumberOfBytesToRead > pBF->nBytes) {
|
|
nNumberOfBytesToRead = (pBF->nBytes - ctx->m_Pointer.QuadPart) & 0xffffffff;
|
|
}
|
|
*lpNumberOfBytesRead = nNumberOfBytesToRead;
|
|
memcpy(lpBuffer, pBF->buffer + ctx->m_Pointer.QuadPart, nNumberOfBytesToRead);
|
|
return TRUE;
|
|
}
|
|
BOOL bf_WriteFile(file_context_t* ctx, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
|
|
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) {
|
|
struct buffer_file* pBF = (struct buffer_file*)ctx->m_HookData;
|
|
if (!(pBF->access & GENERIC_WRITE)) {
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return FALSE;
|
|
}
|
|
|
|
if (ctx->m_Pointer.QuadPart > pBF->nBytes) {
|
|
*lpNumberOfBytesWritten = 0;
|
|
return TRUE;
|
|
}
|
|
if (ctx->m_Pointer.QuadPart + nNumberOfBytesToWrite > pBF->nBytes) {
|
|
nNumberOfBytesToWrite = (pBF->nBytes - ctx->m_Pointer.QuadPart) & 0xffffffff;
|
|
}
|
|
*lpNumberOfBytesWritten = nNumberOfBytesToWrite;
|
|
memcpy(pBF->buffer + ctx->m_Pointer.QuadPart, lpBuffer, nNumberOfBytesToWrite);
|
|
return TRUE;
|
|
}
|
|
|
|
void hook_file_with_buffer(LPCWSTR filename, LPBYTE buffer, DWORD nBytes, DWORD access) {
|
|
file_hook_t* hook = new_file_hook(filename);
|
|
|
|
struct buffer_file** ppBF = &((struct buffer_file*)hook->hook_data);
|
|
*ppBF = malloc(sizeof **ppBF);
|
|
(*ppBF)->buffer = buffer;
|
|
(*ppBF)->nBytes = nBytes;
|
|
(*ppBF)->access = access;
|
|
|
|
hook->ReadFile = bf_ReadFile;
|
|
hook->WriteFile = bf_WriteFile;
|
|
|
|
hook_file(hook);
|
|
}
|
|
|
|
drive_redirect_t DRIVE_REDIRECT_TABLE[] = {
|
|
// Note: Had to create last_shime.log
|
|
{ .drive = "C:\\Documents and Settings\\AppUser\\temp\\", .path = ".\\dev\\temp\\" },
|
|
{ .drive = "E:\\", .path = "\\\\.\\E:" },
|
|
// {.drive = "C:\\ProgramData/boost_interprocess/", .path = "\\\\.\\ipc\\"},
|
|
};
|
|
|
|
char _redirected_path[MAX_PATH];
|
|
|
|
file_hook_t* find_hook(LPCWSTR lpFileName) {
|
|
file_hook_t* file_hook = file_hook_list;
|
|
while (file_hook != NULL) {
|
|
if (wcscmp(lpFileName, file_hook->filename) == 0 ||
|
|
(file_hook->altFilename != NULL && wcscmp(lpFileName, file_hook->altFilename) == 0)) {
|
|
return file_hook;
|
|
}
|
|
file_hook = file_hook->next;
|
|
}
|
|
return NULL;
|
|
};
|
|
|
|
char WORKING_DIR[MAX_PATH + 1] = { 0 };
|
|
char get_gamedata_drive() {
|
|
if (WORKING_DIR[0] == 0x00) {
|
|
GetCurrentDirectoryA(sizeof WORKING_DIR, WORKING_DIR);
|
|
}
|
|
return WORKING_DIR[0];
|
|
}
|
|
|
|
inline char char_lower(char value) {
|
|
if ('A' <= value && value <= 'Z') return value - 'A' + 'a';
|
|
return value;
|
|
}
|
|
|
|
BOOL redirect_path(LPCSTR path, LPCSTR* redirected) {
|
|
for (int i = 0; i < sizeof DRIVE_REDIRECT_TABLE / sizeof DRIVE_REDIRECT_TABLE[0]; i++) {
|
|
drive_redirect_t row = DRIVE_REDIRECT_TABLE[i];
|
|
if (strncmp(path, row.drive, strlen(row.drive)) == 0) {
|
|
log_trace(HOOKS_LOGGER, "Redirecting '%s' to '%s'", path, row.path);
|
|
|
|
size_t new_len = strlen(path) - strlen(row.drive) + strlen(row.path);
|
|
strcpy_s(_redirected_path, new_len + 1, row.path);
|
|
|
|
char* dst = _redirected_path + strlen(row.path);
|
|
size_t len = strlen(path) - strlen(row.drive);
|
|
const char* src = path + strlen(row.drive);
|
|
|
|
for (; len > 0; len--) (dst++)[0] = (src++)[0];
|
|
dst[0] = 0;
|
|
log_trace(HOOKS_LOGGER, "New filename: '%s'", _redirected_path);
|
|
|
|
make_dirs(_redirected_path);
|
|
*redirected = _redirected_path;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// Don't redirect local paths
|
|
GetCurrentDirectoryA(sizeof WORKING_DIR, WORKING_DIR);
|
|
if (strstr(path, WORKING_DIR) == path) {
|
|
puts("SKIP");
|
|
return FALSE;
|
|
}
|
|
|
|
if ((('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) &&
|
|
path[1] == ':' && (path[2] == '/' || path[2] == '\\')) {
|
|
ZeroMemory(_redirected_path, sizeof _redirected_path);
|
|
snprintf(_redirected_path, sizeof _redirected_path, "dev\\%c\\%s", char_lower(path[0]),
|
|
path + 3);
|
|
|
|
make_dirs(_redirected_path);
|
|
*redirected = _redirected_path;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL redirect_path_w(LPCWSTR lpFileName, LPCSTR* redirected) {
|
|
char cFileName[MAX_PATH] = { 0 };
|
|
WideCharToMultiByte(CP_ACP, 0, lpFileName, wcslen(lpFileName), cFileName, sizeof cFileName,
|
|
NULL, NULL);
|
|
return redirect_path(cFileName, redirected);
|
|
}
|
|
|
|
HANDLE WINAPI FakeCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
|
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
|
|
HANDLE hTemplateFile) {
|
|
file_hook_t* found_fh = find_hook(lpFileName);
|
|
if (found_fh != NULL) {
|
|
HANDLE handle = open_hook(found_fh);
|
|
log_info(HOOKS_LOGGER, "CreateFileW(%ls) -> 0x%p", lpFileName, handle);
|
|
return handle;
|
|
}
|
|
|
|
HANDLE handle;
|
|
|
|
LPCSTR redirected;
|
|
if (redirect_path_w(lpFileName, &redirected)) {
|
|
handle = TrueCreateFileA(redirected, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
|
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
|
log_misc(HOOKS_LOGGER, "CreateFileW(%s) -> 0x%p", redirected, handle);
|
|
} else {
|
|
handle = TrueCreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
|
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
|
log_misc(HOOKS_LOGGER, "CreateFileW(%ls) -> 0x%p", lpFileName, handle);
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
HANDLE WINAPI FakeCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
|
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
|
|
HANDLE hTemplateFile) {
|
|
WCHAR wideFileName[MAX_PATH + 1];
|
|
MultiByteToWideChar(CP_ACP, 0, lpFileName, -1, (LPWSTR)&wideFileName, MAX_PATH + 1);
|
|
|
|
file_hook_t* found_fh = find_hook(wideFileName);
|
|
if (found_fh != NULL) {
|
|
HANDLE handle = open_hook(found_fh);
|
|
log_info(HOOKS_LOGGER, "CreateFileA(%s) -> 0x%p", lpFileName, handle);
|
|
return handle;
|
|
}
|
|
|
|
redirect_path(lpFileName, &lpFileName);
|
|
HANDLE handle = TrueCreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
|
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
|
log_misc(HOOKS_LOGGER, "CreateFileA(%s) -> 0x%p", lpFileName, handle);
|
|
return handle;
|
|
}
|
|
|
|
BOOL WINAPI FakePathFileExistsA(LPCSTR pszPath) {
|
|
log_misc(HOOKS_LOGGER, "PathFileExists(%s)", pszPath);
|
|
redirect_path(pszPath, &pszPath);
|
|
BOOL ret = TruePathFileExistsA(pszPath);
|
|
return ret;
|
|
}
|
|
BOOL WINAPI FakePathFileExistsW(LPCWSTR pszPath) {
|
|
log_misc(HOOKS_LOGGER, "PathFileExists(%ls)", pszPath);
|
|
LPCSTR redirected;
|
|
if (redirect_path_w(pszPath, &redirected)) {
|
|
return TruePathFileExistsA(redirected);
|
|
}
|
|
return TruePathFileExistsW(pszPath);
|
|
}
|
|
|
|
BOOL WINAPI FakeDeleteFileA(LPCSTR pszPath) {
|
|
LPCSTR redirected;
|
|
if (redirect_path(pszPath, &redirected)) {
|
|
return TrueDeleteFileA(redirected);
|
|
}
|
|
return TrueDeleteFileA(pszPath);
|
|
}
|
|
BOOL WINAPI FakeDeleteFileW(LPCWSTR pszPath) {
|
|
LPCSTR redirected;
|
|
if (redirect_path_w(pszPath, &redirected)) {
|
|
return TrueDeleteFileA(redirected);
|
|
}
|
|
return TrueDeleteFileW(pszPath);
|
|
}
|
|
|
|
BOOL WINAPI FakeDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
|
|
DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
|
|
LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) {
|
|
open_hook_t* pHData = GetDataForHandle(hDevice, HDATA_FILE);
|
|
if (pHData == NULL) {
|
|
log_trace(HOOKS_LOGGER, "DeviceIoControl(0x%p, 0x%08x, 0x%p, 0x%x, -, 0x%x, 0, 0)", hDevice,
|
|
dwIoControlCode, lpInBuffer, nInBufferSize, nOutBufferSize);
|
|
|
|
return TrueDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer,
|
|
nOutBufferSize, lpBytesReturned, lpOverlapped);
|
|
}
|
|
|
|
file_hook_t* file_hook = pHData->hook;
|
|
if (!file_hook->DeviceIoControl) {
|
|
log_error(HOOKS_LOGGER, "DeviceIoControl(%ls) unimplemented", file_hook->filename);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL ret =
|
|
file_hook->DeviceIoControl(&(pHData->ctx), dwIoControlCode, lpInBuffer, nInBufferSize,
|
|
lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
|
|
if (lpOverlapped) {
|
|
SetEvent(lpOverlapped->hEvent);
|
|
if (ret && lpBytesReturned) {
|
|
lpOverlapped->InternalHigh = *lpBytesReturned;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
DWORD WINAPI FakeSetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh,
|
|
DWORD dwMoveMethod) {
|
|
open_hook_t* pHData = GetDataForHandle(hFile, HDATA_FILE);
|
|
if (pHData == NULL)
|
|
return TrueSetFilePointer(hFile, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod);
|
|
|
|
if (dwMoveMethod == FILE_BEGIN) {
|
|
if (*lpDistanceToMoveHigh)
|
|
pHData->ctx.m_Pointer.HighPart = *lpDistanceToMoveHigh;
|
|
else
|
|
pHData->ctx.m_Pointer.HighPart = 0;
|
|
pHData->ctx.m_Pointer.LowPart = lDistanceToMove;
|
|
} else if (dwMoveMethod == FILE_END) {
|
|
log_error("files", "FILE_END unimplemented");
|
|
return 0xFFFFFFFF;
|
|
} else {
|
|
if (lpDistanceToMoveHigh) pHData->ctx.m_Pointer.HighPart += *lpDistanceToMoveHigh;
|
|
pHData->ctx.m_Pointer.LowPart += lDistanceToMove;
|
|
}
|
|
|
|
return pHData->ctx.m_Pointer.LowPart;
|
|
}
|
|
|
|
BOOL WINAPI FakeSetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove,
|
|
PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod) {
|
|
open_hook_t* pHData = GetDataForHandle(hFile, HDATA_FILE);
|
|
if (pHData != NULL) {
|
|
if (dwMoveMethod == FILE_BEGIN) {
|
|
pHData->ctx.m_Pointer = liDistanceToMove;
|
|
} else if (dwMoveMethod == FILE_END) {
|
|
log_error("files", "FILE_END unimplemented");
|
|
return FALSE;
|
|
} else {
|
|
pHData->ctx.m_Pointer.QuadPart += liDistanceToMove.QuadPart;
|
|
}
|
|
if (lpNewFilePointer) lpNewFilePointer->QuadPart = pHData->ctx.m_Pointer.QuadPart;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return TrueSetFilePointerEx(hFile, liDistanceToMove, lpNewFilePointer, dwMoveMethod);
|
|
}
|
|
|
|
DWORD WINAPI FakeGetFileSizeEx(HANDLE hFile, PLARGE_INTEGER lpFileSize) {
|
|
open_hook_t* pHData = GetDataForHandle(hFile, HDATA_FILE);
|
|
if (pHData == NULL) {
|
|
return TrueGetFileSizeEx(hFile, lpFileSize);
|
|
}
|
|
file_hook_t* file_hook = pHData->hook;
|
|
if (!file_hook->GetFileSizeEx) {
|
|
log_error(HOOKS_LOGGER, "GetFileSizeEx(%ls) unimplemented", file_hook->filename);
|
|
return FALSE;
|
|
}
|
|
return file_hook->GetFileSizeEx(&(pHData->ctx), lpFileSize);
|
|
}
|
|
|
|
DWORD WINAPI FakeWriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
|
|
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) {
|
|
if (hFile == GetStdHandle(STD_OUTPUT_HANDLE) || hFile == GetStdHandle(STD_ERROR_HANDLE)) {
|
|
return TrueWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten,
|
|
lpOverlapped);
|
|
}
|
|
|
|
open_hook_t* pHData = GetDataForHandle(hFile, HDATA_FILE);
|
|
if (pHData == NULL) {
|
|
return TrueWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten,
|
|
lpOverlapped);
|
|
}
|
|
|
|
file_hook_t* file_hook = pHData->hook;
|
|
if (!file_hook->WriteFile) {
|
|
log_error(HOOKS_LOGGER, "WriteFile(%ls) unimplemented", file_hook->filename);
|
|
return FALSE;
|
|
}
|
|
BOOL ret = file_hook->WriteFile(&(pHData->ctx), lpBuffer, nNumberOfBytesToWrite,
|
|
lpNumberOfBytesWritten, lpOverlapped);
|
|
if (ret) pHData->ctx.m_Pointer.QuadPart += *lpNumberOfBytesWritten;
|
|
return ret;
|
|
}
|
|
|
|
BOOL WINAPI FakeReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
|
|
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) {
|
|
if (hFile == GetStdHandle(STD_INPUT_HANDLE)) {
|
|
return TrueReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead,
|
|
lpOverlapped);
|
|
}
|
|
// log_misc(HOOKS_LOGGER, "ReadFile(%d) %d", hFile, nNumberOfBytesToRead);
|
|
|
|
open_hook_t* pHData = GetDataForHandle(hFile, HDATA_FILE);
|
|
if (pHData == NULL) {
|
|
return TrueReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead,
|
|
lpOverlapped);
|
|
}
|
|
|
|
file_hook_t* file_hook = pHData->hook;
|
|
|
|
if (!file_hook->ReadFile) {
|
|
log_error(HOOKS_LOGGER, "ReadFile(%ls) unimplemented", file_hook->filename);
|
|
return FALSE;
|
|
}
|
|
BOOL ret = file_hook->ReadFile(&(pHData->ctx), lpBuffer, nNumberOfBytesToRead,
|
|
lpNumberOfBytesRead, lpOverlapped);
|
|
if (ret) pHData->ctx.m_Pointer.QuadPart += *lpNumberOfBytesRead;
|
|
return ret;
|
|
}
|
|
|
|
BOOL WINAPI FakeCloseHandle(HANDLE hObject) {
|
|
RemoveDataForHandle(hObject, HDATA_ANY);
|
|
return TrueCloseHandle(hObject);
|
|
}
|
|
|
|
int WINAPIV Fake_stat64i32(const char* path, struct _stat64i32* buffer) {
|
|
redirect_path((char*)path, &path);
|
|
return True_stat64i32(path, buffer);
|
|
};
|
|
|
|
DWORD WINAPI FakeGetFileAttributesA(LPCSTR lpFileName) {
|
|
// The game quits out if MiniDump is present!
|
|
if (strcmp(lpFileName, "Y:\\MiniDump\\") == 0) {
|
|
return 0;
|
|
}
|
|
|
|
LPCSTR redirected;
|
|
if (redirect_path(lpFileName, &redirected)) {
|
|
return TrueGetFileAttributesA(redirected);
|
|
}
|
|
return TrueGetFileAttributesA(lpFileName);
|
|
}
|
|
DWORD WINAPI FakeGetFileAttributesW(LPCWSTR lpFileName) {
|
|
LPCSTR redirected;
|
|
if (redirect_path_w(lpFileName, &redirected)) {
|
|
return TrueGetFileAttributesA(redirected);
|
|
}
|
|
return TrueGetFileAttributesW(lpFileName);
|
|
}
|
|
|
|
void hook_io() {
|
|
hook("Kernel32.dll", "DeviceIoControl", FakeDeviceIoControl, (void**)&TrueDeviceIoControl);
|
|
|
|
hook("Kernel32.dll", "CreateFileA", FakeCreateFileA, (void**)&TrueCreateFileA);
|
|
hook("Kernel32.dll", "CreateFileW", FakeCreateFileW, (void**)&TrueCreateFileW);
|
|
|
|
hook("Kernel32.dll", "CloseHandle", FakeCloseHandle, (void**)&TrueCloseHandle);
|
|
hook("Kernel32.dll", "SetFilePointer", FakeSetFilePointer, (void**)&TrueSetFilePointer);
|
|
hook("Kernel32.dll", "SetFilePointerEx", FakeSetFilePointerEx, (void**)&TrueSetFilePointerEx);
|
|
hook("Kernel32.dll", "WriteFile", FakeWriteFile, (void**)&TrueWriteFile);
|
|
hook("Kernel32.dll", "ReadFile", FakeReadFile, (void**)&TrueReadFile);
|
|
hook("Kernel32.dll", "GetFileSizeEx", FakeGetFileSizeEx, (void**)&TrueGetFileSizeEx);
|
|
|
|
hook("Shlwapi.dll", "PathFileExistsA", FakePathFileExistsA, (void**)&TruePathFileExistsA);
|
|
hook("Shlwapi.dll", "PathFileExistsW", FakePathFileExistsW, (void**)&TruePathFileExistsW);
|
|
hook("Kernel32.dll", "DeleteFileA", FakeDeleteFileA, (void**)&TrueDeleteFileA);
|
|
hook("Kernel32.dll", "DeleteFileW", FakeDeleteFileW, (void**)&TrueDeleteFileW);
|
|
|
|
hook("Kernel32.dll", "GetFileAttributesA", FakeGetFileAttributesA,
|
|
(void**)&TrueGetFileAttributesA);
|
|
hook("Kernel32.dll", "GetFileAttributesW", FakeGetFileAttributesW,
|
|
(void**)&TrueGetFileAttributesW);
|
|
|
|
hook("MSVCR90.DLL", "_stat64i32", Fake_stat64i32, (void**)&True_stat64i32);
|
|
}
|