spicetools/hooks/devicehook.cpp

634 lines
22 KiB
C++

#include "devicehook.h"
#include <vector>
#include "avs/game.h"
#include "util/detour.h"
#include "util/utils.h"
#include <tlhelp32.h>
// std::min
#ifdef min
#undef min
#endif
namespace hooks::device {
bool ENABLE = true;
}
bool DEVICE_CREATEFILE_DEBUG = false;
static std::string PATH_HARD_CODE_COMPARE = "d:/###-###/contents";
static decltype(ClearCommBreak) *ClearCommBreak_orig = nullptr;
static decltype(ClearCommError) *ClearCommError_orig = nullptr;
static decltype(CloseHandle) *CloseHandle_orig = nullptr;
static decltype(CreateFileA) *CreateFileA_orig = nullptr;
static decltype(CreateFileW) *CreateFileW_orig = nullptr;
static decltype(DeviceIoControl) *DeviceIoControl_orig = nullptr;
static decltype(EscapeCommFunction) *EscapeCommFunction_orig = nullptr;
static decltype(GetCommState) *GetCommState_orig = nullptr;
static decltype(GetFileSize) *GetFileSize_orig = nullptr;
static decltype(GetFileSizeEx) *GetFileSizeEx_orig = nullptr;
static decltype(GetFileInformationByHandle) *GetFileInformationByHandle_orig = nullptr;
static decltype(PurgeComm) *PurgeComm_orig = nullptr;
static decltype(ReadFile) *ReadFile_orig = nullptr;
static decltype(SetupComm) *SetupComm_orig = nullptr;
static decltype(SetCommBreak) *SetCommBreak_orig = nullptr;
static decltype(SetCommMask) *SetCommMask_orig = nullptr;
static decltype(SetCommState) *SetCommState_orig = nullptr;
static decltype(SetCommTimeouts) *SetCommTimeouts_orig = nullptr;
static decltype(WriteFile) *WriteFile_orig = nullptr;
static std::vector<CustomHandle *> CUSTOM_HANDLES;
MITMHandle::MITMHandle(LPCWSTR lpFileName, std::string rec_file, bool lpFileNameContains) {
this->lpFileName = lpFileName;
this->rec_file = rec_file;
this->lpFileNameContains = lpFileNameContains;
this->com_pass = true;
}
bool MITMHandle::open(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
// check if file name matches
if (this->lpFileNameContains) {
if (wcsstr(lpFileName, this->lpFileName) == 0)
return false;
} else {
if (wcscmp(lpFileName, this->lpFileName))
return false;
}
// create our own file handle
handle = CreateFileW_orig(lpFileName, dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
// check if it worked - if not device hook will try again without us
return handle != INVALID_HANDLE_VALUE;
}
int MITMHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
DWORD lpNumberOfBytesRead = 0;
auto res = ReadFile_orig(handle, lpBuffer, nNumberOfBytesToRead,
&lpNumberOfBytesRead, NULL);
if (res) {
// record
log_info("mitm", "read: {}", bin2hex((uint8_t*) lpBuffer, lpNumberOfBytesRead));
// pass
return lpNumberOfBytesRead;
} else {
return -1;
}
}
int MITMHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
DWORD lpNumberOfBytesWritten = 0;
auto res = WriteFile_orig(handle, lpBuffer, nNumberOfBytesToWrite,
&lpNumberOfBytesWritten, NULL);
if (res) {
// record
log_info("mitm", "write: {}", bin2hex((uint8_t*) lpBuffer, lpNumberOfBytesWritten));
// pass
return lpNumberOfBytesWritten;
} else {
return -1;
}
}
int MITMHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer,
DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) {
DWORD lpBytesReturned = 0;
auto res = DeviceIoControl_orig(handle, dwIoControlCode, lpInBuffer, nInBufferSize,
lpOutBuffer, nOutBufferSize, &lpBytesReturned, NULL);
if (res) {
// record
log_info("mitm", "device_io");
return lpBytesReturned;
} else {
return -1;
}
}
size_t MITMHandle::bytes_available() {
COMSTAT status;
ClearCommError_orig(handle, NULL, &status);
return status.cbInQue;
}
bool MITMHandle::close() {
return CloseHandle_orig(handle);
}
static inline CustomHandle *get_custom_handle(HANDLE handle) {
// TODO: we can make a custom allocator for the handles and
// add a simple range check instead of going through the
// whole list each time
// find handle in list
for (auto custom_handle : CUSTOM_HANDLES) {
if (reinterpret_cast<HANDLE>(custom_handle) == handle
|| custom_handle->handle == handle) {
return custom_handle;
}
}
// no handle found - hooks will call original functions for this
return nullptr;
}
static HANDLE WINAPI CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
HANDLE result = INVALID_HANDLE_VALUE;
// convert to wide char
WCHAR lpFileNameW[512] { 0 };
if (!MultiByteToWideChar(CP_ACP, 0, lpFileName, -1, lpFileNameW, std::size(lpFileNameW))) {
return result;
}
// debug
if (DEVICE_CREATEFILE_DEBUG && lpFileName != nullptr) {
log_info("devicehook", "CreateFileA(\"{}\") => len: {}", lpFileName, strlen(lpFileName));
}
// check custom handles
if (!CUSTOM_HANDLES.empty()) {
for (auto handle : CUSTOM_HANDLES) {
if (handle->open(lpFileNameW, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile)) {
SetLastError(0);
if (handle->handle != INVALID_HANDLE_VALUE) {
result = handle->handle;
} else {
result = (HANDLE) handle;
}
break;
}
}
}
// hard coded paths fix
auto lpFileNameLen = wcslen(lpFileNameW);
bool fix = true;
for (size_t i = 0, c = 0; i < lpFileNameLen && (c = PATH_HARD_CODE_COMPARE[i]) != 0; i++) {
if (c != '#' && lpFileName[i] != (wchar_t) PATH_HARD_CODE_COMPARE[i]) {
fix = false;
break;
}
}
// do the fix
if (fix && lpFileNameLen >= PATH_HARD_CODE_COMPARE.size()) {
auto hcLen = PATH_HARD_CODE_COMPARE.size();
auto buffer = std::make_unique<char[]>(lpFileNameLen + 1);
buffer[0] = '.';
for (size_t i = 0; i < lpFileNameLen - hcLen; i++) {
buffer[i + 1] = lpFileName[hcLen + i];
}
if (DEVICE_CREATEFILE_DEBUG) {
log_info("devicehook", "CreateFileA (fix): {}", buffer.get());
}
return CreateFileA_orig(buffer.get(), dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
// fallback
if (result == INVALID_HANDLE_VALUE) {
result = CreateFileA_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
// return result
return result;
}
static HANDLE WINAPI CreateFileW_hook(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
HANDLE result = INVALID_HANDLE_VALUE;
// debug
if (DEVICE_CREATEFILE_DEBUG && lpFileName != nullptr) {
log_info("devicehook", "CreateFileW: {}", ws2s(lpFileName));
}
// check custom handles
if (!CUSTOM_HANDLES.empty()) {
for (auto handle : CUSTOM_HANDLES) {
if (handle->open(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile)) {
SetLastError(0);
if (handle->handle != INVALID_HANDLE_VALUE) {
result = handle->handle;
} else {
result = (HANDLE) handle;
}
break;
}
}
}
// hard coded paths fix
bool fix = true;
auto lpFileNameLen = wcslen(lpFileName);
for (size_t i = 0, c = 0; i < lpFileNameLen && (c = PATH_HARD_CODE_COMPARE[i]) != 0; i++) {
if (c != '#' && lpFileName[i] != (wchar_t) PATH_HARD_CODE_COMPARE[i]) {
fix = false;
break;
}
}
// do the fix
if (fix && lpFileNameLen >= PATH_HARD_CODE_COMPARE.size()) {
auto hcLen = PATH_HARD_CODE_COMPARE.size();
auto buffer = std::make_unique<wchar_t[]>(lpFileNameLen + 1);
buffer[0] = '.';
for (size_t i = 0; i < lpFileNameLen - hcLen; i++) {
buffer[i + 1] = lpFileName[hcLen + i];
}
if (DEVICE_CREATEFILE_DEBUG) {
log_info("devicehook", "CreateFileW (fix): {}", ws2s(buffer.get()));
}
return CreateFileW_orig(buffer.get(), dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
// fallback
if (result == INVALID_HANDLE_VALUE) {
result = CreateFileW_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
// return result
return result;
}
static BOOL WINAPI ReadFile_hook(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped)
{
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle) {
int value = custom_handle->read(lpBuffer, nNumberOfBytesToRead);
if (value >= 0) {
SetLastError(0);
*lpNumberOfBytesRead = (DWORD) value;
return true;
} else {
SetLastError(0xD);
return false;
}
}
// fallback
return ReadFile_orig(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
}
static BOOL WINAPI WriteFile_hook(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
{
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle) {
int value = custom_handle->write(lpBuffer, nNumberOfBytesToWrite);
if (value >= 0) {
SetLastError(0);
*lpNumberOfBytesWritten = (DWORD) value;
return true;
} else {
SetLastError(0xD);
return false;
}
}
// fallback
return WriteFile_orig(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
}
static BOOL WINAPI DeviceIoControl_hook(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped)
{
auto *custom_handle = get_custom_handle(hDevice);
if (custom_handle) {
int count = custom_handle->device_io(dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize);
if (count >= 0) {
SetLastError(0);
*lpBytesReturned = (DWORD) count;
if (lpOverlapped) {
SetEvent(lpOverlapped->hEvent);
}
return true;
} else {
log_info("devicehook", "device_io failed");
SetLastError(0xD);
return false;
}
}
// fallback
return DeviceIoControl_orig(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize,
lpBytesReturned, lpOverlapped);
}
static DWORD WINAPI GetFileSize_hook(HANDLE hFile, LPDWORD lpFileSizeHigh) {
//log_info("devicehook", "GetFileSizeHook hit");
return GetFileSize_orig(hFile, lpFileSizeHigh);
}
static BOOL WINAPI GetFileSizeEx_hook(HANDLE hFile, PLARGE_INTEGER lpFileSizeHigh) {
//log_info("devicehook", "GetFileSizeExHook hit");
return GetFileSizeEx_orig(hFile, lpFileSizeHigh);
}
static BOOL WINAPI GetFileInformationByHandle_hook(HANDLE hFile,
LPBY_HANDLE_FILE_INFORMATION lpFileInformation)
{
// custom handle
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle) {
SetLastError(0);
custom_handle->file_info(lpFileInformation);
return TRUE;
}
return GetFileInformationByHandle_orig(hFile, lpFileInformation);
}
static BOOL WINAPI ClearCommBreak_hook(HANDLE hFile) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
return TRUE;
}
return ClearCommBreak_orig(hFile);
}
static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
if (lpStat) {
lpStat->fXoffSent = 1;
/*
* Some games may check the input queue size.
* QMA does not even attempt to read if this is set to 0.
* We just set this to 255 and hope games do not rely on this for buffer sizes.
*
* Message from the future: As it turned out, some games (CCJ) do in fact rely on this value.
*/
lpStat->cbInQue = custom_handle->bytes_available();
}
return TRUE;
}
return ClearCommError_orig(hFile, lpErrors, lpStat);
}
static BOOL WINAPI EscapeCommFunction_hook(HANDLE hFile, DWORD dwFunc) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
return TRUE;
}
return EscapeCommFunction_orig(hFile, dwFunc);
}
static BOOL WINAPI GetCommState_hook(HANDLE hFile, LPDCB lpDCB) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
auto *comm_state = &custom_handle->comm_state;
memcpy(lpDCB, comm_state, std::min(static_cast<size_t>(comm_state->DCBlength), sizeof(*comm_state)));
return TRUE;
}
return GetCommState_orig(hFile, lpDCB);
}
static BOOL WINAPI PurgeComm_hook(HANDLE hFile, DWORD dwFlags) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
return TRUE;
}
return PurgeComm_orig(hFile, dwFlags);
}
static BOOL WINAPI SetupComm_hook(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
return TRUE;
}
return SetupComm_orig(hFile, dwInQueue, dwOutQueue);
}
static BOOL WINAPI SetCommBreak_hook(HANDLE hFile) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
return TRUE;
}
return SetCommBreak_orig(hFile);
}
static BOOL WINAPI SetCommMask_hook(HANDLE hFile, DWORD dwEvtMask) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
return TRUE;
}
return SetCommMask_orig(hFile, dwEvtMask);
}
static BOOL WINAPI SetCommState_hook(HANDLE hFile, LPDCB lpDCB) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle) {
// sanity check
if (lpDCB->DCBlength <= sizeof(custom_handle->comm_state)) {
memcpy(&custom_handle->comm_state, lpDCB, lpDCB->DCBlength);
}
return TRUE;
}
return SetCommState_orig(hFile, lpDCB);
}
static BOOL WINAPI SetCommTimeouts_hook(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts) {
auto *custom_handle = get_custom_handle(hFile);
if (custom_handle && !custom_handle->com_pass) {
memcpy(&custom_handle->comm_timeouts, lpCommTimeouts, sizeof(custom_handle->comm_timeouts));
return TRUE;
}
return SetCommTimeouts_orig(hFile, lpCommTimeouts);
}
static BOOL WINAPI CloseHandle_hook(HANDLE hObject) {
auto *custom_handle = get_custom_handle(hObject);
if (custom_handle) {
SetLastError(0);
return custom_handle->close();
}
// call original
return CloseHandle_orig(hObject);
}
static void suspend_or_resume_other_threads(bool suspending) {
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE) {
return;
}
THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hThreadSnap, &te32)) {
do {
if (te32.th32OwnerProcessID == GetCurrentProcessId()) {
if(te32.th32ThreadID == GetCurrentThreadId()) {
continue;
}
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
if (hThread) {
if (suspending) {
SuspendThread(hThread);
} else {
ResumeThread(hThread);
}
CloseHandle(hThread);
}
}
} while (Thread32Next(hThreadSnap, &te32));
}
CloseHandle(hThreadSnap);
}
void devicehook_init(HMODULE module) {
if (!hooks::device::ENABLE) {
return;
}
#define STORE(value, expr) { \
auto tmp = (expr); \
if ((value) == nullptr) { \
(value) = tmp; \
} \
}
/*
// initialize only once
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
*/
log_info("devicehook", "init");
suspend_or_resume_other_threads(true);
// IAT hooks
STORE(ClearCommBreak_orig, detour::iat_try("ClearCommBreak", ClearCommBreak_hook, module));
STORE(ClearCommError_orig, detour::iat_try("ClearCommError", ClearCommError_hook, module));
STORE(CloseHandle_orig, detour::iat_try("CloseHandle", CloseHandle_hook, module));
STORE(CreateFileA_orig, detour::iat_try("CreateFileA", CreateFileA_hook, module));
STORE(CreateFileW_orig, detour::iat_try("CreateFileW", CreateFileW_hook, module));
STORE(DeviceIoControl_orig, detour::iat_try("DeviceIoControl", DeviceIoControl_hook, module));
STORE(EscapeCommFunction_orig, detour::iat_try("EscapeCommFunction", EscapeCommFunction_hook, module));
STORE(GetCommState_orig, detour::iat_try("GetCommState", GetCommState_hook, module));
STORE(GetFileSize_orig, detour::iat_try("GetFileSize", GetFileSize_hook, module));
STORE(GetFileSizeEx_orig, detour::iat_try("GetFileSize", GetFileSizeEx_hook, module));
STORE(GetFileInformationByHandle_orig, detour::iat_try(
"GetFileInformationByHandle", GetFileInformationByHandle_hook, module));
STORE(PurgeComm_orig, detour::iat_try("PurgeComm", PurgeComm_hook, module));
STORE(ReadFile_orig, detour::iat_try("ReadFile", ReadFile_hook, module));
STORE(SetupComm_orig, detour::iat_try("SetupComm", SetupComm_hook, module));
STORE(SetCommBreak_orig, detour::iat_try("SetCommBreak", SetCommBreak_hook, module));
STORE(SetCommMask_orig, detour::iat_try("SetCommMask", SetCommMask_hook, module));
STORE(SetCommState_orig, detour::iat_try("SetCommState", SetCommState_hook, module));
STORE(SetCommTimeouts_orig, detour::iat_try("SetCommTimeouts", SetCommTimeouts_hook, module));
STORE(WriteFile_orig, detour::iat_try("WriteFile", WriteFile_hook, module));
suspend_or_resume_other_threads(false);
#undef STORE
}
void devicehook_init_trampoline() {
// initialize only once
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
suspend_or_resume_other_threads(true);
detour::trampoline_try("kernel32.dll", "ClearCommBreak", ClearCommBreak_hook, &ClearCommBreak_orig);
detour::trampoline_try("kernel32.dll", "ClearCommError", ClearCommError_hook, &ClearCommError_orig);
detour::trampoline_try("kernel32.dll", "CloseHandle", CloseHandle_hook, &CloseHandle_orig);
detour::trampoline_try("kernel32.dll", "CreateFileA", CreateFileA_hook, &CreateFileA_orig);
detour::trampoline_try("kernel32.dll", "CreateFileW", CreateFileW_hook, &CreateFileW_orig);
detour::trampoline_try("kernel32.dll", "DeviceIoControl", DeviceIoControl_hook, &DeviceIoControl_orig);
detour::trampoline_try("kernel32.dll", "EscapeCommFunction", EscapeCommFunction_hook, &EscapeCommFunction_orig);
detour::trampoline_try("kernel32.dll", "WriteFile", WriteFile_hook, &WriteFile_orig);
detour::trampoline_try("kernel32.dll", "GetFileSize", GetFileSize_hook, &GetFileSize_orig);
detour::trampoline_try("kernel32.dll", "GetFileSizeEx", GetFileSizeEx_hook, &GetFileSizeEx_orig);
detour::trampoline_try("kernel32.dll", "GetFileInformationByHandle",
GetFileInformationByHandle_hook, &GetFileInformationByHandle_orig);
detour::trampoline_try("kernel32.dll", "GetCommState", GetCommState_hook, &GetCommState_orig);
detour::trampoline_try("kernel32.dll", "PurgeComm", PurgeComm_hook, &PurgeComm_orig);
detour::trampoline_try("kernel32.dll", "ReadFile", ReadFile_hook, &ReadFile_orig);
detour::trampoline_try("kernel32.dll", "SetupComm", SetupComm_hook, &SetupComm_orig);
detour::trampoline_try("kernel32.dll", "SetCommBreak", SetCommBreak_hook, &SetCommBreak_orig);
detour::trampoline_try("kernel32.dll", "SetCommMask", SetCommMask_hook, &SetCommMask_orig);
detour::trampoline_try("kernel32.dll", "SetCommState", SetCommState_hook, &SetCommState_orig);
detour::trampoline_try("kernel32.dll", "SetCommTimeouts", SetCommTimeouts_hook, &SetCommTimeouts_orig);
suspend_or_resume_other_threads(false);
}
void devicehook_add(CustomHandle *device_handle) {
CUSTOM_HANDLES.push_back(device_handle);
}
void devicehook_dispose() {
// clean up custom handles
for (auto handle : CUSTOM_HANDLES) {
delete handle;
}
CUSTOM_HANDLES.clear();
}