swdc: fixed steering wheel buttons, improved start.bat

This commit is contained in:
Dniel97 2024-01-16 17:56:24 +01:00
parent 0affc96e3e
commit cc0b6b0953
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
14 changed files with 280 additions and 92 deletions

View File

@ -111,6 +111,7 @@ $(BUILD_DIR_ZIP)/swdc.zip:
$(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \
$(BUILD_DIR_64)/swdchook/swdchook.dll \ $(BUILD_DIR_64)/swdchook/swdchook.dll \
$(DIST_DIR)/swdc/segatools.ini \ $(DIST_DIR)/swdc/segatools.ini \
$(DIST_DIR)/swdc/config_hook.json \
$(DIST_DIR)/swdc/start.bat \ $(DIST_DIR)/swdc/start.bat \
$(BUILD_DIR_ZIP)/swdc $(BUILD_DIR_ZIP)/swdc
$(V)cp pki/billing.pub \ $(V)cp pki/billing.pub \

6
dist/swdc/config_hook.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"emoney" :
{
"enable" : false
}
}

View File

@ -6,7 +6,7 @@ option=
; Create an empty directory somewhere and insert the path here. ; Create an empty directory somewhere and insert the path here.
; This directory may be shared between multiple SEGA games. ; This directory may be shared between multiple SEGA games.
; NOTE: This has nothing to do with Windows %APPDATA%. ; NOTE: This has nothing to do with Windows %APPDATA%.
appdata= appdata=appdata
[aime] [aime]
; Controls emulation of the Aime card reader assembly. ; Controls emulation of the Aime card reader assembly.

36
dist/swdc/start.bat vendored
View File

@ -2,12 +2,36 @@
pushd %~dp0 pushd %~dp0
rem Matching Server REM Root directory
start /min ..\..\..\Tools\tdrserver.exe set ROOT_DIR=WindowsNoEditor
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 Matching Server paths
REM Valid -launch parameters are "PC", "Cabinet" and "MiniCabinet set MATCHING_SERVER_FILE_NAME=tdrserver.exe
inject -d -k swdchook.dll ..\Todoroki\Binaries\Win64\Todoroki-Win64-Shipping.exe -launch=MiniCabinet -ABSLOG="..\..\..\..\..\Userdata\Todoroki.log" -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED 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 amdaemon.exe > nul 2>&1
taskkill /f /im tdrserver.exe > nul 2>&1 taskkill /f /im tdrserver.exe > nul 2>&1

View File

