ekt: initial code

This commit is contained in:
2025-08-31 20:47:24 +02:00
parent 4788e54398
commit d9210b52e1
26 changed files with 2295 additions and 0 deletions

View File

@ -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 \

5
dist/ekt/config_hook.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"common": {
"language": "english"
}
}

18
dist/ekt/launch_satellite.bat vendored Normal file
View File

@ -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

18
dist/ekt/launch_terminal.bat vendored Normal file
View File

@ -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

170
dist/ekt/segatools_satellite.ini vendored Normal file
View File

@ -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

158
dist/ekt/segatools_terminal.ini vendored Normal file
View File

@ -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

117
games/ekthook/config.c Normal file
View File

@ -0,0 +1,117 @@
#include <assert.h>
#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);
}

41
games/ekthook/config.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <stddef.h>
#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);

192
games/ekthook/dllmain.c Normal file
View File

@ -0,0 +1,192 @@
#include <windows.h>
#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);
}

118
games/ekthook/ekt-dll.c Normal file
View File

@ -0,0 +1,118 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#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;
}

24
games/ekthook/ekt-dll.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <windows.h>
#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);

49
games/ekthook/ekthook.def Normal file
View File

@ -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

208
games/ekthook/io4.c Normal file
View File

@ -0,0 +1,208 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#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;
}

7
games/ekthook/io4.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/io4.h"
HRESULT ekt_io4_hook_init(const struct io4_config *cfg, bool is_terminal);

32
games/ekthook/meson.build Normal file
View File

@ -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'
],
)

510
games/ekthook/y3.c Normal file
View File

@ -0,0 +1,510 @@
#include <windows.h>
#include <shellapi.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <sys/time.h>
#include "hook/table.h"
#include "hooklib/dll.h"
#include "util/dprintf.h"
#include "ekthook/config.h"
#include "ekthook/y3.h"
#include <assert.h>
#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;
}

57
games/ekthook/y3.h Normal file
View File

@ -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);

10
games/ektio/backend.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
#include "ektio/ektio.h"
struct ekt_io_backend {
void (*get_gamebtns)(uint32_t *gamebtn);
void (*get_trackball)(uint8_t *x, uint8_t *y);
};

76
games/ektio/config.c Normal file
View File

@ -0,0 +1,76 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "ektio/config.h"
#include <xinput.h>
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);
}

61
games/ektio/config.h Normal file
View File

@ -0,0 +1,61 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
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);

100
games/ektio/ektio.c Normal file
View File

@ -0,0 +1,100 @@
#include <windows.h>
#include <xinput.h>
#include <math.h>
#include <stdint.h>
#include "ektio/ektio.h"
#include <assert.h>
#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;
}

105
games/ektio/ektio.h Normal file
View File

@ -0,0 +1,105 @@
#pragma once
#include <windows.h>
#include <stdint.h>
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);

172
games/ektio/keyboard.c Normal file
View File

@ -0,0 +1,172 @@
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <stdint.h>
#include <limits.h>
#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;
}

8
games/ektio/keyboard.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <windows.h>
#include "ektio/backend.h"
#include "ektio/config.h"
HRESULT ekt_kb_init(const struct ekt_kb_config *cfg, const struct ekt_io_backend **backend);

18
games/ektio/meson.build Normal file
View File

@ -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',
],
)

View File

@ -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')