diff --git a/Package.mk b/Package.mk index 0b60a36..f85d86f 100644 --- a/Package.mk +++ b/Package.mk @@ -203,6 +203,22 @@ $(BUILD_DIR_ZIP)/cm.zip: $(V)strip $(BUILD_DIR_ZIP)/cm/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/cm ; zip -r ../cm.zip * +$(BUILD_DIR_ZIP)/tokyo.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/tokyo + $(V)mkdir -p $(BUILD_DIR_ZIP)/tokyo/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/tokyohook/tokyohook.dll \ + $(DIST_DIR)/tokyo/config_hook.json \ + $(DIST_DIR)/tokyo/segatools.ini \ + $(DIST_DIR)/tokyo/start.bat \ + $(BUILD_DIR_ZIP)/tokyo + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/tokyo/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/tokyo/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/tokyo ; zip -r ../tokyo.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/config \ $(DOC_DIR)/chunihook.md \ @@ -225,6 +241,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/mu3.zip \ $(BUILD_DIR_ZIP)/mai2.zip \ $(BUILD_DIR_ZIP)/cm.zip \ + $(BUILD_DIR_ZIP)/tokyo.zip \ $(BUILD_DIR_ZIP)/fgo.zip \ CHANGELOG.md \ README.md \ diff --git a/README.md b/README.md index 4e4e0e3..ad64f5f 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,33 @@ # Segatools -Version: `2024-03-13` +Version: `2024-08-20` Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms. ## List of supported games +* Card Maker + * starting from Card Maker * CHUNITHM * up to [CHUNITHM PARADISE LOST](doc/chunihook.md) * starting from CHUNITHM NEW!! * crossbeats REV. * up to crossbeats REV. SUNRISE +* Fate/Grand Order + * Fate/Grand Order Arcade +* Hatsune Miku: Project DIVA Arcade + * up to Future Tone * Initial D * [Initial D Arcade Stage Zero](doc/idzhook.md) * Initial D THE ARCADE -* Hatsune Miku: Project DIVA Arcade - * up to Future Tone -* SEGA World Drivers Championship - * SEGA World Drivers Championship 2019 -* Fate/Grand Order - * Fate/Grand Order Arcade -* O.N.G.E.K.I. - * starting from O.N.G.E.K.I. * maimai DX * starting from maimai DX -* Card Maker - * starting from Card Maker +* Mario & Sonic + * Mario & Sonic at the Tokyo 2020 Olympics Arcade +* O.N.G.E.K.I. + * starting from O.N.G.E.K.I. +* SEGA World Drivers Championship + * SEGA World Drivers Championship 2019 * WACCA * starting from WACCA diff --git a/dist/tokyo/config_hook.json b/dist/tokyo/config_hook.json new file mode 100644 index 0000000..3758b3d --- /dev/null +++ b/dist/tokyo/config_hook.json @@ -0,0 +1,9 @@ +{ + "network" : + { + "property" : + { + "dhcp" : true + } + } +} diff --git a/dist/tokyo/segatools.ini b/dist/tokyo/segatools.ini new file mode 100644 index 0000000..8c68b53 --- /dev/null +++ b/dist/tokyo/segatools.ini @@ -0,0 +1,199 @@ +; ----------------------------------------------------------------------------- +; 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 OPxx 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= + +; ----------------------------------------------------------------------------- +; 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.149.0` and this value is set to `205`, then the +; local host's virtualized LAN IP is `192.168.149.205`). +addrSuffix=205 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.149.0 + +; Override the keychip's region code. +; 1: JAPAN (ALL.Net, Japanese language, Option support enabled) +; 4: EXPORT (Local networking only, English language, No option support) +; 8: CHINA +; +; NOTE: Changing this setting causes a factory reset. The language can be +; changed in the game settings, so it's possible to run the JAPAN region +; with English language. +region=1 + +[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 + +; For Mario & Sonic at the Tokyo 2020 Olympics Arcade, DipSw 1/2/3 must be set +; as the following: +; Cabinet ID 1 (Server): 1 0 0 +; Cabinet ID 2 (Client): 0 1 0 +; Cabinet ID 3 (Client): 0 0 1 +; Cabinet ID 4 (Client): 0 1 1 +dipsw1=1 +dipsw2=0 +dipsw3=0 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable emulation of the 15093-04 controlled lights, which handle the cabinet +; LEDs. +enable=1 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[zinput] +; Disables the built-in DirectInput support, which is used to support a +; controller out of the box. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[tokyoio] +; To use a custom Mario & Sonic at the Tokyo 2020 Olympics Arcade IO DLL enter +; its path here. Leave empty if you want to use Segatools built-in keyboard/ +; gamepad input. +path= + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; Input API selection for IO4 input emulator. +; Set "xinput" to use a gamepad and "keyboard" to use a keyboard. +mode=xinput + +; Mario & Sonic at the Tokyo 2020 Olympics Arcade Control Panel +; +; |--|------------------ Main-Assy ------------------|--| +; | | YELLOW | | +; | | --- | | +; | | ( O ) | | +; |--| BLUE --- RED |--| +; | | --- PUSH CENTER --- | | +; | | ( O ) /---------------\ ( O ) | | +; | | --- / \ --- | | +; | | PUSH LEFT / \ PUSH RIGHT| | +; |--|---------/ Floor Assy \---------|--| +; | | |JUMP SENSE JUMP SENSE| | | +; | | |1|---------------|-|-------------->|1| | | +; | | | | Foot Panel | | Foot Panel | | | | +; | | |2|<- - - - - - - |-| - - - - - - - |2| | | +; | | | | | | | | | | +; | | |3| -FOOT SENSE - |-| - FOOT SENSE->|3| | | +; | | | | L | | R | | | | +; | | |4|<- - - - - - - |-| - - - - - - - |4| | | +; | | | | | | | | | | +; | | |5| - - - - - - - |-| - - - - - - ->|5| | | +; | | | | | | | | | | +; | | |6|<--------------|-|---------------|6| | | +; | | | | | | +; | | | | | | +; |--|----|-------------------------------------|----|--| +; + +; XInput bindings +; +; X Push Left Blue +; Y Push Center Yellow +; B Push Right Red +; D-Pad Left Push Left Blue +; D-Pad Right Push Right Red +; Left Trigger Foot Sense L/Jump Sense +; Right Trigger Foot Sense R/Jump Sense + +[keyboard] +; Keyboard bindings + +; Keyoard: Push button settings + +; PUSH LEFT (BLUE) button virtual-key code. Default is the A key. +leftBlue=0x41 +; PUSH CENTER (YELLOW) button virtual-key code. Default is the S key. +centerYellow=0x53 +; PUSH RIGHT (RED) button virtual-key code. Default is the D key. +rightRed=0x44 + +; Keyboard: Sensor settings +; FOOT SENSE L (LEFT) button virtual-key code. Default is the Left Arrow key. +footLeft=0x25 +; FOOT SENSE R (RIGHT) button virtual-key code. Default is the Right Arrow key. +footRight=0x27 + +; Keyboard: Jump sensor settings +; All jump sensors will also trigger the FOOT SENSE L and FOOT SENSE R buttons. +; JUMP SENSOR 1 button virtual-key code. Default is the Z key. +jump1=0x5A +; JUMP SENSOR 2 button virtual-key code. Default is the X key. +jump2=0x58 +; JUMP SENSOR 3 button virtual-key code. Default is the C key. +jump3=0x43 +; JUMP SENSOR 4 button virtual-key code. Default is the B key. +jump4=0x42 +; JUMP SENSOR 5 button virtual-key code. Default is the N key. +jump5=0x4E +; JUMP SENSOR 6 button virtual-key code. Default is the M key. +jump6=0x4D + +; Virtual-key code for all jump sensors. Default is the Space key. +jumpAll=0x20 diff --git a/dist/tokyo/start.bat b/dist/tokyo/start.bat new file mode 100644 index 0000000..01c4ddf --- /dev/null +++ b/dist/tokyo/start.bat @@ -0,0 +1,57 @@ +@echo off +pushd %~dp0 + +set DAEMON_WAIT_SECONDS=5 + +set AMDAEMON_CFG=config_common.json ^ +config_ch.json ^ +config_ex.json ^ +config_jp.json ^ +config_st1_ch.json ^ +config_st1_ex.json ^ +config_st1_jp.json ^ +config_st2_ch.json ^ +config_st2_ex.json ^ +config_st2_jp.json ^ +config_st3_ch.json ^ +config_st3_ex.json ^ +config_st3_jp.json ^ +config_st4_ch.json ^ +config_st4_ex.json ^ +config_st4_jp.json ^ +config_laninstall_server_ch.json ^ +config_laninstall_client1_ch.json ^ +config_laninstall_client2_ch.json ^ +config_laninstall_client3_ch.json ^ +config_laninstall_server_ex.json ^ +config_laninstall_client1_ex.json ^ +config_laninstall_client2_ex.json ^ +config_laninstall_client3_ex.json ^ +config_laninstall_server_jp.json ^ +config_laninstall_client1_jp.json ^ +config_laninstall_client2_jp.json ^ +config_laninstall_client3_jp.json ^ +config_hook.json + +start /min "AM Daemon" inject -d -k tokyohook.dll amdaemon.exe -c %AMDAEMON_CFG% +timeout %DAEMON_WAIT_SECONDS% > nul 2>&1 + +REM --------------------------------------------------------------------------- +REM Set configuration +REM --------------------------------------------------------------------------- + +REM Configuration values to be passed to the game executable. +REM All known values: +REM -forceapi:11 +REM -forcehal +REM -forcevsync:0/1 +REM -fullscreen +REM -windowed +REM Note: -windowed is recommended as the game looks sharper in windowed mode. +inject -d -k tokyohook.dll app.exe -windowed + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause diff --git a/meson.build b/meson.build index 40f62e3..c5b3d40 100644 --- a/meson.build +++ b/meson.build @@ -108,6 +108,7 @@ subdir('mai2io') subdir('cmio') subdir('mercuryio') subdir('cxbio') +subdir('tokyoio') subdir('fgoio') subdir('chunihook') @@ -123,4 +124,5 @@ subdir('mai2hook') subdir('cmhook') subdir('mercuryhook') subdir('cxbhook') +subdir('tokyohook') subdir('fgohook') diff --git a/tokyohook/config.c b/tokyohook/config.c new file mode 100644 index 0000000..e9735d2 --- /dev/null +++ b/tokyohook/config.c @@ -0,0 +1,110 @@ +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "platform/config.h" + +#include "tokyohook/config.h" + +void tokyo_dll_config_load( + struct tokyo_dll_config *cfg, + const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"tokyoio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no = GetPrivateProfileIntW(L"led15093", L"portNo", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0x90, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAED9, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-04", + 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"6704 ", + 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 zinput_config_load(struct zinput_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"zinput", L"enable", 1, filename); +} + +void tokyo_hook_config_load( + struct tokyo_hook_config *cfg, + const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + zinput_config_load(&cfg->zinput, filename); + led15093_config_load(&cfg->led15093, filename); + tokyo_dll_config_load(&cfg->dll, filename); +} diff --git a/tokyohook/config.h b/tokyohook/config.h new file mode 100644 index 0000000..54dc751 --- /dev/null +++ b/tokyohook/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" + +#include "tokyohook/tokyo-dll.h" +#include "tokyohook/zinput.h" + +#include "platform/config.h" + +struct tokyo_hook_config { + struct platform_config platform; + struct dvd_config dvd; + struct io4_config io4; + struct led15093_config led15093; + struct zinput_config zinput; + struct tokyo_dll_config dll; +}; + +void tokyo_dll_config_load( + struct tokyo_dll_config *cfg, + const wchar_t *filename); + +void tokyo_hook_config_load( + struct tokyo_hook_config *cfg, + const wchar_t *filename); diff --git a/tokyohook/dllmain.c b/tokyohook/dllmain.c new file mode 100644 index 0000000..e39fcff --- /dev/null +++ b/tokyohook/dllmain.c @@ -0,0 +1,112 @@ +/* + "Mario & Sonic at the Tokyo 2020 Olympics Arcade" (tokyo) hook + + Devices + + USB: 837-15257 "Type 4" I/O Board + COM1: 837-15093-04 LED Controller Board +*/ + +#include + +#include + +#include "board/io4.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "tokyohook/config.h" +#include "tokyohook/io4.h" +#include "tokyohook/tokyo-dll.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE tokyo_hook_mod; +static process_entry_t tokyo_startup; +static struct tokyo_hook_config tokyo_hook_cfg; + +static DWORD CALLBACK tokyo_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin tokyo_pre_startup ---\n"); + + /* Load config */ + + tokyo_hook_config_load(&tokyo_hook_cfg, L".\\segatools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&tokyo_hook_cfg.dvd, tokyo_hook_mod); + zinput_hook_init(&tokyo_hook_cfg.zinput); + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &tokyo_hook_cfg.platform, + "SDFV", + "ACA1", + tokyo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = tokyo_dll_init(&tokyo_hook_cfg.dll, tokyo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = led15093_hook_init(&tokyo_hook_cfg.led15093, + tokyo_dll.led_init, tokyo_dll.led_set_leds, 1, 1, 1, 2); + + if (FAILED(hr)) { + return hr; + } + + hr = tokyo_io4_hook_init(&tokyo_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize debug helpers */ + + spike_hook_init(L".\\segatools.ini"); + + dprintf("--- End tokyo_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return tokyo_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + tokyo_hook_mod = mod; + + hr = process_hijack_startup(tokyo_pre_startup, &tokyo_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/tokyohook/io4.c b/tokyohook/io4.c new file mode 100644 index 0000000..b2924c7 --- /dev/null +++ b/tokyohook/io4.c @@ -0,0 +1,163 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "tokyohook/tokyo-dll.h" + +#include "util/dprintf.h" + +static HRESULT tokyo_io4_poll(void *ctx, struct io4_state *state); +static HRESULT tokyo_io4_write_gpio(uint8_t* payload, size_t len); + +static uint16_t coins; + +static const struct io4_ops tokyo_io4_ops = { + .poll = tokyo_io4_poll, + .write_gpio = tokyo_io4_write_gpio, +}; + +HRESULT tokyo_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(tokyo_dll.init != NULL); + + hr = io4_hook_init(cfg, &tokyo_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return tokyo_dll.init(); +} + +static HRESULT tokyo_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + uint8_t sense; + HRESULT hr; + + assert(tokyo_dll.get_opbtns != NULL); + assert(tokyo_dll.get_gamebtns != NULL); + assert(tokyo_dll.get_sensors != NULL); + + memset(state, 0, sizeof(*state)); + + opbtn = 0; + gamebtn = 0; + sense = 0; + + tokyo_dll.get_opbtns(&opbtn); + tokyo_dll.get_gamebtns(&gamebtn); + tokyo_dll.get_sensors(&sense); + + if (opbtn & TOKYO_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & TOKYO_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & TOKYO_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + /* Update gamebtns */ + + if (gamebtn & TOKYO_IO_GAMEBTN_BLUE) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & TOKYO_IO_GAMEBTN_YELLOW) { + state->buttons[0] |= 1 << 0; + } + + if (gamebtn & TOKYO_IO_GAMEBTN_RED) { + state->buttons[0] |= 1 << 15; + } + + /* Update sensors */ + + // Invert the logic so that it's active high + if (!(sense & TOKYO_IO_SENSE_FOOT_LEFT)) { + state->buttons[0] |= 1 << 13; + } + + if (!(sense & TOKYO_IO_SENSE_FOOT_RIGHT)) { + state->buttons[1] |= 1 << 13; + } + + if (sense & TOKYO_IO_SENSE_JUMP_1) { + state->buttons[0] |= 1 << 12; + } + + if (sense & TOKYO_IO_SENSE_JUMP_2) { + state->buttons[1] |= 1 << 12; + } + + if (sense & TOKYO_IO_SENSE_JUMP_3) { + state->buttons[0] |= 1 << 11; + } + + if (sense & TOKYO_IO_SENSE_JUMP_4) { + state->buttons[1] |= 1 << 11; + } + + if (sense & TOKYO_IO_SENSE_JUMP_5) { + state->buttons[0] |= 1 << 10; + } + + if (sense & TOKYO_IO_SENSE_JUMP_6) { + state->buttons[1] |= 1 << 10; + } + + return S_OK; +} + +static HRESULT tokyo_io4_write_gpio(uint8_t* payload, size_t len) +{ + // Just fast fail if there aren't enough bytes in the payload + if (len < 3) + return S_OK; + + // This command is used for lights in Mario & Sonic at the Tokyo 2020 Olympics + // Arcade, but it only contains button lights, and only in the first 3 bytes of + // the payload; everything else is padding to make the payload 62 bytes. The + // rest of the cabinet lights and the side button lights are handled separately, + // by the 15093 lights controller. + uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 | + (uint8_t)(payload[1]) << 16 | + (uint8_t)(payload[2]) << 8); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as RGB values. + uint8_t rgb_out[5 * 3] = { + lights_data & TOKYO_IO_LED_LEFT_BLUE ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CENTER_YELLOW ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_RIGHT_RED ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_LEFT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_LEFT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_LEFT_B ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_RIGHT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_RIGHT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_RIGHT_B ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_LEFT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_LEFT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_LEFT_B ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_RIGHT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_RIGHT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_RIGHT_B ? 0xFF : 0x00, + }; + + tokyo_dll.led_set_leds(1, rgb_out); + + return S_OK; +} diff --git a/tokyohook/io4.h b/tokyohook/io4.h new file mode 100644 index 0000000..508d802 --- /dev/null +++ b/tokyohook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT tokyo_io4_hook_init(const struct io4_config *cfg); diff --git a/tokyohook/meson.build b/tokyohook/meson.build new file mode 100644 index 0000000..1e4b1d8 --- /dev/null +++ b/tokyohook/meson.build @@ -0,0 +1,32 @@ +shared_library( + 'tokyohook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'tokyohook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + tokyoio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'zinput.c', + 'zinput.h', + 'tokyo-dll.c', + 'tokyo-dll.h', + ], +) diff --git a/tokyohook/tokyo-dll.c b/tokyohook/tokyo-dll.c new file mode 100644 index 0000000..0187205 --- /dev/null +++ b/tokyohook/tokyo-dll.c @@ -0,0 +1,115 @@ +#include + +#include +#include + +#include "tokyohook/tokyo-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym tokyo_dll_syms[] = { + { + .sym = "tokyo_io_init", + .off = offsetof(struct tokyo_dll, init), + }, { + .sym = "tokyo_io_get_opbtns", + .off = offsetof(struct tokyo_dll, get_opbtns), + }, { + .sym = "tokyo_io_get_gamebtns", + .off = offsetof(struct tokyo_dll, get_gamebtns), + }, { + .sym = "tokyo_io_get_sensors", + .off = offsetof(struct tokyo_dll, get_sensors), + }, { + .sym = "tokyo_io_led_init", + .off = offsetof(struct tokyo_dll, led_init), + }, { + .sym = "tokyo_io_led_set_colors", + .off = offsetof(struct tokyo_dll, led_set_leds), + } +}; + +struct tokyo_dll tokyo_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 tokyo_dll_init(const struct tokyo_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("Tokyo IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Tokyo IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "tokyo_io_get_api_version"); + + if (get_api_version != NULL) { + tokyo_dll.api_version = get_api_version(); + } else { + tokyo_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose tokyo_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (tokyo_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Tokyo IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + tokyo_dll.api_version); + + goto end; + } + + sym = tokyo_dll_syms; + hr = dll_bind(&tokyo_dll, src, &sym, _countof(tokyo_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Tokyo 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/tokyohook/tokyo-dll.h b/tokyohook/tokyo-dll.h new file mode 100644 index 0000000..9bfeb5a --- /dev/null +++ b/tokyohook/tokyo-dll.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "tokyoio/tokyoio.h" + +struct tokyo_dll { + uint16_t api_version; + HRESULT (*init)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_sensors)(uint8_t *sense); + HRESULT (*gpio_out)(uint32_t state); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); +}; + +struct tokyo_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct tokyo_dll tokyo_dll; + +HRESULT tokyo_dll_init(const struct tokyo_dll_config *cfg, HINSTANCE self); diff --git a/tokyohook/tokyohook.def b/tokyohook/tokyohook.def new file mode 100644 index 0000000..d60bf17 --- /dev/null +++ b/tokyohook/tokyohook.def @@ -0,0 +1,20 @@ +LIBRARY tokyohook + +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 + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + tokyo_io_get_api_version + tokyo_io_init + tokyo_io_get_opbtns + tokyo_io_get_gamebtns + tokyo_io_get_sensors + tokyo_io_led_init + tokyo_io_led_set_colors diff --git a/tokyohook/zinput.c b/tokyohook/zinput.c new file mode 100644 index 0000000..2152d6c --- /dev/null +++ b/tokyohook/zinput.c @@ -0,0 +1,118 @@ +#include +#include +#include + +#include +#include + +#include "tokyohook/config.h" +#include "tokyohook/zinput.h" + +#include "hook/table.h" + +#include "util/lib.h" +#include "util/dprintf.h" + +HRESULT WINAPI hook_DirectInput8Create( + HINSTANCE hinst, + DWORD dwVersion, + REFIID riidltf, + LPVOID *ppvOut, + LPUNKNOWN punkOuter); + +static HRESULT WINAPI hook_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags); + +static unsigned long WINAPI hook_AddRef(IUnknown *self); +static unsigned long WINAPI hook_Release(IUnknown *self); + +static const IDirectInput8WVtbl api_vtbl = { + .EnumDevices = hook_EnumDevices, + .AddRef = (void *) hook_AddRef, + .Release = (void *) hook_Release, +}; + +static const IDirectInput8W api = { (void *) &api_vtbl }; + +static const struct hook_symbol zinput_hook_syms[] = { + { + .name = "DirectInput8Create", + .patch = hook_DirectInput8Create, + .link = NULL, + } +}; + +HRESULT zinput_hook_init(struct zinput_config *cfg) +{ + wchar_t *module_path; + wchar_t *file_name; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + module_path = module_file_name(NULL); + + if (module_path != NULL) { + file_name = PathFindFileNameW(module_path); + + free(module_path); + module_path = NULL; + + _wcslwr(file_name); + + if (wcsstr(file_name, L"amdaemon") != NULL) { + // dprintf("Executable filename contains 'amdaemon', disabling zinput\n"); + return S_OK; + } + } + + hook_table_apply( + NULL, + "dinput8.dll", + zinput_hook_syms, + _countof(zinput_hook_syms)); + + return S_OK; +} + +HRESULT WINAPI hook_DirectInput8Create( + HINSTANCE hinst, + DWORD dwVersion, + REFIID riidltf, + LPVOID *ppvOut, + LPUNKNOWN punkOuter) +{ + dprintf("ZInput: Blocking built-in DirectInput support\n"); + *ppvOut = (void *) &api; + + return DI_OK; +} + +static HRESULT WINAPI hook_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags) +{ + dprintf("ZInput: %s\n", __func__); + + return DI_OK; +} + +static unsigned long WINAPI hook_AddRef(IUnknown *self) +{ + return 1; +} + +static unsigned long WINAPI hook_Release(IUnknown *self) +{ + return 1; +} diff --git a/tokyohook/zinput.h b/tokyohook/zinput.h new file mode 100644 index 0000000..13a46cd --- /dev/null +++ b/tokyohook/zinput.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +struct zinput_config { + bool enable; +}; + +HRESULT zinput_hook_init(struct zinput_config *cfg); diff --git a/tokyoio/backend.h b/tokyoio/backend.h new file mode 100644 index 0000000..52fee61 --- /dev/null +++ b/tokyoio/backend.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "tokyoio/tokyoio.h" + +struct tokyo_io_backend { + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_sensors)(uint8_t *sense); +}; diff --git a/tokyoio/config.c b/tokyoio/config.c new file mode 100644 index 0000000..0ae2d26 --- /dev/null +++ b/tokyoio/config.c @@ -0,0 +1,54 @@ +#include + +#include +#include +#include + +#include "tokyoio/config.h" + + +void tokyo_kb_config_load( + struct tokyo_kb_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + /* Load game button keyboard bindings */ + cfg->vk_push_left_b = GetPrivateProfileIntW(L"keyboard", L"leftBlue", 'A', filename); + cfg->vk_push_center_y = GetPrivateProfileIntW(L"keyboard", L"centerYellow", 'S', filename); + cfg->vk_push_right_r = GetPrivateProfileIntW(L"keyboard", L"rightRed", 'D', filename); + + /* Load sensor keyboard bindings */ + cfg->vk_foot_l = GetPrivateProfileIntW(L"keyboard", L"footLeft", VK_LEFT, filename); + cfg->vk_foot_r = GetPrivateProfileIntW(L"keyboard", L"footRight", VK_RIGHT, filename); + cfg->vk_jump_1 = GetPrivateProfileIntW(L"keyboard", L"jump1", 'Z', filename); + cfg->vk_jump_2 = GetPrivateProfileIntW(L"keyboard", L"jump2", 'X', filename); + cfg->vk_jump_3 = GetPrivateProfileIntW(L"keyboard", L"jump3", 'C', filename); + cfg->vk_jump_4 = GetPrivateProfileIntW(L"keyboard", L"jump4", 'B', filename); + cfg->vk_jump_5 = GetPrivateProfileIntW(L"keyboard", L"jump5", 'N', filename); + cfg->vk_jump_6 = GetPrivateProfileIntW(L"keyboard", L"jump6", 'M', filename); + cfg->vk_jump_all = GetPrivateProfileIntW(L"keyboard", L"jumpAll", VK_SPACE, filename); +} + +void tokyo_io_config_load( + struct tokyo_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); + + GetPrivateProfileStringW( + L"io4", + L"mode", + L"xinput", + cfg->mode, + _countof(cfg->mode), + filename); + + tokyo_kb_config_load(&cfg->kb, filename); +} diff --git a/tokyoio/config.h b/tokyoio/config.h new file mode 100644 index 0000000..839b2c3 --- /dev/null +++ b/tokyoio/config.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include + +struct tokyo_kb_config { + uint8_t vk_push_left_b; + uint8_t vk_push_center_y; + uint8_t vk_push_right_r; + uint8_t vk_foot_l; + uint8_t vk_foot_r; + uint8_t vk_jump_1; + uint8_t vk_jump_2; + uint8_t vk_jump_3; + uint8_t vk_jump_4; + uint8_t vk_jump_5; + uint8_t vk_jump_6; + uint8_t vk_jump_all; +}; + +struct tokyo_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + wchar_t mode[9]; + + struct tokyo_kb_config kb; +}; + +void tokyo_io_config_load( + struct tokyo_io_config *cfg, + const wchar_t *filename); diff --git a/tokyoio/dllmain.c b/tokyoio/dllmain.c new file mode 100644 index 0000000..76c81ac --- /dev/null +++ b/tokyoio/dllmain.c @@ -0,0 +1,135 @@ +#include + +#include +#include +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" +#include "tokyoio/kb.h" +#include "tokyoio/tokyoio.h" +#include "tokyoio/xi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static struct tokyo_io_config tokyo_io_cfg; +static const struct tokyo_io_backend *tokyo_io_backend; +static bool tokyo_io_coin; + +uint16_t tokyo_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT tokyo_io_init(void) +{ + HINSTANCE inst; + HRESULT hr; + + assert(tokyo_io_backend == NULL); + + inst = GetModuleHandleW(NULL); + + if (inst == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("GetModuleHandleW failed: %lx\n", hr); + + return hr; + } + + tokyo_io_config_load(&tokyo_io_cfg, L".\\segatools.ini"); + + if (wstr_ieq(tokyo_io_cfg.mode, L"keyboard")) { + hr = tokyo_kb_init(&tokyo_io_cfg.kb, &tokyo_io_backend); + } else if (wstr_ieq(tokyo_io_cfg.mode, L"xinput")) { + hr = tokyo_xi_init(&tokyo_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("IDAC IO: Invalid IO mode \"%S\", use keyboard or xinput\n", + tokyo_io_cfg.mode); + } + + return hr; +} + +void tokyo_io_get_opbtns(uint8_t *opbtn_out) +{ + uint8_t opbtn; + + assert(tokyo_io_backend != NULL); + assert(opbtn_out != NULL); + + opbtn = 0; + + /* Common operator buttons, not backend-specific */ + + if (GetAsyncKeyState(tokyo_io_cfg.vk_test) & 0x8000) { + opbtn |= TOKYO_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(tokyo_io_cfg.vk_service) & 0x8000) { + opbtn |= TOKYO_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(tokyo_io_cfg.vk_coin) & 0x8000) { + if (!tokyo_io_coin) { + tokyo_io_coin = true; + opbtn |= TOKYO_IO_OPBTN_COIN; + } + } else { + tokyo_io_coin = false; + } + + *opbtn_out = opbtn; +} + + +void tokyo_io_get_gamebtns(uint8_t *gamebtn_out) +{ + assert(tokyo_io_backend != NULL); + assert(gamebtn_out != NULL); + + tokyo_io_backend->get_gamebtns(gamebtn_out); +} + +void tokyo_io_get_sensors(uint8_t *sense_out) +{ + assert(sense_out != NULL); + assert(tokyo_io_backend != NULL); + + tokyo_io_backend->get_sensors(sense_out); +} + +HRESULT tokyo_io_led_init(void) +{ + return S_OK; +} + +void tokyo_io_led_set_colors(uint8_t board, uint8_t *rgb) +{ +#if 0 + if (board == 0) { + dprintf("Board 0:\n"); + // Change GRB order to RGB order + for (int i = 0; i < 27; i++) { + dprintf("Tokyo LED: MONITOR LEFT: %02X %02X %02X\n", rgb[i * 3 + 1], rgb[i * 3], rgb[i * 3 + 2]); + } + + for (int i = 27; i < 54; i++) { + dprintf("Tokyo LED: MONITOR RIGHT: %d, %02X %02X %02X\n", i, rgb[i * 3 + 1], rgb[i * 3], rgb[i * 3 + 2]); + } + } else { + dprintf("Board 1:\n"); + dprintf("Tokyo LED: LEFT BLUE: %02X\n", rgb[0]); + dprintf("Tokyo LED: CENTER YELLOW: %02X\n", rgb[1]); + dprintf("Tokyo LED: RIGHT RED: %02X\n", rgb[2]); + dprintf("Tokyo LED: CONTROL LEFT: %02X %02X %02X\n", rgb[3], rgb[4], rgb[5]); + dprintf("Tokyo LED: CONTROL RIGHT: %02X %02X %02X\n", rgb[6], rgb[7], rgb[8]); + dprintf("Tokyo LED: FLOOR LEFT: %02X %02X %02X\n", rgb[9], rgb[10], rgb[11]); + dprintf("Tokyo LED: FLOOR RIGHT: %02X %02X %02X\n", rgb[12], rgb[13], rgb[14]); + } +#endif + + return; +} diff --git a/tokyoio/kb.c b/tokyoio/kb.c new file mode 100644 index 0000000..3b3c34d --- /dev/null +++ b/tokyoio/kb.c @@ -0,0 +1,135 @@ +#include + +#include +#include +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" +#include "tokyoio/kb.h" +#include "tokyoio/tokyoio.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static HRESULT tokyo_kb_config_apply(const struct tokyo_kb_config *cfg); + +static void tokyo_kb_get_gamebtns(uint8_t *gamebtn_out); +static void tokyo_kb_get_sensors(uint8_t *sense_out); + +static const struct tokyo_io_backend tokyo_kb_backend = { + .get_gamebtns = tokyo_kb_get_gamebtns, + .get_sensors = tokyo_kb_get_sensors, +}; + +static struct tokyo_kb_config tokyo_kb_cfg; + +HRESULT tokyo_kb_init(const struct tokyo_kb_config *cfg, const struct tokyo_io_backend **backend) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(backend != NULL); + + hr = tokyo_kb_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + dprintf("TokyoIO: Using keyboard input\n"); + *backend = &tokyo_kb_backend; + return S_OK; +} + +static HRESULT tokyo_kb_config_apply(const struct tokyo_kb_config *cfg) +{ + tokyo_kb_cfg = *cfg; + + return S_OK; +} + +static void tokyo_kb_get_gamebtns(uint8_t *gamebtn_out) +{ + uint8_t gamebtn; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + /* PUSH BUTTON inputs */ + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_push_left_b) & 0x8000) { + gamebtn |= TOKYO_IO_GAMEBTN_BLUE; + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_push_center_y) & 0x8000) { + gamebtn |= TOKYO_IO_GAMEBTN_YELLOW; + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_push_right_r) & 0x8000) { + gamebtn |= TOKYO_IO_GAMEBTN_RED; + } + + *gamebtn_out = gamebtn; +} + +static void tokyo_kb_get_sensors(uint8_t *sense_out) +{ + uint8_t sense; + + assert(sense_out != NULL); + + sense = 0; + + /* FOOT SENSOR inputs */ + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_foot_l) & 0x8000) { + sense |= TOKYO_IO_SENSE_FOOT_LEFT; + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_foot_r) & 0x8000) { + sense |= TOKYO_IO_SENSE_FOOT_RIGHT; + } + + /* JUMP SENSOR inputs */ + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_1) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_1); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_2) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_2); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_3) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_3); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_4) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_4); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_5) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_5); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_6) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_6); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_all) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT+ TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_1 + TOKYO_IO_SENSE_JUMP_2 + + TOKYO_IO_SENSE_JUMP_3 + TOKYO_IO_SENSE_JUMP_4 + + TOKYO_IO_SENSE_JUMP_5 + TOKYO_IO_SENSE_JUMP_6); + } + + *sense_out = sense; +} diff --git a/tokyoio/kb.h b/tokyoio/kb.h new file mode 100644 index 0000000..2a6691f --- /dev/null +++ b/tokyoio/kb.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" + +HRESULT tokyo_kb_init( + const struct tokyo_kb_config *cfg, + const struct tokyo_io_backend **backend); diff --git a/tokyoio/meson.build b/tokyoio/meson.build new file mode 100644 index 0000000..aa4e198 --- /dev/null +++ b/tokyoio/meson.build @@ -0,0 +1,21 @@ +tokyoio_lib = static_library( + 'tokyoio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'backend.h', + 'config.c', + 'config.h', + 'dllmain.c', + 'tokyoio.h', + 'kb.c', + 'kb.h', + 'xi.c', + 'xi.h', + ], +) diff --git a/tokyoio/tokyoio.h b/tokyoio/tokyoio.h new file mode 100644 index 0000000..6edec63 --- /dev/null +++ b/tokyoio/tokyoio.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +#include + +enum { + TOKYO_IO_OPBTN_TEST = 0x01, + TOKYO_IO_OPBTN_SERVICE = 0x02, + TOKYO_IO_OPBTN_COIN = 0x04, +}; + +enum { + TOKYO_IO_GAMEBTN_BLUE = 0x01, + TOKYO_IO_GAMEBTN_YELLOW = 0x02, + TOKYO_IO_GAMEBTN_RED = 0x04, +}; + +enum { + TOKYO_IO_SENSE_FOOT_LEFT = 0x01, + TOKYO_IO_SENSE_FOOT_RIGHT = 0x02, + TOKYO_IO_SENSE_JUMP_1 = 0x04, + TOKYO_IO_SENSE_JUMP_2 = 0x08, + TOKYO_IO_SENSE_JUMP_3 = 0x10, + TOKYO_IO_SENSE_JUMP_4 = 0x20, + TOKYO_IO_SENSE_JUMP_5 = 0x40, + TOKYO_IO_SENSE_JUMP_6 = 0x80, +}; + +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + TOKYO_IO_LED_LEFT_BLUE = 1 << 31, + TOKYO_IO_LED_CENTER_YELLOW = 1 << 30, + TOKYO_IO_LED_RIGHT_RED = 1 << 29, + TOKYO_IO_LED_CONTROL_LEFT_R = 1 << 25, + TOKYO_IO_LED_CONTROL_LEFT_G = 1 << 24, + TOKYO_IO_LED_CONTROL_LEFT_B = 1 << 23, + TOKYO_IO_LED_CONTROL_RIGHT_R = 1 << 22, + TOKYO_IO_LED_CONTROL_RIGHT_G = 1 << 21, + TOKYO_IO_LED_CONTROL_RIGHT_B = 1 << 20, + TOKYO_IO_LED_FLOOR_LEFT_R = 1 << 19, + TOKYO_IO_LED_FLOOR_LEFT_G = 1 << 18, + TOKYO_IO_LED_FLOOR_LEFT_B = 1 << 17, + TOKYO_IO_LED_FLOOR_RIGHT_R = 1 << 16, + TOKYO_IO_LED_FLOOR_RIGHT_G = 1 << 15, + TOKYO_IO_LED_FLOOR_RIGHT_B = 1 << 14, +}; + +/* Get the version of the Mario & Sonic at the Olympic Games Tokyo 2020 Arcade + Edition 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 tokyo_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after tokyo_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT tokyo_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 tokyo_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + TOKYO_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 tokyo_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + TOKYO_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 tokyo_io_get_gamebtns(uint8_t *gamebtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + TOKYO_IO_SENSE enum above: this contains bit mask definitions for button + states returned in *sense. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void tokyo_io_get_sensors(uint8_t *sense); + +/* Initialize LED emulation. This function will be called before any + other tokyo_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. + + Minimum API version: 0x0100 */ + +HRESULT tokyo_io_led_init(void); + +/* Update the RGB LEDs. rgb is a pointer to an array of up to 54 * 3 = 162 bytes. + + Mario & Sonic at the Tokyo 2020 Olympics Arcade uses one board with 15 LEDs for + all buttons, control panel, and floor LEDs. Board 1 is just used for the + left and right monitor LEDs. + + Board 0 has 54 LEDs (GRB order): + [0]-[26]: left monitor LEDs + [27]-[53]: right monitor LEDs + + Board 1 has 15 LEDs (RGB order): + [0]: left blue LED + [1]: center yellow LED + [2]: right red LED + [3]-[5]: left control panel LEDs + [6]-[8]: right control panel LEDs + [9]-[11]: left floor LEDs + [12]-[14]: right floor LEDs + + Each rgb value is comprised of 3 bytes in G,R,B order for board 0 and R,G,B + order for board 1. The tricky part is that the board 0 is called from app and + the board 1 is called from amdaemon. So the library must be able to handle both + calls, using shared memory f.e. This is up to the developer to decide how to + handle this, recommended way is to use the amdaemon process as the main one + and the app process as a sub one. + + Minimum API version: 0x0100 */ + +void tokyo_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/tokyoio/xi.c b/tokyoio/xi.c new file mode 100644 index 0000000..b6b5749 --- /dev/null +++ b/tokyoio/xi.c @@ -0,0 +1,130 @@ +#include +#include +#include + +#include +#include +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" +#include "tokyoio/tokyoio.h" +#include "tokyoio/xi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static void tokyo_xi_get_gamebtns(uint8_t *gamebtn_out); +static void tokyo_xi_get_sensors(uint8_t *sense_out); + +static const struct tokyo_io_backend tokyo_xi_backend = { + .get_gamebtns = tokyo_xi_get_gamebtns, + .get_sensors = tokyo_xi_get_sensors, +}; + +HRESULT tokyo_xi_init(const struct tokyo_io_backend **backend) +{ + wchar_t dll_path[MAX_PATH]; + HMODULE xinput; + HRESULT hr; + UINT path_pos; + + assert(backend != NULL); + + dprintf("TokyoIO: IO4: Using XInput controller\n"); + *backend = &tokyo_xi_backend; + return S_OK; +} + +static void tokyo_xi_get_gamebtns(uint8_t *gamebtn_out) +{ + uint8_t gamebtn; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + XINPUT_STATE xi; + WORD xb; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + /* PUSH BUTTON inputs */ + + if ((xb & XINPUT_GAMEPAD_X) || (xb & XINPUT_GAMEPAD_DPAD_LEFT)) { + gamebtn |= TOKYO_IO_GAMEBTN_BLUE; + } + + if (xb & XINPUT_GAMEPAD_Y || (xb & XINPUT_GAMEPAD_A)) { + gamebtn |= TOKYO_IO_GAMEBTN_YELLOW; + } + + if ((xb & XINPUT_GAMEPAD_B) || (xb & XINPUT_GAMEPAD_DPAD_RIGHT)) { + gamebtn |= TOKYO_IO_GAMEBTN_RED; + } + + *gamebtn_out = gamebtn; +} + +static void tokyo_xi_get_sensors(uint8_t *sense_out) +{ + uint8_t sense; + + XINPUT_STATE xi; + WORD xb; + BYTE xt_l; + BYTE xt_r; + + assert(sense_out != NULL); + + sense = 0; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + xt_l = xi.Gamepad.bLeftTrigger; + xt_r = xi.Gamepad.bRightTrigger; + + float xt_l_f = xt_l / 255.0f; + float xt_r_f = xt_r / 255.0f; + + // Normalize both triggers to 0..1 and find the max directly + float trigger = fmaxf(xt_l_f, xt_r_f); + + const int max_jump_levels = 6; + float jump_threshold = 1.0f / max_jump_levels; + + /* FOOT SENSOR inputs */ + + // Determine if both foot sensors should be set + bool left_active = xt_l_f > jump_threshold; + bool right_active = xt_r_f > jump_threshold; + + // Set foot sensors based on individual trigger activity + if (left_active) { + sense |= TOKYO_IO_SENSE_FOOT_LEFT; + } + if (right_active) { + sense |= TOKYO_IO_SENSE_FOOT_RIGHT; + } + + /* JUMP SENSOR inputs */ + + // If both triggers are active, set jump levels and both foot sensors + if (left_active && right_active) { + float trigger_avg = (xt_l_f + xt_r_f) / 2.0f; + + // Calculate the appropriate jump level + for (int i = 1; i <= max_jump_levels; ++i) { + if (trigger_avg >= i * jump_threshold) { + sense |= (TOKYO_IO_SENSE_JUMP_1 << (i - 1)); + } else { + break; + } + } + } + + *sense_out = sense; +} diff --git a/tokyoio/xi.h b/tokyoio/xi.h new file mode 100644 index 0000000..368fff3 --- /dev/null +++ b/tokyoio/xi.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" + +HRESULT tokyo_xi_init(const struct tokyo_io_backend **backend);