From cc0b6b0953e268fe29365ed11bc2079786e2a9d6 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Tue, 16 Jan 2024 17:56:24 +0100 Subject: [PATCH] swdc: fixed steering wheel buttons, improved start.bat --- Package.mk | 1 + dist/swdc/config_hook.json | 6 + dist/swdc/segatools.ini | 2 +- dist/swdc/start.bat | 36 +++++- swdchook/dllmain.c | 18 +-- swdchook/io4.c | 65 ++++++++--- swdchook/swdc-dll.c | 3 - swdchook/swdc-dll.h | 1 - swdchook/swdchook.def | 1 - swdchook/zinput.c | 220 +++++++++++++++++++++++++++++-------- swdchook/zinput.h | 2 +- swdcio/di.c | 3 - swdcio/swdcio.def | 1 - swdcio/swdcio.h | 13 +-- 14 files changed, 280 insertions(+), 92 deletions(-) create mode 100644 dist/swdc/config_hook.json diff --git a/Package.mk b/Package.mk index 438421e..55ae693 100644 --- a/Package.mk +++ b/Package.mk @@ -111,6 +111,7 @@ $(BUILD_DIR_ZIP)/swdc.zip: $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ $(BUILD_DIR_64)/swdchook/swdchook.dll \ $(DIST_DIR)/swdc/segatools.ini \ + $(DIST_DIR)/swdc/config_hook.json \ $(DIST_DIR)/swdc/start.bat \ $(BUILD_DIR_ZIP)/swdc $(V)cp pki/billing.pub \ diff --git a/dist/swdc/config_hook.json b/dist/swdc/config_hook.json new file mode 100644 index 0000000..6c33540 --- /dev/null +++ b/dist/swdc/config_hook.json @@ -0,0 +1,6 @@ +{ + "emoney" : + { + "enable" : false + } +} diff --git a/dist/swdc/segatools.ini b/dist/swdc/segatools.ini index fb93e50..4e88bf3 100644 --- a/dist/swdc/segatools.ini +++ b/dist/swdc/segatools.ini @@ -6,7 +6,7 @@ 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= +appdata=appdata [aime] ; Controls emulation of the Aime card reader assembly. diff --git a/dist/swdc/start.bat b/dist/swdc/start.bat index c5e7733..a44605b 100644 --- a/dist/swdc/start.bat +++ b/dist/swdc/start.bat @@ -2,12 +2,36 @@ pushd %~dp0 -rem Matching Server -start /min ..\..\..\Tools\tdrserver.exe -REM start /min inject -d -k swdchook.dll amdaemon.exe -c config.json config_LanClient.json config_MiniCabinet.json config_hook.json -start /min inject -d -k swdchook.dll amdaemon.exe -c config.json config_LanServer.json config_MiniCabinet.json -REM Valid -launch parameters are "PC", "Cabinet" and "MiniCabinet -inject -d -k swdchook.dll ..\Todoroki\Binaries\Win64\Todoroki-Win64-Shipping.exe -launch=MiniCabinet -ABSLOG="..\..\..\..\..\Userdata\Todoroki.log" -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED +REM Root directory +set ROOT_DIR=WindowsNoEditor + +rem Matching Server paths +set MATCHING_SERVER_FILE_NAME=tdrserver.exe +set MATCHING_SERVER_PATH=..\Tools\%MATCHING_SERVER_FILE_NAME% + +rem AM Daemon paths +set DAEMON_DIR=%ROOT_DIR%\AMDaemon +set DAEMON_CONFIG_PATH=%DAEMON_DIR%\config.json +rem Make sure to use appdata=appdata in segatools.ini +set DAEMON_CHECK_LAN_SERVER_PATH=appdata\SDDS\LanServer.dat + +rem Check if LanServer.dat is present +if exist "%DAEMON_CHECK_LAN_SERVER_PATH%" ( + set DAEMON_LAN_CONFIG_PATH=%DAEMON_DIR%\config_LanServer.json + + start "matching_server" /min %MATCHING_SERVER_PATH% +) else ( + set DAEMON_LAN_CONFIG_PATH=%DAEMON_DIR%\config_LanClient.json +) + +start "AM Daemon" /min inject -d -k swdchook.dll "%DAEMON_DIR%\amdaemon.exe" -c %DAEMON_CONFIG_PATH% -c %DAEMON_LAN_CONFIG_PATH% config_hook.json + +REM Launch Todoroki +set APP_EXE_DIR=WindowsNoEditor\Todoroki\Binaries\Win64 +set APP_EXE_PATH=%APP_EXE_DIR%\Todoroki-Win64-Shipping.exe + +REM Valid -launch parameters are "Cabinet" or "MiniCabinet" +inject -d -k swdchook.dll "%APP_EXE_PATH%" -launch="MiniCabinet" -ABSLOG="..\Userdata\Todoroki.log" -UserDir="..\Userdata" -NotInstalled -UNATTENDED taskkill /f /im amdaemon.exe > nul 2>&1 taskkill /f /im tdrserver.exe > nul 2>&1 diff --git a/swdchook/dllmain.c b/swdchook/dllmain.c index b33c711..e5a47d2 100644 --- a/swdchook/dllmain.c +++ b/swdchook/dllmain.c @@ -16,7 +16,6 @@ #include "swdchook/config.h" #include "swdchook/swdc-dll.h" #include "swdchook/io4.h" -#include "swdchook/zinput.h" #include "platform/platform.h" @@ -39,7 +38,6 @@ static DWORD CALLBACK swdc_pre_startup(void) /* Hook Win32 APIs */ serial_hook_init(); - zinput_hook_init(&swdc_hook_cfg.zinput, swdc_hook_mod); dvd_hook_init(&swdc_hook_cfg.dvd, swdc_hook_mod); /* Initialize emulation hooks */ @@ -54,6 +52,12 @@ static DWORD CALLBACK swdc_pre_startup(void) goto fail; } + hr = swdc_dll_init(&swdc_hook_cfg.dll, swdc_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + hr = sg_reader_hook_init(&swdc_hook_cfg.aime, 3, 3, swdc_hook_mod); if (FAILED(hr)) { @@ -66,18 +70,16 @@ static DWORD CALLBACK swdc_pre_startup(void) return hr; } - hr = swdc_dll_init(&swdc_hook_cfg.dll, swdc_hook_mod); - - if (FAILED(hr)) { - goto fail; - } - hr = swdc_io4_hook_init(&swdc_hook_cfg.io4); if (FAILED(hr)) { goto fail; } + /* Hook external DLL APIs */ + + zinput_hook_init(&swdc_hook_cfg.zinput); + /* Initialize debug helpers */ spike_hook_init(L".\\segatools.ini"); diff --git a/swdchook/io4.c b/swdchook/io4.c index 10736ef..be2749e 100644 --- a/swdchook/io4.c +++ b/swdchook/io4.c @@ -6,9 +6,13 @@ #include "board/io4.h" +#include "util/dprintf.h" + #include "swdchook/swdc-dll.h" -#include "util/dprintf.h" +static HANDLE mmf; +static HRESULT init_mmf(void); +static void swdc_set_gamebtns(uint16_t value); static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state); static uint16_t coins; @@ -17,8 +21,7 @@ static const struct io4_ops swdc_io4_ops = { .poll = swdc_io4_poll, }; -HRESULT swdc_io4_hook_init(const struct io4_config *cfg) -{ +HRESULT swdc_io4_hook_init(const struct io4_config *cfg) { HRESULT hr; assert(swdc_dll.init != NULL); @@ -29,30 +32,54 @@ HRESULT swdc_io4_hook_init(const struct io4_config *cfg) return hr; } + hr = init_mmf(); + + if (FAILED(hr)) { + return hr; + } + return swdc_dll.init(); } -static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) -{ +// Function to initialize the memory-mapped file +static HRESULT init_mmf(void) { + mmf = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 2, "SWDCButton"); + if (mmf == NULL) { + return S_FALSE; + } + + swdc_set_gamebtns(0); + + return S_OK; +} + +void swdc_set_gamebtns(uint16_t value) { + // WaitForSingleObject(mutex, INFINITE); + + // Update the memory-mapped file + LPVOID mmf_view = MapViewOfFile(mmf, FILE_MAP_ALL_ACCESS, 0, 0, 2); + if (mmf_view != NULL) { + uint16_t* ptr = (uint16_t*)mmf_view; + *ptr = value; + + UnmapViewOfFile(mmf_view); + } + + // ReleaseMutex(mutex); +} + +static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) { uint8_t opbtn; uint16_t gamebtn; struct swdc_io_analog_state analog_state; HRESULT hr; - assert(swdc_dll.poll != NULL); assert(swdc_dll.get_opbtns != NULL); assert(swdc_dll.get_gamebtns != NULL); assert(swdc_dll.get_analogs != NULL); memset(state, 0, sizeof(*state)); memset(&analog_state, 0, sizeof(analog_state)); - - hr = swdc_dll.poll(); - - if (FAILED(hr)) { - return hr; - } - opbtn = 0; gamebtn = 0; @@ -99,7 +126,17 @@ static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) state->buttons[0] |= 1 << 2; } - /* Update steering wheel buttons */ +/* + Update steering wheel buttons + + Those are connected to the SEGA838-15415 INDICATOR BD MAIN + USB board which is not emulated for now. So those buttons + are hooked to the built-in XInput support. +*/ + + /* Instead update gamebtns for the file mapping */ + + swdc_set_gamebtns(gamebtn); if (gamebtn & SWDC_IO_GAMEBTN_STEERING_BLUE) { state->buttons[1] |= 1 << 15; diff --git a/swdchook/swdc-dll.c b/swdchook/swdc-dll.c index 47120ee..80d4526 100644 --- a/swdchook/swdc-dll.c +++ b/swdchook/swdc-dll.c @@ -12,9 +12,6 @@ const struct dll_bind_sym swdc_dll_syms[] = { { .sym = "swdc_io_init", .off = offsetof(struct swdc_dll, init), - }, { - .sym = "swdc_io_poll", - .off = offsetof(struct swdc_dll, poll), }, { .sym = "swdc_io_get_opbtns", .off = offsetof(struct swdc_dll, get_opbtns), diff --git a/swdchook/swdc-dll.h b/swdchook/swdc-dll.h index 8e13a2c..87781c8 100644 --- a/swdchook/swdc-dll.h +++ b/swdchook/swdc-dll.h @@ -7,7 +7,6 @@ struct swdc_dll { uint16_t api_version; HRESULT (*init)(void); - HRESULT (*poll)(void); void (*get_opbtns)(uint8_t *opbtn); void (*get_gamebtns)(uint16_t *gamebtn); void (*get_analogs)(struct swdc_io_analog_state *out); diff --git a/swdchook/swdchook.def b/swdchook/swdchook.def index 37b47bd..160cb7d 100644 --- a/swdchook/swdchook.def +++ b/swdchook/swdchook.def @@ -13,7 +13,6 @@ EXPORTS amDllVideoSetResolution @3 swdc_io_get_api_version swdc_io_init - swdc_io_poll swdc_io_get_opbtns swdc_io_get_gamebtns swdc_io_get_analogs diff --git a/swdchook/zinput.c b/swdchook/zinput.c index 264102b..5a14800 100644 --- a/swdchook/zinput.c +++ b/swdchook/zinput.c @@ -1,31 +1,61 @@ #include -#include +#include #include +#include #include +#include +#include + +#include "board/io4.h" + +#include "hook/table.h" +#include "util/dprintf.h" +#include "util/lib.h" #include "swdchook/config.h" #include "swdchook/zinput.h" -#include "hook/table.h" +static struct zinput_config zinput_config; +static bool zinput_hook_initted; +static bool zinput_controller_init = false; -#include "util/dprintf.h" +static HRESULT init_mmf(void); +static HANDLE mmf; +static uint16_t* swdc_gamebtn; + +/* Hooked functions */ DWORD WINAPI hook_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState); +DWORD WINAPI hook_XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); +// Not needed for now? +DWORD WINAPI hook_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities); -static const struct hook_symbol zinput_hook_syms[] = { +// Yup SEGA imports XInput functions via ordinal. FUN! +static struct hook_symbol zinput_hook_syms[] = { { - .name = "XInputGetState", - .patch = hook_XInputGetState, - .link = NULL + .name = "XInputGetState", + .ordinal = 0x0002, + .patch = hook_XInputGetState, + .link = NULL + }, { + .name = "XInputSetState", + .ordinal = 0x0003, + .patch = hook_XInputSetState, + .link = NULL + }, { + // Not needed for now? + .name = "XInputGetCapabilities", + .patch = hook_XInputGetCapabilities, + .link = NULL }, }; -static struct zinput_config zinput_config; -static bool zinput_hook_initted; - -void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self) +void zinput_hook_init(struct zinput_config *cfg) { + wchar_t *module_path; + wchar_t *file_name; + assert(cfg != NULL); if (!cfg->enable) { @@ -36,45 +66,149 @@ void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self) return; } - memcpy(&zinput_config, cfg, sizeof(*cfg)); - hook_table_apply( - NULL, - "XINPUT9_1_0.dll", - zinput_hook_syms, - _countof(zinput_hook_syms)); + 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; + } + } hook_table_apply( - NULL, - "XINPUT1_1.dll", - zinput_hook_syms, - _countof(zinput_hook_syms)); - - hook_table_apply( - NULL, - "XINPUT1_2.dll", - zinput_hook_syms, - _countof(zinput_hook_syms)); - - hook_table_apply( - NULL, - "XINPUT1_3.dll", - zinput_hook_syms, - _countof(zinput_hook_syms)); - - hook_table_apply( - NULL, - "XINPUT1_4.dll", - zinput_hook_syms, - _countof(zinput_hook_syms)); + NULL, + "XINPUT1_3.dll", + zinput_hook_syms, + _countof(zinput_hook_syms)); + + if (FAILED(init_mmf())) { + return; + } zinput_hook_initted = true; - dprintf("ZInput: Blocking built-in XInput support\n"); + dprintf("ZInput: Hooking built-in XInput support\n"); } -DWORD WINAPI hook_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) -{ +bool zinput_connect_controller(bool enable) { + zinput_controller_init = enable; + dprintf("zinput_connect_controller\n"); + return true; +} + +static HRESULT init_mmf(void) { + // Create or open memory-mapped file + mmf = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 2, "SWDCButton"); + if (mmf == NULL) { + return S_FALSE; + } + + // Map the memory-mapped file + swdc_gamebtn = (uint16_t*)MapViewOfFile(mmf, FILE_MAP_ALL_ACCESS, 0, 0, 2); + + return S_OK; +} + +DWORD WINAPI hook_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities) { + // dprintf("ZInput: XInputGetCapabilities hook hit\n"); + + if (!zinput_controller_init) { + zinput_connect_controller(true); + } + + if (dwFlags > XINPUT_FLAG_GAMEPAD) { + return ERROR_BAD_ARGUMENTS; + } + + if (zinput_controller_init && dwUserIndex == 0) { + pCapabilities->Flags = XINPUT_CAPS_VOICE_SUPPORTED; + pCapabilities->Type = XINPUT_DEVTYPE_GAMEPAD; + pCapabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD; + + pCapabilities->Gamepad.wButtons = 0xF3FF; + + pCapabilities->Gamepad.bLeftTrigger = 0xFF; + pCapabilities->Gamepad.bRightTrigger = 0xFF; + + pCapabilities->Gamepad.sThumbLX = (SHORT)0xFFC0; + pCapabilities->Gamepad.sThumbLY = (SHORT)0xFFC0; + pCapabilities->Gamepad.sThumbRX = (SHORT)0xFFC0; + pCapabilities->Gamepad.sThumbRY = (SHORT)0xFFC0; + + pCapabilities->Vibration.wLeftMotorSpeed = 0xFF; + pCapabilities->Vibration.wRightMotorSpeed = 0xFF; + + return ERROR_SUCCESS; + } else { + return ERROR_DEVICE_NOT_CONNECTED; + } +} + +DWORD WINAPI hook_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) { // dprintf("ZInput: XInputGetState hook hit\n"); - return ERROR_SUCCESS; + if (!zinput_controller_init) { + zinput_connect_controller(true); + } + + if (zinput_controller_init && dwUserIndex == 0) { + XINPUT_GAMEPAD gamepad_state = {0}; + gamepad_state.wButtons = 0; + + /* Read filemapping for for the gamebtns */ + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_BLUE) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_X; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_RED) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_B; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_GREEN) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_A; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_YELLOW) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_Y; + } + if (pState->dwPacketNumber == UINT_MAX) + pState->dwPacketNumber = 0; + else + pState->dwPacketNumber++; + + pState->Gamepad = gamepad_state; + return ERROR_SUCCESS; + } else { + return ERROR_DEVICE_NOT_CONNECTED; + } +} + +DWORD WINAPI hook_XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) { + // dprintf("ZInput: XInputSetState hook hit\n"); + + if (!zinput_controller_init) { + zinput_connect_controller(true); + } + + if (zinput_controller_init && dwUserIndex == 0) { + return ERROR_SUCCESS; + } else { + return ERROR_DEVICE_NOT_CONNECTED; + } } diff --git a/swdchook/zinput.h b/swdchook/zinput.h index ee3e6ad..dc883dc 100644 --- a/swdchook/zinput.h +++ b/swdchook/zinput.h @@ -8,4 +8,4 @@ struct zinput_config { bool enable; }; -void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self); +void zinput_hook_init(struct zinput_config *cfg); diff --git a/swdcio/di.c b/swdcio/di.c index 09f25d0..da6e4b5 100644 --- a/swdcio/di.c +++ b/swdcio/di.c @@ -28,9 +28,6 @@ static BOOL CALLBACK swdc_di_enum_callback( static BOOL CALLBACK swdc_di_enum_callback_pedals( const DIDEVICEINSTANCEW *dev, void *ctx); -static BOOL CALLBACK swdc_di_enum_callback_shifter( - const DIDEVICEINSTANCEW *dev, - void *ctx); static void swdc_di_get_buttons(uint16_t *gamebtn_out); static uint8_t swdc_di_decode_pov(DWORD pov); static void swdc_di_get_analogs(struct swdc_io_analog_state *out); diff --git a/swdcio/swdcio.def b/swdcio/swdcio.def index c2cb0ce..ca63aca 100644 --- a/swdcio/swdcio.def +++ b/swdcio/swdcio.def @@ -2,7 +2,6 @@ LIBRARY swdcio EXPORTS swdc_io_init - swdc_io_poll swdc_io_get_opbtns swdc_io_get_gamebtns swdc_io_get_analogs diff --git a/swdcio/swdcio.h b/swdcio/swdcio.h index 5ce593c..7155273 100644 --- a/swdcio/swdcio.h +++ b/swdcio/swdcio.h @@ -55,7 +55,7 @@ struct swdc_io_analog_state { uint16_t swdc_io_get_api_version(void); /* Initialize the IO DLL. This is the second function that will be called on - your DLL, after mu3_io_get_api_version. + your DLL, after SWDC_io_get_api_version. All subsequent calls to this API may originate from arbitrary threads. @@ -63,15 +63,8 @@ uint16_t swdc_io_get_api_version(void); HRESULT swdc_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 swdc_io_poll(void); - /* Get the state of the cabinet's operator buttons as of the last poll. See - MU3_IO_OPBTN enum above: this contains bit mask definitions for button + SWDC_IO_OPBTN enum above: this contains bit mask definitions for button states returned in *opbtn. All buttons are active-high. Minimum API version: 0x0100 */ @@ -79,7 +72,7 @@ HRESULT swdc_io_poll(void); void swdc_io_get_opbtns(uint8_t *opbtn); /* Get the state of the cabinet's gameplay buttons as of the last poll. See - MU3_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into + SWDC_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into a left hand side set of inputs and a right hand side set of inputs: the bit mappings are the same in both cases.