@ -16,7 +16,6 @@
#include "swdchook/config.h" #include "swdchook/config.h"
#include "swdchook/swdc-dll.h" #include "swdchook/swdc-dll.h"
#include "swdchook/io4.h" #include "swdchook/io4.h"
#include "swdchook/zinput.h"
#include "platform/platform.h" #include "platform/platform.h"
@ -39,7 +38,6 @@ static DWORD CALLBACK swdc_pre_startup(void)
/* Hook Win32 APIs */ /* Hook Win32 APIs */
serial_hook_init(); serial_hook_init();
zinput_hook_init(&swdc_hook_cfg.zinput, swdc_hook_mod);
dvd_hook_init(&swdc_hook_cfg.dvd, swdc_hook_mod); dvd_hook_init(&swdc_hook_cfg.dvd, swdc_hook_mod);
/* Initialize emulation hooks */ /* Initialize emulation hooks */
@ -54,6 +52,12 @@ static DWORD CALLBACK swdc_pre_startup(void)
goto fail; 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); hr = sg_reader_hook_init(&swdc_hook_cfg.aime, 3, 3, swdc_hook_mod);
if (FAILED(hr)) { if (FAILED(hr)) {
@ -66,18 +70,16 @@ static DWORD CALLBACK swdc_pre_startup(void)
return hr; 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); hr = swdc_io4_hook_init(&swdc_hook_cfg.io4);
if (FAILED(hr)) { if (FAILED(hr)) {
goto fail; goto fail;
} }
/* Hook external DLL APIs */
zinput_hook_init(&swdc_hook_cfg.zinput);
/* Initialize debug helpers */ /* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini"); spike_hook_init(L".\\segatools.ini");

View File

@ -6,9 +6,13 @@
#include "board/io4.h" #include "board/io4.h"
#include "util/dprintf.h"
#include "swdchook/swdc-dll.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 HRESULT swdc_io4_poll(void *ctx, struct io4_state *state);
static uint16_t coins; static uint16_t coins;
@ -17,8 +21,7 @@ static const struct io4_ops swdc_io4_ops = {
.poll = swdc_io4_poll, .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; HRESULT hr;
assert(swdc_dll.init != NULL); assert(swdc_dll.init != NULL);
@ -29,30 +32,54 @@ HRESULT swdc_io4_hook_init(const struct io4_config *cfg)
return hr; return hr;
} }
hr = init_mmf();
if (FAILED(hr)) {
return hr;
}
return swdc_dll.init(); 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; uint8_t opbtn;
uint16_t gamebtn; uint16_t gamebtn;
struct swdc_io_analog_state analog_state; struct swdc_io_analog_state analog_state;
HRESULT hr; HRESULT hr;
assert(swdc_dll.poll != NULL);
assert(swdc_dll.get_opbtns != NULL); assert(swdc_dll.get_opbtns != NULL);
assert(swdc_dll.get_gamebtns != NULL); assert(swdc_dll.get_gamebtns != NULL);
assert(swdc_dll.get_analogs != NULL); assert(swdc_dll.get_analogs != NULL);
memset(state, 0, sizeof(*state)); memset(state, 0, sizeof(*state));
memset(&analog_state, 0, sizeof(analog_state)); memset(&analog_state, 0, sizeof(analog_state));
hr = swdc_dll.poll();
if (FAILED(hr)) {
return hr;
}
opbtn = 0; opbtn = 0;
gamebtn = 0; gamebtn = 0;
@ -99,7 +126,17 @@ static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state)
state->buttons[0] |= 1 << 2; 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) { if (gamebtn & SWDC_IO_GAMEBTN_STEERING_BLUE) {
state->buttons[1] |= 1 << 15; state->buttons[1] |= 1 << 15;

View File

@ -12,9 +12,6 @@ const struct dll_bind_sym swdc_dll_syms[] = {
{ {
.sym = "swdc_io_init", .sym = "swdc_io_init",
.off = offsetof(struct swdc_dll, init), .off = offsetof(struct swdc_dll, init),
}, {
.sym = "swdc_io_poll",
.off = offsetof(struct swdc_dll, poll),
}, { }, {
.sym = "swdc_io_get_opbtns", .sym = "swdc_io_get_opbtns",
.off = offsetof(struct swdc_dll, get_opbtns), .off = offsetof(struct swdc_dll, get_opbtns),

View File

@ -7,7 +7,6 @@
struct swdc_dll { struct swdc_dll {
uint16_t api_version; uint16_t api_version;
HRESULT (*init)(void); HRESULT (*init)(void);
HRESULT (*poll)(void);
void (*get_opbtns)(uint8_t *opbtn); void (*get_opbtns)(uint8_t *opbtn);
void (*get_gamebtns)(uint16_t *gamebtn); void (*get_gamebtns)(uint16_t *gamebtn);
void (*get_analogs)(struct swdc_io_analog_state *out); void (*get_analogs)(struct swdc_io_analog_state *out);

View File

@ -13,7 +13,6 @@ EXPORTS
amDllVideoSetResolution @3 amDllVideoSetResolution @3
swdc_io_get_api_version swdc_io_get_api_version
swdc_io_init swdc_io_init
swdc_io_poll
swdc_io_get_opbtns swdc_io_get_opbtns
swdc_io_get_gamebtns swdc_io_get_gamebtns
swdc_io_get_analogs swdc_io_get_analogs

View File

@ -1,31 +1,61 @@
#include <windows.h> #include <windows.h>
#include <xinput.h> #include <shlwapi.h>
#include <assert.h> #include <assert.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <xinput.h>
#include "board/io4.h"
#include "hook/table.h"
#include "util/dprintf.h"
#include "util/lib.h"
#include "swdchook/config.h" #include "swdchook/config.h"
#include "swdchook/zinput.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_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", .name = "XInputGetState",
.ordinal = 0x0002,
.patch = hook_XInputGetState, .patch = hook_XInputGetState,
.link = NULL .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; void zinput_hook_init(struct zinput_config *cfg)
static bool zinput_hook_initted;
void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self)
{ {
wchar_t *module_path;
wchar_t *file_name;
assert(cfg != NULL); assert(cfg != NULL);
if (!cfg->enable) { if (!cfg->enable) {
@ -36,24 +66,21 @@ void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self)
return; return;
} }
memcpy(&zinput_config, cfg, sizeof(*cfg)); module_path = module_file_name(NULL);
hook_table_apply(
NULL,
"XINPUT9_1_0.dll",
zinput_hook_syms,
_countof(zinput_hook_syms));
hook_table_apply( if (module_path != NULL) {
NULL, file_name = PathFindFileNameW(module_path);
"XINPUT1_1.dll",
zinput_hook_syms,
_countof(zinput_hook_syms));
hook_table_apply( free(module_path);
NULL, module_path = NULL;
"XINPUT1_2.dll",
zinput_hook_syms, _wcslwr(file_name);
_countof(zinput_hook_syms));
if (wcsstr(file_name, L"amdaemon") != NULL) {
// dprintf("Executable filename contains 'amdaemon', disabling zinput\n");
return;
}
}
hook_table_apply( hook_table_apply(
NULL, NULL,
@ -61,20 +88,127 @@ void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self)
zinput_hook_syms, zinput_hook_syms,
_countof(zinput_hook_syms)); _countof(zinput_hook_syms));
hook_table_apply( if (FAILED(init_mmf())) {
NULL, return;
"XINPUT1_4.dll", }
zinput_hook_syms,
_countof(zinput_hook_syms));
zinput_hook_initted = true; 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: XInputGetState hook hit\n"); 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; return ERROR_SUCCESS;
} else {
return ERROR_DEVICE_NOT_CONNECTED;
}
}
DWORD WINAPI hook_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) {
// dprintf("ZInput: XInputGetState hook hit\n");
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;
}
} }

View File

@ -8,4 +8,4 @@ struct zinput_config {
bool enable; bool enable;
}; };
void zinput_hook_init(struct zinput_config *cfg, HINSTANCE self); void zinput_hook_init(struct zinput_config *cfg);

View File

@ -28,9 +28,6 @@ static BOOL CALLBACK swdc_di_enum_callback(
static BOOL CALLBACK swdc_di_enum_callback_pedals( static BOOL CALLBACK swdc_di_enum_callback_pedals(
const DIDEVICEINSTANCEW *dev, const DIDEVICEINSTANCEW *dev,
void *ctx); 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 void swdc_di_get_buttons(uint16_t *gamebtn_out);
static uint8_t swdc_di_decode_pov(DWORD pov); static uint8_t swdc_di_decode_pov(DWORD pov);
static void swdc_di_get_analogs(struct swdc_io_analog_state *out); static void swdc_di_get_analogs(struct swdc_io_analog_state *out);

View File

@ -2,7 +2,6 @@ LIBRARY swdcio
EXPORTS EXPORTS
swdc_io_init swdc_io_init
swdc_io_poll
swdc_io_get_opbtns swdc_io_get_opbtns
swdc_io_get_gamebtns swdc_io_get_gamebtns
swdc_io_get_analogs swdc_io_get_analogs

View File

@ -55,7 +55,7 @@ struct swdc_io_analog_state {
uint16_t swdc_io_get_api_version(void); uint16_t swdc_io_get_api_version(void);
/* Initialize the IO DLL. This is the second function that will be called on /* 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. 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); 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 /* 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. states returned in *opbtn. All buttons are active-high.
Minimum API version: 0x0100 */ Minimum API version: 0x0100 */
@ -79,7 +72,7 @@ HRESULT swdc_io_poll(void);
void swdc_io_get_opbtns(uint8_t *opbtn); void swdc_io_get_opbtns(uint8_t *opbtn);
/* Get the state of the cabinet's gameplay buttons as of the last poll. See /* 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 a left hand side set of inputs and a right hand side set of inputs: the bit
mappings are the same in both cases. mappings are the same in both cases.