From 7051b849fa5d3cfdfc2cb42cf1928c8eaf599b13 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 00:43:06 -0400 Subject: [PATCH] Make HKB hook public, with unity option fixes --- Package.mk | 16 +++++ dist/hkb/segatools.ini | 41 +++++++++++ dist/hkb/start.bat | 11 +++ hkbhook/config.c | 52 ++++++++++++++ hkbhook/config.h | 33 +++++++++ hkbhook/dllmain.c | 154 +++++++++++++++++++++++++++++++++++++++++ hkbhook/hkb-dll.c | 103 +++++++++++++++++++++++++++ hkbhook/hkb-dll.h | 19 +++++ hkbhook/hkbhook.def | 22 ++++++ hkbhook/io4.c | 109 +++++++++++++++++++++++++++++ hkbhook/io4.h | 7 ++ hkbhook/led.c | 98 ++++++++++++++++++++++++++ hkbhook/led.h | 39 +++++++++++ hkbhook/meson.build | 35 ++++++++++ hkbhook/unity.c | 105 ++++++++++++++++++++++++++++ hkbhook/unity.h | 3 + hkbio/config.c | 31 +++++++++ hkbio/config.h | 24 +++++++ hkbio/hkbio.c | 74 ++++++++++++++++++++ hkbio/hkbio.h | 47 +++++++++++++ hkbio/meson.build | 13 ++++ meson.build | 2 + platform/vfs.c | 16 +++++ 23 files changed, 1054 insertions(+) create mode 100644 dist/hkb/segatools.ini create mode 100644 dist/hkb/start.bat create mode 100644 hkbhook/config.c create mode 100644 hkbhook/config.h create mode 100644 hkbhook/dllmain.c create mode 100644 hkbhook/hkb-dll.c create mode 100644 hkbhook/hkb-dll.h create mode 100644 hkbhook/hkbhook.def create mode 100644 hkbhook/io4.c create mode 100644 hkbhook/io4.h create mode 100644 hkbhook/led.c create mode 100644 hkbhook/led.h create mode 100644 hkbhook/meson.build create mode 100644 hkbhook/unity.c create mode 100644 hkbhook/unity.h create mode 100644 hkbio/config.c create mode 100644 hkbio/config.h create mode 100644 hkbio/hkbio.c create mode 100644 hkbio/hkbio.h create mode 100644 hkbio/meson.build diff --git a/Package.mk b/Package.mk index b7351f0..3285579 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)/hkb.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/hkb + $(V)mkdir -p $(BUILD_DIR_ZIP)/hkb/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/hkbhook/hkbhook.dll \ + $(DIST_DIR)/hkb/segatools.ini \ + $(DIST_DIR)/hkb/start.bat \ + $(BUILD_DIR_ZIP)/hkb + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/hkb/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/hkb/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/hkb ; zip -r ../hkb.zip * + $(BUILD_DIR_ZIP)/mai2.zip: $(V)echo ... $@ $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2 @@ -136,6 +151,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/idz.zip \ $(BUILD_DIR_ZIP)/mercury.zip \ $(BUILD_DIR_ZIP)/mu3.zip \ + $(BUILD_DIR_ZIP)/hkb.zip \ $(BUILD_DIR_ZIP)/mai2.zip \ CHANGELOG.md \ README.md \ diff --git a/dist/hkb/segatools.ini b/dist/hkb/segatools.ini new file mode 100644 index 0000000..7a692b1 --- /dev/null +++ b/dist/hkb/segatools.ini @@ -0,0 +1,41 @@ +[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.155.0 + +[gfx] +enable=1 + +[io4] +test=0x31 +service=0x32 +coin=0x33 diff --git a/dist/hkb/start.bat b/dist/hkb/start.bat new file mode 100644 index 0000000..def2945 --- /dev/null +++ b/dist/hkb/start.bat @@ -0,0 +1,11 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +start inject.exe -d -k hkbhook.dll amdaemon.exe -f -c common.json terminal.json satellite.json +inject.exe -d -k hkbhook.dll hkb.exe -screen-fullscreen 0 -screen-quality Fantastic -silent-crashes -logFile gameLog.txt + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/hkbhook/config.c b/hkbhook/config.c new file mode 100644 index 0000000..ac7159b --- /dev/null +++ b/hkbhook/config.c @@ -0,0 +1,52 @@ +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "hkbhook/config.h" + +#include "platform/config.h" + +void hkb_dll_config_load( + struct hkb_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"hkbio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void hkb_led_config_load( + struct hkb_led_config *cfg, + const wchar_t *filename) +{ + cfg->enable = GetPrivateProfileIntW(L"led", L"enable", 1, filename); +} + +void hkb_hook_config_load( + struct hkb_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); + hkb_dll_config_load(&cfg->dll, filename); + hkb_led_config_load(&cfg->led, filename); +} diff --git a/hkbhook/config.h b/hkbhook/config.h new file mode 100644 index 0000000..47a89dc --- /dev/null +++ b/hkbhook/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "gfxhook/gfx.h" + +#include "hooklib/dvd.h" + +#include "hkbhook/hkb-dll.h" + +#include "platform/config.h" + +#include "hkbhook/led.h" + +struct hkb_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct hkb_dll_config dll; + struct hkb_led_config led; +}; + +void hkb_dll_config_load( + struct hkb_dll_config *cfg, + const wchar_t *filename); + +void hkb_hook_config_load( + struct hkb_hook_config *cfg, + const wchar_t *filename); diff --git a/hkbhook/dllmain.c b/hkbhook/dllmain.c new file mode 100644 index 0000000..6fb7f5a --- /dev/null +++ b/hkbhook/dllmain.c @@ -0,0 +1,154 @@ +#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 "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" +#include "hooklib/cursor.h" + +#include "hkbhook/config.h" +#include "hkbhook/io4.h" +#include "hkbhook/led.h" +#include "hkbhook/hkb-dll.h" +#include "hkbhook/unity.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE hkb_hook_mod; +static process_entry_t hkb_startup; +static struct hkb_hook_config hkb_hook_cfg; + +static DWORD CALLBACK hkb_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin hkb_pre_startup ---\n"); + + /* Load config */ + + hkb_hook_config_load(&hkb_hook_cfg, L".\\segatools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&hkb_hook_cfg.dvd, hkb_hook_mod); + gfx_hook_init(&hkb_hook_cfg.gfx); + gfx_d3d9_hook_init(&hkb_hook_cfg.gfx, hkb_hook_mod); + gfx_d3d11_hook_init(&hkb_hook_cfg.gfx, hkb_hook_mod); + gfx_dxgi_hook_init(&hkb_hook_cfg.gfx, hkb_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &hkb_hook_cfg.platform, + "SDEC", + "ACA2", + hkb_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + cursor_hook_init(); + // COM1: Touch + // COM2: LED (Satalite) + // COM3: Reader (Satalite) + // COM3: LED (Terminal) + if (FAILED(hr)) { + goto fail; + } + + if (hkb_hook_cfg.platform.nusec.platform_id[0] != '\0' && hkb_hook_cfg.platform.nusec.platform_id[3] == '4') { + hr = led_hook_init(&hkb_hook_cfg.led, 3); + if (FAILED(hr)) { + goto fail; + } + hr = sg_reader_hook_init(&hkb_hook_cfg.aime, 1, hkb_hook_mod); + if (FAILED(hr)) { + goto fail; + } + hr = vfd_hook_init(4); + } + + else { + hr = led_hook_init(&hkb_hook_cfg.led, 2); + if (FAILED(hr)) { + goto fail; + } + hr = sg_reader_hook_init(&hkb_hook_cfg.aime, 3, hkb_hook_mod); + if (FAILED(hr)) { + goto fail; + } + hr = vfd_hook_init(1); + } + + + if (FAILED(hr)) { + goto fail; + } + + hr = hkb_dll_init(&hkb_hook_cfg.dll, hkb_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = hkb_io4_hook_init(&hkb_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 `hkbhook` initialization. */ + + unity_hook_init(); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\segatools.ini"); + + dprintf("--- End hkb_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return hkb_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + hkb_hook_mod = mod; + + hr = process_hijack_startup(hkb_pre_startup, &hkb_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/hkbhook/hkb-dll.c b/hkbhook/hkb-dll.c new file mode 100644 index 0000000..4363a40 --- /dev/null +++ b/hkbhook/hkb-dll.c @@ -0,0 +1,103 @@ +#include + +#include +#include + +#include "hkbhook/hkb-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym hkb_dll_syms[] = { + { + .sym = "hkb_io_init", + .off = offsetof(struct hkb_dll, init), + }, { + .sym = "hkb_io_poll", + .off = offsetof(struct hkb_dll, poll), + }, +}; + +struct hkb_dll hkb_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 hkb_dll_init(const struct hkb_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("Ongeki IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Ongeki IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "hkb_io_get_api_version"); + + if (get_api_version != NULL) { + hkb_dll.api_version = get_api_version(); + } else { + hkb_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose hkb_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (hkb_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Ongeki IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + hkb_dll.api_version); + + goto end; + } + + sym = hkb_dll_syms; + hr = dll_bind(&hkb_dll, src, &sym, _countof(hkb_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Ongeki 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/hkbhook/hkb-dll.h b/hkbhook/hkb-dll.h new file mode 100644 index 0000000..95104b3 --- /dev/null +++ b/hkbhook/hkb-dll.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "hkbio/hkbio.h" + +struct hkb_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(uint8_t *opbtn, uint8_t *gamebtn); +}; + +struct hkb_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct hkb_dll hkb_dll; + +HRESULT hkb_dll_init(const struct hkb_dll_config *cfg, HINSTANCE self); diff --git a/hkbhook/hkbhook.def b/hkbhook/hkbhook.def new file mode 100644 index 0000000..7919e78 --- /dev/null +++ b/hkbhook/hkbhook.def @@ -0,0 +1,22 @@ +LIBRARY hkbhook + +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 + hkb_io_get_api_version + hkb_io_init + hkb_io_poll diff --git a/hkbhook/io4.c b/hkbhook/io4.c new file mode 100644 index 0000000..8795362 --- /dev/null +++ b/hkbhook/io4.c @@ -0,0 +1,109 @@ +#include +#include +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "hkbhook/hkb-dll.h" + +#include "util/dprintf.h" + +bool hkb_io_coin = false; +uint16_t hkb_io_coins = 0; + +static HRESULT hkb_io4_poll(void *ctx, struct io4_state *state); + +static const struct io4_ops hkb_io4_ops = { + .poll = hkb_io4_poll, +}; + +HRESULT hkb_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(hkb_dll.init != NULL); + + hr = io4_hook_init(cfg, &hkb_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return hkb_dll.init(); +} + +static HRESULT hkb_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + HRESULT hr; + + assert(hkb_dll.poll != NULL); + + memset(state, 0, sizeof(*state)); + + hr = hkb_dll.poll(&opbtn, &gamebtn); + + if (FAILED(hr)) { + return hr; + } + + if (opbtn & HKB_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & HKB_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & HKB_IO_OPBTN_COIN) { + if (!hkb_io_coin) { + hkb_io_coin = true; + hkb_io_coins++; + state->buttons[0] |= 1 << 25; + } + } + else { + hkb_io_coin = false; + } + + state->chutes[0] = 128 + 256 * hkb_io_coins; + + if (gamebtn & HKB_IO_GAMEBTN_RIGHT) { + state->buttons[1] |= 1 << 1; + } + + if (gamebtn & HKB_IO_GAMEBTN_LEFT) { + state->buttons[1] |= 1 << 0; + } + + if (gamebtn & HKB_IO_GAMEBTN_UP) { + state->buttons[1] |= 1 << 15; + } + + if (gamebtn & HKB_IO_GAMEBTN_DOWN) { + state->buttons[1] |= 1 << 14; + } + + if (gamebtn & HKB_IO_GAMEBTN_ENTER) { + state->buttons[1] |= 1 << 13; + } + + if (gamebtn & HKB_IO_GAMEBTN_CANCEL) { + state->buttons[1] |= 1 << 12; + } + + if (gamebtn & HKB_IO_GAMEBTN_ARR_RIGHT) { + state->buttons[1] |= 1 << 2; + } + + if (gamebtn & HKB_IO_GAMEBTN_ARR_LEFT) { + state->buttons[1] |= 1 << 3; + } + + return S_OK; +} diff --git a/hkbhook/io4.h b/hkbhook/io4.h new file mode 100644 index 0000000..87a19a6 --- /dev/null +++ b/hkbhook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT hkb_io4_hook_init(const struct io4_config *cfg); diff --git a/hkbhook/led.c b/hkbhook/led.c new file mode 100644 index 0000000..9ade332 --- /dev/null +++ b/hkbhook/led.c @@ -0,0 +1,98 @@ +#include +#include +#include + +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +#include "hkbhook/led.h" + +static HRESULT hkb_led_handle_irp(struct irp *irp); +static HRESULT hkb_led_handle_irp_locked(struct irp *irp); + +static CRITICAL_SECTION hkb_led_lock; +static bool hkb_led_started; +static struct uart hkb_led_uart; +static uint8_t hkb_led_written_bytes[520]; +static uint8_t hkb_led_readable_bytes[520]; + +HRESULT led_hook_init(const struct hkb_led_config *cfg, uint8_t port) +{ + if (!cfg->enable) { + return S_OK; + } + + InitializeCriticalSection(&hkb_led_lock); + + uart_init(&hkb_led_uart, port); + hkb_led_uart.written.bytes = hkb_led_written_bytes; + hkb_led_uart.written.nbytes = sizeof(hkb_led_written_bytes); + hkb_led_uart.readable.bytes = hkb_led_readable_bytes; + hkb_led_uart.readable.nbytes = sizeof(hkb_led_readable_bytes); + + return iohook_push_handler(hkb_led_handle_irp); +} + +static HRESULT hkb_led_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&hkb_led_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&hkb_led_lock); + hr = hkb_led_handle_irp_locked(irp); + LeaveCriticalSection(&hkb_led_lock); + + return hr; +} + +static HRESULT hkb_led_handle_irp_locked(struct irp *irp) +{ + HRESULT hr; + +#if 1 + if (irp->op == IRP_OP_WRITE) { + dprintf("WRITE:\n"); + dump_const_iobuf(&irp->write); + } +#endif + +#if 0 + if (irp->op == IRP_OP_READ) { + dprintf("READ:\n"); + dump_iobuf(&hkb_led_uart.readable); + } +#endif + + if (irp->op == IRP_OP_OPEN) { + /* Unfortunately the card reader UART gets opened and closed + repeatedly */ + + if (!hkb_led_started) { + dprintf("Led: Open\n"); + + hkb_led_started = true; + + } else { + return S_OK; + } + } + + hr = uart_handle_irp(&hkb_led_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + hkb_led_uart.written.pos = 0; + + return hr; +} diff --git a/hkbhook/led.h b/hkbhook/led.h new file mode 100644 index 0000000..8952b36 --- /dev/null +++ b/hkbhook/led.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include + +#pragma pack(push, 1) +struct hkb_led_config { + bool enable; +}; +struct hbk_led_packet_head { + uint8_t sync; + uint8_t dst; + uint8_t src; + uint8_t len; +}; +struct hbk_led_req_header { + struct hbk_led_packet_head head; + uint8_t cmd; +}; +struct hbk_led_resp_header { + struct hbk_led_packet_head head; + uint8_t status; + uint8_t cmd; + uint8_t report; +}; +struct hbk_led_packet_tail { + uint8_t checksum; +}; +struct hbk_led_req_any { + struct hbk_led_req_header header; + struct hbk_led_packet_tail tail; +}; +struct hbk_led_resp_any { + struct hbk_led_resp_header header; + struct hbk_led_packet_tail tail; +}; +#pragma pack(pop) + +HRESULT led_hook_init(const struct hkb_led_config *cfg, uint8_t port); \ No newline at end of file diff --git a/hkbhook/meson.build b/hkbhook/meson.build new file mode 100644 index 0000000..3e01abd --- /dev/null +++ b/hkbhook/meson.build @@ -0,0 +1,35 @@ +shared_library( + 'hkbhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'hkbhook.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, + hkbio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'led.c', + 'led.h', + 'hkb-dll.c', + 'hkb-dll.h', + 'unity.h', + 'unity.c', + ], +) diff --git a/hkbhook/unity.c b/hkbhook/unity.c new file mode 100644 index 0000000..8c0aac1 --- /dev/null +++ b/hkbhook/unity.c @@ -0,0 +1,105 @@ +#include + +#include + +#include "hook/table.h" +#include "hook/iohook.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" +#include "hooklib/reg.h" +#include "hook/procaddr.h" +#include "hooklib/serial.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", + L"HKBSys_api.dll", + L"amdaemon_api.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); + reg_hook_insert_hooks(result); + proc_addr_insert_hooks(result); + serial_hook_apply_hooks(result); + iohook_apply_hooks(result); + } + } + + return result; +} diff --git a/hkbhook/unity.h b/hkbhook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/hkbhook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/hkbio/config.c b/hkbio/config.c new file mode 100644 index 0000000..6a1120b --- /dev/null +++ b/hkbio/config.c @@ -0,0 +1,31 @@ +#include + +#include +#include +#include + +#include "hkbio/config.h" + +void hkb_io_config_load( + struct hkb_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", '1', filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename); + + cfg->vk_up = GetPrivateProfileIntW(L"io4", L"up", VK_UP, filename); + cfg->vk_down = GetPrivateProfileIntW(L"io4", L"down", VK_DOWN, filename); + cfg->vk_left = GetPrivateProfileIntW(L"io4", L"left", VK_LEFT, filename); + cfg->vk_right = GetPrivateProfileIntW(L"io4", L"right", VK_RIGHT, filename); + cfg->vk_enter = GetPrivateProfileIntW(L"io4", L"enter", VK_SPACE, filename); + cfg->vk_cancel = GetPrivateProfileIntW(L"io4", L"cancel", VK_LSHIFT, filename); + cfg->vk_arr_right = GetPrivateProfileIntW(L"io4", L"right_arrow", 'A', filename); + cfg->vk_arr_left = GetPrivateProfileIntW(L"io4", L"left_arrow", 'D', filename); +} diff --git a/hkbio/config.h b/hkbio/config.h new file mode 100644 index 0000000..921de16 --- /dev/null +++ b/hkbio/config.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include + +struct hkb_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_up; + uint8_t vk_down; + uint8_t vk_left; + uint8_t vk_right; + uint8_t vk_enter; + uint8_t vk_cancel; + uint8_t vk_arr_right; + uint8_t vk_arr_left; +}; + +void hkb_io_config_load( + struct hkb_io_config *cfg, + const wchar_t *filename); diff --git a/hkbio/hkbio.c b/hkbio/hkbio.c new file mode 100644 index 0000000..ed860a5 --- /dev/null +++ b/hkbio/hkbio.c @@ -0,0 +1,74 @@ +#include +#include + +#include +#include + +#include "hkbio/hkbio.h" +#include "hkbio/config.h" + +static struct hkb_io_config cfg; + +uint16_t hkb_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT hkb_io_init(void) +{ + hkb_io_config_load(&cfg, L".\\segatools.ini"); + return S_OK; +} + +HRESULT hkb_io_poll(uint8_t *opbtn, uint8_t *gamebtn) +{ + + *opbtn = 0; + *gamebtn = 0; + + if (GetAsyncKeyState(cfg.vk_test) & 0x8000) { + *opbtn |= HKB_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(cfg.vk_service) & 0x8000) { + *opbtn |= HKB_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(cfg.vk_coin) & 0x8000) { + *opbtn |= HKB_IO_OPBTN_COIN; + } + + if (GetAsyncKeyState(cfg.vk_right) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_RIGHT; + } + + if (GetAsyncKeyState(cfg.vk_left) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_LEFT; + } + + if (GetAsyncKeyState(cfg.vk_up) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_UP; + } + + if (GetAsyncKeyState(cfg.vk_down) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_DOWN; + } + + if (GetAsyncKeyState(cfg.vk_enter) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_ENTER; + } + + if (GetAsyncKeyState(cfg.vk_cancel) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_CANCEL; + } + + if (GetAsyncKeyState(cfg.vk_arr_right) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_ARR_RIGHT; + } + + if (GetAsyncKeyState(cfg.vk_arr_left) & 0x8000) { + *gamebtn |= HKB_IO_GAMEBTN_ARR_LEFT; + } + + return S_OK; +} diff --git a/hkbio/hkbio.h b/hkbio/hkbio.h new file mode 100644 index 0000000..40aec37 --- /dev/null +++ b/hkbio/hkbio.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include + +enum { + HKB_IO_OPBTN_TEST = 0x01, + HKB_IO_OPBTN_SERVICE = 0x02, + HKB_IO_OPBTN_COIN = 0x04, +}; + +enum { + HKB_IO_GAMEBTN_RIGHT = 0x01, + HKB_IO_GAMEBTN_LEFT = 0x02, + HKB_IO_GAMEBTN_UP = 0x04, + HKB_IO_GAMEBTN_DOWN = 0x08, + HKB_IO_GAMEBTN_ENTER = 0x10, + HKB_IO_GAMEBTN_CANCEL = 0x20, + HKB_IO_GAMEBTN_ARR_RIGHT = 0x40, + HKB_IO_GAMEBTN_ARR_LEFT = 0x80, +}; + +/* Get the version of the Ongeki 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 hkb_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after hkb_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT hkb_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 hkb_io_poll(uint8_t *opbtn, uint8_t *gamebtn); diff --git a/hkbio/meson.build b/hkbio/meson.build new file mode 100644 index 0000000..c1048fc --- /dev/null +++ b/hkbio/meson.build @@ -0,0 +1,13 @@ +hkbio_lib = static_library( + 'hkbio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'hkbio.c', + 'hkbio.h', + 'config.c', + 'config.h', + ], +) diff --git a/meson.build b/meson.build index 0a9a9fe..e7fcafa 100644 --- a/meson.build +++ b/meson.build @@ -62,6 +62,7 @@ subdir('mu3io') subdir('mercuryio') subdir('cxbio') subdir('mai2io') +subdir('hkbio') subdir('chunihook') subdir('divahook') @@ -72,3 +73,4 @@ subdir('mu3hook') subdir('mercuryhook') subdir('cxbhook') subdir('mai2hook') +subdir('hkbhook') diff --git a/platform/vfs.c b/platform/vfs.c index e02114f..f489978 100644 --- a/platform/vfs.c +++ b/platform/vfs.c @@ -36,12 +36,20 @@ static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes); static wchar_t* my_System_getAppRootPath(); static wchar_t* (*next_System_getAppRootPath)(); +static wchar_t* my_AppImage_getOptionMountRootPath(); +static wchar_t* (*next_AppImage_getOptionMountRootPath)(); + static const struct hook_symbol amdaemon_syms[] = { { .name = "System_getAppRootPath", .patch = my_System_getAppRootPath, .link = (void **) &next_System_getAppRootPath, }, + { + .name = "AppImage_getOptionMountRootPath", + .patch = my_AppImage_getOptionMountRootPath, + .link = (void **) &next_AppImage_getOptionMountRootPath, + }, }; static wchar_t game[5] = {0}; @@ -505,3 +513,11 @@ static wchar_t* my_System_getAppRootPath() wcscat_s(path, MAX_PATH, L"\\"); return path; } + +static wchar_t* my_AppImage_getOptionMountRootPath() +{ + wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH); + wcscpy_s(path, MAX_PATH, vfs_config.option); + wcscat_s(path, MAX_PATH, L"\\"); + return path; +}