#include "qks.h" #include #include "util/libutils.h" #include "util/fileutils.h" #include "util/utils.h" #include "util/detour.h" #include "acioemu/handle.h" #include "bi2x_hook.h" namespace games::qks { std::string QKS_INJECT_ARGS = ""; const std::wstring portName = L"\\\\.\\COM1"; static acioemu::ACIOHandle *acioHandle = nullptr; static std::string commandLine; static bool isOpened = false; static decltype(AddVectoredExceptionHandler) *AddVectoredExceptionHandler_orig = nullptr; static decltype(ShowCursor) *ShowCursor_orig = nullptr; static decltype(CreateFileA) *execexe_CreateFileA_orig = nullptr; static decltype(CloseHandle) *execexe_CloseHandle_orig = nullptr; static decltype(GetCommandLineA) *GetCommandLineA_orig = nullptr; static HANDLE WINAPI execexe_CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { const auto lpFileNameW = s2ws(lpFileName); if (lpFileNameW == portName) { if (!isOpened) { isOpened = acioHandle->open(portName.c_str()); } else { // NOTE: For some reason, QKS repeatedly opens and closes the COM port. // In the existing implementation of `icca.cpp`, the game side would specify an impossible // number of units and call `log_fatal()`, causing the game to crash, so the handle is reused. log_info("qks", "ignored handle open. ({})", ws2s(portName)); } SetLastError(0); return (HANDLE)acioHandle; } return execexe_CreateFileA_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } static WINBOOL WINAPI execexe_CloseHandle_hook(HANDLE hObject) { if (hObject == acioHandle && isOpened) { log_info("qks", "ignored handle close. ({})", ws2s(portName)); return TRUE; } return execexe_CloseHandle_orig(hObject); } static int WINAPI ShowCursor_hook(BOOL bShow) { return TRUE; } static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) { return INVALID_HANDLE_VALUE; } static LPSTR WINAPI GetCommandLineA_hook() { return (LPSTR)commandLine.c_str(); } void QKSGame::attach() { Game::attach(); // create required files fileutils::dir_create_recursive("dev/raw/log"); fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0); // create optional files (suppress warning) fileutils::bin_write("dev/raw/error.txt", nullptr, 0); // preload libraries libutils::load_library("execexe.dll"); libutils::load_library("libaio.dll"); libutils::load_library("libaio-iob.dll"); libutils::load_library("libaio-iob2_video.dll"); libutils::load_library("virtualsurroundnative.dll"); detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(7), execexe_CloseHandle_hook,&execexe_CloseHandle_orig); detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9), execexe_CreateFileA_hook,&execexe_CreateFileA_orig); // insert BI2X hooks bi2x_hook_init(); // add card reader acioHandle = new acioemu::ACIOHandle(portName.c_str()); devicehook_init_trampoline(); devicehook_add(acioHandle); } void QKSGame::post_attach() { Game::post_attach(); detour::trampoline_try("kernel32.dll", "AddVectoredExceptionHandler", AddVectoredExceptionHandler_hook, &AddVectoredExceptionHandler_orig); detour::trampoline_try("kernel32.dll", "GetCommandLineA", GetCommandLineA_hook, &GetCommandLineA_orig); detour::trampoline_try("user32.dll", "ShowCursor", ShowCursor_hook, &ShowCursor_orig); commandLine = std::format("{} {}", GetCommandLineA_orig(), QKS_INJECT_ARGS); } void QKSGame::detach() { Game::detach(); devicehook_dispose(); } }