diff --git a/.vscode/settings.json b/.vscode/settings.json index 9eb12eb..a751a55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,19 @@ { "editor.formatOnSave": false, "mesonbuild.configureOnOpen": false, + "files.associations": { + "string.h": "c", + "stdbool.h": "c", + "windows.h": "c", + "dprintf.h": "c", + "touch.h": "c", + "mai2-dll.h": "c", + "led.h": "c", + "path.h": "c", + "reg.h": "c", + "platform.h": "c", + "procaddr.h": "c", + "table.h": "c", + "serial.h": "c" + }, } diff --git a/Package.mk b/Package.mk index 850f9ee..b7351f0 100644 --- a/Package.mk +++ b/Package.mk @@ -104,6 +104,21 @@ $(BUILD_DIR_ZIP)/mu3.zip: $(V)strip $(BUILD_DIR_ZIP)/mu3/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/mu3 ; zip -r ../mu3.zip * +$(BUILD_DIR_ZIP)/mai2.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2 + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mai2hook/mai2hook.dll \ + $(DIST_DIR)/mai2/segatools.ini \ + $(DIST_DIR)/mai2/start.bat \ + $(BUILD_DIR_ZIP)/mai2 + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mai2/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mai2 ; zip -r ../mai2.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/config \ $(DOC_DIR)/chunihook.md \ @@ -121,6 +136,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/idz.zip \ $(BUILD_DIR_ZIP)/mercury.zip \ $(BUILD_DIR_ZIP)/mu3.zip \ + $(BUILD_DIR_ZIP)/mai2.zip \ CHANGELOG.md \ README.md \ diff --git a/dist/mai2/segatools.ini b/dist/mai2/segatools.ini new file mode 100644 index 0000000..25fdc31 --- /dev/null +++ b/dist/mai2/segatools.ini @@ -0,0 +1,44 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; 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=appdata +option=option + +[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 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=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.172.0 + +[gfx] +enable=1 + +[io4] +; Delete +test=0x2E +; End +service=0x23 +; Insert +coin=0x2D diff --git a/dist/mai2/start.bat b/dist/mai2/start.bat new file mode 100644 index 0000000..2d6bd8f --- /dev/null +++ b/dist/mai2/start.bat @@ -0,0 +1,11 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +start inject -d -k mai2hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json +inject.exe -d -k mai2hook.dll Sinmai.exe -screen-fullscreen 0 -screen-width 2160 -screen-height 1920 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/hooklib/procaddr.c b/hooklib/procaddr.c index 2409f28..c29471f 100644 --- a/hooklib/procaddr.c +++ b/hooklib/procaddr.c @@ -76,8 +76,13 @@ static void proc_addr_hook_init(void) InitializeCriticalSection(&proc_addr_hook_lock); + proc_addr_insert_hooks(NULL); +} + +void proc_addr_insert_hooks(HMODULE target) +{ hook_table_apply( - NULL, + target, "kernel32.dll", win32_hooks, _countof(win32_hooks)); diff --git a/hooklib/procaddr.h b/hooklib/procaddr.h index 750e154..ce72940 100644 --- a/hooklib/procaddr.h +++ b/hooklib/procaddr.h @@ -15,4 +15,6 @@ HRESULT proc_addr_table_push( const char *target, struct hook_symbol *syms, size_t nsyms -); \ No newline at end of file +); + +void proc_addr_insert_hooks(HMODULE target); diff --git a/hooklib/reg.c b/hooklib/reg.c index cc3c584..ddda6e9 100644 --- a/hooklib/reg.c +++ b/hooklib/reg.c @@ -7,6 +7,7 @@ #include "hook/table.h" #include "hooklib/reg.h" +#include "hooklib/procaddr.h" #include "util/dprintf.h" #include "util/str.h" @@ -99,6 +100,29 @@ static LSTATUS WINAPI hook_RegGetValueW( uint32_t *numData ); +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); /* Link pointers */ static LSTATUS (WINAPI *next_RegOpenKeyExW)( @@ -155,6 +179,30 @@ static LSTATUS (WINAPI *next_RegGetValueW)( uint32_t *numData ); +static LSTATUS (WINAPI *next_RegQueryInfoKeyW)( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS (WINAPI *next_RegEnumValueW)( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); + static const struct hook_symbol reg_hook_syms[] = { { .name = "RegOpenKeyExW", @@ -184,6 +232,14 @@ static const struct hook_symbol reg_hook_syms[] = { .name = "RegGetValueW", .patch = hook_RegGetValueW, .link = (void **) &next_RegGetValueW, + }, { + .name = "RegQueryInfoKeyW", + .patch = hook_RegQueryInfoKeyW, + .link = (void **) &next_RegQueryInfoKeyW, + }, { + .name = "RegEnumValueW", + .patch = hook_RegEnumValueW, + .link = (void **) &next_RegEnumValueW, } }; @@ -254,11 +310,23 @@ static void reg_hook_init(void) InitializeCriticalSection(®_hook_lock); dprintf("Reg hook init\n"); + reg_hook_insert_hooks(NULL); + + proc_addr_table_push( + "ADVAPI32.dll", + (struct hook_symbol *) reg_hook_syms, + _countof(reg_hook_syms)); + +} + +void reg_hook_insert_hooks(HMODULE target) +{ hook_table_apply( - NULL, + target, "advapi32.dll", reg_hook_syms, _countof(reg_hook_syms)); + } static LRESULT reg_hook_propagate_hr(HRESULT hr) @@ -331,6 +399,7 @@ static LSTATUS reg_hook_open_locked( /* Assume reg keys are referenced from a root key and not from some intermediary key */ key = ®_hook_keys[i]; + //dprintf("Reg: %ls vs %ls\n", name, key->name); if (key->root == parent && wstr_ieq(key->name, name)) { break; @@ -821,6 +890,99 @@ static LSTATUS WINAPI hook_RegGetValueW( return err; } +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime) +{ + struct reg_hook_key *key; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hKey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryInfoKeyW( + hKey, + lpClass, + lpcchClass, + lpReserved, + lpcSubKeys, + lpcbMaxSubKeyLen, + lpcbMaxClassLen, + lpcValues, + lpcbMaxValueNameLen, + lpcbMaxValueLen, + lpcbSecurityDescriptor, + lpftLastWriteTime); + } + + // This is the only one I've seen even be changed, so it's all I'm doing + // until I see otherwise. + *lpcValues = key->nvals; + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) +{ + struct reg_hook_key *key; + HRESULT hr; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hkey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegEnumValueW( + hkey, + dwIndex, + lpValueName, + lpcchValueName, + lpReserved, + lpType, + lpData, + lpcbData); + } + + if (dwIndex >= key->nvals) { + LeaveCriticalSection(®_hook_lock); + return ERROR_NO_MORE_ITEMS; // Pretty sure this is what it actually returns here? + } + + wcscpy_s(lpValueName, *lpcchValueName, key->vals[dwIndex].name); + *lpcchValueName = wcslen(key->vals[dwIndex].name); + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + HRESULT reg_hook_read_bin( void *bytes, uint32_t *nbytes, diff --git a/hooklib/reg.h b/hooklib/reg.h index eb280c6..20e3dda 100644 --- a/hooklib/reg.h +++ b/hooklib/reg.h @@ -12,6 +12,8 @@ struct reg_hook_val { uint32_t type; }; +void reg_hook_insert_hooks(HMODULE target); + HRESULT reg_hook_push_key( HKEY root, const wchar_t *name, diff --git a/mai2hook/config.c b/mai2hook/config.c new file mode 100644 index 0000000..0e25293 --- /dev/null +++ b/mai2hook/config.c @@ -0,0 +1,66 @@ +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "mai2hook/config.h" + +#include "platform/config.h" + +void mai2_dll_config_load( + struct mai2_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mai2io", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void touch_config_load( + struct touch_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"touch", L"enable", 1, filename); +} + +void led_config_load( + struct led_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"led", L"enable", 1, filename); +} + +void mai2_hook_config_load( + struct mai2_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); + gfx_config_load(&cfg->gfx, filename); + mai2_dll_config_load(&cfg->dll, filename); + touch_config_load(&cfg->touch, filename); + led_config_load(&cfg->led, filename); +} diff --git a/mai2hook/config.h b/mai2hook/config.h new file mode 100644 index 0000000..00c4170 --- /dev/null +++ b/mai2hook/config.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "gfxhook/gfx.h" + +#include "hooklib/dvd.h" + +#include "mai2hook/mai2-dll.h" +#include "mai2hook/touch.h" +#include "mai2hook/led.h" + +#include "platform/config.h" + +struct mai2_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct mai2_dll_config dll; + struct touch_config touch; + struct led_config led; +}; + +void mai2_dll_config_load( + struct mai2_dll_config *cfg, + const wchar_t *filename); + +void mai2_hook_config_load( + struct mai2_hook_config *cfg, + const wchar_t *filename); diff --git a/mai2hook/dllmain.c b/mai2hook/dllmain.c new file mode 100644 index 0000000..2ad76be --- /dev/null +++ b/mai2hook/dllmain.c @@ -0,0 +1,157 @@ +#include + +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "gfxhook/d3d9.h" +#include "gfxhook/d3d11.h" +#include "gfxhook/dxgi.h" +#include "gfxhook/gfx.h" + +#include "hook/process.h" +#include "hook/table.h" +#include "hook/iohook.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" +#include "hooklib/path.h" +#include "hooklib/reg.h" +#include "hooklib/procaddr.h" + +#include "mai2hook/config.h" +#include "mai2hook/io4.h" +#include "mai2hook/mai2-dll.h" +#include "mai2hook/unity.h" +#include "mai2hook/touch.h" +#include "mai2hook/led.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE mai2_hook_mod; +static process_entry_t mai2_startup; +static struct mai2_hook_config mai2_hook_cfg; + +static DWORD CALLBACK mai2_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin mai2_pre_startup ---\n"); + + /* Load config */ + + mai2_hook_config_load(&mai2_hook_cfg, L".\\segatools.ini"); + + HMODULE mono = LoadLibraryW(L".\\MonoBleedingEdge\\EmbedRuntime\\mono-2.0-bdwgc.dll"); // load this so we can hook it maybe + if (mono == NULL) { + dprintf("Failed to load mono-2.0-bdwgc.dll\n"); + } else { + serial_hook_apply_hooks(mono); + iohook_apply_hooks(mono); + } + + /* Hook Win32 APIs */ + + dvd_hook_init(&mai2_hook_cfg.dvd, mai2_hook_mod); + gfx_hook_init(&mai2_hook_cfg.gfx); + gfx_d3d9_hook_init(&mai2_hook_cfg.gfx, mai2_hook_mod); + gfx_d3d11_hook_init(&mai2_hook_cfg.gfx, mai2_hook_mod); + gfx_dxgi_hook_init(&mai2_hook_cfg.gfx, mai2_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &mai2_hook_cfg.platform, + "SDEZ", + "ACA1", + mai2_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&mai2_hook_cfg.aime, 1, mai2_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(2); + + if (FAILED(hr)) { + goto fail; + } + + hr = mai2_dll_init(&mai2_hook_cfg.dll, mai2_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = mai2_io4_hook_init(&mai2_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + // TODO: The handling of the fake registry COM values is extraordinarly lazy + // and I need to make a better method for tacking fake values onto common keys + // that multiple modules may try to use. + hr = led_hook_init(&mai2_hook_cfg.led); + + if (FAILED(hr)) { + goto fail; + } + + hr = touch_hook_init(&mai2_hook_cfg.touch); + + 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 `mai2hook` initialization. */ + + unity_hook_init(); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\segatools.ini"); + + dprintf("--- End mai2_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return mai2_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + mai2_hook_mod = mod; + + hr = process_hijack_startup(mai2_pre_startup, &mai2_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/mai2hook/io4.c b/mai2hook/io4.c new file mode 100644 index 0000000..6daef92 --- /dev/null +++ b/mai2hook/io4.c @@ -0,0 +1,147 @@ +#include + +#include +#include +#include +#include + +#include "board/io4.h" + +#include "mai2hook/mai2-dll.h" + +#include "util/dprintf.h" + +bool mai2_io_coin = false; +uint16_t mai2_io_coins = 0; + +static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state); + +static const struct io4_ops mai2_io4_ops = { + .poll = mai2_io4_poll, +}; + +HRESULT mai2_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(mai2_dll.init != NULL); + + hr = io4_hook_init(cfg, &mai2_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return mai2_dll.init(); +} + +static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn = 0; + uint8_t player1 = 0; + uint8_t player2 = 0; + HRESULT hr; + + assert(mai2_dll.poll != NULL); + + hr = mai2_dll.poll(&opbtn, &player1, &player2); + + if (FAILED(hr)) { + return hr; + } + + if (opbtn & MAI2_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & MAI2_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & MAI2_IO_P1_START) { + state->buttons[0] |= 1 << 1; + } + + if (opbtn & MAI2_IO_P2_START) { + state->buttons[1] |= 1 << 4; + } + + if (!(player1 & MAI2_IO_GAMEBTN_1)) { + state->buttons[0] |= 1 << 2; + } + + if (!(player1 & MAI2_IO_GAMEBTN_2)) { + state->buttons[0] |= 1 << 3; + } + + if (!(player1 & MAI2_IO_GAMEBTN_3)) { + state->buttons[0] |= 1 << 0; + } + + if (!(player1 & MAI2_IO_GAMEBTN_4)) { + state->buttons[0] |= 1 << 15; + } + + if (!(player1 & MAI2_IO_GAMEBTN_5)) { + state->buttons[0] |= 1 << 14; + } + + if (!(player1 & MAI2_IO_GAMEBTN_6)) { + state->buttons[0] |= 1 << 13; + } + + if (!(player1 & MAI2_IO_GAMEBTN_7)) { + state->buttons[0] |= 1 << 12; + } + + if (!(player1 & MAI2_IO_GAMEBTN_8)) { + state->buttons[0] |= 1 << 11; + } + + if (!(player2 & MAI2_IO_GAMEBTN_1)) { + state->buttons[1] |= 1 << 2; + } + + if (!(player2 & MAI2_IO_GAMEBTN_2)) { + state->buttons[1] |= 1 << 3; + } + + if (!(player2 & MAI2_IO_GAMEBTN_3)) { + state->buttons[1] |= 1 << 0; + } + + if (!(player2 & MAI2_IO_GAMEBTN_4)) { + state->buttons[1] |= 1 << 15; + } + + if (!(player2 & MAI2_IO_GAMEBTN_5)) { + state->buttons[1] |= 1 << 14; + } + + if (!(player2 & MAI2_IO_GAMEBTN_6)) { + state->buttons[1] |= 1 << 13; + } + + if (!(player2 & MAI2_IO_GAMEBTN_7)) { + state->buttons[1] |= 1 << 12; + } + + if (!(player2 & MAI2_IO_GAMEBTN_8)) { + state->buttons[1] |= 1 << 11; + } + + + if (opbtn & MAI2_IO_OPBTN_COIN) { + if (!mai2_io_coin) { + mai2_io_coin = true; + mai2_io_coins++; + } + } + else { + mai2_io_coin = false; + } + + state->chutes[0] = 128 + 256 * mai2_io_coins; + + return S_OK; +} diff --git a/mai2hook/io4.h b/mai2hook/io4.h new file mode 100644 index 0000000..5a0a2a0 --- /dev/null +++ b/mai2hook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT mai2_io4_hook_init(const struct io4_config *cfg); diff --git a/mai2hook/led.c b/mai2hook/led.c new file mode 100644 index 0000000..438c859 --- /dev/null +++ b/mai2hook/led.c @@ -0,0 +1,210 @@ +#include +#include + +#include "hooklib/reg.h" +#include "hooklib/uart.h" +#include "hooklib/fdshark.h" + +#include "mai2hook/led.h" +#include "util/dprintf.h" + +static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes); +static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes); +static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes); +static HRESULT led_handle_irp(struct irp *irp); +static HRESULT led0_handle_irp_locked(struct irp *irp); +static HRESULT led1_handle_irp_locked(struct irp *irp); + +static CRITICAL_SECTION led0_lock; +static struct uart led0_uart; +static uint8_t led0_written_bytes[520]; +static uint8_t led0_readable_bytes[520]; + +static CRITICAL_SECTION led1_lock; +static struct uart led1_uart; +static uint8_t led1_written_bytes[520]; +static uint8_t led1_readable_bytes[520]; + +static const struct reg_hook_val fake_com_keys[] = { + { + .name = L"\\Device\\RealTouchBoard0", + .read = read_fake_com0, + .type = REG_SZ, + },{ + .name = L"\\Device\\RealTouchBoard1", + .read = read_fake_com1, + .type = REG_SZ, + },{ + .name = L"\\Device\\RealLedBoard0", + .read = read_fake_com2, + .type = REG_SZ, + }, +}; + +HRESULT led_hook_init(const struct led_config *cfg) +{ + HRESULT hr; + if (!cfg->enable) { + return S_FALSE; + } + + dprintf("Mai2 LED: Init\n"); + InitializeCriticalSection(&led0_lock); + InitializeCriticalSection(&led1_lock); + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"HARDWARE\\DEVICEMAP\\SERIALCOMM", + fake_com_keys, + _countof(fake_com_keys)); + + if (FAILED(hr)) { + return hr; + } + + uart_init(&led0_uart, 21); + led0_uart.written.bytes = led0_written_bytes; + led0_uart.written.nbytes = sizeof(led0_written_bytes); + led0_uart.readable.bytes = led0_readable_bytes; + led0_uart.readable.nbytes = sizeof(led0_readable_bytes); + + uart_init(&led1_uart, 23); + led1_uart.written.bytes = led1_written_bytes; + led1_uart.written.nbytes = sizeof(led1_written_bytes); + led1_uart.readable.bytes = led1_readable_bytes; + led1_uart.readable.nbytes = sizeof(led1_readable_bytes); + + return iohook_push_handler(led_handle_irp); +} + +static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes) +{ + //dprintf("Mai2 Touch: Read COM3 reg val\n"); + return reg_hook_read_wstr(bytes, nbytes, L"COM3"); +} + +static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes) +{ + //dprintf("Mai2 Touch: Read COM4 reg val\n"); + return reg_hook_read_wstr(bytes, nbytes, L"COM4"); +} + +static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes) +{ + //dprintf("Mai2 LED: Read COM20 reg val\n"); + return reg_hook_read_wstr(bytes, nbytes, L"COM20"); +} + +static HRESULT led_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (uart_match_irp(&led0_uart, irp)) { + EnterCriticalSection(&led0_lock); + hr = led0_handle_irp_locked(irp); + LeaveCriticalSection(&led0_lock); + } + else if (uart_match_irp(&led1_uart, irp)) { + EnterCriticalSection(&led1_lock); + hr = led1_handle_irp_locked(irp); + LeaveCriticalSection(&led1_lock); + } + else { + return iohook_invoke_next(irp); + } + + return hr; +} +static HRESULT led0_handle_irp_locked(struct irp *irp) +{ + HRESULT hr = S_OK; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Mai2 led0: Starting backend\n"); + //hr = mai2_dll.led_init(); + + if (FAILED(hr)) { + dprintf("Mai2 led: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&led0_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX0 Buffer:\n"); + dump_iobuf(&led0_uart.written); +#endif + //hr = led_frame_decode(&req, &led0_uart.written, 0); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Mai2 led: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + //hr = led_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Mai2 led: Processing error: %x\n", (int) hr); + } + + return hr; + } +} + +static HRESULT led1_handle_irp_locked(struct irp *irp) +{ + HRESULT hr = S_OK; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Mai2 led1: Starting backend\n"); + //hr = mai2_dll.led_init(); + + if (FAILED(hr)) { + dprintf("Mai2 led: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&led0_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX0 Buffer:\n"); + dump_iobuf(&led0_uart.written); +#endif + //hr = led_frame_decode(&req, &led0_uart.written, 0); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Mai2 led: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + //hr = led_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Mai2 led: Processing error: %x\n", (int) hr); + } + + return hr; + } +} \ No newline at end of file diff --git a/mai2hook/led.h b/mai2hook/led.h new file mode 100644 index 0000000..86712b7 --- /dev/null +++ b/mai2hook/led.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +struct led_config { + bool enable; +}; + +HRESULT led_hook_init(const struct led_config *cfg); diff --git a/mai2hook/mai2-dll.c b/mai2hook/mai2-dll.c new file mode 100644 index 0000000..0cda957 --- /dev/null +++ b/mai2hook/mai2-dll.c @@ -0,0 +1,103 @@ +#include + +#include +#include + +#include "mai2hook/mai2-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym mai2_dll_syms[] = { + { + .sym = "mai2_io_init", + .off = offsetof(struct mai2_dll, init), + }, { + .sym = "mai2_io_poll", + .off = offsetof(struct mai2_dll, poll), + }, +}; + +struct mai2_dll mai2_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 mai2_dll_init(const struct mai2_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("Mai2 IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Mai2 IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "mai2_io_get_api_version"); + + if (get_api_version != NULL) { + mai2_dll.api_version = get_api_version(); + } else { + mai2_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose mai2_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (mai2_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Mai2 IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + mai2_dll.api_version); + + goto end; + } + + sym = mai2_dll_syms; + hr = dll_bind(&mai2_dll, src, &sym, _countof(mai2_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Mai2 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/mai2hook/mai2-dll.h b/mai2hook/mai2-dll.h new file mode 100644 index 0000000..1813fe0 --- /dev/null +++ b/mai2hook/mai2-dll.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "mai2io/mai2io.h" + +struct mai2_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(uint8_t *opbtn, uint8_t *p1, uint8_t *p2); +}; + +struct mai2_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct mai2_dll mai2_dll; + +HRESULT mai2_dll_init(const struct mai2_dll_config *cfg, HINSTANCE self); diff --git a/mai2hook/mai2hook.def b/mai2hook/mai2hook.def new file mode 100644 index 0000000..af9ba8e --- /dev/null +++ b/mai2hook/mai2hook.def @@ -0,0 +1,22 @@ +LIBRARY mai2hook + +EXPORTS + CreateDXGIFactory + CreateDXGIFactory1 + CreateDXGIFactory2 + D3D11CreateDevice + D3D11CreateDeviceAndSwapChain + Direct3DCreate9 + 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 + mai2_io_get_api_version + mai2_io_init + mai2_io_poll diff --git a/mai2hook/meson.build b/mai2hook/meson.build new file mode 100644 index 0000000..b61d6b4 --- /dev/null +++ b/mai2hook/meson.build @@ -0,0 +1,37 @@ +shared_library( + 'mai2hook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mai2hook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + gfxhook_lib, + hooklib_lib, + mai2io_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'led.c', + 'led.h', + 'io4.c', + 'io4.h', + 'mai2-dll.c', + 'mai2-dll.h', + 'touch.c', + 'touch.h', + 'unity.h', + 'unity.c', + ], +) diff --git a/mai2hook/touch.c b/mai2hook/touch.c new file mode 100644 index 0000000..9a5131c --- /dev/null +++ b/mai2hook/touch.c @@ -0,0 +1,233 @@ +#include +#include +#include + +#include "hooklib/reg.h" +#include "hooklib/uart.h" +#include "hooklib/fdshark.h" + +#include "mai2hook/touch.h" +#include "mai2hook/mai2-dll.h" + +#include "util/dprintf.h" + +const char CMD_START = '{'; +const char CMD_END = '}'; +const char RESP_START = '('; +const char RESP_END = ')'; +const char BLANK = '@'; + +// Commands with parameters +const char CMD_RATIO[1] = "r"; +const char CMD_SENS[1] = "k"; +const char CMD_SENS_CHECK[2] = "th"; + +// Commands that never change +const char CMD_RESET[7] = "{RSET}"; // Reset board to default state +const char CMD_STAT[7] = "{STAT}"; // Start sending touch state +const char CMD_HALT[7] = "{HALT}"; // Stop sending touch state + +static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes); +static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes); +static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes); +static HRESULT touch_handle_irp(struct irp *irp); +static HRESULT touch0_handle_irp_locked(struct irp *irp); +static HRESULT touch1_handle_irp_locked(struct irp *irp); + +bool touch0_auto = false; +bool touch1_auto = false; + +static CRITICAL_SECTION touch0_lock; +static struct uart touch0_uart; +static uint8_t touch0_written_bytes[520]; +static uint8_t touch0_readable_bytes[520]; + +static CRITICAL_SECTION touch1_lock; +static struct uart touch1_uart; +static uint8_t touch1_written_bytes[520]; +static uint8_t touch1_readable_bytes[520]; + +static const struct reg_hook_val fake_com_keys[] = { + { + .name = L"\\Device\\RealTouchBoard0", + .read = read_fake_com0, + .type = REG_SZ, + },{ + .name = L"\\Device\\RealTouchBoard1", + .read = read_fake_com1, + .type = REG_SZ, + },{ + .name = L"\\Device\\RealLedBoard0", + .read = read_fake_com2, + .type = REG_SZ, + }, +}; + +HRESULT touch_hook_init(const struct touch_config *cfg) +{ + HRESULT hr; + if (!cfg->enable) { + return S_FALSE; + } + + dprintf("Mai2 touch: Init\n"); + InitializeCriticalSection(&touch0_lock); + InitializeCriticalSection(&touch1_lock); + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"HARDWARE\\DEVICEMAP\\SERIALCOMM", + fake_com_keys, + _countof(fake_com_keys)); + + if (FAILED(hr)) { + return hr; + } + + uart_init(&touch0_uart, 3); + touch0_uart.written.bytes = touch0_written_bytes; + touch0_uart.written.nbytes = sizeof(touch0_written_bytes); + touch0_uart.readable.bytes = touch0_readable_bytes; + touch0_uart.readable.nbytes = sizeof(touch0_readable_bytes); + + uart_init(&touch1_uart, 4); + touch1_uart.written.bytes = touch1_written_bytes; + touch1_uart.written.nbytes = sizeof(touch1_written_bytes); + touch1_uart.readable.bytes = touch1_readable_bytes; + touch1_uart.readable.nbytes = sizeof(touch1_readable_bytes); + + return iohook_push_handler(touch_handle_irp); +} + +static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes) +{ + // dprintf("Mai2 Touch: Read COM3 reg val\n"); + return reg_hook_read_wstr(bytes, nbytes, L"COM3"); +} + +static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes) +{ + // dprintf("Mai2 Touch: Read COM4 reg val\n"); + return reg_hook_read_wstr(bytes, nbytes, L"COM4"); +} + +static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes) +{ + // dprintf("Mai2 LED: Read COM20 reg val\n"); + return reg_hook_read_wstr(bytes, nbytes, L"COM20"); +} + +static HRESULT touch_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (uart_match_irp(&touch0_uart, irp)) { + EnterCriticalSection(&touch0_lock); + hr = touch0_handle_irp_locked(irp); + LeaveCriticalSection(&touch0_lock); + } + else if (uart_match_irp(&touch1_uart, irp)) { + EnterCriticalSection(&touch1_lock); + hr = touch1_handle_irp_locked(irp); + LeaveCriticalSection(&touch1_lock); + } + else { + return iohook_invoke_next(irp); + } + + return hr; +} + +static HRESULT touch0_handle_irp_locked(struct irp *irp) +{ + HRESULT hr = S_OK; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Mai2 touch0: Starting backend\n"); + //hr = mai2_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Mai2 touch: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch0_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX0 Buffer:\n"); + dump_iobuf(&touch0_uart.written); +#endif + //hr = touch_frame_decode(&req, &touch0_uart.written, 0); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Mai2 touch: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + //hr = touch_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Mai2 touch: Processing error: %x\n", (int) hr); + } + + return hr; + } +} + +static HRESULT touch1_handle_irp_locked(struct irp *irp) +{ + HRESULT hr = S_OK; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Mai2 touch1: Starting backend\n"); + //hr = mai2_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Mai2 touch: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch0_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX0 Buffer:\n"); + dump_iobuf(&touch0_uart.written); +#endif + //hr = touch_frame_decode(&req, &touch0_uart.written, 0); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Mai2 touch: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + //hr = touch_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Mai2 touch: Processing error: %x\n", (int) hr); + } + + return hr; + } +} \ No newline at end of file diff --git a/mai2hook/touch.h b/mai2hook/touch.h new file mode 100644 index 0000000..70c8b24 --- /dev/null +++ b/mai2hook/touch.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +struct touch_config { + bool enable; +}; + +HRESULT touch_hook_init(const struct touch_config *cfg); diff --git a/mai2hook/unity.c b/mai2hook/unity.c new file mode 100644 index 0000000..5d1157d --- /dev/null +++ b/mai2hook/unity.c @@ -0,0 +1,112 @@ +#include + +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" +#include "hooklib/reg.h" +#include "hooklib/procaddr.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 HMODULE WINAPI my_LoadLibraryExW(const wchar_t *name, HANDLE hFile, DWORD dwFlags); +static HMODULE (WINAPI *next_LoadLibraryExW)(const wchar_t *name, HANDLE hFile, DWORD dwFlags); + +static const struct hook_symbol unity_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = my_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + },{ + .name = "LoadLibraryExW", + .patch = my_LoadLibraryExW, + .link = (void **) &next_LoadLibraryExW, + } +}; + +static const wchar_t *target_modules[] = { + L"mono-2.0-bdwgc.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_LoadLibraryExW(const wchar_t *name, HANDLE hFile, DWORD dwFlags) +{ + //dprintf("Unity: LoadLibraryExW %ls\n", name); + return my_LoadLibraryW(name); +} + +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); + reg_hook_insert_hooks(result); + proc_addr_insert_hooks(result); + } + } + + return result; +} diff --git a/mai2hook/unity.h b/mai2hook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/mai2hook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/mai2io/config.c b/mai2io/config.c new file mode 100644 index 0000000..307d5e3 --- /dev/null +++ b/mai2io/config.c @@ -0,0 +1,46 @@ +#include + +#include +#include +#include + +#include "mai2io/config.h" + +static const int mai2_io_1p_default[] = {'W', 'E', 'D', 'C', 'X', 'Z', 'A', 'Q'}; +static const int mai2_io_2p_default[] = {0x68, 0x69, 0x66, 0x63, 0x62, 0x61, 0x64, 0x67}; + +void mai2_io_config_load( + struct mai2_io_config *cfg, + const wchar_t *filename) +{ + wchar_t key[240]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_DELETE, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_END, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_INSERT, filename); + cfg->vk_p1_start = GetPrivateProfileIntW(L"io4", L"p1_start", '1', filename); + cfg->vk_p2_start = GetPrivateProfileIntW(L"io4", L"p2_start", '2', filename); + + for (i = 0 ; i < 8 ; i++) { + swprintf_s(key, _countof(key), L"1p_btn%i", i + 1); + cfg->vk_p1_btn[i] = GetPrivateProfileIntW( + L"button", + key, + mai2_io_1p_default[i], + filename); + } + + for (i = 0 ; i < 8 ; i++) { + swprintf_s(key, _countof(key), L"2p_btn%i", i + 1); + cfg->vk_p2_btn[i] = GetPrivateProfileIntW( + L"button", + key, + mai2_io_2p_default[i], + filename); + } + +} diff --git a/mai2io/config.h b/mai2io/config.h new file mode 100644 index 0000000..c4e0a6e --- /dev/null +++ b/mai2io/config.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include + +struct mai2_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_p1_start; + uint8_t vk_p2_start; + uint8_t vk_p1_btn[8]; + uint8_t vk_p2_btn[8]; +}; + +void mai2_io_config_load( + struct mai2_io_config *cfg, + const wchar_t *filename); diff --git a/mai2io/mai2io.c b/mai2io/mai2io.c new file mode 100644 index 0000000..f489151 --- /dev/null +++ b/mai2io/mai2io.c @@ -0,0 +1,83 @@ +#include +#include + +#include +#include + +#include "mai2io/mai2io.h" +#include "mai2io/config.h" + +static struct mai2_io_config mai2_io_cfg; + +uint16_t mai2_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT mai2_io_init(void) +{ + mai2_io_config_load(&mai2_io_cfg, L".\\segatools.ini"); + return S_OK; +} + +HRESULT mai2_io_poll(uint8_t *opbtn, uint8_t *player1, uint8_t *player2) +{ + uint8_t opts = 0; + uint8_t p1 = 0; + uint8_t p2 = 0; + + if (GetAsyncKeyState(mai2_io_cfg.vk_test) & 0x8000) { + opts |= MAI2_IO_OPBTN_TEST; + }if (GetAsyncKeyState(mai2_io_cfg.vk_service) & 0x8000) { + opts |= MAI2_IO_OPBTN_SERVICE; + }if (GetAsyncKeyState(mai2_io_cfg.vk_coin) & 0x8000) { + opts |= MAI2_IO_OPBTN_COIN; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_start) & 0x8000) { + opts |= MAI2_IO_P1_START; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_start) & 0x8000) { + opts |= MAI2_IO_P2_START; + } + + *opbtn = opts; + + if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[0]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_1; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[1]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_2; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[2]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_3; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[3]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_4; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[4]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_5; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[5]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_6; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[6]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_7; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[7]) & 0x8000) { + p1 |= MAI2_IO_GAMEBTN_8; + } + + *player1 = p1; + + if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[0]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_1; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[1]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_2; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[2]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_3; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[3]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_4; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[4]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_5; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[5]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_6; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[6]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_7; + }if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[7]) & 0x8000) { + p2 |= MAI2_IO_GAMEBTN_8; + } + + *player2 = p2; + return S_OK; +} diff --git a/mai2io/mai2io.h b/mai2io/mai2io.h new file mode 100644 index 0000000..9890738 --- /dev/null +++ b/mai2io/mai2io.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include + +enum { + MAI2_IO_OPBTN_TEST = 0x01, + MAI2_IO_OPBTN_SERVICE = 0x02, + MAI2_IO_OPBTN_COIN = 0x04, + MAI2_IO_P1_START = 0x08, + MAI2_IO_P2_START = 0x10, +}; + +enum { + MAI2_IO_GAMEBTN_1 = 0x01, + MAI2_IO_GAMEBTN_2 = 0x02, + MAI2_IO_GAMEBTN_3 = 0x04, + MAI2_IO_GAMEBTN_4 = 0x08, + MAI2_IO_GAMEBTN_5 = 0x10, + MAI2_IO_GAMEBTN_6 = 0x20, + MAI2_IO_GAMEBTN_7 = 0x40, + MAI2_IO_GAMEBTN_8 = 0x80, +}; + +/* Get the version of the Mai2 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 mai2_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mai2_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT mai2_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 mai2_io_poll(uint8_t *opbtn, uint8_t *player1, uint8_t *player2); diff --git a/mai2io/meson.build b/mai2io/meson.build new file mode 100644 index 0000000..2ad109a --- /dev/null +++ b/mai2io/meson.build @@ -0,0 +1,16 @@ +mai2io_lib = static_library( + 'mai2io', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'mai2io.c', + 'mai2io.h', + 'config.c', + 'config.h', + ], +) diff --git a/meson.build b/meson.build index b5a3d5f..0a9a9fe 100644 --- a/meson.build +++ b/meson.build @@ -61,6 +61,7 @@ subdir('idzio') subdir('mu3io') subdir('mercuryio') subdir('cxbio') +subdir('mai2io') subdir('chunihook') subdir('divahook') @@ -70,3 +71,4 @@ subdir('minihook') subdir('mu3hook') subdir('mercuryhook') subdir('cxbhook') +subdir('mai2hook') diff --git a/subprojects/capnhook.wrap b/subprojects/capnhook.wrap index af30eae..c62f130 100644 --- a/subprojects/capnhook.wrap +++ b/subprojects/capnhook.wrap @@ -1,4 +1,4 @@ [wrap-git] directory = capnhook -url = https://github.com/decafcode/capnhook -revision = 69f7e3b48c2e0ff5be1d7a83cdcc2597a458357b +url = https://github.com/Hay1tsme/capnhook +revision = 888d068d58e68cf702e0cee872959a71413a7b55