From 92a6b1bbdb273baead7ff22b07d4cc0d0761a3c1 Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:36:02 +0200 Subject: [PATCH] hkb: Add support for Chrono Regalia --- Package.mk | 20 +++ dist/hkb/config_hook_satellite.json | 5 + dist/hkb/config_hook_terminal.json | 10 ++ dist/hkb/launch_satellite.bat | 14 ++ dist/hkb/launch_terminal.bat | 14 ++ dist/hkb/segatools_satellite.ini | 145 ++++++++++++++++ dist/hkb/segatools_terminal.ini | 185 +++++++++++++++++++++ games/hkbhook/amptw.c | 40 +++++ games/hkbhook/amptw.h | 6 + games/hkbhook/config.c | 82 +++++++++ games/hkbhook/config.h | 32 ++++ games/hkbhook/dllmain.c | 170 +++++++++++++++++++ games/hkbhook/hkb-dll.c | 119 +++++++++++++ games/hkbhook/hkb-dll.h | 23 +++ games/hkbhook/hkbhook.def | 32 ++++ games/hkbhook/io4.c | 106 ++++++++++++ games/hkbhook/io4.h | 7 + games/hkbhook/meson.build | 32 ++++ games/hkbio/backend.h | 10 ++ games/hkbio/config.c | 67 ++++++++ games/hkbio/config.h | 44 +++++ games/hkbio/di-dev.c | 71 ++++++++ games/hkbio/di-dev.h | 15 ++ games/hkbio/di.c | 249 ++++++++++++++++++++++++++++ games/hkbio/di.h | 7 + games/hkbio/hkbio.c | 103 ++++++++++++ games/hkbio/hkbio.h | 85 ++++++++++ games/hkbio/keyboard.c | 70 ++++++++ games/hkbio/keyboard.h | 9 + games/hkbio/meson.build | 24 +++ games/hkbio/wnd.c | 67 ++++++++ games/hkbio/wnd.h | 5 + meson.build | 2 + 33 files changed, 1870 insertions(+) create mode 100644 dist/hkb/config_hook_satellite.json create mode 100644 dist/hkb/config_hook_terminal.json create mode 100644 dist/hkb/launch_satellite.bat create mode 100644 dist/hkb/launch_terminal.bat create mode 100644 dist/hkb/segatools_satellite.ini create mode 100644 dist/hkb/segatools_terminal.ini create mode 100644 games/hkbhook/amptw.c create mode 100644 games/hkbhook/amptw.h create mode 100644 games/hkbhook/config.c create mode 100644 games/hkbhook/config.h create mode 100644 games/hkbhook/dllmain.c create mode 100644 games/hkbhook/hkb-dll.c create mode 100644 games/hkbhook/hkb-dll.h create mode 100644 games/hkbhook/hkbhook.def create mode 100644 games/hkbhook/io4.c create mode 100644 games/hkbhook/io4.h create mode 100644 games/hkbhook/meson.build create mode 100644 games/hkbio/backend.h create mode 100644 games/hkbio/config.c create mode 100644 games/hkbio/config.h create mode 100644 games/hkbio/di-dev.c create mode 100644 games/hkbio/di-dev.h create mode 100644 games/hkbio/di.c create mode 100644 games/hkbio/di.h create mode 100644 games/hkbio/hkbio.c create mode 100644 games/hkbio/hkbio.h create mode 100644 games/hkbio/keyboard.c create mode 100644 games/hkbio/keyboard.h create mode 100644 games/hkbio/meson.build create mode 100644 games/hkbio/wnd.c create mode 100644 games/hkbio/wnd.h diff --git a/Package.mk b/Package.mk index e85047f..779a100 100644 --- a/Package.mk +++ b/Package.mk @@ -308,6 +308,25 @@ $(BUILD_DIR_ZIP)/sekito.zip: $(V)strip $(BUILD_DIR_ZIP)/sekito/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/sekito ; zip -r ../sekito.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_GAMES_64)/hkbhook/hkbhook.dll \ + $(DIST_DIR)/hkb/segatools_satellite.ini \ + $(DIST_DIR)/hkb/segatools_terminal.ini \ + $(DIST_DIR)/hkb/config_hook_satellite.ini \ + $(DIST_DIR)/hkb/config_hook_terminal.ini \ + $(DIST_DIR)/hkb/launch_satellite.bat \ + $(DIST_DIR)/hkb/launch_terminal.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)/doc.zip: \ $(DOC_DIR)/config \ $(DOC_DIR)/chunihook.md \ @@ -336,6 +355,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/apm3.zip \ $(BUILD_DIR_ZIP)/ekt.zip \ $(BUILD_DIR_ZIP)/sekito.zip \ + $(BUILD_DIR_ZIP)/hkb.zip \ CHANGELOG.md \ README.md \ diff --git a/dist/hkb/config_hook_satellite.json b/dist/hkb/config_hook_satellite.json new file mode 100644 index 0000000..1e0d221 --- /dev/null +++ b/dist/hkb/config_hook_satellite.json @@ -0,0 +1,5 @@ +{ + "common": { + "language": "english" + } +} \ No newline at end of file diff --git a/dist/hkb/config_hook_terminal.json b/dist/hkb/config_hook_terminal.json new file mode 100644 index 0000000..2af6079 --- /dev/null +++ b/dist/hkb/config_hook_terminal.json @@ -0,0 +1,10 @@ +{ + "common": { + "language": "english" + }, + "network": { + "property": { + "dhcp": true + } + } +} \ No newline at end of file diff --git a/dist/hkb/launch_satellite.bat b/dist/hkb/launch_satellite.bat new file mode 100644 index 0000000..fd78dec --- /dev/null +++ b/dist/hkb/launch_satellite.bat @@ -0,0 +1,14 @@ +@echo off +set SEGATOOLS_CONFIG_PATH=.\segatools_satellite.ini + +pushd %~dp0 + +start "AM Daemon" /min inject -d -k hkbhook.dll amdaemon.exe -c common.json satellite.json terminal.json config_hook_satellite.json + +inject -d -k hkbhook.dll hkb.exe -logFile satellite.log -screen-fullscreen 0 -popupwindow -screen-quality Fantastic + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/hkb/launch_terminal.bat b/dist/hkb/launch_terminal.bat new file mode 100644 index 0000000..507254c --- /dev/null +++ b/dist/hkb/launch_terminal.bat @@ -0,0 +1,14 @@ +@echo off +set SEGATOOLS_CONFIG_PATH=.\segatools_terminal.ini + +pushd %~dp0 + +start "AM Daemon" /min inject -d -k hkbhook.dll amdaemon.exe -c common.json satellite.json terminal.json config_hook_terminal.json + +inject -d -k hkbhook.dll hkb.exe -logFile terminal.log -screen-fullscreen 0 -popupwindow -screen-quality Fantastic + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/hkb/segatools_satellite.ini b/dist/hkb/segatools_satellite.ini new file mode 100644 index 0000000..e860dc6 --- /dev/null +++ b/dist/hkb/segatools_satellite.ini @@ -0,0 +1,145 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +; Virtual-key code. If this button is **held** then the emulated IC card reader +; emulates an IC card in its proximity. A variety of different IC cards can be +; emulated; the exact choice of card that is emulated depends on the presence or +; absence of the configured card ID files. Default is the Return key. +scan=0x0D + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[keychip] +; Keychip serial number. Keychip serials observed in the wild follow this +; pattern: `A\d{2}(E|X)-(01|20)[ABCDU]\d{8}`. +id=A69E-01A88888888 + +; 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.189.0 + +; Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This +; is actually supposed to be a separate three-character `platformId` and +; integer `modelType` setting, but they are combined here for convenience. +; 2 = Satellite (ST) +; 4 = Terminal (TM) +platformId=ACA2 + +[pcbid] +; Set the Windows host name. This should be an ALLS MAIN ID, without the +; hyphen (which is not a valid character in a Windows host name). +serialNo=ACAE01A99999999 + +[system] +; Enable ALLS system settings. +enable=1 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 0 on exactly one machine and set this to 1 on all others. +dipsw1=0 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[unity] +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + +[touch] +; WinTouch emulation setting. (set to 0 for native touchscreens) +enable=1 +; Enable coordinate remapping (set to 0 if windowed mode cursor offset occurs) +remap=1 +cursor=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable the 837-15093-06 board emulation. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +[hkbio] +; To use a custom Chrono Regalia IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard/gamepad input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 diff --git a/dist/hkb/segatools_terminal.ini b/dist/hkb/segatools_terminal.ini new file mode 100644 index 0000000..be040f3 --- /dev/null +++ b/dist/hkb/segatools_terminal.ini @@ -0,0 +1,185 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +; Virtual-key code. If this button is **held** then the emulated IC card reader +; emulates an IC card in its proximity. A variety of different IC cards can be +; emulated; the exact choice of card that is emulated depends on the presence or +; absence of the configured card ID files. Default is the Return key. +scan=0x0D + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[keychip] +; Keychip serial number. Keychip serials observed in the wild follow this +; pattern: `A\d{2}(E|X)-(01|20)[ABCDU]\d{8}`. +id=A69E-01A88888888 + +; 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.189.0 + +; Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This +; is actually supposed to be a separate three-character `platformId` and +; integer `modelType` setting, but they are combined here for convenience. +; 2 = Satellite (ST) +; 4 = Terminal (TM) +platformId=ACA4 + +[pcbid] +; Set the Windows host name. This should be an ALLS MAIN ID, without the +; hyphen (which is not a valid character in a Windows host name). +serialNo=ACAE01A99999999 + +[system] +; Enable ALLS system settings. +enable=1 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 0 on exactly one machine and set this to 1 on all others. +dipsw1=0 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[unity] +; Enable Unity hook. This will allow you to run custom .NET code before the game +enable=1 + +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable the 837-15093-06 board emulation. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +[hkbio] +; To use a custom Chrono Regalia IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard/gamepad input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; Input API selection for IO4 input emulator. +; Use "keyboard" for keyboard or "dinput" for DirectInput. +mode=keyboard + +[keyboard] + +cancel=0x53 +decide=0x41 + +up=0x26 +right=0x27 +down=0x28 +left=0x25 + +; Pagination buttons +left2=0x4F +right2=0x57 + +[dinput] +; Name of the DirectInput device to use (or any text that occurs in its name) +; Example: TE2+ +; +; If this is left blank then the first DirectInput device will be used. +deviceName= + +; DirectInput button numbers to map to terminal inputs. Note that buttons are +; numbered from 1; some software numbers buttons from 0. + +test=1 +service=2 +cancel=3 +decide=4 + +; Pagination buttons +left2=5 +right2=6 + +; Additional directional keys. These only have to be set if your +; controller reports them as buttons rather than a hat switch. +up=7 +right=8 +down=9 +left=10 diff --git a/games/hkbhook/amptw.c b/games/hkbhook/amptw.c new file mode 100644 index 0000000..150ecbb --- /dev/null +++ b/games/hkbhook/amptw.c @@ -0,0 +1,40 @@ +/* + * Hook for an ominous touch panel dll. Works via WinTouch by default, falls back to a SHARP touch panel on port 1 otherwise (which is taken by the VFD already), however all this dll does is memcpy'ing some data around and has no references to WinTouch functions, and doesn't start any threads, so the relevant call get stubbed here. /shrug + */ + +#include "amptw.h" + +#include +#include + +#include "hook/procaddr.h" +#include "hook/table.h" +#include "hooklib/touch.h" +#include "util/dprintf.h" + +static int hook_Amptw_getResult(void* pContext, int* pResult); + +static const struct hook_symbol touch_hooks[] = { + {.name = "Amptw_getResult", .patch = hook_Amptw_getResult},}; + +void amptw_hook_init(const struct touch_screen_config* cfg, HMODULE hmodule) { + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + amptw_apply_hooks(hmodule); +} + +void amptw_apply_hooks(HMODULE module) { + hook_table_apply(module, "amptw.dll", touch_hooks, _countof(touch_hooks)); + + proc_addr_table_push(module, "amptw.dll", touch_hooks, + _countof(touch_hooks)); +} + +static int hook_Amptw_getResult(void* pContext, int* pResult) { + *pResult = 0; + return 0; +} \ No newline at end of file diff --git a/games/hkbhook/amptw.h b/games/hkbhook/amptw.h new file mode 100644 index 0000000..13c1cc9 --- /dev/null +++ b/games/hkbhook/amptw.h @@ -0,0 +1,6 @@ +#pragma once + +#include "hooklib/touch.h" + +void amptw_hook_init(const struct touch_screen_config* cfg, HMODULE hmodule); +void amptw_apply_hooks(HMODULE module); \ No newline at end of file diff --git a/games/hkbhook/config.c b/games/hkbhook/config.c new file mode 100644 index 0000000..f0b314d --- /dev/null +++ b/games/hkbhook/config.c @@ -0,0 +1,82 @@ +#include +#include + +#include "board/config.h" + +#include "hooklib/config.h" + +#include "hkbhook/config.h" + +#include "platform/config.h" +#include "unityhook/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 led15093_config_load(struct led15093_config* cfg, + const wchar_t* filename) { + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, + filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, + filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, + filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0xA0, filename); + cfg->fw_sum = + GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAA53, filename); + + GetPrivateProfileStringW(L"led15093", L"boardNumber", L"15093-06", tmpstr, + _countof(tmpstr), filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW(L"led15093", L"chipNumber", L"6710A", tmpstr, + _countof(tmpstr), filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW(L"led15093", L"bootChipNumber", L"6709 ", tmpstr, + _countof(tmpstr), filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) { + cfg->boot_chip_number[i] = ' '; + } +} + +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); + touch_screen_config_load(&cfg->touch, filename); + led15093_config_load(&cfg->led15093, filename); + unity_config_load(&cfg->unity, filename); + hkb_dll_config_load(&cfg->dll, filename); + vfd_config_load(&cfg->vfd, filename); +} \ No newline at end of file diff --git a/games/hkbhook/config.h b/games/hkbhook/config.h new file mode 100644 index 0000000..528803b --- /dev/null +++ b/games/hkbhook/config.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" + +#include "gfxhook/config.h" + +#include "hkbhook/hkb-dll.h" + +#include "platform/config.h" +#include "unityhook/config.h" + +struct hkb_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct vfd_config vfd; + struct touch_screen_config touch; + struct led15093_config led15093; + struct unity_config unity; + struct hkb_dll_config dll; +}; + +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); \ No newline at end of file diff --git a/games/hkbhook/dllmain.c b/games/hkbhook/dllmain.c new file mode 100644 index 0000000..0a26449 --- /dev/null +++ b/games/hkbhook/dllmain.c @@ -0,0 +1,170 @@ +/* + "Chrono Regalia" (hakobune) hook + + Devices + + USB: 837-15257-01 "Type 4" I/O Board + + [Satellite] + + COM1: 200-6275 VFD GP1232A02A FUTABA board + COM2: 837-15093-06 LED Controller Board + COM3: 837-15396 "Gen 3" Aime Reader + + [Terminal] + + COM1: 837-15396 "Gen 3" Aime Reader + COM3: 837-15093-06 LED Controller Board + COM4: 200-6275 VFD GP1232A02A FUTABA board +*/ + +#include + +#include + +#include "board/led15093.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 "hkbhook/amptw.h" +#include "hkbhook/config.h" +#include "hkbhook/io4.h" +#include "hkbhook/hkb-dll.h" + +#include "platform/platform.h" +#include "unityhook/hook.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE hkb_hook_mod; +static process_entry_t hkb_startup; +static struct hkb_hook_config hkb_hook_cfg; + +void unity_hook_callback(HMODULE module, const wchar_t* p) { + if (wcscmp(p, L"amptw.dll") == 0) { + if (hkb_hook_cfg.touch.enable) { + touch_hook_insert_hooks(module); + amptw_apply_hooks(module); + } + } +} + +static DWORD CALLBACK hkb_pre_startup(void) { + HRESULT hr; + bool is_terminal; + + dprintf("--- Begin hkb_pre_startup ---\n"); + + /* Load config */ + + hkb_hook_config_load(&hkb_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&hkb_hook_cfg.dvd, hkb_hook_mod); + touch_screen_hook_init(&hkb_hook_cfg.touch, hkb_hook_mod); + touch_hook_insert_hooks(hkb_hook_mod); + serial_hook_init(); + + /* Initialize Terminal/Satellite hooks */ + if (strncmp(hkb_hook_cfg.platform.nusec.platform_id, "ACA4", 4) == 0) { + // Terminal + is_terminal = true; + } else if (strncmp(hkb_hook_cfg.platform.nusec.platform_id, "ACA2", 4) == + 0) { + // Satellite + is_terminal = false; + } else { + // Unknown + dprintf("Unknown platform ID: %s\n", + hkb_hook_cfg.platform.nusec.platform_id); + goto fail; + } + + dprintf("HKB: Cabinet Type: %s\n", is_terminal ? "Terminal" : "Satellite"); + + /* Hook external DLL APIs */ + + amptw_hook_init(&hkb_hook_cfg.touch, hkb_hook_mod); + + /* Initialize emulation hooks */ + + hr = platform_hook_init(&hkb_hook_cfg.platform, "SDEC", + is_terminal ? "ACA4" : "ACA2", hkb_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&hkb_hook_cfg.aime, is_terminal ? 1 : 3, 3, + hkb_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = hkb_dll_init(&hkb_hook_cfg.dll, hkb_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(&hkb_hook_cfg.vfd, is_terminal ? 4 : 1); + + if (FAILED(hr)) { + goto fail; + } + + hr = hkb_io4_hook_init(&hkb_hook_cfg.io4, is_terminal); + + if (FAILED(hr)) { + goto fail; + } + + unsigned int led_port_no[2] = {is_terminal ? 3 : 2, 0}; + hr = led15093_hook_init(&hkb_hook_cfg.led15093, hkb_dll.led_init, + hkb_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + unity_hook_init(&hkb_hook_cfg.unity, hkb_hook_mod, unity_hook_callback); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + 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) { + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + hkb_hook_mod = mod; + + HRESULT 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); +} \ No newline at end of file diff --git a/games/hkbhook/hkb-dll.c b/games/hkbhook/hkb-dll.c new file mode 100644 index 0000000..64b50e9 --- /dev/null +++ b/games/hkbhook/hkb-dll.c @@ -0,0 +1,119 @@ +#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), + }, + { + .sym = "hkb_io_get_opbtns", + .off = offsetof(struct hkb_dll, get_opbtns), + }, + { + .sym = "hkb_io_get_gamebtns", + .off = offsetof(struct hkb_dll, get_gamebtns), + }, + { + .sym = "hkb_io_led_init", + .off = offsetof(struct hkb_dll, led_init), + }, + { + .sym = "hkb_io_led_set_colors", + .off = offsetof(struct hkb_dll, led_set_leds), + } +}; + +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("HKB IO: Failed to load IO DLL: %lx: %S\n", hr, cfg->path); + + goto end; + } + + dprintf("HKB 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( + "HKB 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( + "HKB 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; +} \ No newline at end of file diff --git a/games/hkbhook/hkb-dll.h b/games/hkbhook/hkb-dll.h new file mode 100644 index 0000000..30f2b13 --- /dev/null +++ b/games/hkbhook/hkb-dll.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "hkbio/hkbio.h" + +struct hkb_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t* opbtn); + void (*get_gamebtns)(uint8_t* gamebtn); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t* rgb); +}; + +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); \ No newline at end of file diff --git a/games/hkbhook/hkbhook.def b/games/hkbhook/hkbhook.def new file mode 100644 index 0000000..1a78311 --- /dev/null +++ b/games/hkbhook/hkbhook.def @@ -0,0 +1,32 @@ +LIBRARY hkbhook + +EXPORTS + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_vfd_set_text + aime_io_vfd_set_state + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + aime_io_nfc_get_mifare_uid + aime_io_nfc_mifare_select + aime_io_nfc_mifare_set_key + aime_io_nfc_mifare_authenticate + aime_io_nfc_mifare_read_block + aime_io_nfc_felica_transact + aime_io_nfc_radio_on + aime_io_nfc_radio_off + aime_io_nfc_to_update_mode + aime_io_nfc_send_hex_data + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + hkb_io_get_api_version + hkb_io_get_gamebtns + hkb_io_get_opbtns + hkb_io_init + hkb_io_poll + hkb_io_led_init + hkb_io_led_set_colors diff --git a/games/hkbhook/io4.c b/games/hkbhook/io4.c new file mode 100644 index 0000000..b66b70f --- /dev/null +++ b/games/hkbhook/io4.c @@ -0,0 +1,106 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "hkbhook/hkb-dll.h" + +#include "util/dprintf.h" + +static HRESULT hkb_io4_poll(void* ctx, struct io4_state* state); +static uint16_t coins; +static bool io_is_terminal; + +static const struct io4_ops hkb_io4_ops = {.poll = hkb_io4_poll,}; + +HRESULT hkb_io4_hook_init(const struct io4_config* cfg, bool is_terminal) { + HRESULT hr; + + assert(hkb_dll.init != NULL); + + io_is_terminal = is_terminal; + + hr = io4_hook_init(cfg, &hkb_io4_ops, NULL, L"hkb", false); + + 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); + assert(hkb_dll.get_opbtns != NULL); + assert(hkb_dll.get_gamebtns != NULL); + + memset(state, 0, sizeof(*state)); + + hr = hkb_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + gamebtn = 0; + + hkb_dll.get_opbtns(&opbtn); + hkb_dll.get_gamebtns(&gamebtn); + + 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) { + coins++; + } + state->chutes[0] = coins << 8; + + if (io_is_terminal) { + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_CANCEL) { + state->buttons[1] |= 1 << 12; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_DECIDE) { + state->buttons[1] |= 1 << 13; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_LEFT) { + state->buttons[1] |= 1 << 0; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_UP) { + state->buttons[1] |= 1 << 15; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_RIGHT) { + state->buttons[1] |= 1 << 1; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_DOWN) { + state->buttons[1] |= 1 << 14; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_LEFT_2) { + state->buttons[1] |= 1 << 3; + } + + if (gamebtn & HKB_IO_GAMEBTN_TERMINAL_RIGHT_2) { + state->buttons[1] |= 1 << 2; + } + } + + return S_OK; +} \ No newline at end of file diff --git a/games/hkbhook/io4.h b/games/hkbhook/io4.h new file mode 100644 index 0000000..78b2220 --- /dev/null +++ b/games/hkbhook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT hkb_io4_hook_init(const struct io4_config* cfg, bool is_terminal); \ No newline at end of file diff --git a/games/hkbhook/meson.build b/games/hkbhook/meson.build new file mode 100644 index 0000000..8adc50a --- /dev/null +++ b/games/hkbhook/meson.build @@ -0,0 +1,32 @@ +shared_library( + 'hkbhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'hkbhook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + board_lib, + gfxhook_lib, + hooklib_lib, + hkbio_lib, + platform_lib, + unityhook_lib, + util_lib, + ], + sources : [ + 'amptw.c', + 'amptw.h', + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'hkb-dll.c', + 'hkb-dll.h', + ], +) diff --git a/games/hkbio/backend.h b/games/hkbio/backend.h new file mode 100644 index 0000000..e30b78c --- /dev/null +++ b/games/hkbio/backend.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "hkbio/hkbio.h" + +struct hkb_io_backend { + void (*get_gamebtns)(uint16_t* gamebtn); + void (*get_opbtns)(uint8_t* opbtn); +}; \ No newline at end of file diff --git a/games/hkbio/config.c b/games/hkbio/config.c new file mode 100644 index 0000000..940a219 --- /dev/null +++ b/games/hkbio/config.c @@ -0,0 +1,67 @@ +#include + +#include +#include +#include + +#include "hkbio/config.h" + +#include + + +void hkb_kb_config_load(struct hkb_kb_config* cfg, const wchar_t* filename) { + cfg->vk_terminal_decide = GetPrivateProfileIntW( + L"keyboard", L"decide", 'A', filename); + cfg->vk_terminal_cancel = GetPrivateProfileIntW( + L"keyboard", L"cancel", 'S', filename); + cfg->vk_terminal_up = GetPrivateProfileIntW(L"keyboard", L"up", VK_UP, + filename); + cfg->vk_terminal_right = GetPrivateProfileIntW( + L"keyboard", L"right", VK_RIGHT, filename); + cfg->vk_terminal_down = GetPrivateProfileIntW( + L"keyboard", L"down", VK_DOWN, filename); + cfg->vk_terminal_left = GetPrivateProfileIntW( + L"keyboard", L"left", VK_LEFT, filename); + cfg->vk_terminal_left_2 = GetPrivateProfileIntW( + L"keyboard", L"left2", 'Q', filename); + cfg->vk_terminal_right_2 = GetPrivateProfileIntW( + L"keyboard", L"right2", 'W', filename); +} + +void hkb_di_config_load(struct hkb_di_config* cfg, const wchar_t* filename) { + + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW(L"dinput", L"deviceName", L"", cfg->device_name, + _countof(cfg->device_name), filename); + + cfg->test = GetPrivateProfileIntW(L"dinput", L"test", 0, filename); + cfg->service = GetPrivateProfileIntW(L"dinput", L"service", 0, filename); + + cfg->up = GetPrivateProfileIntW(L"dinput", L"up", 0, filename); + cfg->right = GetPrivateProfileIntW(L"dinput", L"right", 0, filename); + cfg->down = GetPrivateProfileIntW(L"dinput", L"down", 0, filename); + cfg->left = GetPrivateProfileIntW(L"dinput", L"left", 0, filename); + + cfg->decide = GetPrivateProfileIntW(L"dinput", L"decide", 0, filename); + cfg->cancel = GetPrivateProfileIntW(L"dinput", L"cancel", 0, filename); + cfg->left_2 = GetPrivateProfileIntW(L"dinput", L"left2", 0, filename); + cfg->right_2 = GetPrivateProfileIntW(L"dinput", L"right2", 0, filename); +} + +void hkb_io_config_load(struct hkb_io_config* cfg, const wchar_t* filename) { + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); + cfg->vk_service = + GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + + GetPrivateProfileStringW(L"io4", L"mode", L"keyboard", cfg->mode, + _countof(cfg->mode), filename); + + hkb_kb_config_load(&cfg->kb, filename); + hkb_di_config_load(&cfg->di, filename); +} \ No newline at end of file diff --git a/games/hkbio/config.h b/games/hkbio/config.h new file mode 100644 index 0000000..7eb542e --- /dev/null +++ b/games/hkbio/config.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +struct hkb_kb_config { + uint8_t vk_terminal_up; + uint8_t vk_terminal_right; + uint8_t vk_terminal_down; + uint8_t vk_terminal_left; + uint8_t vk_terminal_left_2; + uint8_t vk_terminal_right_2; + uint8_t vk_terminal_cancel; + uint8_t vk_terminal_decide; +}; + +struct hkb_di_config { + wchar_t device_name[64]; + uint8_t test; + uint8_t service; + uint8_t left_2; + uint8_t right_2; + uint8_t cancel; + uint8_t decide; + uint8_t up; + uint8_t right; + uint8_t down; + uint8_t left; +}; + +struct hkb_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + + wchar_t mode[12]; + struct hkb_kb_config kb; + struct hkb_di_config di; +}; + +void hkb_kb_config_load(struct hkb_kb_config* cfg, const wchar_t* filename); +void hkb_io_config_load(struct hkb_io_config* cfg, const wchar_t* filename); \ No newline at end of file diff --git a/games/hkbio/di-dev.c b/games/hkbio/di-dev.c new file mode 100644 index 0000000..2cb3d76 --- /dev/null +++ b/games/hkbio/di-dev.c @@ -0,0 +1,71 @@ +#include +#include + +#include + +#include "hkbio/di-dev.h" + +#include "util/dprintf.h" + +HRESULT hkb_di_dev_start(IDirectInputDevice8W* dev, HWND wnd) { + assert(dev != NULL); + assert(wnd != NULL); + + HRESULT hr = IDirectInputDevice8_SetCooperativeLevel( + dev, wnd, DISCL_BACKGROUND | DISCL_EXCLUSIVE); + + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); + + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); + + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); + + return hr; + } + + return hr; +} + + +HRESULT hkb_di_dev_poll(IDirectInputDevice8W* dev, HWND wnd, + union hkb_di_state* out) { + MSG msg; + + assert(dev != NULL); + assert(wnd != NULL); + assert(out != NULL); + + memset(out, 0, sizeof(*out)); + + /* Pump our dummy window's message queue just in case DirectInput or an + IHV DirectInput driver somehow relies on it */ + + while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + HRESULT hr = IDirectInputDevice8_GetDeviceState(dev, sizeof(out->st), &out->st); + + if (FAILED(hr)) { + dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); + } + + /* JVS lacks a protocol for reporting hardware errors from poll command + responses, so this ends up returning zeroed input state instead. */ + + return hr; +} \ No newline at end of file diff --git a/games/hkbio/di-dev.h b/games/hkbio/di-dev.h new file mode 100644 index 0000000..f530a87 --- /dev/null +++ b/games/hkbio/di-dev.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include + +union hkb_di_state { + DIJOYSTATE st; + uint8_t bytes[sizeof(DIJOYSTATE)]; +}; + +HRESULT hkb_di_dev_start(IDirectInputDevice8W* dev, HWND wnd); +HRESULT hkb_di_dev_poll(IDirectInputDevice8W* dev, HWND wnd, + union hkb_di_state* out); \ No newline at end of file diff --git a/games/hkbio/di.c b/games/hkbio/di.c new file mode 100644 index 0000000..2386a80 --- /dev/null +++ b/games/hkbio/di.c @@ -0,0 +1,249 @@ +#include +#include + +#include "hkbio/backend.h" +#include "hkbio/config.h" +#include "hkbio/di.h" +#include "hkbio/di-dev.h" +#include "hkbio/wnd.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static HRESULT hkb_di_config_apply(const struct hkb_di_config* cfg); +static BOOL CALLBACK hkb_di_enum_callback(const DIDEVICEINSTANCEW* dev, + void* ctx); +static void hkb_di_get_gamebtns(uint16_t* gamebtn_out); +static void hkb_di_get_opbtns(uint8_t* opbtn_out); +static uint8_t hkb_di_decode_pov(DWORD pov); + +static const struct hkb_io_backend hkb_di_backend = { + .get_gamebtns = hkb_di_get_gamebtns, + .get_opbtns = hkb_di_get_opbtns +}; + +static HWND hkb_di_wnd; +static IDirectInput8W* hkb_di_api; +static IDirectInputDevice8W* hkb_di_dev; +static uint8_t hkb_di_decide; +static uint8_t hkb_di_cancel; +static uint8_t hkb_di_service; +static uint8_t hkb_di_test; +static uint8_t hkb_di_extra_up; +static uint8_t hkb_di_extra_right; +static uint8_t hkb_di_extra_down; +static uint8_t hkb_di_extra_left; +static uint8_t hkb_di_left_2; +static uint8_t hkb_di_right_2; + +static union hkb_di_state state = {0}; + +HRESULT hkb_di_init(const struct hkb_di_config* cfg, HINSTANCE inst, + const struct hkb_io_backend** backend) { + assert(cfg != NULL); + assert(backend != NULL); + + *backend = NULL; + + HRESULT hr = hkb_di_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + hr = hkb_io_wnd_create(inst, &hkb_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + hr = DirectInput8Create(inst, DIRECTINPUT_VERSION, &IID_IDirectInput8W, + (void**)&hkb_di_api, NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: DirectInput8Create failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInput8_EnumDevices(hkb_di_api, DI8DEVCLASS_GAMECTRL, + hkb_di_enum_callback, (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int)hr); + + return hr; + } + + if (hkb_di_dev == NULL) { + dprintf("Stick: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = hkb_di_dev_start(hkb_di_dev, hkb_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + dprintf("DirectInput: Controller initialized\n"); + + *backend = &hkb_di_backend; + + return S_OK; +} + +static HRESULT hkb_di_config_apply(const struct hkb_di_config* cfg) { + if (cfg->decide > 32) { + dprintf("Stick: Invalid start button: %i\n", cfg->decide); + + return E_INVALIDARG; + } + + if (cfg->cancel > 32) { + dprintf("Stick: Invalid home button: %i\n", cfg->cancel); + + return E_INVALIDARG; + } + + if (cfg->left_2 > 32) { + dprintf("Stick: Invalid home button: %i\n", cfg->left_2); + + return E_INVALIDARG; + } + + if (cfg->right_2 > 32) { + dprintf("Stick: Invalid home button: %i\n", cfg->right_2); + + return E_INVALIDARG; + } + + /* Print some debug output to make sure config works... */ + + dprintf("Stick: --- Begin configuration ---\n"); + dprintf("Stick: Device name . . . . : Contains \"%S\"\n", cfg->device_name); + dprintf("Stick: Decision button . : %i\n", cfg->decide); + dprintf("Stick: Cancel button . . : %i\n", cfg->cancel); + dprintf("Stick: Left page button . : %i\n", cfg->left_2); + dprintf("Stick: Right page button : %i\n", cfg->right_2); + dprintf("Stick: Service button . . : %i\n", cfg->service); + dprintf("Stick: Test button . . . : %i\n", cfg->test); + dprintf("Stick: --- End configuration ---\n"); + + hkb_di_decide = cfg->decide; + hkb_di_cancel = cfg->cancel; + hkb_di_left_2 = cfg->left_2; + hkb_di_right_2 = cfg->right_2; + hkb_di_service = cfg->service; + hkb_di_test = cfg->test; + + hkb_di_extra_up = cfg->up; + hkb_di_extra_right = cfg->right; + hkb_di_extra_down = cfg->down; + hkb_di_extra_left = cfg->left; + + return S_OK; +} + +static BOOL CALLBACK hkb_di_enum_callback(const DIDEVICEINSTANCEW* dev, + void* ctx) { + const struct hkb_di_config* cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->device_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Stick: Using DirectInput device \"%S\"\n", dev->tszProductName); + + HRESULT hr = IDirectInput8_CreateDevice(hkb_di_api, &dev->guidInstance, + &hkb_di_dev, NULL); + + if (FAILED(hr)) { + dprintf("Stick: CreateDevice failed: %08x\n", (int)hr); + } + + return DIENUM_STOP; +} + +static void hkb_di_get_gamebtns(uint16_t* gamebtn_out) { + assert(gamebtn_out != NULL); + + HRESULT hr = hkb_di_dev_poll(hkb_di_dev, hkb_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + uint16_t gamebtn = hkb_di_decode_pov(state.st.rgdwPOV[0]); + + if (hkb_di_extra_up && state.st.rgbButtons[hkb_di_extra_up - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_UP; + } + + if (hkb_di_extra_right && state.st.rgbButtons[hkb_di_extra_right - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_RIGHT; + } + + if (hkb_di_extra_down && state.st.rgbButtons[hkb_di_extra_down - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_DOWN; + } + + if (hkb_di_extra_left && state.st.rgbButtons[hkb_di_extra_left - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_LEFT; + } + + if (hkb_di_decide && state.st.rgbButtons[hkb_di_decide - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_DECIDE; + } + + if (hkb_di_cancel && state.st.rgbButtons[hkb_di_cancel - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_CANCEL; + } + + if (hkb_di_left_2 && state.st.rgbButtons[hkb_di_left_2 - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_LEFT_2; + } + + if (hkb_di_right_2 && state.st.rgbButtons[hkb_di_right_2 - 1]) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_RIGHT_2; + } + + *gamebtn_out = gamebtn; +} + +static uint8_t hkb_di_decode_pov(DWORD pov) { + switch (pov) { + case 0: + return HKB_IO_GAMEBTN_TERMINAL_UP; + case 4500: + return HKB_IO_GAMEBTN_TERMINAL_UP | HKB_IO_GAMEBTN_TERMINAL_RIGHT; + case 9000: + return HKB_IO_GAMEBTN_TERMINAL_RIGHT; + case 13500: + return HKB_IO_GAMEBTN_TERMINAL_RIGHT | HKB_IO_GAMEBTN_TERMINAL_DOWN; + case 18000: + return HKB_IO_GAMEBTN_TERMINAL_DOWN; + case 22500: + return HKB_IO_GAMEBTN_TERMINAL_DOWN | HKB_IO_GAMEBTN_TERMINAL_LEFT; + case 27000: + return HKB_IO_GAMEBTN_TERMINAL_LEFT; + case 31500: + return HKB_IO_GAMEBTN_TERMINAL_LEFT | HKB_IO_GAMEBTN_TERMINAL_UP; + default: + return 0; + } +} + +static void hkb_di_get_opbtns(uint8_t* opbtn_out) { + uint8_t opbtn = 0; + + if (hkb_di_service && state.st.rgbButtons[hkb_di_service - 1]) { + opbtn |= HKB_IO_OPBTN_SERVICE; + } + if (hkb_di_test && state.st.rgbButtons[hkb_di_test - 1]) { + opbtn |= HKB_IO_OPBTN_TEST; + } + + *opbtn_out = opbtn; +} \ No newline at end of file diff --git a/games/hkbio/di.h b/games/hkbio/di.h new file mode 100644 index 0000000..a900ef3 --- /dev/null +++ b/games/hkbio/di.h @@ -0,0 +1,7 @@ +#pragma once + +#include "hkbio/backend.h" +#include "hkbio/config.h" + +HRESULT hkb_di_init(const struct hkb_di_config* cfg, HINSTANCE inst, + const struct hkb_io_backend** backend); \ No newline at end of file diff --git a/games/hkbio/hkbio.c b/games/hkbio/hkbio.c new file mode 100644 index 0000000..8e34e3a --- /dev/null +++ b/games/hkbio/hkbio.c @@ -0,0 +1,103 @@ +#include + +#include + +#include "hkbio/hkbio.h" + +#include + +#include "hkbio/keyboard.h" +#include "hkbio/di.h" +#include "hkbio/config.h" +#include "util/dprintf.h" +#include "util/env.h" +#include "util/str.h" + +static uint8_t hkb_opbtn; +static uint8_t hkb_gamebtn; +static int16_t hkb_stick_x; +static int16_t hkb_stick_y; +static struct hkb_io_config hkb_io_cfg; +static const struct hkb_io_backend* hkb_io_backend; +static bool hkb_io_coin; + +uint16_t hkb_io_get_api_version(void) { + return 0x0100; +} + +HRESULT hkb_io_init(void) { + hkb_io_config_load(&hkb_io_cfg, get_config_path()); + + HRESULT hr; + + HINSTANCE inst = GetModuleHandleW(NULL); + + if (inst == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("GetModuleHandleW failed: %lx\n", hr); + + return hr; + } + + if (wstr_ieq(hkb_io_cfg.mode, L"keyboard")) { + hr = hkb_kb_init(&hkb_io_cfg.kb, &hkb_io_backend); + } else if (wstr_ieq(hkb_io_cfg.mode, L"dinput")) { + hr = hkb_di_init(&hkb_io_cfg.di, inst, &hkb_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("HKB IO: Invalid IO mode \"%S\", use keyboard or dinput\n", + hkb_io_cfg.mode); + } + + return hr; +} + +HRESULT hkb_io_poll(void) { + assert(hkb_io_backend != NULL); + + hkb_opbtn = 0; + hkb_gamebtn = 0; + hkb_stick_x = 0; + hkb_stick_y = 0; + + if (GetAsyncKeyState(hkb_io_cfg.vk_test) & 0x8000) { + hkb_opbtn |= HKB_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(hkb_io_cfg.vk_service) & 0x8000) { + hkb_opbtn |= HKB_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(hkb_io_cfg.vk_coin) & 0x8000) { + if (!hkb_io_coin) { + hkb_io_coin = true; + hkb_opbtn |= HKB_IO_OPBTN_COIN; + } + } else { + hkb_io_coin = false; + } + + return S_OK; +} + +void hkb_io_get_opbtns(uint8_t* opbtn) { + assert(hkb_io_backend != NULL); + assert(opbtn != NULL); + + hkb_io_backend->get_opbtns(opbtn); +} + +void hkb_io_get_gamebtns(uint16_t* btn) { + assert(hkb_io_backend != NULL); + assert(btn != NULL); + + hkb_io_backend->get_gamebtns(btn); +} + +HRESULT hkb_io_led_init(void) { + return S_OK; +} + +void hkb_io_led_set_colors(uint8_t board, uint8_t* rgb) { + return; +} \ No newline at end of file diff --git a/games/hkbio/hkbio.h b/games/hkbio/hkbio.h new file mode 100644 index 0000000..2b5a00f --- /dev/null +++ b/games/hkbio/hkbio.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include + +enum { + HKB_IO_OPBTN_TEST = 0x01, + HKB_IO_OPBTN_SERVICE = 0x02, + HKB_IO_OPBTN_COIN = 0x04, +}; + +enum { + HKB_IO_GAMEBTN_TERMINAL_LEFT = 0x01, + HKB_IO_GAMEBTN_TERMINAL_UP = 0x02, + HKB_IO_GAMEBTN_TERMINAL_RIGHT = 0x04, + HKB_IO_GAMEBTN_TERMINAL_DOWN = 0x08, + HKB_IO_GAMEBTN_TERMINAL_LEFT_2 = 0x10, + HKB_IO_GAMEBTN_TERMINAL_RIGHT_2 = 0x20, + HKB_IO_GAMEBTN_TERMINAL_DECIDE = 0x40, + HKB_IO_GAMEBTN_TERMINAL_CANCEL = 0x80, +}; + +/* Get the version of the Chrono Regalia 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(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + HKB_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 hkb_io_get_opbtns(uint8_t* opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + HKB_IO_GAMEBTN enum above: this contains bit mask definitions for button + states returned in *gamebtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void hkb_io_get_gamebtns(uint16_t* gamebtn); + +/* Get the position of the cabinet stick as of the last poll. The center + position should be equal to or close to 32767. + + Minimum API version: 0x0100 */ + +void hkb_io_get_analogs(int16_t* stick_x, int16_t* stick_y); + +/* Initialize LED emulation. This function will be called before any + other hkb_io_led_*() function calls. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. */ + +HRESULT hkb_io_led_init(void); + +/* Update the RGB LEDs. + + Exact layout is TBD. */ + +void hkb_io_led_set_colors(uint8_t board, uint8_t* rgb); \ No newline at end of file diff --git a/games/hkbio/keyboard.c b/games/hkbio/keyboard.c new file mode 100644 index 0000000..c8fb3ee --- /dev/null +++ b/games/hkbio/keyboard.c @@ -0,0 +1,70 @@ +#include + +#include +#include + +#include "hkbio/backend.h" +#include "hkbio/config.h" +#include "hkbio/hkbio.h" +#include "hkbio/keyboard.h" + +#include "util/dprintf.h" + +static void hkb_kb_get_gamebtns(uint16_t* gamebtn_out); + +static const struct hkb_io_backend hkb_kb_backend = { + .get_gamebtns = hkb_kb_get_gamebtns}; + +static struct hkb_kb_config config; + +HRESULT hkb_kb_init(const struct hkb_kb_config* cfg, + const struct hkb_io_backend** backend) { + assert(cfg != NULL); + assert(backend != NULL); + + dprintf("Keyboard: Using keyboard input\n"); + *backend = &hkb_kb_backend; + config = *cfg; + + return S_OK; +} + +static void hkb_kb_get_gamebtns(uint16_t* gamebtn_out) { + assert(gamebtn_out != NULL); + + uint8_t gamebtn = 0; + + if (GetAsyncKeyState(config.vk_terminal_cancel) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_CANCEL; + } + + if (GetAsyncKeyState(config.vk_terminal_decide) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_DECIDE; + } + + if (GetAsyncKeyState(config.vk_terminal_up) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_UP; + } + + if (GetAsyncKeyState(config.vk_terminal_right) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_RIGHT; + } + + if (GetAsyncKeyState(config.vk_terminal_down) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_DOWN; + } + + if (GetAsyncKeyState(config.vk_terminal_left) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_LEFT; + } + + if (GetAsyncKeyState(config.vk_terminal_left_2) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_LEFT_2; + } + + if (GetAsyncKeyState(config.vk_terminal_right_2) & 0x8000) { + gamebtn |= HKB_IO_GAMEBTN_TERMINAL_RIGHT_2; + } + + *gamebtn_out = gamebtn; +} \ No newline at end of file diff --git a/games/hkbio/keyboard.h b/games/hkbio/keyboard.h new file mode 100644 index 0000000..567cd62 --- /dev/null +++ b/games/hkbio/keyboard.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "hkbio/backend.h" +#include "hkbio/config.h" + +HRESULT hkb_kb_init(const struct hkb_kb_config* cfg, + const struct hkb_io_backend** backend); \ No newline at end of file diff --git a/games/hkbio/meson.build b/games/hkbio/meson.build new file mode 100644 index 0000000..712a69f --- /dev/null +++ b/games/hkbio/meson.build @@ -0,0 +1,24 @@ +hkbio_lib = static_library( + 'hkbio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + dinput8_lib, + ], + sources : [ + 'hkbio.c', + 'hkbio.h', + 'config.c', + 'config.h', + 'backend.h', + 'keyboard.c', + 'keyboard.h', + 'di.c', + 'di.h', + 'di-dev.c', + 'di-dev.h', + 'wnd.c', + 'wnd.h', + ], +) diff --git a/games/hkbio/wnd.c b/games/hkbio/wnd.c new file mode 100644 index 0000000..8f5e770 --- /dev/null +++ b/games/hkbio/wnd.c @@ -0,0 +1,67 @@ +#include + +#include +#include + +#include "util/dprintf.h" + +/* DirectInput requires a window for correct initialization (and also force + feedback), so this source file provides some utilities for creating a + generic message-only window. */ + +static LRESULT WINAPI hkb_io_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); + +HRESULT hkb_io_wnd_create(HINSTANCE inst, HWND* out) { + HRESULT hr; + WNDCLASSEXW wcx; + HWND hwnd; + + assert(inst != NULL); /* We are not an EXE */ + assert(out != NULL); + + *out = NULL; + + memset(&wcx, 0, sizeof(wcx)); + wcx.cbSize = sizeof(wcx); + wcx.lpfnWndProc = hkb_io_wnd_proc; + wcx.hInstance = inst; + wcx.lpszClassName = L"hkbIO"; + + ATOM atom = RegisterClassExW(&wcx); + + if (atom == 0) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("hkbIO: RegisterClassExW failed: %08x\n", (int)hr); + + goto fail; + } + + hwnd = CreateWindowExW(0, (wchar_t*)(intptr_t)atom, L"", 0, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, NULL, inst, NULL); + + if (hwnd == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("hkbIO: CreateWindowExW failed: %08x\n", (int)hr); + + goto fail; + } + + *out = hwnd; + + return S_OK; + +fail: + UnregisterClassW((wchar_t*)(intptr_t)atom, inst); + + return hr; +} + +static LRESULT WINAPI hkb_io_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) { + switch (msg) { + default: + return DefWindowProcW(hwnd, msg, wparam, lparam); + } +} \ No newline at end of file diff --git a/games/hkbio/wnd.h b/games/hkbio/wnd.h new file mode 100644 index 0000000..c3d0bd3 --- /dev/null +++ b/games/hkbio/wnd.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +HRESULT hkb_io_wnd_create(HINSTANCE inst, HWND* out); \ No newline at end of file diff --git a/meson.build b/meson.build index 7d35c79..c52d8f2 100644 --- a/meson.build +++ b/meson.build @@ -140,6 +140,7 @@ subdir('games/kemonoio') subdir('games/apm3io') subdir('games/sekitoio') subdir('games/ektio') +subdir('games/hkbio') subdir('games/chunihook') subdir('games/divahook') @@ -159,3 +160,4 @@ subdir('games/kemonohook') subdir('games/apm3hook') subdir('games/ekthook') subdir('games/sekitohook') +subdir('games/hkbhook')