diff --git a/Package.mk b/Package.mk index 3c974e7..95816ac 100644 --- a/Package.mk +++ b/Package.mk @@ -256,6 +256,24 @@ $(BUILD_DIR_ZIP)/apm3.zip: $(V)strip $(BUILD_DIR_ZIP)/apm3/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/apm3 ; zip -r ../apm3.zip * +$(BUILD_DIR_ZIP)/ekt.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/ekt + $(V)mkdir -p $(BUILD_DIR_ZIP)/ekt/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/ekthook/ekthook.dll \ + $(DIST_DIR)/ekt/segatools_terminal.ini \ + $(DIST_DIR)/ekt/segatools_satellite.ini \ + $(DIST_DIR)/ekt/launch_terminal.bat \ + $(DIST_DIR)/ekt/launch_satellite.bat \ + $(DIST_DIR)/ekt/config_hook.json \ + $(BUILD_DIR_ZIP)/ekt + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/ekt/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/ekt/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/ekt ; zip -r ../ekt.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/config \ $(DOC_DIR)/chunihook.md \ @@ -282,6 +300,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/fgo.zip \ $(BUILD_DIR_ZIP)/kemono.zip \ $(BUILD_DIR_ZIP)/apm3.zip \ + $(BUILD_DIR_ZIP)/ekt.zip \ CHANGELOG.md \ README.md \ diff --git a/dist/ekt/config_hook.json b/dist/ekt/config_hook.json new file mode 100644 index 0000000..1e0d221 --- /dev/null +++ b/dist/ekt/config_hook.json @@ -0,0 +1,5 @@ +{ + "common": { + "language": "english" + } +} \ No newline at end of file diff --git a/dist/ekt/launch_satellite.bat b/dist/ekt/launch_satellite.bat new file mode 100644 index 0000000..5ae973f --- /dev/null +++ b/dist/ekt/launch_satellite.bat @@ -0,0 +1,18 @@ +@echo off +set SEGATOOLS_CONFIG_PATH=..\segatools_satellite.ini + +pushd %~dp0 + +pushd PackageBase +start /min "AM Daemon" ..\inject -d -k ..\ekthook.dll amdaemon.exe -c config_sate.json ..\config_hook.json +popd +pushd exe +..\inject -d -k ..\ekthook.dll ekt.exe -logfile game.log -screen-fullscreen 0 -screen-width 1920 -screen-height 1080 -screen-quality Ultra -silent-crashes +popd +exit /b 0 + +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/ekt/launch_terminal.bat b/dist/ekt/launch_terminal.bat new file mode 100644 index 0000000..f1ab059 --- /dev/null +++ b/dist/ekt/launch_terminal.bat @@ -0,0 +1,18 @@ +@echo off +set SEGATOOLS_CONFIG_PATH=..\segatools_terminal.ini + +pushd %~dp0 + +pushd PackageBase +start /min "AM Daemon" ..\inject -d -k ..\ekthook.dll amdaemon.exe -c config_sate.json ..\config_hook.json +popd +pushd exe +..\inject -d -k ..\ekthook.dll ekt.exe -logfile game.log -screen-fullscreen 0 -screen-width 1920 -screen-height 1080 -screen-quality Ultra -silent-crashes +popd +exit /b 0 + +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/ekt/segatools_satellite.ini b/dist/ekt/segatools_satellite.ini new file mode 100644 index 0000000..55908f4 --- /dev/null +++ b/dist/ekt/segatools_satellite.ini @@ -0,0 +1,170 @@ +; ----------------------------------------------------------------------------- +; 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 + +[led15093] +; 837-15093-06 LED board emulation setting. +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. +; Use `ACA1` for the Satellite. +; Use `ACA2` for the Terminal. +platformId=ACA1 + +[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 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 1 on exactly one machine and set this to 0 on all others. +dipsw1=1 + +; ----------------------------------------------------------------------------- +; Misc. hook 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= + +; ----------------------------------------------------------------------------- +; 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 APM3 IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in gamepad input. +path= + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal +; (not prefixed with 0x) virtual-key codes, a list of which can be found here: +; +; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +; +; This is, admittedly, not the most user-friendly configuration method in the +; world. An improved solution will be provided later. + +[io4] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 +; 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 + +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_down=0x6D +vol_up=0x6B + diff --git a/dist/ekt/segatools_terminal.ini b/dist/ekt/segatools_terminal.ini new file mode 100644 index 0000000..6fe2af9 --- /dev/null +++ b/dist/ekt/segatools_terminal.ini @@ -0,0 +1,158 @@ +; ----------------------------------------------------------------------------- +; 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 + +[led15093] +; 837-15093-06 LED board emulation setting. +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. +; Use `ACA1` for the Satellite. +; Use `ACA2` for the Terminal. +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 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 1 on exactly one machine and set this to 0 on all others. +dipsw1=1 + +; ----------------------------------------------------------------------------- +; Misc. hook 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= + +; ----------------------------------------------------------------------------- +; 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 APM3 IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in gamepad input. +path= + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal +; (not prefixed with 0x) virtual-key codes, a list of which can be found here: +; +; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +; +; This is, admittedly, not the most user-friendly configuration method in the +; world. An improved solution will be provided later. + +[io4] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 +; 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/ekthook/config.c b/games/ekthook/config.c new file mode 100644 index 0000000..a6a7953 --- /dev/null +++ b/games/ekthook/config.c @@ -0,0 +1,117 @@ +#include + +#include "amex/amex.h" +#include "amex/config.h" + +#include "board/config.h" + +#include "hooklib/config.h" + +#include "platform/config.h" + +#include "ekthook/config.h" +#include "ekthook/ekt-dll.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 ekt_dll_config_load( + struct ekt_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"ektio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void ekt_hook_config_load( + struct ekt_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); + ekt_dll_config_load(&cfg->dll, filename); +} + +void y3_config_load(struct y3_config *cfg, const wchar_t *filename){ + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"y3", L"enable", 1, filename); + cfg->ws_enable = GetPrivateProfileIntW(L"y3", L"ws_enable", 1, filename); + cfg->ws_port = GetPrivateProfileIntW(L"y3", L"ws_port", 3597, filename); + cfg->ws_timeout = GetPrivateProfileIntW(L"y3", L"ws_timeout", 30000, filename); +} diff --git a/games/ekthook/config.h b/games/ekthook/config.h new file mode 100644 index 0000000..e73c015 --- /dev/null +++ b/games/ekthook/config.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "ekt-dll.h" +#include "amex/amex.h" + +#include "board/sg-reader.h" +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" + +#include "platform/config.h" +#include "unityhook/config.h" + +struct y3_config { + uint8_t enable; + uint8_t ws_enable; + uint16_t ws_port; + uint32_t ws_timeout; +}; + +struct ekt_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 ekt_dll_config dll; + struct unity_config unity; +}; + +void ekt_hook_config_load( + struct ekt_hook_config *cfg, + const wchar_t *filename); + +void y3_config_load( + struct y3_config *cfg, + const wchar_t *filename); diff --git a/games/ekthook/dllmain.c b/games/ekthook/dllmain.c new file mode 100644 index 0000000..e1d2d3a --- /dev/null +++ b/games/ekthook/dllmain.c @@ -0,0 +1,192 @@ +#include + + +#include "ekt-dll.h" +#include "board/sg-reader.h" +#include "board/led15093.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "ekthook/config.h" +#include "ekthook/io4.h" +#include "ekthook/y3.h" +#include "hook/iohook.h" + +#include "platform/platform.h" +#include "unityhook/hook.h" + +#include "util/dprintf.h" +#include "util/env.h" + +/* + * Eiketsu Taisen + * + * IP: 192.168.189.0 + * + * SATELLITE: Model Type 1 + * + * COM2: LED + * COM3: AIME + * COM3: "TM LED" + * COM4: FPR / Y3 + * + * TERMINAL: Model Type 2 + * + * COM1: Aime + * COM3: LED + * + */ + +static HMODULE ekt_hook_mod; +static process_entry_t ekt_startup; +static struct ekt_hook_config ekt_hook_cfg; + +static const wchar_t *target_modules[] = { + L"Y3CodeReaderNE.dll", +}; + +static const size_t target_modules_len = _countof(target_modules); + +void unity_hook_callback(HMODULE hmodule, const wchar_t* p) { + dprintf("Unity: Hook callback: %ls\n", p); + + for (size_t i = 0; i < target_modules_len; i++) { + if (_wcsicmp(p, target_modules[i]) == 0) { + serial_hook_apply_hooks(hmodule); + iohook_apply_hooks(hmodule); + } + } +} + +static DWORD CALLBACK ekt_pre_startup(void) +{ + HMODULE d3dc; + HMODULE dbghelp; + HRESULT hr; + bool is_terminal; + + dprintf("--- Begin ekt_pre_startup ---\n"); + + /* Pin the D3D shader compiler. This makes startup much faster. */ + + d3dc = LoadLibraryA("d3dcompiler_43.dll"); + + if (d3dc != NULL) { + dprintf("Pinned shader compiler, hMod=%p\n", d3dc); + } else { + dprintf("Failed to load shader compiler!\n"); + } + + /* Pin dbghelp so the path hooks apply to it. */ + + dbghelp = LoadLibraryW(L"dbghelp.dll"); + + if (dbghelp != NULL) { + dprintf("Pinned debug helper library, hMod=%p\n", dbghelp); + } else { + dprintf("Failed to load debug helper library!\n"); + } + + /* Load config */ + + ekt_hook_config_load(&ekt_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + serial_hook_init(); + + /* Initialize emulation hooks */ + + // Terminal = AAV1, Satellite = AAV2 + hr = platform_hook_init( + &ekt_hook_cfg.platform, + "SDGY", + "AAV1", + ekt_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize Terminal/Satellite hooks */ + if (strncmp(ekt_hook_cfg.platform.nusec.platform_id, "AAV1", 4) == 0) { + // Terminal + is_terminal = true; + } else if (strncmp(ekt_hook_cfg.platform.nusec.platform_id, "AAV2", 4) == 0) { + // Satellite + is_terminal = false; + } else { + // Unknown + dprintf("Unknown platform ID: %s\n", ekt_hook_cfg.platform.nusec.platform_id); + goto fail; + } + + if (FAILED(hr)) { + goto fail; + } + + hr = ekt_dll_init(&ekt_hook_cfg.dll, ekt_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = ekt_io4_hook_init(&ekt_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(&ekt_hook_cfg.led15093, + ekt_dll.led_init, ekt_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + + hr = sg_reader_hook_init(&ekt_hook_cfg.aime, is_terminal ? 1 : 3, ekt_hook_cfg.aime.gen, ekt_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + unity_hook_init(&ekt_hook_cfg.unity, ekt_hook_mod, unity_hook_callback); + + y3_hook_init(&ekt_hook_cfg.y3, ekt_hook_mod); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End ekt_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return ekt_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + ekt_hook_mod = mod; + + hr = process_hijack_startup(ekt_pre_startup, &ekt_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/ekthook/ekt-dll.c b/games/ekthook/ekt-dll.c new file mode 100644 index 0000000..95a9fd1 --- /dev/null +++ b/games/ekthook/ekt-dll.c @@ -0,0 +1,118 @@ +#include + +#include +#include + +#include "ekthook/ekt-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym ekt_dll_syms[] = { + { + .sym = "ekt_io_init", + .off = offsetof(struct ekt_dll, init), + }, { + .sym = "ekt_io_poll", + .off = offsetof(struct ekt_dll, poll), + }, { + .sym = "ekt_io_get_opbtns", + .off = offsetof(struct ekt_dll, get_opbtns), + }, { + .sym = "ekt_io_get_gamebtns", + .off = offsetof(struct ekt_dll, get_gamebtns), + }, { + .sym = "ekt_io_get_trackball_position", + .off = offsetof(struct ekt_dll, get_trackball_position), + }, { + .sym = "ekt_io_led_init", + .off = offsetof(struct ekt_dll, led_init), + }, { + .sym = "ekt_io_led_set_colors", + .off = offsetof(struct ekt_dll, led_set_leds), + } +}; + +struct ekt_dll ekt_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 ekt_dll_init(const struct ekt_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("EKT IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("EKT IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "ekt_io_get_api_version"); + + if (get_api_version != NULL) { + ekt_dll.api_version = get_api_version(); + } else { + ekt_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose ekt_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (ekt_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("EKT IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + ekt_dll.api_version); + + goto end; + } + + sym = ekt_dll_syms; + hr = dll_bind(&ekt_dll, src, &sym, _countof(ekt_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("EKT 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/ekthook/ekt-dll.h b/games/ekthook/ekt-dll.h new file mode 100644 index 0000000..b69ac8b --- /dev/null +++ b/games/ekthook/ekt-dll.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "ektio/ektio.h" + +struct ekt_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)(uint8_t *x, uint8_t *y); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); +}; + +struct ekt_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct ekt_dll ekt_dll; + +HRESULT ekt_dll_init(const struct ekt_dll_config *cfg, HINSTANCE self); diff --git a/games/ekthook/ekthook.def b/games/ekthook/ekthook.def new file mode 100644 index 0000000..2132811 --- /dev/null +++ b/games/ekthook/ekthook.def @@ -0,0 +1,49 @@ +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 + 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/ekthook/io4.c b/games/ekthook/io4.c new file mode 100644 index 0000000..eea05bc --- /dev/null +++ b/games/ekthook/io4.c @@ -0,0 +1,208 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "ekthook/ekt-dll.h" + +#include "util/dprintf.h" + +static HRESULT ekt_io4_poll(void *ctx, struct io4_state *state); +static uint16_t coins; + +static const struct io4_ops ekt_io4_ops = { + .poll = ekt_io4_poll, +}; + +static bool io_is_terminal; + +HRESULT ekt_io4_hook_init(const struct io4_config *cfg, bool is_terminal) +{ + HRESULT hr; + + assert(ekt_dll.init != NULL); + + hr = io4_hook_init(cfg, &ekt_io4_ops, NULL); + + io_is_terminal = is_terminal; + + if (FAILED(hr)) { + return hr; + } + + return ekt_dll.init(); +} + +static HRESULT ekt_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn, x, y; + uint32_t gamebtn; + HRESULT hr; + + assert(ekt_dll.poll != NULL); + assert(ekt_dll.get_opbtns != NULL); + assert(ekt_dll.get_gamebtns != NULL); + assert(ekt_dll.get_trackball_position != NULL); + + memset(state, 0, sizeof(*state)); + + hr = ekt_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + gamebtn = 0; + x = 0; + y = 0; + + ekt_dll.get_opbtns(&opbtn); + ekt_dll.get_gamebtns(&gamebtn); + ekt_dll.get_trackball_position(&x, &y); + + if (opbtn & EKT_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & EKT_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & EKT_IO_OPBTN_SW1) { + state->buttons[0] |= 1 << 0; // TODO + } + + if (opbtn & EKT_IO_OPBTN_SW2) { + state->buttons[0] |= 1 << 0; // TODO + } + + if (opbtn & EKT_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + // TODO: indexes + + if (!io_is_terminal) { + if (gamebtn & EKT_IO_GAMEBTN_HOUGU) { + state->buttons[1] |= 1 << 12; + } + + if (gamebtn & EKT_IO_GAMEBTN_MENU) { + state->buttons[0] |= 1 << 7; + } + + if (gamebtn & EKT_IO_GAMEBTN_START) { + state->buttons[0] |= 1 << 5; + } + + if (gamebtn & EKT_IO_GAMEBTN_STRATAGEM) { + state->buttons[0] |= 1 << 2; + } + + if (gamebtn & EKT_IO_GAMEBTN_STRATAGEM_LOCK) { + state->buttons[0] |= 1 << 2; + } + + if (gamebtn & EKT_IO_GAMEBTN_VOL_DOWN) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_VOL_UP) { + state->buttons[0] |= 1 << 4; + } + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_0) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_1) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_2) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_3) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_4) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_5) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_6) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_7) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_8) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_9) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_CLEAR) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (gamebtn & EKT_IO_GAMEBTN_NUMPAD_ENTER) { + state->buttons[0] |= 1 << (io_is_terminal ? 4 : 4); + } + + if (io_is_terminal) { + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_CANCEL) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_DECIDE) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_LEFT) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_UP) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_RIGHT) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_DOWN) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_LEFT_2) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & EKT_IO_GAMEBTN_TERMINAL_RIGHT_2) { + state->buttons[0] |= 1 << 4; + } + } + + state->adcs[0] = 1; + state->adcs[1] = 1; + state->adcs[2] = x; + state->adcs[3] = y; + + return S_OK; +} diff --git a/games/ekthook/io4.h b/games/ekthook/io4.h new file mode 100644 index 0000000..5610a8a --- /dev/null +++ b/games/ekthook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT ekt_io4_hook_init(const struct io4_config *cfg, bool is_terminal); diff --git a/games/ekthook/meson.build b/games/ekthook/meson.build new file mode 100644 index 0000000..31f5e59 --- /dev/null +++ b/games/ekthook/meson.build @@ -0,0 +1,32 @@ +shared_library( + 'ekthook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'ekthook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep') + ], + link_with : [ + aimeio_lib, + board_lib, + ektio_lib, + hooklib_lib, + jvs_lib, + platform_lib, + unityhook_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'ekt-dll.c', + 'ekt-dll.h', + 'io4.c', + 'io4.h', + 'y3.c', + 'y3.h' + ], +) diff --git a/games/ekthook/y3.c b/games/ekthook/y3.c new file mode 100644 index 0000000..77f61ee --- /dev/null +++ b/games/ekthook/y3.c @@ -0,0 +1,510 @@ +#include +#include + +#include +#include +#include +#include + +#include "hook/table.h" +#include "hooklib/dll.h" +#include "util/dprintf.h" + +#include "ekthook/config.h" +#include "ekthook/y3.h" + +#include + +#define CALL __cdecl + +CALL float API_DLLVersion(); +CALL uint32_t API_GetLastError(int* hDevice); +CALL uint32_t API_GetErrorMessage(uint32_t errNo, char* szMessage, int numBytes); +CALL int* API_Connect(char* szPortName); +CALL int API_Close(int* hDevice); +CALL int API_Start(int* hDevice); +CALL int API_Stop(int* hDevice); +CALL float API_GetFirmVersion(int* hDevice); +CALL uint32_t API_GetFirmName(int* hDevice); +CALL uint32_t API_GetTargetCode(int* hDevice); +CALL uint32_t API_GetStatus(int* hDevice); +CALL uint32_t API_GetCounter(int* hDevice); +CALL int API_ClearError(int* hDevice); +CALL int API_Reset(int* hDevice, bool isHardReset); +CALL int API_GetCardInfo(int* hDevice, int numCards, struct CardInfo* pCardInfo); +CALL int API_GetCardInfoCharSize(); +CALL int API_FirmwareUpdate(int* hDevice, uint32_t address, uint32_t size, uint8_t* buffer); +CALL int API_Calibration(int* hDevice, int calib); +CALL int API_GetCalibrationResult(int* hDevice, int calib, uint32_t* result); +CALL uint32_t API_GetProcTime(int* hDevice); +CALL uint32_t API_GetMemStatus(int* hDevice); +CALL uint32_t API_GetMemCounter(int* hDevice); +CALL int API_SetParameter(int* hDevice, uint32_t uParam, uint32_t* pParam); +CALL int API_GetParameter(int* hDevice, uint32_t uParam, uint32_t* pParam); + +signed int CALL API_SetDevice(int a1, int a2); +signed int CALL API_SetCommand(int a1, int a2, int a3, int *a4); +signed int CALL API_SetSysControl(int a1, int a2, int *a3); +signed int CALL API_GetSysControl(int a1, int a2, int *a3); +int CALL API_TestReset(int a1); +signed int API_DebugReset(int a1, ...); +int CALL API_GetBoardType(int a1); +int CALL API_GetCardDataSize(int a1); +int CALL API_GetFirmDate(int a1); +int API_SystemCommand(int a1, char a2, ...); +int CALL API_CalcCheckSum(DWORD *a1, int a2, int a3); +int CALL API_GetCheckSumResult(int a1); +int CALL API_BlockRead(int a1, int a2, int a3, SIZE_T dwBytes); +int CALL API_GetBlockReadResult(int a1, void *a2); +int CALL API_BlockWrite(int a1, int a2, int a3, SIZE_T dwBytes, void *a5); +signed int CALL API_GetDebugParam(int a1, int a2, DWORD *a3); + +static const struct hook_symbol Y3_hooks[] = { + { + .name = "API_DLLVersion", + .patch = API_DLLVersion, + .link = NULL + }, + { + .name = "API_GetLastError", + .patch = API_GetLastError, + .link = NULL + }, + { + .name = "API_GetErrorMessage", + .patch = API_GetErrorMessage, + .link = NULL + }, + { + .name = "API_Connect", + .patch = API_Connect, + .link = NULL + }, + { + .name = "API_Close", + .patch = API_Close, + .link = NULL + }, + { + .name = "API_Start", + .patch = API_Start, + .link = NULL + }, + { + .name = "API_Stop", + .patch = API_Stop, + .link = NULL + }, + { + .name = "API_GetFirmVersion", + .patch = API_GetFirmVersion, + .link = NULL + }, + { + .name = "API_GetFirmName", + .patch = API_GetFirmName, + .link = NULL + }, + { + .name = "API_GetTargetCode", + .patch = API_GetTargetCode, + .link = NULL + }, + { + .name = "API_GetStatus", + .patch = API_GetStatus, + .link = NULL + }, + { + .name = "API_GetCounter", + .patch = API_GetCounter, + .link = NULL + }, + { + .name = "API_Reset", + .patch = API_Reset, + .link = NULL + }, + { + .name = "API_GetCardInfo", + .patch = API_GetCardInfo, + .link = NULL + }, + { + .name = "API_GetCardInfoCharSize", + .patch = API_GetCardInfoCharSize, + .link = NULL + }, + { + .name = "API_FirmwareUpdate", + .patch = API_FirmwareUpdate, + .link = NULL + }, + { + .name = "API_Calibration", + .patch = API_Calibration, + .link = NULL + }, + { + .name = "API_GetCalibrationResult", + .patch = API_GetCalibrationResult, + .link = NULL + }, + { + .name = "API_GetProcTime", + .patch = API_GetProcTime, + .link = NULL + }, + { + .name = "API_GetMemStatus", + .patch = API_GetMemStatus, + .link = NULL + }, + { + .name = "API_GetMemCounter", + .patch = API_GetMemCounter, + .link = NULL + }, + { + .name = "API_SetParameter", + .patch = API_SetParameter, + .link = NULL + }, + { + .name = "API_GetParameter", + .patch = API_GetParameter, + .link = NULL + }, + { + .name = "API_SetDevice", + .patch = API_SetDevice, + .link = NULL + }, + { + .name = "API_SetCommand", + .patch = API_SetCommand, + .link = NULL + }, + { + .name = "API_SetSysControl", + .patch = API_SetSysControl, + .link = NULL + }, + { + .name = "API_GetSysControl", + .patch = API_GetSysControl, + .link = NULL + }, + { + .name = "API_TestReset", + .patch = API_TestReset, + .link = NULL + }, + { + .name = "API_DebugReset", + .patch = API_DebugReset, + .link = NULL + }, + { + .name = "API_GetBoardType", + .patch = API_GetBoardType, + .link = NULL + }, + { + .name = "API_GetCardDataSize", + .patch = API_GetCardDataSize, + .link = NULL + }, + { + .name = "API_GetFirmDate", + .patch = API_GetFirmDate, + .link = NULL + }, + { + .name = "API_SystemCommand", + .patch = API_SystemCommand, + .link = NULL + }, + { + .name = "API_CalcCheckSum", + .patch = API_CalcCheckSum, + .link = NULL + }, + { + .name = "API_GetCheckSumResult", + .patch = API_GetCheckSumResult, + .link = NULL + }, + { + .name = "API_BlockRead", + .patch = API_BlockRead, + .link = NULL + }, + { + .name = "API_GetBlockReadResult", + .patch = API_GetBlockReadResult, + .link = NULL + }, + { + .name = "API_BlockWrite", + .patch = API_BlockWrite, + .link = NULL + }, + { + .name = "API_GetDebugParam", + .patch = API_GetDebugParam, + .link = NULL + }, +}; + +static struct y3_config y3_config; + +#define MAX_CARD_SIZE 32 +static struct CardInfo card_data[MAX_CARD_SIZE]; + +static const int* Y3_COM_FIELD = (int*)10; +static const int* Y3_COM_PRINT = (int*)11; + + +void y3_hook_init(const struct y3_config *cfg, HINSTANCE self){ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&y3_config, cfg, sizeof(*cfg)); + + hook_table_apply(NULL, "Y3CodeReaderNE.dll", Y3_hooks, _countof(Y3_hooks)); + + if (self != NULL) { + dll_hook_push(self, L"Y3CodeReaderNE.dll"); + } + + memset(card_data, 0, sizeof(card_data)); + + dprintf("Y3: enabled\n"); +} + +CALL float API_DLLVersion(){ + dprintf("Y3: %s\n", __func__); + return 1; +} +CALL uint32_t API_GetLastError(int* hDevice){ + dprintf("Y3: %s\n", __func__); + if (!y3_config.enable) { + return 1; + } + return 0; +} +CALL uint32_t API_GetErrorMessage(uint32_t errNo, char* szMessage, int numBytes){ + dprintf("Y3: %s\n", __func__); + if (!y3_config.enable) { + strcpy_s(szMessage, numBytes, "Y3Emu is disabled but emulator DLL is called"); + } else { + strcpy_s(szMessage, numBytes, "No error - Y3Emu"); + } + return 0; +} +CALL int* API_Connect(char* szPortName){ + dprintf("Y3: %s(%s)\n", __func__, szPortName); +if ((GetAsyncKeyState(0x11) & 0x8000) && (GetAsyncKeyState(0x42) & 0x8000)) { + dprintf("Y3: Ctrl+B is active!\n"); + return NULL; + } + if (!y3_config.enable) { + return NULL; + } else { + char number[2]; + strncpy(number, szPortName+3, 2); + return (int*)(uintptr_t)atoi(number); + } +} +CALL int API_Close(int* hDevice){ + dprintf("Y3: %s(%p)\n", __func__, hDevice); + return 0; +} +CALL int API_Start(int* hDevice){ + dprintf("Y3: %s(%p)\n", __func__, hDevice); + return 0; +} +CALL int API_Stop(int* hDevice){ + dprintf("Y3: %s(%p)\n", __func__, hDevice); + return 0; +} +CALL float API_GetFirmVersion(int* hDevice){ + dprintf("Y3: %s(%p)\n", __func__, hDevice); + return 1; +} +CALL uint32_t API_GetFirmName(int* hDevice){ + dprintf("Y3: %s(%p)\n", __func__, hDevice); + if (hDevice == Y3_COM_FIELD){ + dprintf("This Y3 device is FIELD\n"); + return 1380992595; + } else if (hDevice == Y3_COM_PRINT){ + dprintf("This Y3 device is PRINTER\n"); + //DebugBreak(); + return 1414680659; + } else { + dprintf("This Y3 device is UNKNOWN\n"); + } + return 0; +} +CALL uint32_t API_GetTargetCode(int* hDevice){ + dprintf("Y3: %s(%p)\n", __func__, hDevice); + if (hDevice == Y3_COM_FIELD){ + dprintf("This Y3 device is FIELD\n"); + return 810698323; + } else if (hDevice == Y3_COM_PRINT){ + dprintf("This Y3 device is PRINTER\n"); + //DebugBreak(); + return 810831955; + } else { + dprintf("This Y3 device is UNKNOWN\n"); + } + return 1162760014; +} +CALL uint32_t API_GetStatus(int* hDevice){ + //dprintf("Y3: %s\n", __func__); + return 0; +} +CALL uint32_t API_GetCounter(int* hDevice){ + //dprintf("Y3: %s\n", __func__); + return 0; +} +CALL int API_ClearError(int* hDevice){ + dprintf("Y3: %s\n", __func__); + return 0; +} +CALL int API_Reset(int* hDevice, bool isHardReset){ + dprintf("Y3: %s\n", __func__); + return 0; +} +CALL int API_GetCardInfo(int* hDevice, int numCards, struct CardInfo* pCardInfo){ + //dprintf("Y3: %s(%p), %d\n", __func__, hDevice, numCards); + // ret = num cards + // numCards = max cards + + if (hDevice == Y3_COM_PRINT){ + pCardInfo[0].fX = 0; + pCardInfo[0].fY = 0; + pCardInfo[0].fAngle = 0; + pCardInfo[0].eCardType = TYPE0; + pCardInfo[0].eCardStatus = MARKER; + pCardInfo[0].uID = 0x10; + pCardInfo[0].nNumChars = 0; + pCardInfo[0].ubChar0.Data = 0; + pCardInfo[0].ubChar1.Data = 0x4000; + pCardInfo[0].ubChar2.Data = 0; + pCardInfo[0].ubChar3.Data = 0x0; // 40 + pCardInfo[0].ubChar4.Data = 0; + pCardInfo[0].ubChar5.Data = 0; + return 1; + } else if (numCards == MAX_CARD_SIZE){ + memcpy(pCardInfo, card_data, sizeof(card_data)); + return MAX_CARD_SIZE; + } + + return 0; +} +CALL int API_GetCardInfoCharSize(){ + dprintf("Y3: %s\n", __func__); + return 0; +} +CALL int API_FirmwareUpdate(int* hDevice, uint32_t address, uint32_t size, uint8_t* buffer){ + dprintf("Y3: %s\n", __func__); + return 1; // not supported +} +CALL int API_Calibration(int* hDevice, int calib){ + dprintf("Y3: %s\n", __func__); + return 1; +} +CALL int API_GetCalibrationResult(int* hDevice, int calib, uint32_t* result){ + dprintf("Y3: %s\n", __func__); + return 1; +} +CALL uint32_t API_GetProcTime(int* hDevice){ + //dprintf("Y3: %s\n", __func__); + return 0; +} +CALL uint32_t API_GetMemStatus(int* hDevice){ + dprintf("Y3: %s\n", __func__); + return 0; +} +CALL uint32_t API_GetMemCounter(int* hDevice){ + dprintf("Y3: %s\n", __func__); + return 0; +} +CALL int API_SetParameter(int* hDevice, uint32_t uParam, uint32_t* pParam){ + dprintf("Y3: %s\n", __func__); + return 0; +} +CALL int API_GetParameter(int* hDevice, uint32_t uParam, uint32_t* pParam){ + dprintf("Y3: %s\n", __func__); + return 0; +} + +signed int CALL API_SetDevice(int a1, int a2){ + dprintf("Y3: %s\n", __func__); + return 0; +} +signed int CALL API_SetCommand(int a1, int a2, int a3, int *a4){ + dprintf("Y3: %s\n", __func__); + return 0; +} +signed int CALL API_SetSysControl(int a1, int a2, int *a3){ + dprintf("Y3: %s\n", __func__); + return 0; +} +signed int CALL API_GetSysControl(int a1, int a2, int *a3){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_TestReset(int a1){ + dprintf("Y3: %s\n", __func__); + return 0; +} +signed int API_DebugReset(int a1, ...){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_GetBoardType(int a1){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_GetCardDataSize(int a1){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_GetFirmDate(int a1){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int API_SystemCommand(int a1, char a2, ...){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_CalcCheckSum(DWORD *a1, int a2, int a3){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_GetCheckSumResult(int a1){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_BlockRead(int a1, int a2, int a3, SIZE_T dwBytes){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_GetBlockReadResult(int a1, void *a2){ + dprintf("Y3: %s\n", __func__); + return 0; +} +int CALL API_BlockWrite(int a1, int a2, int a3, SIZE_T dwBytes, void *a5){ + dprintf("Y3: %s\n", __func__); + return 0; +} +signed int CALL API_GetDebugParam(int a1, int a2, DWORD *a3){ + dprintf("Y3: %s\n", __func__); + return 0; +} diff --git a/games/ekthook/y3.h b/games/ekthook/y3.h new file mode 100644 index 0000000..73d2b66 --- /dev/null +++ b/games/ekthook/y3.h @@ -0,0 +1,57 @@ +enum Y3WebsocketProtocol { + Y3_WS_PROTO_ERROR = 0, + Y3_WS_PROTO_CARD_DATA = 2, + Y3_WS_PROTO_PING = 1, + Y3_WS_PROTO_GAME_TITLE = 3, +}; + +struct CardByteData { + unsigned int Data; +}; + +struct CardInfo { + float fX; // 0x00|0 + float fY; // 0x04|4 + float fAngle; // 0x08|8 + int eCardType; // 0x0C|12 + int eCardStatus; // 0x10|16 + unsigned int uID; // 0x14|20 + int nNumChars; // 0x18|24 + struct CardByteData ubChar0; // 0x1C|28 + struct CardByteData ubChar1; // 0x20|32 + struct CardByteData ubChar2; // 0x24|36 + struct CardByteData ubChar3; // 0x28|40 + struct CardByteData ubChar4; // 0x2C|44 + struct CardByteData ubChar5; // 0x30|48 +}; + +enum Type { + // Token: 0x04000084 RID: 132 + TYPE0 = 0, + // Token: 0x04000085 RID: 133 + TYPE1, + // Token: 0x04000086 RID: 134 + TYPE2, + // Token: 0x04000087 RID: 135 + TYPE3, + // Token: 0x04000088 RID: 136 + TYPE4, + // Token: 0x04000089 RID: 137 + TYPE5, + // Token: 0x0400008A RID: 138 + TYPE6, + // Token: 0x0400008B RID: 139 + TYPE7 = 7 +}; + +enum Status { + INVALID = 0, + // Token: 0x0400008E RID: 142 + VALID = 1, + // Token: 0x0400008F RID: 143 + INFERENCE = 2, + // Token: 0x04000090 RID: 144 + MARKER = 3 +}; + +void y3_hook_init(const struct y3_config *cfg, HINSTANCE self); diff --git a/games/ektio/backend.h b/games/ektio/backend.h new file mode 100644 index 0000000..0d09655 --- /dev/null +++ b/games/ektio/backend.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "ektio/ektio.h" + +struct ekt_io_backend { + void (*get_gamebtns)(uint32_t *gamebtn); + void (*get_trackball)(uint8_t *x, uint8_t *y); +}; diff --git a/games/ektio/config.c b/games/ektio/config.c new file mode 100644 index 0000000..236271b --- /dev/null +++ b/games/ektio/config.c @@ -0,0 +1,76 @@ +#include + +#include +#include +#include + +#include "ektio/config.h" + +#include + + +void ekt_kb_config_load( + struct ekt_kb_config *cfg, + const wchar_t *filename) { + + cfg->vk_menu = GetPrivateProfileIntW(L"io4", L"menu", 'A', filename); + cfg->vk_start = GetPrivateProfileIntW(L"io4", L"start", 'S', filename); + cfg->vk_stratagem = GetPrivateProfileIntW(L"io4", L"stratagem", 'D', filename); + cfg->vk_stratagem_lock = GetPrivateProfileIntW(L"io4", L"stratagem_lock", 'F', filename); + cfg->vk_hougu = GetPrivateProfileIntW(L"io4", L"hougu", 'G', filename); + + cfg->vk_tenkey_0 = GetPrivateProfileIntW(L"io4", L"tenkey_0", VK_NUMPAD0, filename); + cfg->vk_tenkey_1 = GetPrivateProfileIntW(L"io4", L"tenkey_1", VK_NUMPAD1, filename); + cfg->vk_tenkey_2 = GetPrivateProfileIntW(L"io4", L"tenkey_2", VK_NUMPAD2, filename); + cfg->vk_tenkey_3 = GetPrivateProfileIntW(L"io4", L"tenkey_3", VK_NUMPAD3, filename); + cfg->vk_tenkey_4 = GetPrivateProfileIntW(L"io4", L"tenkey_4", VK_NUMPAD4, filename); + cfg->vk_tenkey_5 = GetPrivateProfileIntW(L"io4", L"tenkey_5", VK_NUMPAD5, filename); + cfg->vk_tenkey_6 = GetPrivateProfileIntW(L"io4", L"tenkey_6", VK_NUMPAD6, filename); + cfg->vk_tenkey_7 = GetPrivateProfileIntW(L"io4", L"tenkey_7", VK_NUMPAD7, filename); + cfg->vk_tenkey_8 = GetPrivateProfileIntW(L"io4", L"tenkey_8", VK_NUMPAD8, filename); + cfg->vk_tenkey_9 = GetPrivateProfileIntW(L"io4", L"tenkey_9", VK_NUMPAD9, filename); + cfg->vk_tenkey_clear = GetPrivateProfileIntW(L"io4", L"tenkey_clear", VK_DECIMAL, filename); + cfg->vk_tenkey_enter = GetPrivateProfileIntW(L"io4", L"tenkey_enter", VK_RETURN, filename); + + cfg->vk_vol_down = GetPrivateProfileIntW(L"io4", L"vol_down", VK_SUBTRACT, filename); + cfg->vk_vol_up = GetPrivateProfileIntW(L"io4", L"vol_up", VK_ADD, filename); + + cfg->vk_terminal_decide = GetPrivateProfileIntW(L"io4", L"decide", 'A', filename); + cfg->vk_terminal_cancel = GetPrivateProfileIntW(L"io4", L"cancel", 'S', filename); + cfg->vk_terminal_up = GetPrivateProfileIntW(L"io4", L"up", VK_UP, filename); + cfg->vk_terminal_right = GetPrivateProfileIntW(L"io4", L"right", VK_RIGHT, filename); + cfg->vk_terminal_down = GetPrivateProfileIntW(L"io4", L"down", VK_DOWN, filename); + cfg->vk_terminal_left = GetPrivateProfileIntW(L"io4", L"left", VK_LEFT, filename); + cfg->vk_terminal_left_2 = GetPrivateProfileIntW(L"io4", L"left2", 'Q', filename); + cfg->vk_terminal_right_2 = GetPrivateProfileIntW(L"io4", L"right2", 'W', filename); + + cfg->x_down = GetPrivateProfileIntW(L"io4", L"trackball_left", VK_LEFT, filename); + cfg->x_up = GetPrivateProfileIntW(L"io4", L"trackball_right", VK_RIGHT, filename); + cfg->y_down = GetPrivateProfileIntW(L"io4", L"trackball_up", VK_UP, filename); + cfg->y_up = GetPrivateProfileIntW(L"io4", L"trackball_down", VK_DOWN, filename); + cfg->speed = GetPrivateProfileIntW(L"io4", L"speed_modifier", 1, filename); +} + +void ekt_io_config_load( + struct ekt_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); + + ekt_kb_config_load(&cfg->kb, filename); +} diff --git a/games/ektio/config.h b/games/ektio/config.h new file mode 100644 index 0000000..0764a8b --- /dev/null +++ b/games/ektio/config.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include + +struct ekt_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_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 ekt_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 ekt_kb_config kb; +}; + +void ekt_kb_config_load(struct ekt_kb_config *cfg, const wchar_t *filename); +void ekt_io_config_load( + struct ekt_io_config *cfg, + const wchar_t *filename); diff --git a/games/ektio/ektio.c b/games/ektio/ektio.c new file mode 100644 index 0000000..a2e6a0b --- /dev/null +++ b/games/ektio/ektio.c @@ -0,0 +1,100 @@ +#include +#include +#include + +#include + +#include "ektio/ektio.h" + +#include + +#include "keyboard.h" +#include "ektio/config.h" +#include "util/dprintf.h" +#include "util/env.h" +#include "util/str.h" + +static uint8_t ekt_opbtn; +static uint32_t ekt_gamebtn; +static uint8_t ekt_stick_x; +static uint8_t ekt_stick_y; +static struct ekt_io_config ekt_io_cfg; +static const struct ekt_io_backend* ekt_io_backend; +static bool ekt_io_coin; + +uint16_t ekt_io_get_api_version(void) { + return 0x0100; +} + +HRESULT ekt_io_init(void) { + ekt_io_config_load(&ekt_io_cfg, get_config_path()); + + HRESULT hr; + + if (wstr_ieq(ekt_io_cfg.mode, L"keyboard")) { + hr = ekt_kb_init(&ekt_io_cfg.kb, &ekt_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("EKT IO: Invalid IO mode \"%S\", use keyboard\n", + ekt_io_cfg.mode); + } + + return hr; +} + +HRESULT ekt_io_poll(void) { + assert(ekt_io_backend != NULL); + + ekt_opbtn = 0; + ekt_gamebtn = 0; + ekt_stick_x = 0; + ekt_stick_y = 0; + + if (GetAsyncKeyState(ekt_io_cfg.vk_test) & 0x8000) { + ekt_opbtn |= EKT_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(ekt_io_cfg.vk_service) & 0x8000) { + ekt_opbtn |= EKT_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(ekt_io_cfg.vk_coin) & 0x8000) { + if (!ekt_io_coin) { + ekt_io_coin = true; + ekt_opbtn |= EKT_IO_OPBTN_COIN; + } + } else { + ekt_io_coin = false; + } + + return S_OK; +} + +void ekt_io_get_opbtns(uint8_t* opbtn) { + if (opbtn != NULL) { + *opbtn = ekt_opbtn; + } +} + +void ekt_io_get_gamebtns(uint32_t* btn) { + assert(ekt_io_backend != NULL); + assert(btn != NULL); + + ekt_io_backend->get_gamebtns(btn); +} + +void ekt_io_get_trackball_position(uint8_t* stick_x, uint8_t* stick_y) { + assert(ekt_io_backend != NULL); + assert(stick_x != NULL); + assert(stick_y != NULL); + + ekt_io_backend->get_trackball(stick_x, stick_y); +} + +HRESULT ekt_io_led_init(void) { + return S_OK; +} + +void ekt_io_led_set_colors(uint8_t board, uint8_t* rgb) { + return; +} diff --git a/games/ektio/ektio.h b/games/ektio/ektio.h new file mode 100644 index 0000000..45ca73e --- /dev/null +++ b/games/ektio/ektio.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include + +enum { + EKT_IO_OPBTN_TEST = 0x01, + EKT_IO_OPBTN_SERVICE = 0x02, + EKT_IO_OPBTN_COIN = 0x04, + EKT_IO_OPBTN_SW1 = 0x08, + EKT_IO_OPBTN_SW2 = 0x10, +}; + +enum { + EKT_IO_GAMEBTN_MENU = 0x01, + EKT_IO_GAMEBTN_START = 0x02, + EKT_IO_GAMEBTN_STRATAGEM = 0x04, + EKT_IO_GAMEBTN_STRATAGEM_LOCK = 0x08, + EKT_IO_GAMEBTN_HOUGU = 0x10, + EKT_IO_GAMEBTN_NUMPAD_0 = 0x100, + EKT_IO_GAMEBTN_NUMPAD_1 = 0x200, + EKT_IO_GAMEBTN_NUMPAD_2 = 0x400, + EKT_IO_GAMEBTN_NUMPAD_3 = 0x800, + EKT_IO_GAMEBTN_NUMPAD_4 = 0x1000, + EKT_IO_GAMEBTN_NUMPAD_5 = 0x2000, + EKT_IO_GAMEBTN_NUMPAD_6 = 0x4000, + EKT_IO_GAMEBTN_NUMPAD_7 = 0x8000, + EKT_IO_GAMEBTN_NUMPAD_8 = 0x10000, + EKT_IO_GAMEBTN_NUMPAD_9 = 0x20000, + EKT_IO_GAMEBTN_NUMPAD_CLEAR = 0x40000, + EKT_IO_GAMEBTN_NUMPAD_ENTER = 0x80000, + EKT_IO_GAMEBTN_VOL_UP = 0x100000, + EKT_IO_GAMEBTN_VOL_DOWN = 0x200000, + EKT_IO_GAMEBTN_TERMINAL_LEFT = 0x400000, + EKT_IO_GAMEBTN_TERMINAL_UP = 0x800000, + EKT_IO_GAMEBTN_TERMINAL_RIGHT = 0x1000000, + EKT_IO_GAMEBTN_TERMINAL_DOWN = 0x2000000, + EKT_IO_GAMEBTN_TERMINAL_LEFT_2 = 0x4000000, + EKT_IO_GAMEBTN_TERMINAL_RIGHT_2 = 0x8000000, + EKT_IO_GAMEBTN_TERMINAL_DECIDE = 0x10000000, + EKT_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 ekt_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after ekt_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT ekt_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 ekt_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + EKT_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 ekt_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + EKT_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 ekt_io_get_gamebtns(uint32_t *gamebtn); + +/* Get the position of the trackball as of the last poll. + + Minimum API version: 0x0100 */ + +void ekt_io_get_trackball_position(uint8_t *stick_x, uint8_t *stick_y); + +/* Initialize LED emulation. This function will be called before any + other ekt_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 ekt_io_led_init(void); + +/* Update the RGB LEDs. + + Exact layout is TBD. */ + +void ekt_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/games/ektio/keyboard.c b/games/ektio/keyboard.c new file mode 100644 index 0000000..2b8bebe --- /dev/null +++ b/games/ektio/keyboard.c @@ -0,0 +1,172 @@ +#include + +#include +#include +#include +#include + +#include "ektio/backend.h" +#include "ektio/config.h" +#include "ektio/ektio.h" +#include "ektio/keyboard.h" + +#include "util/dprintf.h" + +static void ekt_kb_get_gamebtns(uint32_t* gamebtn_out); +static void ekt_kb_get_trackball(uint8_t* x, uint8_t* y); + +static const struct ekt_io_backend ekt_kb_backend = { + .get_gamebtns = ekt_kb_get_gamebtns, + .get_trackball = ekt_kb_get_trackball +}; + +static uint8_t current_x; +static uint8_t current_y; + +static struct ekt_kb_config config; + +HRESULT ekt_kb_init(const struct ekt_kb_config* cfg, const struct ekt_io_backend** backend) { + assert(cfg != NULL); + assert(backend != NULL); + + dprintf("Keyboard: Using keyboard input\n"); + *backend = &ekt_kb_backend; + config = *cfg; + + return S_OK; +} + +static void ekt_kb_get_gamebtns(uint32_t* gamebtn_out) { + assert(gamebtn_out != NULL); + + uint8_t gamebtn = 0; + + if (GetAsyncKeyState(config.vk_hougu) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_HOUGU; + } + + if (GetAsyncKeyState(config.vk_menu) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_MENU; + } + + if (GetAsyncKeyState(config.vk_start) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_START; + } + + if (GetAsyncKeyState(config.vk_stratagem) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_STRATAGEM; + } + + if (GetAsyncKeyState(config.vk_stratagem_lock) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_STRATAGEM_LOCK; + } + + if (GetAsyncKeyState(config.vk_tenkey_0) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_0; + } + + if (GetAsyncKeyState(config.vk_tenkey_1) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_1; + } + + if (GetAsyncKeyState(config.vk_tenkey_2) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_2; + } + + if (GetAsyncKeyState(config.vk_tenkey_3) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_3; + } + + if (GetAsyncKeyState(config.vk_tenkey_4) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_4; + } + + if (GetAsyncKeyState(config.vk_tenkey_5) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_5; + } + + if (GetAsyncKeyState(config.vk_tenkey_6) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_6; + } + + if (GetAsyncKeyState(config.vk_tenkey_7) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_7; + } + + if (GetAsyncKeyState(config.vk_tenkey_8) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_8; + } + + if (GetAsyncKeyState(config.vk_tenkey_9) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_9; + } + + if (GetAsyncKeyState(config.vk_tenkey_clear) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_CLEAR; + } + + if (GetAsyncKeyState(config.vk_tenkey_enter) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_NUMPAD_ENTER; + } + + if (GetAsyncKeyState(config.vk_vol_down) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_VOL_DOWN; + } + + if (GetAsyncKeyState(config.vk_vol_up) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_VOL_UP; + } + + if (GetAsyncKeyState(config.vk_terminal_cancel) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_CANCEL; + } + + if (GetAsyncKeyState(config.vk_terminal_decide) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_DECIDE; + } + + if (GetAsyncKeyState(config.vk_terminal_up) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_UP; + } + + if (GetAsyncKeyState(config.vk_terminal_right) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_RIGHT; + } + + if (GetAsyncKeyState(config.vk_terminal_down) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_DOWN; + } + + if (GetAsyncKeyState(config.vk_terminal_left) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_LEFT; + } + + if (GetAsyncKeyState(config.vk_terminal_left_2) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_LEFT_2; + } + + if (GetAsyncKeyState(config.vk_terminal_right_2) & 0x8000) { + gamebtn |= EKT_IO_GAMEBTN_TERMINAL_RIGHT_2; + } + + *gamebtn_out = gamebtn; +} + +static void ekt_kb_get_trackball(uint8_t* x, uint8_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/ektio/keyboard.h b/games/ektio/keyboard.h new file mode 100644 index 0000000..d0b25dd --- /dev/null +++ b/games/ektio/keyboard.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "ektio/backend.h" +#include "ektio/config.h" + +HRESULT ekt_kb_init(const struct ekt_kb_config *cfg, const struct ekt_io_backend **backend); diff --git a/games/ektio/meson.build b/games/ektio/meson.build new file mode 100644 index 0000000..4ea0e68 --- /dev/null +++ b/games/ektio/meson.build @@ -0,0 +1,18 @@ +ektio_lib = static_library( + 'ektio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + xinput_lib, + ], + sources : [ + 'ektio.c', + 'ektio.h', + 'config.c', + 'config.h', + 'backend.h', + 'keyboard.c', + 'keyboard.h', + ], +) diff --git a/meson.build b/meson.build index 76e9577..18dbdde 100644 --- a/meson.build +++ b/meson.build @@ -125,6 +125,7 @@ subdir('games/tokyoio') subdir('games/fgoio') subdir('games/kemonoio') subdir('games/apm3io') +subdir('games/ektio') subdir('games/chunihook') subdir('games/divahook') @@ -142,3 +143,4 @@ subdir('games/tokyohook') subdir('games/fgohook') subdir('games/kemonohook') subdir('games/apm3hook') +subdir('games/ekthook')