From f33fe0f2aebf4fbdaefe1f5ad78e9fdbef140c90 Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:14:00 +0200 Subject: [PATCH] sekito: add hook --- Package.mk | 21 +++ common/hooklib/printer_cx.c | 1 + dist/sekito/card_player.html | 224 ++++++++++++++++++++++++++++ dist/sekito/config_hook.json | 13 ++ dist/sekito/launch_satellite.bat | 15 ++ dist/sekito/launch_terminal.bat | 19 +++ dist/sekito/segatools_satellite.ini | 174 +++++++++++++++++++++ dist/sekito/segatools_terminal.ini | 137 +++++++++++++++++ games/sekitohook/config.c | 107 +++++++++++++ games/sekitohook/config.h | 37 +++++ games/sekitohook/dllmain.c | 178 ++++++++++++++++++++++ games/sekitohook/io4.c | 212 ++++++++++++++++++++++++++ games/sekitohook/io4.h | 17 +++ games/sekitohook/meson.build | 31 ++++ games/sekitohook/sekito-dll.c | 118 +++++++++++++++ games/sekitohook/sekito-dll.h | 24 +++ games/sekitohook/sekitohook.def | 61 ++++++++ games/sekitoio/backend.h | 10 ++ games/sekitoio/config.c | 77 ++++++++++ games/sekitoio/config.h | 62 ++++++++ games/sekitoio/keyboard.c | 164 ++++++++++++++++++++ games/sekitoio/keyboard.h | 8 + games/sekitoio/meson.build | 18 +++ games/sekitoio/sekitoio.c | 108 ++++++++++++++ games/sekitoio/sekitoio.h | 103 +++++++++++++ meson.build | 3 +- 26 files changed, 1941 insertions(+), 1 deletion(-) create mode 100644 dist/sekito/card_player.html create mode 100644 dist/sekito/config_hook.json create mode 100644 dist/sekito/launch_satellite.bat create mode 100644 dist/sekito/launch_terminal.bat create mode 100644 dist/sekito/segatools_satellite.ini create mode 100644 dist/sekito/segatools_terminal.ini create mode 100644 games/sekitohook/config.c create mode 100644 games/sekitohook/config.h create mode 100644 games/sekitohook/dllmain.c create mode 100644 games/sekitohook/io4.c create mode 100644 games/sekitohook/io4.h create mode 100644 games/sekitohook/meson.build create mode 100644 games/sekitohook/sekito-dll.c create mode 100644 games/sekitohook/sekito-dll.h create mode 100644 games/sekitohook/sekitohook.def create mode 100644 games/sekitoio/backend.h create mode 100644 games/sekitoio/config.c create mode 100644 games/sekitoio/config.h create mode 100644 games/sekitoio/keyboard.c create mode 100644 games/sekitoio/keyboard.h create mode 100644 games/sekitoio/meson.build create mode 100644 games/sekitoio/sekitoio.c create mode 100644 games/sekitoio/sekitoio.h diff --git a/Package.mk b/Package.mk index a96bd0c..5e597a8 100644 --- a/Package.mk +++ b/Package.mk @@ -275,6 +275,27 @@ $(BUILD_DIR_ZIP)/ekt.zip: $(V)strip $(BUILD_DIR_ZIP)/ekt/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/ekt ; zip -r ../ekt.zip * +$(BUILD_DIR_ZIP)/sekito.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/sekito + $(V)mkdir -p $(BUILD_DIR_ZIP)/sekito/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject_x86.exe \ + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject_x64.exe \ + $(BUILD_DIR_GAMES_64)/sekitohook/sekitohook_x64.dll \ + $(BUILD_DIR_GAMES_32)/sekitohook/sekitohook_x86.dll \ + $(DIST_DIR)/sekito/segatools_terminal.ini \ + $(DIST_DIR)/sekito/segatools_satellite.ini \ + $(DIST_DIR)/sekito/launch_terminal.bat \ + $(DIST_DIR)/sekito/launch_satellite.bat \ + $(DIST_DIR)/sekito/card_player.html \ + $(DIST_DIR)/sekito/config_hook.json \ + $(BUILD_DIR_ZIP)/sekito + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/sekito/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/sekito/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/sekito ; zip -r ../sekito.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/config \ $(DOC_DIR)/chunihook.md \ diff --git a/common/hooklib/printer_cx.c b/common/hooklib/printer_cx.c index 0275197..2372a12 100644 --- a/common/hooklib/printer_cx.c +++ b/common/hooklib/printer_cx.c @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/dist/sekito/card_player.html b/dist/sekito/card_player.html new file mode 100644 index 0000000..98e4e08 --- /dev/null +++ b/dist/sekito/card_player.html @@ -0,0 +1,224 @@ + + + + + Taisen Card Field + + + + + +
+ +
+
+
Please wait...
+
+ +
+
+ + \ No newline at end of file diff --git a/dist/sekito/config_hook.json b/dist/sekito/config_hook.json new file mode 100644 index 0000000..6319f45 --- /dev/null +++ b/dist/sekito/config_hook.json @@ -0,0 +1,13 @@ +{ + "common": { + "language": "english" + }, + "network": { + "property": { + "dhcp": true + } + }, + "allnet_auth": { + "type": "1.0" + } +} \ No newline at end of file diff --git a/dist/sekito/launch_satellite.bat b/dist/sekito/launch_satellite.bat new file mode 100644 index 0000000..e8190c5 --- /dev/null +++ b/dist/sekito/launch_satellite.bat @@ -0,0 +1,15 @@ +@echo off +set SEGATOOLS_CONFIG_PATH=.\segatools_satellite.ini + +pushd %~dp0 + +start "AM Daemon" /min inject_x64 -d -k sekitohook_x64.dll amdaemon.exe -c config_new.json -c config_video_single.json -c config_video_multi.json -c config_input_sate.json -c config_input_terminal.json -c config_input_terminal_exp.json -c config_hook.json + +inject_x86 -d -k sekitohook_x86.dll appSate.exe + +taskkill /f /im appSate.exe > nul 2>&1 +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/sekito/launch_terminal.bat b/dist/sekito/launch_terminal.bat new file mode 100644 index 0000000..44dcc44 --- /dev/null +++ b/dist/sekito/launch_terminal.bat @@ -0,0 +1,19 @@ +@echo off +set SEGATOOLS_CONFIG_PATH=.\segatools_satellite.ini + +pushd %~dp0 + +start "AM Daemon" /min inject_x64 -d -k sekitohook_x64.dll amdaemon.exe -c config_new.json -c config_video_single.json -c config_video_multi.json -c config_input_sate.json -c config_input_terminal.json -c config_input_terminal_exp.json -c config_hook.json + +call server\server_start.bat + +inject_x86 -d -k sekitohook_x86.dll appTerminal.exe + +taskkill /f /im appTerminal.exe > nul 2>&1 +taskkill /f /im amdaemon.exe > nul 2>&1 + +call server\server_stop.bat + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/sekito/segatools_satellite.ini b/dist/sekito/segatools_satellite.ini new file mode 100644 index 0000000..19d3451 --- /dev/null +++ b/dist/sekito/segatools_satellite.ini @@ -0,0 +1,174 @@ +; ----------------------------------------------------------------------------- +; 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 + +; ----------------------------------------------------------------------------- +; 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 + +; ----------------------------------------------------------------------------- +; 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. +; 1 = Terminal (TM) +; 2 = Satellite (ST) +platformId=AAV2 + +[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 +; ----------------------------------------------------------------------------- + +[flatPanelReader] +; Enable the Y3 board emulation. +enable=1 + +[y3ws] +; Enable the Y3 websocket server. +enable=1 +; Set the TCP port on which the Y3 websocket server runs. +port=3594 + +; ----------------------------------------------------------------------------- +; 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= + +[ektio] +; To use a custom Eiketsu Taisen IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard/gamepad input. +path= + +[y3io] +; To use a custom Y3 IO DLL enter its path here. +; Leave empty if you want to use ... TBA ... +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 + +; SW1. Default is the 4 key. +sw1=0x34 +; SW2. Default is the 5 key. +sw2=0x35 + +; Input API selection for IO4 input emulator. +; For now only "keyboard" is supported. +mode=keyboard + +[keyboard] + +menu=0x41 +start=0x42 +stratagem=0x43 +stratagem_lock=0x44 +hougu=0x45 +ryuuha=0x46 + +tenkey_0=0x60 +tenkey_1=0x61 +tenkey_2=0x62 +tenkey_3=0x63 +tenkey_4=0x64 +tenkey_5=0x65 +tenkey_6=0x66 +tenkey_7=0x67 +tenkey_8=0x68 +tenkey_9=0x69 +tenkey_clear=0x6E +tenkey_enter=0x0D + +vol_up=0x21 +vol_down=0x22 + +trackball_up=0x26 +trackball_right=0x27 +trackball_down=0x28 +trackball_left=0x25 +speed_modifier=10 diff --git a/dist/sekito/segatools_terminal.ini b/dist/sekito/segatools_terminal.ini new file mode 100644 index 0000000..7f68cb3 --- /dev/null +++ b/dist/sekito/segatools_terminal.ini @@ -0,0 +1,137 @@ +; ----------------------------------------------------------------------------- +; 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 + +; ----------------------------------------------------------------------------- +; 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 + +; ----------------------------------------------------------------------------- +; 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. +; 1 = Terminal (TM) +; 2 = Satellite (ST) +platformId=AAV1 + +[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 + +; ----------------------------------------------------------------------------- +; 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= + +[ektio] +; To use a custom Eiketsu Taisen 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 + +; SW1. Default is the 4 key. +sw1=0x34 +; SW2. Default is the 5 key. +sw2=0x35 + +; Input API selection for IO4 input emulator. +; For now only "keyboard" is supported. +mode=keyboard + +[keyboard] + +cancel=0x53 +decide=0x41 + +up=0x26 +right=0x27 +down=0x28 +left=0x25 + +left_2=0x4F +right_2=0x57 diff --git a/games/sekitohook/config.c b/games/sekitohook/config.c new file mode 100644 index 0000000..a98ace9 --- /dev/null +++ b/games/sekitohook/config.c @@ -0,0 +1,107 @@ +#include +#include + +#include "board/config.h" + +#include "sekitohook/config.h" +#include "sekitohook/sekito-dll.h" + +#include "hooklib/config.h" + +#include "platform/config.h" + +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 sekito_dll_config_load( + struct sekito_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"sekitoio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + + +void sekito_hook_config_load( + struct sekito_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); + io4_config_load(&cfg->io4, filename); + dvd_config_load(&cfg->dvd, filename); + led15093_config_load(&cfg->led15093, filename); + y3_config_load(&cfg->y3, filename); + printer_chc_config_load(&cfg->printer, filename); + unity_config_load(&cfg->unity, filename); + sekito_dll_config_load(&cfg->dll, filename); +} diff --git a/games/sekitohook/config.h b/games/sekitohook/config.h new file mode 100644 index 0000000..54ebd36 --- /dev/null +++ b/games/sekitohook/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "board/sg-reader.h" +#include "board/config.h" +#include "board/led15093.h" + +#include "sekitohook/sekito-dll.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" +#include "hooklib/printer_chc.h" + +#include "platform/config.h" + +#include "unityhook/config.h" + +struct sekito_hook_config { + struct platform_config platform; + struct aime_config aime; + struct io4_config io4; + struct dvd_config dvd; + struct led15093_config led15093; + struct y3_config y3; + struct sekito_dll_config dll; + struct unity_config unity; + struct printer_chc_config printer; +}; + +void sekito_dll_config_load( + struct sekito_dll_config *cfg, + const wchar_t *filename); + +void sekito_hook_config_load( + struct sekito_hook_config *cfg, + const wchar_t *filename); diff --git a/games/sekitohook/dllmain.c b/games/sekitohook/dllmain.c new file mode 100644 index 0000000..5269556 --- /dev/null +++ b/games/sekitohook/dllmain.c @@ -0,0 +1,178 @@ +/* + "Sangokushi Taisen" (sekito) hook + + Devices + + USB: 837-15257-01 "Type 4" I/O Board + COM12: 837-15396 "Gen 3" Aime Reader + + [Satellite] + + USB: Printer + COM1: 837-15093-06 LED Controller Board + COM11: Printer Camera + + [Terminal] + + COM1: 837-15396 "Gen 3" Aime Reader + COM3: 837-15093-06 LED Controller Board +*/ + +#include + +#include + +#include "sekito-dll.h" +#include "board/sg-reader.h" +#include "board/led15093.h" + +#include "hook/process.h" +#include "hook/iohook.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "sekitohook/config.h" +#include "sekitohook/io4.h" +#include "hooklib/printer_cx.h" + +#include "platform/platform.h" + +#include "unityhook/hook.h" + +#include "util/dprintf.h" +#include "util/env.h" +#include "hooklib/y3-dll.h" +#include "hooklib/y3.h" + +static HMODULE sekito_hook_mod; +static process_entry_t sekito_startup; +static struct sekito_hook_config sekito_hook_cfg; + +static void unity_hook_callback(HMODULE hmodule, const wchar_t* p) { + netenv_hook_apply_hooks(hmodule); +} + +static DWORD CALLBACK sekito_pre_startup(void) +{ + HRESULT hr; + bool is_terminal; + + dprintf("--- Begin sekito_pre_startup ---\n"); + + /* Load config */ + + sekito_hook_config_load(&sekito_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&sekito_hook_cfg.dvd, sekito_hook_mod); + serial_hook_init(); + + /* Hook external DLL APIs */ + + hr = y3_hook_init(&sekito_hook_cfg.y3, sekito_hook_mod, get_config_path()); + + if (FAILED(hr)) { + goto fail; + } + + printer_chc_hook_init(&sekito_hook_cfg.printer, 0, sekito_hook_mod); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &sekito_hook_cfg.platform, + "SDDD", + "AAV2", + sekito_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize Terminal/Satellite hooks */ + if (strncmp(sekito_hook_cfg.platform.nusec.platform_id, "AAV1", 4) == 0) { + // Terminal + is_terminal = true; + } else if (strncmp(sekito_hook_cfg.platform.nusec.platform_id, "AAV2", 4) == 0) { + // Satellite + is_terminal = false; + } else { + // Unknown + dprintf("Unknown platform ID: %s\n", sekito_hook_cfg.platform.nusec.platform_id); + goto fail; + } + + // LED: terminal uses COM 3 and satellite use COM 2 + unsigned int led_port_no[2] = {is_terminal ? 3 : 2, 0}; + + hr = sekito_dll_init(&sekito_hook_cfg.dll, sekito_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sekito_io4_hook_init(&sekito_hook_cfg.io4, is_terminal); + + if (FAILED(hr)) { + goto fail; + } + + hr = led15093_hook_init(&sekito_hook_cfg.led15093, + sekito_dll.led_init, sekito_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&sekito_hook_cfg.aime, 12, 3, + sekito_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + if (is_terminal) { + + hr = sg_reader_hook_init(&sekito_hook_cfg.aime, 1, 3, + sekito_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + } + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End sekito_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return sekito_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + sekito_hook_mod = mod; + + hr = process_hijack_startup(sekito_pre_startup, &sekito_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/sekitohook/io4.c b/games/sekitohook/io4.c new file mode 100644 index 0000000..4103ef1 --- /dev/null +++ b/games/sekitohook/io4.c @@ -0,0 +1,212 @@ +#include "io4.h" + +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "sekitohook/sekito-dll.h" + +#include "util/dprintf.h" + +static HRESULT sekito_io4_poll(void *ctx, struct io4_state *state); +static uint16_t coins; + +static const struct io4_ops sekito_io4_ops = { + .poll = sekito_io4_poll, +}; + +static bool io_is_terminal; + +HRESULT sekito_io4_hook_init(const struct io4_config *cfg, bool is_terminal) +{ + HRESULT hr; + + assert(sekito_dll.init != NULL); + + hr = io4_hook_init(cfg, &sekito_io4_ops, NULL); + + io_is_terminal = is_terminal; + + if (FAILED(hr)) { + return hr; + } + + return sekito_dll.init(); +} + +static HRESULT sekito_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint16_t x, y; + uint32_t gamebtn; + HRESULT hr; + + assert(sekito_dll.poll != NULL); + assert(sekito_dll.get_opbtns != NULL); + assert(sekito_dll.get_gamebtns != NULL); + assert(sekito_dll.get_trackball_position != NULL); + + memset(state, 0, sizeof(*state)); + + hr = sekito_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + gamebtn = 0; + x = 0; + y = 0; + + sekito_dll.get_opbtns(&opbtn); + sekito_dll.get_gamebtns(&gamebtn); + sekito_dll.get_trackball_position(&x, &y); + + if (opbtn & SEKITO_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & SEKITO_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & SEKITO_IO_OPBTN_SW1) { + state->buttons[0] |= 1 << 10; + } + + if (opbtn & SEKITO_IO_OPBTN_SW2) { + state->buttons[0] |= 1 << 11; + } + + if (opbtn & SEKITO_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + if (!io_is_terminal) { + if (gamebtn & SEKITO_IO_GAMEBTN_HOUGU) { + state->buttons[1] |= 1 << 6; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_MENU) { + state->buttons[1] |= 1 << 4; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_START) { + state->buttons[0] |= 1 << 15; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_STRATAGEM) { + state->buttons[1] |= 1 << 7; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_STRATAGEM_LOCK) { + state->buttons[1] |= 1 << 5; + } + + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_0) { + state->buttons[0] |= SEKITO_NUMPAD_C2; + state->buttons[0] |= SEKITO_NUMPAD_R4; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_1) { + state->buttons[0] |= SEKITO_NUMPAD_C1; + state->buttons[0] |= SEKITO_NUMPAD_R1; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_2) { + state->buttons[0] |= SEKITO_NUMPAD_C2; + state->buttons[0] |= SEKITO_NUMPAD_R1; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_3) { + state->buttons[0] |= SEKITO_NUMPAD_C3; + state->buttons[0] |= SEKITO_NUMPAD_R1; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_4) { + state->buttons[0] |= SEKITO_NUMPAD_C1; + state->buttons[0] |= SEKITO_NUMPAD_R2; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_5) { + state->buttons[0] |= SEKITO_NUMPAD_C2; + state->buttons[0] |= SEKITO_NUMPAD_R2; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_6) { + state->buttons[0] |= SEKITO_NUMPAD_C3; + state->buttons[0] |= SEKITO_NUMPAD_R2; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_7) { + state->buttons[0] |= SEKITO_NUMPAD_C1; + state->buttons[0] |= SEKITO_NUMPAD_R3; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_8) { + state->buttons[0] |= SEKITO_NUMPAD_C2; + state->buttons[0] |= SEKITO_NUMPAD_R3; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_9) { + state->buttons[0] |= SEKITO_NUMPAD_C3; + state->buttons[0] |= SEKITO_NUMPAD_R3; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_CLEAR) { + state->buttons[0] |= SEKITO_NUMPAD_C1; + state->buttons[0] |= SEKITO_NUMPAD_R4; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_NUMPAD_ENTER) { + state->buttons[0] |= SEKITO_NUMPAD_C3; + state->buttons[0] |= SEKITO_NUMPAD_R4; + } + + if (io_is_terminal) { + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_CANCEL) { + state->buttons[1] |= 1 << 0; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_DECIDE) { + state->buttons[1] |= 1 << 1; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_LEFT) { + state->buttons[0] |= 1 << 3; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_UP) { + state->buttons[0] |= 1 << 5; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_RIGHT) { + state->buttons[0] |= 1 << 2; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_DOWN) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_LEFT_2) { + state->buttons[1] |= 1 << 3; + } + + if (gamebtn & SEKITO_IO_GAMEBTN_TERMINAL_RIGHT_2) { + state->buttons[1] |= 1 << 2; + } + } + + state->spinners[2] = x; + state->spinners[3] = y; + + return S_OK; +} diff --git a/games/sekitohook/io4.h b/games/sekitohook/io4.h new file mode 100644 index 0000000..78fa910 --- /dev/null +++ b/games/sekitohook/io4.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include "board/io4.h" + +enum { + SEKITO_NUMPAD_R1 = 1 << 9, + SEKITO_NUMPAD_R2 = 1 << 8, + SEKITO_NUMPAD_R3 = 1 << 7, + SEKITO_NUMPAD_R4 = 1 << 6, + SEKITO_NUMPAD_C1 = 1 << 5, + SEKITO_NUMPAD_C2 = 1 << 4, + SEKITO_NUMPAD_C3 = 1 << 3 +}; + +HRESULT sekito_io4_hook_init(const struct io4_config *cfg, bool is_terminal); diff --git a/games/sekitohook/meson.build b/games/sekitohook/meson.build new file mode 100644 index 0000000..96a9403 --- /dev/null +++ b/games/sekitohook/meson.build @@ -0,0 +1,31 @@ +shared_library( + 'sekitohook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'sekitohook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep') + ], + link_with : [ + aimeio_lib, + board_lib, + sekitoio_lib, + hooklib_lib, + jvs_lib, + platform_lib, + unityhook_lib, + util_lib, + y3io_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'sekito-dll.c', + 'sekito-dll.h', + ], +) diff --git a/games/sekitohook/sekito-dll.c b/games/sekitohook/sekito-dll.c new file mode 100644 index 0000000..a0d9b2e --- /dev/null +++ b/games/sekitohook/sekito-dll.c @@ -0,0 +1,118 @@ +#include + +#include +#include + +#include "sekitohook/sekito-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym sekito_dll_syms[] = { + { + .sym = "sekito_io_init", + .off = offsetof(struct sekito_dll, init), + }, { + .sym = "sekito_io_poll", + .off = offsetof(struct sekito_dll, poll), + }, { + .sym = "sekito_io_get_opbtns", + .off = offsetof(struct sekito_dll, get_opbtns), + }, { + .sym = "sekito_io_get_gamebtns", + .off = offsetof(struct sekito_dll, get_gamebtns), + }, { + .sym = "sekito_io_get_trackball_position", + .off = offsetof(struct sekito_dll, get_trackball_position), + }, { + .sym = "sekito_io_led_init", + .off = offsetof(struct sekito_dll, led_init), + }, { + .sym = "sekito_io_led_set_colors", + .off = offsetof(struct sekito_dll, led_set_leds), + } +}; + +struct sekito_dll sekito_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 sekito_dll_init(const struct sekito_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("Sekito IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Sekito IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "sekito_io_get_api_version"); + + if (get_api_version != NULL) { + sekito_dll.api_version = get_api_version(); + } else { + sekito_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose sekito_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (sekito_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Sekito IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + sekito_dll.api_version); + + goto end; + } + + sym = sekito_dll_syms; + hr = dll_bind(&sekito_dll, src, &sym, _countof(sekito_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Sekito 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/games/sekitohook/sekito-dll.h b/games/sekitohook/sekito-dll.h new file mode 100644 index 0000000..1f2560f --- /dev/null +++ b/games/sekitohook/sekito-dll.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "sekitoio/sekitoio.h" + +struct sekito_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint32_t *gamebtn); + void (*get_trackball_position)(uint16_t *x, uint16_t *y); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); +}; + +struct sekito_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct sekito_dll sekito_dll; + +HRESULT sekito_dll_init(const struct sekito_dll_config *cfg, HINSTANCE self); diff --git a/games/sekitohook/sekitohook.def b/games/sekitohook/sekitohook.def new file mode 100644 index 0000000..e680aee --- /dev/null +++ b/games/sekitohook/sekitohook.def @@ -0,0 +1,61 @@ +LIBRARY taisenhook + +EXPORTS + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + sekito_io_get_api_version + sekito_io_get_gamebtns + sekito_io_get_opbtns + sekito_io_get_trackball_position + sekito_io_init + sekito_io_poll + sekito_io_led_init + sekito_io_led_set_colors + y3_io_get_api_version + y3_io_init + y3_io_close + y3_io_get_cards + API_DLLVersion @1 + API_GetLastError @2 + API_GetErrorMessage @3 + API_Connect @4 + API_Close @5 + API_Start @6 + API_Stop @7 + API_GetFirmVersion @8 + API_GetFirmName @9 + API_GetTargetCode @10 + API_GetStatus @11 + API_GetCounter @12 + API_ClearError @13 + API_Reset @14 + API_GetCardInfo @15 + API_GetCardInfoCharSize @16 + API_SetDevice @17 + API_SetCommand @18 + API_FirmwareUpdate @19 + API_Calibration @20 + API_GetCalibrationResult @21 + API_GetProcTime @22 + API_GetMemStatus @23 + API_GetMemCounter @24 + API_SetSysControl @25 + API_GetSysControl @26 + API_SetParameter @27 + API_GetParameter @28 + API_TestReset @29 + API_DebugReset @30 + API_GetBoardType @31 + API_GetCardDataSize @32 + API_GetFirmDate @33 + API_SystemCommand @34 + API_CalcCheckSum @35 + API_GetCheckSumResult @36 + API_BlockRead @37 + API_GetBlockReadResult @38 + API_BlockWrite @39 + API_GetDebugParam @40 diff --git a/games/sekitoio/backend.h b/games/sekitoio/backend.h new file mode 100644 index 0000000..6c84ca5 --- /dev/null +++ b/games/sekitoio/backend.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "sekitoio/sekitoio.h" + +struct sekito_io_backend { + void (*get_gamebtns)(uint32_t *gamebtn); + void (*get_trackball)(uint16_t *x, uint16_t *y); +}; diff --git a/games/sekitoio/config.c b/games/sekitoio/config.c new file mode 100644 index 0000000..7f900c3 --- /dev/null +++ b/games/sekitoio/config.c @@ -0,0 +1,77 @@ +#include + +#include +#include +#include + +#include "sekitoio/config.h" + +#include + + +void sekito_kb_config_load( + struct sekito_kb_config *cfg, + const wchar_t *filename) { + + cfg->vk_menu = GetPrivateProfileIntW(L"keyboard", L"menu", 'A', filename); + cfg->vk_start = GetPrivateProfileIntW(L"keyboard", L"start", 'S', filename); + cfg->vk_stratagem = GetPrivateProfileIntW(L"keyboard", L"stratagem", 'D', filename); + cfg->vk_stratagem_lock = GetPrivateProfileIntW(L"keyboard", L"stratagem_lock", 'F', filename); + cfg->vk_hougu = GetPrivateProfileIntW(L"keyboard", L"hougu", 'G', filename); + cfg->vk_ryuuha = GetPrivateProfileIntW(L"keyboard", L"ryuuha", 'H', filename); + + cfg->vk_tenkey_0 = GetPrivateProfileIntW(L"keyboard", L"tenkey_0", VK_NUMPAD0, filename); + cfg->vk_tenkey_1 = GetPrivateProfileIntW(L"keyboard", L"tenkey_1", VK_NUMPAD1, filename); + cfg->vk_tenkey_2 = GetPrivateProfileIntW(L"keyboard", L"tenkey_2", VK_NUMPAD2, filename); + cfg->vk_tenkey_3 = GetPrivateProfileIntW(L"keyboard", L"tenkey_3", VK_NUMPAD3, filename); + cfg->vk_tenkey_4 = GetPrivateProfileIntW(L"keyboard", L"tenkey_4", VK_NUMPAD4, filename); + cfg->vk_tenkey_5 = GetPrivateProfileIntW(L"keyboard", L"tenkey_5", VK_NUMPAD5, filename); + cfg->vk_tenkey_6 = GetPrivateProfileIntW(L"keyboard", L"tenkey_6", VK_NUMPAD6, filename); + cfg->vk_tenkey_7 = GetPrivateProfileIntW(L"keyboard", L"tenkey_7", VK_NUMPAD7, filename); + cfg->vk_tenkey_8 = GetPrivateProfileIntW(L"keyboard", L"tenkey_8", VK_NUMPAD8, filename); + cfg->vk_tenkey_9 = GetPrivateProfileIntW(L"keyboard", L"tenkey_9", VK_NUMPAD9, filename); + cfg->vk_tenkey_clear = GetPrivateProfileIntW(L"keyboard", L"tenkey_clear", VK_DECIMAL, filename); + cfg->vk_tenkey_enter = GetPrivateProfileIntW(L"keyboard", L"tenkey_enter", VK_RETURN, filename); + + cfg->vk_vol_down = GetPrivateProfileIntW(L"keyboard", L"vol_down", VK_NEXT, filename); + cfg->vk_vol_up = GetPrivateProfileIntW(L"keyboard", L"vol_up", VK_PRIOR, 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); + + cfg->x_down = GetPrivateProfileIntW(L"keyboard", L"trackball_left", VK_LEFT, filename); + cfg->x_up = GetPrivateProfileIntW(L"keyboard", L"trackball_right", VK_RIGHT, filename); + cfg->y_down = GetPrivateProfileIntW(L"keyboard", L"trackball_up", VK_UP, filename); + cfg->y_up = GetPrivateProfileIntW(L"keyboard", L"trackball_down", VK_DOWN, filename); + cfg->speed = GetPrivateProfileIntW(L"keyboard", L"speed_modifier", 1, filename); +} + +void sekito_io_config_load( + struct sekito_io_config *cfg, + const wchar_t *filename) +{ + 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_sw1 = GetPrivateProfileIntW(L"io4", L"sw1", '4', filename); + cfg->vk_sw2 = GetPrivateProfileIntW(L"io4", L"sw2", '5', filename); + + GetPrivateProfileStringW( + L"io4", + L"mode", + L"keyboard", + cfg->mode, + _countof(cfg->mode), + filename); + + sekito_kb_config_load(&cfg->kb, filename); +} diff --git a/games/sekitoio/config.h b/games/sekitoio/config.h new file mode 100644 index 0000000..96d4826 --- /dev/null +++ b/games/sekitoio/config.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include + +struct sekito_kb_config { + uint8_t vk_menu; + uint8_t vk_start; + uint8_t vk_stratagem; + uint8_t vk_stratagem_lock; + uint8_t vk_hougu; + uint8_t vk_ryuuha; + + uint8_t vk_tenkey_0; + uint8_t vk_tenkey_1; + uint8_t vk_tenkey_2; + uint8_t vk_tenkey_3; + uint8_t vk_tenkey_4; + uint8_t vk_tenkey_5; + uint8_t vk_tenkey_6; + uint8_t vk_tenkey_7; + uint8_t vk_tenkey_8; + uint8_t vk_tenkey_9; + uint8_t vk_tenkey_clear; + uint8_t vk_tenkey_enter; + + uint8_t vk_vol_down; + uint8_t vk_vol_up; + + 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; + + uint8_t x_down; + uint8_t x_up; + uint8_t y_down; + uint8_t y_up; + uint8_t speed; +}; + +struct sekito_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_sw1; + uint8_t vk_sw2; + + wchar_t mode[12]; + struct sekito_kb_config kb; +}; + +void sekito_kb_config_load(struct sekito_kb_config *cfg, const wchar_t *filename); +void sekito_io_config_load( + struct sekito_io_config *cfg, + const wchar_t *filename); diff --git a/games/sekitoio/keyboard.c b/games/sekitoio/keyboard.c new file mode 100644 index 0000000..c4c0dc9 --- /dev/null +++ b/games/sekitoio/keyboard.c @@ -0,0 +1,164 @@ +#include + +#include +#include +#include +#include + +#include "sekitoio/backend.h" +#include "sekitoio/config.h" +#include "sekitoio/sekitoio.h" +#include "sekitoio/keyboard.h" + +#include "util/dprintf.h" + +static void sekito_kb_get_gamebtns(uint32_t* gamebtn_out); +static void sekito_kb_get_trackball(uint16_t* x, uint16_t* y); + +static const struct sekito_io_backend sekito_kb_backend = { + .get_gamebtns = sekito_kb_get_gamebtns, + .get_trackball = sekito_kb_get_trackball +}; + +static uint16_t current_x; +static uint16_t current_y; + +static struct sekito_kb_config config; + +HRESULT sekito_kb_init(const struct sekito_kb_config* cfg, const struct sekito_io_backend** backend) { + assert(cfg != NULL); + assert(backend != NULL); + + dprintf("Keyboard: Using keyboard input\n"); + *backend = &sekito_kb_backend; + config = *cfg; + + return S_OK; +} + +static void sekito_kb_get_gamebtns(uint32_t* gamebtn_out) { + assert(gamebtn_out != NULL); + + uint32_t gamebtn = 0; + + if (GetAsyncKeyState(config.vk_hougu) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_HOUGU; + } + + if (GetAsyncKeyState(config.vk_menu) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_MENU; + } + + if (GetAsyncKeyState(config.vk_start) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_START; + } + + if (GetAsyncKeyState(config.vk_stratagem) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_STRATAGEM; + } + + if (GetAsyncKeyState(config.vk_stratagem_lock) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_STRATAGEM_LOCK; + } + + if (GetAsyncKeyState(config.vk_tenkey_0) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_0; + } + + if (GetAsyncKeyState(config.vk_tenkey_1) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_1; + } + + if (GetAsyncKeyState(config.vk_tenkey_2) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_2; + } + + if (GetAsyncKeyState(config.vk_tenkey_3) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_3; + } + + if (GetAsyncKeyState(config.vk_tenkey_4) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_4; + } + + if (GetAsyncKeyState(config.vk_tenkey_5) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_5; + } + + if (GetAsyncKeyState(config.vk_tenkey_6) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_6; + } + + if (GetAsyncKeyState(config.vk_tenkey_7) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_7; + } + + if (GetAsyncKeyState(config.vk_tenkey_8) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_8; + } + + if (GetAsyncKeyState(config.vk_tenkey_9) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_9; + } + + if (GetAsyncKeyState(config.vk_tenkey_clear) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_CLEAR; + } + + if (GetAsyncKeyState(config.vk_tenkey_enter) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_NUMPAD_ENTER; + } + + if (GetAsyncKeyState(config.vk_terminal_cancel) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_CANCEL; + } + + if (GetAsyncKeyState(config.vk_terminal_decide) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_DECIDE; + } + + if (GetAsyncKeyState(config.vk_terminal_up) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_UP; + } + + if (GetAsyncKeyState(config.vk_terminal_right) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_RIGHT; + } + + if (GetAsyncKeyState(config.vk_terminal_down) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_DOWN; + } + + if (GetAsyncKeyState(config.vk_terminal_left) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_LEFT; + } + + if (GetAsyncKeyState(config.vk_terminal_left_2) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_LEFT_2; + } + + if (GetAsyncKeyState(config.vk_terminal_right_2) & 0x8000) { + gamebtn |= SEKITO_IO_GAMEBTN_TERMINAL_RIGHT_2; + } + + *gamebtn_out = gamebtn; +} + +static void sekito_kb_get_trackball(uint16_t* x, uint16_t* y) { + assert(x != NULL); + assert(y != NULL); + + if (GetAsyncKeyState(config.x_down) & 0x8000) { + current_x -= config.speed; + } else if (GetAsyncKeyState(config.x_up) & 0x8000) { + current_x += config.speed; + } + if (GetAsyncKeyState(config.y_down) & 0x8000) { + current_y += config.speed; + } else if (GetAsyncKeyState(config.y_up) & 0x8000) { + current_y -= config.speed; + } + + *x = current_x; + *y = current_y; +} diff --git a/games/sekitoio/keyboard.h b/games/sekitoio/keyboard.h new file mode 100644 index 0000000..ae093e8 --- /dev/null +++ b/games/sekitoio/keyboard.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "sekitoio/backend.h" +#include "sekitoio/config.h" + +HRESULT sekito_kb_init(const struct sekito_kb_config *cfg, const struct sekito_io_backend **backend); diff --git a/games/sekitoio/meson.build b/games/sekitoio/meson.build new file mode 100644 index 0000000..15b1e63 --- /dev/null +++ b/games/sekitoio/meson.build @@ -0,0 +1,18 @@ +sekitoio_lib = static_library( + 'sekitoio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + xinput_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'backend.h', + 'keyboard.c', + 'keyboard.h', + 'sekitoio.c', + 'sekitoio.h', + ], +) diff --git a/games/sekitoio/sekitoio.c b/games/sekitoio/sekitoio.c new file mode 100644 index 0000000..75ec654 --- /dev/null +++ b/games/sekitoio/sekitoio.c @@ -0,0 +1,108 @@ +#include +#include +#include + +#include + +#include "sekitoio/sekitoio.h" + +#include + +#include "keyboard.h" +#include "sekitoio/config.h" +#include "util/dprintf.h" +#include "util/env.h" +#include "util/str.h" + +static uint8_t sekito_opbtn; +static uint32_t sekito_gamebtn; +static uint8_t sekito_stick_x; +static uint8_t sekito_stick_y; +static struct sekito_io_config sekito_io_cfg; +static const struct sekito_io_backend* sekito_io_backend; +static bool sekito_io_coin; + +uint16_t sekito_io_get_api_version(void) { + return 0x0100; +} + +HRESULT sekito_io_init(void) { + sekito_io_config_load(&sekito_io_cfg, get_config_path()); + + HRESULT hr; + + if (wstr_ieq(sekito_io_cfg.mode, L"keyboard")) { + hr = sekito_kb_init(&sekito_io_cfg.kb, &sekito_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("Sekito IO: Invalid IO mode \"%S\", use keyboard\n", + sekito_io_cfg.mode); + } + + return hr; +} + +HRESULT sekito_io_poll(void) { + assert(sekito_io_backend != NULL); + + sekito_opbtn = 0; + sekito_gamebtn = 0; + sekito_stick_x = 0; + sekito_stick_y = 0; + + if (GetAsyncKeyState(sekito_io_cfg.vk_test) & 0x8000) { + sekito_opbtn |= SEKITO_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(sekito_io_cfg.vk_service) & 0x8000) { + sekito_opbtn |= SEKITO_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(sekito_io_cfg.vk_sw1) & 0x8000) { + sekito_opbtn |= SEKITO_IO_OPBTN_SW1; + } + + if (GetAsyncKeyState(sekito_io_cfg.vk_sw2) & 0x8000) { + sekito_opbtn |= SEKITO_IO_OPBTN_SW2; + } + + if (GetAsyncKeyState(sekito_io_cfg.vk_coin) & 0x8000) { + if (!sekito_io_coin) { + sekito_io_coin = true; + sekito_opbtn |= SEKITO_IO_OPBTN_COIN; + } + } else { + sekito_io_coin = false; + } + + return S_OK; +} + +void sekito_io_get_opbtns(uint8_t* opbtn) { + if (opbtn != NULL) { + *opbtn = sekito_opbtn; + } +} + +void sekito_io_get_gamebtns(uint32_t* btn) { + assert(sekito_io_backend != NULL); + assert(btn != NULL); + + sekito_io_backend->get_gamebtns(btn); +} + +void sekito_io_get_trackball_position(uint16_t* stick_x, uint16_t* stick_y) { + assert(sekito_io_backend != NULL); + assert(stick_x != NULL); + assert(stick_y != NULL); + + sekito_io_backend->get_trackball(stick_x, stick_y); +} + +HRESULT sekito_io_led_init(void) { + return S_OK; +} + +void sekito_io_led_set_colors(uint8_t board, uint8_t* rgb) { + return; +} diff --git a/games/sekitoio/sekitoio.h b/games/sekitoio/sekitoio.h new file mode 100644 index 0000000..de58daf --- /dev/null +++ b/games/sekitoio/sekitoio.h @@ -0,0 +1,103 @@ +#pragma once + +#include + +#include + +enum { + SEKITO_IO_OPBTN_TEST = 0x01, + SEKITO_IO_OPBTN_SERVICE = 0x02, + SEKITO_IO_OPBTN_COIN = 0x04, + SEKITO_IO_OPBTN_SW1 = 0x08, + SEKITO_IO_OPBTN_SW2 = 0x10, +}; + +enum { + SEKITO_IO_GAMEBTN_MENU = 0x01, + SEKITO_IO_GAMEBTN_START = 0x02, + SEKITO_IO_GAMEBTN_STRATAGEM = 0x04, + SEKITO_IO_GAMEBTN_STRATAGEM_LOCK = 0x08, + SEKITO_IO_GAMEBTN_HOUGU = 0x10, + SEKITO_IO_GAMEBTN_NUMPAD_0 = 0x100, + SEKITO_IO_GAMEBTN_NUMPAD_1 = 0x200, + SEKITO_IO_GAMEBTN_NUMPAD_2 = 0x400, + SEKITO_IO_GAMEBTN_NUMPAD_3 = 0x800, + SEKITO_IO_GAMEBTN_NUMPAD_4 = 0x1000, + SEKITO_IO_GAMEBTN_NUMPAD_5 = 0x2000, + SEKITO_IO_GAMEBTN_NUMPAD_6 = 0x4000, + SEKITO_IO_GAMEBTN_NUMPAD_7 = 0x8000, + SEKITO_IO_GAMEBTN_NUMPAD_8 = 0x10000, + SEKITO_IO_GAMEBTN_NUMPAD_9 = 0x20000, + SEKITO_IO_GAMEBTN_NUMPAD_CLEAR = 0x40000, + SEKITO_IO_GAMEBTN_NUMPAD_ENTER = 0x80000, + SEKITO_IO_GAMEBTN_TERMINAL_LEFT = 0x400000, + SEKITO_IO_GAMEBTN_TERMINAL_UP = 0x800000, + SEKITO_IO_GAMEBTN_TERMINAL_RIGHT = 0x1000000, + SEKITO_IO_GAMEBTN_TERMINAL_DOWN = 0x2000000, + SEKITO_IO_GAMEBTN_TERMINAL_LEFT_2 = 0x4000000, + SEKITO_IO_GAMEBTN_TERMINAL_RIGHT_2 = 0x8000000, + SEKITO_IO_GAMEBTN_TERMINAL_DECIDE = 0x10000000, + SEKITO_IO_GAMEBTN_TERMINAL_CANCEL = 0x20000000, +}; + +/* Get the version of the Eiketsu Taisen 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 sekito_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after sekito_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT sekito_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 sekito_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + SEKITO_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 sekito_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + SEKITO_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 sekito_io_get_gamebtns(uint32_t *gamebtn); + +/* Get the position of the trackball as of the last poll. + + Minimum API version: 0x0100 */ + +void sekito_io_get_trackball_position(uint16_t *stick_x, uint16_t *stick_y); + +/* Initialize LED emulation. This function will be called before any + other sekito_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 sekito_io_led_init(void); + +/* Update the RGB LEDs. + + Exact layout is TBD. */ + +void sekito_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/meson.build b/meson.build index 646d0c8..dcddb46 100644 --- a/meson.build +++ b/meson.build @@ -127,6 +127,7 @@ subdir('games/tokyoio') subdir('games/fgoio') subdir('games/kemonoio') subdir('games/apm3io') +subdir('games/sekitoio') subdir('games/ektio') subdir('games/chunihook') @@ -145,4 +146,4 @@ subdir('games/tokyohook') subdir('games/fgohook') subdir('games/kemonohook') subdir('games/apm3hook') -subdir('games/ekthook') +subdir('games/sekitohook')