diff --git a/cmhook/cm-dll.c b/cmhook/cm-dll.c new file mode 100644 index 0000000..d10719e --- /dev/null +++ b/cmhook/cm-dll.c @@ -0,0 +1,106 @@ +#include + +#include +#include + +#include "cmhook/cm-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym cm_dll_syms[] = { + { + .sym = "cm_io_init", + .off = offsetof(struct cm_dll, init), + }, { + .sym = "cm_io_poll", + .off = offsetof(struct cm_dll, poll), + }, { + .sym = "cm_io_get_opbtns", + .off = offsetof(struct cm_dll, get_opbtns), + } +}; + +struct cm_dll cm_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT cm_dll_init(const struct cm_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("CardMaker IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("CardMaker IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "cm_io_get_api_version"); + + if (get_api_version != NULL) { + cm_dll.api_version = get_api_version(); + } else { + cm_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose cm_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (cm_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("CardMaker IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + cm_dll.api_version); + + goto end; + } + + sym = cm_dll_syms; + hr = dll_bind(&cm_dll, src, &sym, _countof(cm_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("CardMaker IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/cmhook/cm-dll.h b/cmhook/cm-dll.h new file mode 100644 index 0000000..c217efd --- /dev/null +++ b/cmhook/cm-dll.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "cmio/cmio.h" + +struct cm_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); +}; + +struct cm_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct cm_dll cm_dll; + +HRESULT cm_dll_init(const struct cm_dll_config *cfg, HINSTANCE self); diff --git a/cmhook/cmhook.def b/cmhook/cmhook.def new file mode 100644 index 0000000..8590794 --- /dev/null +++ b/cmhook/cmhook.def @@ -0,0 +1,17 @@ +LIBRARY cmhook + +EXPORTS + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + cm_io_get_api_version + cm_io_get_opbtns + cm_io_init + cm_io_poll diff --git a/cmhook/config.c b/cmhook/config.c new file mode 100644 index 0000000..64cbbb2 --- /dev/null +++ b/cmhook/config.c @@ -0,0 +1,42 @@ +#include +#include + +#include "board/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "cmhook/config.h" + +#include "platform/config.h" + +void cm_dll_config_load( + struct cm_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"cmio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void cm_hook_config_load( + struct cm_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + touch_screen_config_load(&cfg->touch, filename); + cm_dll_config_load(&cfg->dll, filename); +} diff --git a/cmhook/config.h b/cmhook/config.h new file mode 100644 index 0000000..c79f47a --- /dev/null +++ b/cmhook/config.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" + +#include "cmhook/cm-dll.h" + +#include "platform/config.h" + +struct cm_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct cm_dll_config dll; + struct touch_screen_config touch; +}; + +void cm_dll_config_load( + struct cm_dll_config *cfg, + const wchar_t *filename); + +void cm_hook_config_load( + struct cm_hook_config *cfg, + const wchar_t *filename); diff --git a/cmhook/dllmain.c b/cmhook/dllmain.c new file mode 100644 index 0000000..ce11763 --- /dev/null +++ b/cmhook/dllmain.c @@ -0,0 +1,119 @@ +#include + +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "cmhook/config.h" +#include "cmhook/io4.h" +#include "cmhook/cm-dll.h" +#include "cmhook/unity.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE cm_hook_mod; +static process_entry_t cm_startup; +static struct cm_hook_config cm_hook_cfg; + +static DWORD CALLBACK cm_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin cm_pre_startup ---\n"); + + /* Load config */ + + cm_hook_config_load(&cm_hook_cfg, L".\\segatools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&cm_hook_cfg.dvd, cm_hook_mod); + touch_screen_hook_init(&cm_hook_cfg.touch, cm_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &cm_hook_cfg.platform, + "SDED", + "ACA1", + cm_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&cm_hook_cfg.aime, 1, cm_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(3); + + if (FAILED(hr)) { + goto fail; + } + + hr = cm_dll_init(&cm_hook_cfg.dll, cm_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = cm_io4_hook_init(&cm_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize Unity native plugin DLL hooks + + There seems to be an issue with other DLL hooks if `LoadLibraryW` is + hooked earlier in the `cmhook` initialization. */ + + unity_hook_init(); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\segatools.ini"); + + dprintf("--- End cm_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return cm_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + cm_hook_mod = mod; + + hr = process_hijack_startup(cm_pre_startup, &cm_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/cmhook/io4.c b/cmhook/io4.c new file mode 100644 index 0000000..073f681 --- /dev/null +++ b/cmhook/io4.c @@ -0,0 +1,69 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "cmhook/cm-dll.h" + +#include "util/dprintf.h" + +static HRESULT cm_io4_poll(void *ctx, struct io4_state *state); +static uint16_t coins; + +static const struct io4_ops cm_io4_ops = { + .poll = cm_io4_poll, +}; + +HRESULT cm_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(cm_dll.init != NULL); + + hr = io4_hook_init(cfg, &cm_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return cm_dll.init(); +} + +static HRESULT cm_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + HRESULT hr; + + assert(cm_dll.poll != NULL); + assert(cm_dll.get_opbtns != NULL); + + memset(state, 0, sizeof(*state)); + + hr = cm_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + + cm_dll.get_opbtns(&opbtn); + + if (opbtn & CM_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & CM_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & CM_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + return S_OK; +} diff --git a/cmhook/io4.h b/cmhook/io4.h new file mode 100644 index 0000000..fb06118 --- /dev/null +++ b/cmhook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT cm_io4_hook_init(const struct io4_config *cfg); diff --git a/cmhook/meson.build b/cmhook/meson.build new file mode 100644 index 0000000..338f773 --- /dev/null +++ b/cmhook/meson.build @@ -0,0 +1,32 @@ +shared_library( + 'cmhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'cmhook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + cmio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'cm-dll.c', + 'cm-dll.h', + 'unity.h', + 'unity.c', + ], +) diff --git a/cmhook/unity.c b/cmhook/unity.c new file mode 100644 index 0000000..efefc32 --- /dev/null +++ b/cmhook/unity.c @@ -0,0 +1,95 @@ +#include + +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" + +#include "util/dprintf.h" + +static void dll_hook_insert_hooks(HMODULE target); + +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); + +static const struct hook_symbol unity_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = my_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, +}; + +static const wchar_t *target_modules[] = { + L"mono.dll", + L"cri_ware_unity.dll", +}; +static const size_t target_modules_len = _countof(target_modules); + +void unity_hook_init(void) +{ + dll_hook_insert_hooks(NULL); +} + +static void dll_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + unity_kernel32_syms, + _countof(unity_kernel32_syms)); +} + +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) +{ + const wchar_t *name_end; + const wchar_t *target_module; + bool already_loaded; + HMODULE result; + size_t name_len; + size_t target_module_len; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + // Check if the module is already loaded + already_loaded = GetModuleHandleW(name) != NULL; + + // Must call the next handler so the DLL reference count is incremented + result = next_LoadLibraryW(name); + + if (!already_loaded && result != NULL) { + name_len = wcslen(name); + + for (size_t i = 0; i < target_modules_len; i++) { + target_module = target_modules[i]; + target_module_len = wcslen(target_module); + + // Check if the newly loaded library is at least the length of + // the name of the target module + if (name_len < target_module_len) { + continue; + } + + name_end = &name[name_len - target_module_len]; + + // Check if the name of the newly loaded library is one of the + // modules the path hooks should be injected into + if (_wcsicmp(name_end, target_module) != 0) { + continue; + } + + dprintf("Unity: Loaded %S\n", target_module); + + dll_hook_insert_hooks(result); + path_hook_insert_hooks(result); + } + } + + return result; +} diff --git a/cmhook/unity.h b/cmhook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/cmhook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/cmio/cmio.c b/cmio/cmio.c new file mode 100644 index 0000000..c2dbfa5 --- /dev/null +++ b/cmio/cmio.c @@ -0,0 +1,54 @@ +#include +#include + +#include +#include + +#include "cmio/cmio.h" +#include "cmio/config.h" + +static uint8_t cm_opbtn; +static struct cm_io_config cm_io_cfg; +static bool cm_io_coin; + +uint16_t cm_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT cm_io_init(void) +{ + cm_io_config_load(&cm_io_cfg, L".\\segatools.ini"); + return S_OK; +} + +HRESULT cm_io_poll(void) +{ + cm_opbtn = 0; + + if (GetAsyncKeyState(cm_io_cfg.vk_test) & 0x8000) { + cm_opbtn |= CM_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(cm_io_cfg.vk_service) & 0x8000) { + cm_opbtn |= CM_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(cm_io_cfg.vk_coin) & 0x8000) { + if (!cm_io_coin) { + cm_io_coin = true; + cm_opbtn |= CM_IO_OPBTN_COIN; + } + } else { + cm_io_coin = false; + } + + return S_OK; +} + +void cm_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = cm_opbtn; + } +} diff --git a/cmio/cmio.h b/cmio/cmio.h new file mode 100644 index 0000000..cb4aa9c --- /dev/null +++ b/cmio/cmio.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include + +enum { + CM_IO_OPBTN_TEST = 0x01, + CM_IO_OPBTN_SERVICE = 0x02, + CM_IO_OPBTN_COIN = 0x04, +}; + +/* Get the version of the CardMaker IO API that this DLL supports. This + function should return a positive 16-bit integer, where the high byte is + the major version and the low byte is the minor version (as defined by the + Semantic Versioning standard). + + The latest API version as of this writing is 0x0100. */ + +uint16_t cm_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after cm_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT cm_io_init(void); + +/* Send any queued outputs (of which there are currently none, though this may + change in subsequent API versions) and retrieve any new inputs. + + Minimum API version: 0x0100 */ + +HRESULT cm_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + cm_IO_OPBTN enum above: this contains bit mask definitions for button + states returned in *opbtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void cm_io_get_opbtns(uint8_t *opbtn); \ No newline at end of file diff --git a/cmio/config.c b/cmio/config.c new file mode 100644 index 0000000..4c37f96 --- /dev/null +++ b/cmio/config.c @@ -0,0 +1,22 @@ +#include + +#include +#include +#include + +#include "cmio/config.h" + +void cm_io_config_load( + struct cm_io_config *cfg, + const wchar_t *filename) +{ + wchar_t key[16]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", '1', filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename); +} diff --git a/cmio/config.h b/cmio/config.h new file mode 100644 index 0000000..d73f6c5 --- /dev/null +++ b/cmio/config.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include + +struct cm_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; +}; + +void cm_io_config_load( + struct cm_io_config *cfg, + const wchar_t *filename); diff --git a/cmio/meson.build b/cmio/meson.build new file mode 100644 index 0000000..74fadda --- /dev/null +++ b/cmio/meson.build @@ -0,0 +1,13 @@ +cmio_lib = static_library( + 'cmio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'cmio.c', + 'cmio.h', + 'config.c', + 'config.h', + ], +) diff --git a/dist/cm/segatools.ini b/dist/cm/segatools.ini new file mode 100644 index 0000000..f2b4ca3 --- /dev/null +++ b/dist/cm/segatools.ini @@ -0,0 +1,53 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + + +[aime] +; Enable aime reader emulation. +enable=1 +aimePath=C:\SEGA\DEVICE\aime.txt +felicaGen=0 + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.100.0 + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are as hexadecimal (prefixed with 0x) or decimal +; (not prefixed with 0x) virtual-key codes, a list of which can be found here: +; +; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +; +; This is, admittedly, not the most user-friendly configuration method in the +; world. An improved solution will be provided later. + +[io4] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 \ No newline at end of file diff --git a/dist/cm/start.bat b/dist/cm/start.bat new file mode 100644 index 0000000..b51c839 --- /dev/null +++ b/dist/cm/start.bat @@ -0,0 +1,12 @@ +@echo off + +pushd %~dp0 + +start /min inject -d -k cmhook.dll amdaemon.exe -f -c config_server.json config_common.json +inject.exe -d -k cmhook.dll CardMaker.exe -screen-fullscreen 0 -popupwindow -screen-width 1080 -screen-height 1920 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/hooklib/config.c b/hooklib/config.c index 5fc9383..30f6a4b 100644 --- a/hooklib/config.c +++ b/hooklib/config.c @@ -14,3 +14,11 @@ void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename) cfg->enable = GetPrivateProfileIntW(L"dvd", L"enable", 1, filename); } + +void touch_screen_config_load(struct touch_screen_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"touch", L"enable", 1, filename); +} diff --git a/hooklib/config.h b/hooklib/config.h index 5da045d..9763274 100644 --- a/hooklib/config.h +++ b/hooklib/config.h @@ -3,5 +3,7 @@ #include #include "hooklib/dvd.h" +#include "hooklib/touch.h" void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename); +void touch_screen_config_load(struct touch_screen_config *cfg, const wchar_t *filename); diff --git a/hooklib/meson.build b/hooklib/meson.build index d112da6..d835904 100644 --- a/hooklib/meson.build +++ b/hooklib/meson.build @@ -27,5 +27,7 @@ hooklib_lib = static_library( 'setupapi.h', 'spike.c', 'spike.h', + 'touch.c', + 'touch.h', ], ) diff --git a/hooklib/touch.c b/hooklib/touch.c new file mode 100644 index 0000000..ca0b8a9 --- /dev/null +++ b/hooklib/touch.c @@ -0,0 +1,171 @@ +/* +This part (touch screen hook) is mostly taken from spicetools, which is GPL. +This means there can be some license issues if you do use this code in some other places without including source code. +*/ +#include + +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/config.h" +#include "hooklib/dll.h" +#include "hooklib/touch.h" + +#include "util/dprintf.h" + +/* API hooks */ + +static int WINAPI hook_GetSystemMetrics( + int nIndex +); + +static BOOL WINAPI hook_RegisterTouchWindow( + HWND hwnd, + ULONG ulFlags +); + +static BOOL WINAPI hook_GetTouchInputInfo( + HANDLE hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize +); + +/* Link pointers */ + +static int (WINAPI *next_GetSystemMetrics)( + int nIndex +); + +static BOOL (WINAPI *next_RegisterTouchWindow)( + HWND hwnd, + ULONG ulFlags +); + +static BOOL (WINAPI *next_GetTouchInputInfo)( + HANDLE hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize +); + +static bool touch_hook_initted; +static struct touch_screen_config touch_config; + +static const struct hook_symbol touch_hooks[] = { + { + .name = "GetSystemMetrics", + .patch = hook_GetSystemMetrics, + .link = (void **) &next_GetSystemMetrics + }, + { + .name = "RegisterTouchWindow", + .patch = hook_RegisterTouchWindow, + .link = (void **) &next_RegisterTouchWindow + }, + { + .name = "GetTouchInputInfo", + .patch = hook_GetTouchInputInfo, + .link = (void **) &next_GetTouchInputInfo + }, +}; + +void touch_screen_hook_init(const struct touch_screen_config *cfg, HINSTANCE self) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (touch_hook_initted) { + return; + } + + touch_hook_initted = true; + + memcpy(&touch_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "user32.dll", touch_hooks, _countof(touch_hooks)); + dprintf("TOUCH: hook enabled.\n"); +} + +// Spicetools misc/wintouchemu.cpp + +static int WINAPI hook_GetSystemMetrics( + int nIndex +) +{ + if (nIndex == 94) return 0x01 | 0x02 | 0x40 | 0x80; + + int orig = next_GetSystemMetrics(nIndex); + + return orig; +} + +static BOOL WINAPI hook_RegisterTouchWindow( + HWND hwnd, + ULONG ulFlags +) +{ + return true; +} + +// Converting mouse event to touch event +// Does not work at current stage +static BOOL WINAPI hook_GetTouchInputInfo( + HANDLE hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize +) +{ + bool result = false; + static bool mouse_state_old = false; + for (UINT input = 0; input < cInputs; input++) { + TOUCHINPUT *touch_input = &pInputs[input]; + + touch_input->x = 0; + touch_input->y = 0; + touch_input->hSource = NULL; + touch_input->dwID = 0; + touch_input->dwFlags = 0; + touch_input->dwMask = 0; + touch_input->dwTime = 0; + touch_input->dwExtraInfo = 0; + touch_input->cxContact = 0; + touch_input->cyContact = 0; + + bool mouse_state = (GetKeyState(VK_LBUTTON) & 0x100) != 0; + + if (mouse_state || mouse_state_old) { + POINT cursorPos; + GetCursorPos(&cursorPos); + + result = true; + touch_input->x = cursorPos.x * 100; + touch_input->y = cursorPos.y * 100; + touch_input->hSource = hTouchInput; + touch_input->dwID = 0; + touch_input->dwFlags = 0; + if (mouse_state && !mouse_state_old) { + touch_input->dwFlags |= TOUCHEVENTF_DOWN; + } else if (mouse_state && mouse_state_old) { + touch_input->dwFlags |= TOUCHEVENTF_MOVE; + } else if (!mouse_state && mouse_state_old) { + touch_input->dwFlags |= TOUCHEVENTF_UP; + } + touch_input->dwMask = 0; + touch_input->dwTime = 0; + touch_input->dwExtraInfo = 0; + touch_input->cxContact = 0; + touch_input->cyContact = 0; + } + mouse_state_old = mouse_state; + } + + return result; +} \ No newline at end of file diff --git a/hooklib/touch.h b/hooklib/touch.h new file mode 100644 index 0000000..f80a25e --- /dev/null +++ b/hooklib/touch.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include + +struct touch_screen_config { + bool enable; +}; + +/* Init is not thread safe because API hook init is not thread safe blah + blah blah you know the drill by now. */ + +void touch_screen_hook_init(const struct touch_screen_config *cfg, HINSTANCE self); \ No newline at end of file diff --git a/meson.build b/meson.build index 5972de2..851e57a 100644 --- a/meson.build +++ b/meson.build @@ -61,6 +61,7 @@ subdir('idzio') subdir('idacio') subdir('swdcio') subdir('mu3io') +subdir('cmio') subdir('mercuryio') subdir('cxbio') @@ -73,5 +74,6 @@ subdir('swdchook') subdir('minihook') subdir('chusanhook') subdir('mu3hook') +subdir('cmhook') subdir('mercuryhook') subdir('cxbhook')