From d521eeb43e6c67a181746184b8c362bd290bd8da Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Thu, 5 Oct 2023 00:46:54 +0200 Subject: [PATCH] idz, idac, swdc: Added separate pedals config, better XInput controls - Configure separate pedals which are not part of the steering wheel (base) - It is now possible to have a different steering wheel, pedal and shifter brand all connected to 3 different USB ports - XInput does not have a deadzone at the end of the max steering anymore and the `restrict` setting works as intended --- Package.mk | 3 -- dist/idac/segatools.ini | 12 ++++-- dist/idz/segatools.ini | 8 +++- dist/swdc/segatools.ini | 8 +++- dist/swdc/start.bat | 4 ++ doc/config/initiald.md | 10 +++++ idacio/config.c | 8 ++++ idacio/config.h | 1 + idacio/di.c | 95 +++++++++++++++++++++++++++++++++++++++-- idacio/xi.c | 77 ++++++++++++++++----------------- idzio/config.c | 10 ++++- idzio/config.h | 1 + idzio/di.c | 95 +++++++++++++++++++++++++++++++++++++++-- idzio/xi.c | 77 ++++++++++++++++----------------- swdcio/config.c | 8 ++++ swdcio/config.h | 1 + swdcio/di.c | 95 +++++++++++++++++++++++++++++++++++++++-- swdcio/xi.c | 79 +++++++++++++++++----------------- 18 files changed, 450 insertions(+), 142 deletions(-) diff --git a/Package.mk b/Package.mk index 72e90a9..69cdbc1 100644 --- a/Package.mk +++ b/Package.mk @@ -148,7 +148,6 @@ $(BUILD_DIR_ZIP)/mu3.zip: $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3/DEVICE $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ $(BUILD_DIR_64)/mu3hook/mu3hook.dll \ - $(DIST_DIR)/mu3/config_hook.json \ $(DIST_DIR)/mu3/segatools.ini \ $(DIST_DIR)/mu3/start.bat \ $(BUILD_DIR_ZIP)/mu3 @@ -164,7 +163,6 @@ $(BUILD_DIR_ZIP)/mai2.zip: $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2/DEVICE $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ $(BUILD_DIR_64)/mai2hook/mai2hook.dll \ - $(DIST_DIR)/mai2/config_hook.json \ $(DIST_DIR)/mai2/segatools.ini \ $(DIST_DIR)/mai2/start.bat \ $(BUILD_DIR_ZIP)/mai2 @@ -180,7 +178,6 @@ $(BUILD_DIR_ZIP)/cm.zip: $(V)mkdir -p $(BUILD_DIR_ZIP)/cm/DEVICE $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ $(BUILD_DIR_64)/cmhook/cmhook.dll \ - $(DIST_DIR)/cm/config_hook.json \ $(DIST_DIR)/cm/segatools.ini \ $(DIST_DIR)/cm/start.bat \ $(BUILD_DIR_ZIP)/cm diff --git a/dist/idac/segatools.ini b/dist/idac/segatools.ini index 7f8988e..22a0568 100644 --- a/dist/idac/segatools.ini +++ b/dist/idac/segatools.ini @@ -110,7 +110,7 @@ autoNeutral=1 ; Not recommended as it will not give you the precision needed for this game. singleStickSteering=1 ; Use linear steering instead of the default non-linear cubing steering. -linearSteering=0 +linearSteering=1 ; Configure deadzones for the left and right thumbsticks. ; The default value for the left stick is 7849, max value is 32767. leftStickDeadzone=7849 @@ -123,6 +123,12 @@ rightStickDeadzone=8689 ; ; If this is left blank then the first DirectInput device will be used. deviceName= +; Name of the DirectInput pedals to use (or any subset thereof). +; Leave blank if you do not have separate pedals; aka the pedals are part of +; the wheel. +; +; The pedals will be mapped to the accelAxis and brakeAxis. +pedalsName= ; Name of the positional shifter to use (or any subset thereof). ; Leave blank if you do not have a positional shifter; a positional shifter ; will be simulated using the configured Shift Down and Shift Up buttons @@ -150,8 +156,8 @@ viewChg=2 left=7 right=8 ; Button mappings for the simulated six-speed shifter. -shiftDn=5 -shiftUp=6 +shiftDn=6 +shiftUp=5 ; Button mappings for the positional shifter, if present. gear1=13 gear2=14 diff --git a/dist/idz/segatools.ini b/dist/idz/segatools.ini index adb0e97..3fae262 100644 --- a/dist/idz/segatools.ini +++ b/dist/idz/segatools.ini @@ -108,7 +108,7 @@ autoNeutral=1 ; Not recommended as it will not give you the precision needed for this game. singleStickSteering=1 ; Use linear steering instead of the default non-linear cubing steering. -linearSteering=0 +linearSteering=1 ; Configure deadzones for the left and right thumbsticks. ; The default value for the left stick is 7849, max value is 32767. leftStickDeadzone=7849 @@ -130,6 +130,12 @@ deviceName= ; ; Example: G29 shifterName= +; Name of the DirectInput pedals to use (or any subset thereof). +; Leave blank if you do not have separate pedals; aka the pedals are part of +; the wheel. +; +; The pedals will be mapped to the accelAxis and brakeAxis. +pedalsName= ; Pedal mappings. Valid axis names are: ; ; X, Y, Z, RX, RY, RZ, U, V diff --git a/dist/swdc/segatools.ini b/dist/swdc/segatools.ini index 74a5400..7f030d0 100644 --- a/dist/swdc/segatools.ini +++ b/dist/swdc/segatools.ini @@ -81,7 +81,7 @@ restrict=128 ; Not recommended as it will not give you the precision needed for this game. singleStickSteering=1 ; Use linear steering instead of the default non-linear cubing steering. -linearSteering=0 +linearSteering=1 ; Configure deadzones for the left and right thumbsticks. ; The default value for the left stick is 7849, max value is 32767. leftStickDeadzone=7849 @@ -94,6 +94,12 @@ rightStickDeadzone=8689 ; ; If this is left blank then the first DirectInput device will be used. deviceName= +; Name of the DirectInput pedals to use (or any subset thereof). +; Leave blank if you do not have separate pedals; aka the pedals are part of +; the wheel. +; +; The pedals will be mapped to the accelAxis and brakeAxis. +pedalsName= ; Pedal mappings. Valid axis names are: ; ; X, Y, Z, RX, RY, RZ, U, V diff --git a/dist/swdc/start.bat b/dist/swdc/start.bat index 526cfdc..020c27f 100644 --- a/dist/swdc/start.bat +++ b/dist/swdc/start.bat @@ -2,11 +2,15 @@ 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 config_hook.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 + taskkill /f /im amdaemon.exe > nul 2>&1 +taskkill /f /im tdrserver.exe > nul 2>&1 echo. echo Game processes have terminated diff --git a/doc/config/initiald.md b/doc/config/initiald.md index d7d6e60..42ec781 100644 --- a/doc/config/initiald.md +++ b/doc/config/initiald.md @@ -92,6 +92,16 @@ don't know the name of your input device, you can find it in the windows controller panel. The quickest way to access it is to press Win+R, then type in `joy.cpl` and look at the list it will display. +### `pedalsName` + +Default ` ` + +Name of the pedals to use (or any subset thereof). Leave blank if you do not +have separate pedals; aka the pedals are part of the wheel. The pedals will +be mapped to the `accelAxis` and `brakeAxis` which would normally be used by +the wheel defined under `deviceName`. The quickest way to access it is to press +Win+R, then type in `joy.cpl` and look at the list it will display. + ### `shifterName` Default ` ` diff --git a/idacio/config.c b/idacio/config.c index 968124b..f6ed605 100644 --- a/idacio/config.c +++ b/idacio/config.c @@ -24,6 +24,14 @@ void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename) _countof(cfg->device_name), filename); + GetPrivateProfileStringW( + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); + GetPrivateProfileStringW( L"dinput", L"shifterName", diff --git a/idacio/config.h b/idacio/config.h index 8f6517a..ec91ce6 100644 --- a/idacio/config.h +++ b/idacio/config.h @@ -10,6 +10,7 @@ struct idac_shifter_config { struct idac_di_config { wchar_t device_name[64]; + wchar_t pedals_name[64]; wchar_t shifter_name[64]; wchar_t brake_axis[16]; wchar_t accel_axis[16]; diff --git a/idacio/di.c b/idacio/di.c index 1edaf9b..75e2c50 100644 --- a/idacio/di.c +++ b/idacio/di.c @@ -26,6 +26,9 @@ static const struct idac_di_axis *idac_di_get_axis(const wchar_t *name); static BOOL CALLBACK idac_di_enum_callback( const DIDEVICEINSTANCEW *dev, void *ctx); +static BOOL CALLBACK idac_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx); static BOOL CALLBACK idac_di_enum_callback_shifter( const DIDEVICEINSTANCEW *dev, void *ctx); @@ -57,6 +60,7 @@ static const struct idac_io_backend idac_di_backend = { static HWND idac_di_wnd; static IDirectInput8W *idac_di_api; static IDirectInputDevice8W *idac_di_dev; +static IDirectInputDevice8W *idac_di_pedals; static IDirectInputDevice8W *idac_di_shifter; static IDirectInputEffect *idac_di_fx; static size_t idac_di_off_brake; @@ -68,6 +72,7 @@ static uint8_t idac_di_start; static uint8_t idac_di_left; static uint8_t idac_di_right; static uint8_t idac_di_gear[6]; +static bool idac_di_use_pedals; static bool idac_di_reverse_brake_axis; static bool idac_di_reverse_accel_axis; @@ -170,6 +175,37 @@ HRESULT idac_di_init( idac_di_dev_start_fx(idac_di_dev, &idac_di_fx); + if (cfg->pedals_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + idac_di_api, + DI8DEVCLASS_GAMECTRL, + idac_di_enum_callback_pedals, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (idac_di_dev == NULL) { + dprintf("Pedals: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = idac_di_dev_start(idac_di_pedals, idac_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + idac_di_use_pedals = true; + } else { + idac_di_use_pedals = false; + } + if (cfg->shifter_name[0] != L'\0') { hr = IDirectInput8_EnumDevices( idac_di_api, @@ -276,8 +312,10 @@ static HRESULT idac_di_config_apply(const struct idac_di_config *cfg) dprintf("Wheel: --- Begin configuration ---\n"); dprintf("Wheel: Device name . . . . : Contains \"%S\"\n", cfg->device_name); - dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); - dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + if (cfg->pedals_name[0] == L'\0') { + dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + } dprintf("Wheel: Start button . . . : %i\n", cfg->start); dprintf("Wheel: View Change button : %i\n", cfg->view_chg); dprintf("Wheel: Left button . . . . : %i\n", cfg->left); @@ -288,6 +326,15 @@ static HRESULT idac_di_config_apply(const struct idac_di_config *cfg) dprintf("Wheel: Reverse Accel Axis : %i\n", cfg->reverse_accel_axis); dprintf("Wheel: --- End configuration ---\n"); + if (cfg->pedals_name[0] != L'\0') { + dprintf("Pedals: --- Begin configuration ---\n"); + dprintf("Pedals: Device name . . . : Contains \"%S\"\n", + cfg->pedals_name); + dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name); + dprintf("Pedals: --- End configuration ---\n"); + } + if (cfg->shifter_name[0] != L'\0') { dprintf("Shifter: --- Begin configuration ---\n"); dprintf("Shifter: Device name . . . : Contains \"%S\"\n", @@ -364,6 +411,34 @@ static BOOL CALLBACK idac_di_enum_callback( return DIENUM_STOP; } +static BOOL CALLBACK idac_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct idac_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + idac_di_api, + &dev->guidInstance, + &idac_di_pedals, + NULL); + + if (FAILED(hr)) { + dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + static BOOL CALLBACK idac_di_enum_callback_shifter( const DIDEVICEINSTANCEW *dev, void *ctx) @@ -518,6 +593,7 @@ static void idac_di_get_shifter_virt(uint8_t *gear) static void idac_di_get_analogs(struct idac_io_analog_state *out) { union idac_di_state state; + union idac_di_state pedals_state; const LONG *brake; const LONG *accel; HRESULT hr; @@ -530,8 +606,19 @@ static void idac_di_get_analogs(struct idac_io_analog_state *out) return; } - brake = (LONG *) &state.bytes[idac_di_off_brake]; - accel = (LONG *) &state.bytes[idac_di_off_accel]; + if (idac_di_use_pedals) { + hr = idac_di_dev_poll(idac_di_pedals, idac_di_wnd, &pedals_state); + + if (FAILED(hr)) { + return; + } + + brake = (LONG *) &pedals_state.bytes[idac_di_off_brake]; + accel = (LONG *) &pedals_state.bytes[idac_di_off_accel]; + } else { + brake = (LONG *) &state.bytes[idac_di_off_brake]; + accel = (LONG *) &state.bytes[idac_di_off_accel]; + } out->wheel = state.st.lX - 32768; diff --git a/idacio/xi.c b/idacio/xi.c index b83aa9d..3b912fc 100644 --- a/idacio/xi.c +++ b/idacio/xi.c @@ -30,6 +30,11 @@ static bool idac_xi_linear_steering; static uint16_t idac_xi_left_stick_deadzone; static uint16_t idac_xi_right_stick_deadzone; +const uint16_t max_stick_value = 32767; +/* Apply steering wheel restriction. Real cabs only report about 76% of + the output value when the wheel is turned to either of its maximum positions. */ +const uint16_t max_wheel_value = 24831; + HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_backend **backend) { HRESULT hr; assert(cfg != NULL); @@ -143,29 +148,39 @@ static void idac_xi_get_shifter(uint8_t *gear) { *gear = idac_shifter_current_gear(); } -static int apply_non_linear_transform(int value, int deadzone_center) { - const int max_input = 32767; - const double power_factor = 3.0; +static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) { + // determine how far the controller is pushed + float magnitude = sqrt(axis*axis); - // Apply deadzone only after passing the center threshold - if (abs(value) < deadzone_center) { - return 0; + // determine the direction the controller is pushed + float norm_axis = axis / magnitude; + + float norm_magnitude = 0.0; + + // check if the controller is outside a circular dead zone + if (magnitude > deadzone) + { + // clip the magnitude at its expected maximum value + if (magnitude > max_stick_value) magnitude = max_stick_value; + + // adjust magnitude relative to the end of the dead zone + magnitude -= deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + norm_magnitude = magnitude / (max_stick_value - deadzone); + } else // if the controller is in the deadzone zero out the magnitude + { + magnitude = 0.0; + norm_magnitude = 0.0; } - // Scale the value to the range [-1.0, 1.0] - double scaled_value = (abs(value) - deadzone_center) / (double)(max_input - deadzone_center); + // apply non-linear transform to the axis + if (!linear_steering) { + return norm_axis * pow(norm_magnitude, 3.0) * max_wheel_value; + } - // Apply a non-linear transform (cubing in this case) and preserve the sign - double signed_value = copysign(pow(scaled_value, power_factor), value); - - // Scale the value back to the range [-32770, 32767] - int transformed_value = (int)(signed_value * max_input); - - // Clamp the value to the range [-32767, 32767] - transformed_value = (transformed_value > max_input) ? max_input : transformed_value; - transformed_value = (transformed_value < -max_input) ? -max_input : transformed_value; - - return transformed_value; + return norm_axis * norm_magnitude * max_wheel_value; } static void idac_xi_get_analogs(struct idac_io_analog_state *out) { @@ -181,27 +196,9 @@ static void idac_xi_get_analogs(struct idac_io_analog_state *out) { left = xi.Gamepad.sThumbLX; right = xi.Gamepad.sThumbRX; - if (!idac_xi_linear_steering) { - // Apply non-linear transform for both sticks - left = apply_non_linear_transform(left, idac_xi_left_stick_deadzone); - right = apply_non_linear_transform(right, idac_xi_right_stick_deadzone); - } else { - if (left < -idac_xi_left_stick_deadzone) { - left += idac_xi_left_stick_deadzone; - } else if (left > idac_xi_left_stick_deadzone) { - left -= idac_xi_left_stick_deadzone; - } else { - left = 0; - } - - if (right < -idac_xi_right_stick_deadzone) { - right += idac_xi_right_stick_deadzone; - } else if (right > idac_xi_right_stick_deadzone) { - right -= idac_xi_right_stick_deadzone; - } else { - right = 0; - } - } + // normalize the steering axis + left = calculate_norm_steering(left, idac_xi_left_stick_deadzone, idac_xi_linear_steering); + right = calculate_norm_steering(right, idac_xi_right_stick_deadzone, idac_xi_linear_steering); if (idac_xi_single_stick_steering) { out->wheel = left; diff --git a/idzio/config.c b/idzio/config.c index ceea591..25b188d 100644 --- a/idzio/config.c +++ b/idzio/config.c @@ -24,6 +24,14 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename) _countof(cfg->device_name), filename); + GetPrivateProfileStringW( + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); + GetPrivateProfileStringW( L"dinput", L"shifterName", @@ -79,7 +87,7 @@ void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename) cfg->single_stick_steering = GetPrivateProfileIntW( L"xinput", L"singleStickSteering", - 0, + 1, filename); cfg->linear_steering = GetPrivateProfileIntW( diff --git a/idzio/config.h b/idzio/config.h index 24934ca..6b4cd20 100644 --- a/idzio/config.h +++ b/idzio/config.h @@ -11,6 +11,7 @@ struct idz_shifter_config { struct idz_di_config { wchar_t device_name[64]; wchar_t shifter_name[64]; + wchar_t pedals_name[64]; wchar_t brake_axis[16]; wchar_t accel_axis[16]; uint8_t start; diff --git a/idzio/di.c b/idzio/di.c index c16be14..5d0a454 100644 --- a/idzio/di.c +++ b/idzio/di.c @@ -26,6 +26,9 @@ static const struct idz_di_axis *idz_di_get_axis(const wchar_t *name); static BOOL CALLBACK idz_di_enum_callback( const DIDEVICEINSTANCEW *dev, void *ctx); +static BOOL CALLBACK idz_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx); static BOOL CALLBACK idz_di_enum_callback_shifter( const DIDEVICEINSTANCEW *dev, void *ctx); @@ -57,6 +60,7 @@ static const struct idz_io_backend idz_di_backend = { static HWND idz_di_wnd; static IDirectInput8W *idz_di_api; static IDirectInputDevice8W *idz_di_dev; +static IDirectInputDevice8W *idz_di_pedals; static IDirectInputDevice8W *idz_di_shifter; static IDirectInputEffect *idz_di_fx; static size_t idz_di_off_brake; @@ -66,6 +70,7 @@ static uint8_t idz_di_shift_up; static uint8_t idz_di_view_chg; static uint8_t idz_di_start; static uint8_t idz_di_gear[6]; +static bool idz_di_use_pedals; static bool idz_di_reverse_brake_axis; static bool idz_di_reverse_accel_axis; @@ -168,6 +173,37 @@ HRESULT idz_di_init( idz_di_dev_start_fx(idz_di_dev, &idz_di_fx); + if (cfg->pedals_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + idz_di_api, + DI8DEVCLASS_GAMECTRL, + idz_di_enum_callback_pedals, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (idz_di_dev == NULL) { + dprintf("Pedals: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = idz_di_dev_start(idz_di_pedals, idz_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + idz_di_use_pedals = true; + } else { + idz_di_use_pedals = false; + } + if (cfg->shifter_name[0] != L'\0') { hr = IDirectInput8_EnumDevices( idz_di_api, @@ -262,8 +298,10 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) dprintf("Wheel: --- Begin configuration ---\n"); dprintf("Wheel: Device name . . . . : Contains \"%S\"\n", cfg->device_name); - dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); - dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + if (cfg->pedals_name[0] == L'\0') { + dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + } dprintf("Wheel: Start button . . . : %i\n", cfg->start); dprintf("Wheel: View Change button : %i\n", cfg->view_chg); dprintf("Wheel: Shift Down button . : %i\n", cfg->shift_dn); @@ -272,6 +310,15 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) dprintf("Wheel: Reverse Accel Axis : %i\n", cfg->reverse_accel_axis); dprintf("Wheel: --- End configuration ---\n"); + if (cfg->pedals_name[0] != L'\0') { + dprintf("Pedals: --- Begin configuration ---\n"); + dprintf("Pedals: Device name . . . : Contains \"%S\"\n", + cfg->pedals_name); + dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name); + dprintf("Pedals: --- End configuration ---\n"); + } + if (cfg->shifter_name[0] != L'\0') { dprintf("Shifter: --- Begin configuration ---\n"); dprintf("Shifter: Device name . . . : Contains \"%S\"\n", @@ -346,6 +393,34 @@ static BOOL CALLBACK idz_di_enum_callback( return DIENUM_STOP; } +static BOOL CALLBACK idz_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct idz_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + idz_di_api, + &dev->guidInstance, + &idz_di_pedals, + NULL); + + if (FAILED(hr)) { + dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + static BOOL CALLBACK idz_di_enum_callback_shifter( const DIDEVICEINSTANCEW *dev, void *ctx) @@ -492,6 +567,7 @@ static void idz_di_jvs_read_shifter_virt(uint8_t *gear) static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out) { union idz_di_state state; + union idz_di_state pedals_state; const LONG *brake; const LONG *accel; HRESULT hr; @@ -504,8 +580,19 @@ static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out) return; } - brake = (LONG *) &state.bytes[idz_di_off_brake]; - accel = (LONG *) &state.bytes[idz_di_off_accel]; + if (idz_di_use_pedals) { + hr = idz_di_dev_poll(idz_di_pedals, idz_di_wnd, &pedals_state); + + if (FAILED(hr)) { + return; + } + + brake = (LONG *) &pedals_state.bytes[idz_di_off_brake]; + accel = (LONG *) &pedals_state.bytes[idz_di_off_accel]; + } else { + brake = (LONG *) &state.bytes[idz_di_off_brake]; + accel = (LONG *) &state.bytes[idz_di_off_accel]; + } out->wheel = state.st.lX - 32768; diff --git a/idzio/xi.c b/idzio/xi.c index 413fa56..4a8391d 100644 --- a/idzio/xi.c +++ b/idzio/xi.c @@ -31,6 +31,11 @@ static bool idz_xi_linear_steering; static uint16_t idz_xi_left_stick_deadzone; static uint16_t idz_xi_right_stick_deadzone; +const uint16_t max_stick_value = 32767; +/* Apply steering wheel restriction. Real cabs only report about 76% of + the output value when the wheel is turned to either of its maximum positions. */ +const uint16_t max_wheel_value = 24831; + HRESULT idz_xi_init(const struct idz_xi_config *cfg, const struct idz_io_backend **backend) { HRESULT hr; @@ -143,29 +148,39 @@ static void idz_xi_jvs_read_shifter(uint8_t *gear) *gear = idz_shifter_current_gear(); } -static int apply_non_linear_transform(int value, int deadzone_center) { - const int max_input = 32767; - const double power_factor = 3.0; +static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) { + // determine how far the controller is pushed + float magnitude = sqrt(axis*axis); - // Apply deadzone only after passing the center threshold - if (abs(value) < deadzone_center) { - return 0; + // determine the direction the controller is pushed + float norm_axis = axis / magnitude; + + float norm_magnitude = 0.0; + + // check if the controller is outside a circular dead zone + if (magnitude > deadzone) + { + // clip the magnitude at its expected maximum value + if (magnitude > max_stick_value) magnitude = max_stick_value; + + // adjust magnitude relative to the end of the dead zone + magnitude -= deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + norm_magnitude = magnitude / (max_stick_value - deadzone); + } else // if the controller is in the deadzone zero out the magnitude + { + magnitude = 0.0; + norm_magnitude = 0.0; } - // Scale the value to the range [-1.0, 1.0] - double scaled_value = (abs(value) - deadzone_center) / (double)(max_input - deadzone_center); + // apply non-linear transform to the axis + if (!linear_steering) { + return norm_axis * pow(norm_magnitude, 3.0) * max_wheel_value; + } - // Apply a non-linear transform (cubing in this case) and preserve the sign - double signed_value = copysign(pow(scaled_value, power_factor), value); - - // Scale the value back to the range [-32770, 32767] - int transformed_value = (int)(signed_value * max_input); - - // Clamp the value to the range [-32767, 32767] - transformed_value = (transformed_value > max_input) ? max_input : transformed_value; - transformed_value = (transformed_value < -max_input) ? -max_input : transformed_value; - - return transformed_value; + return norm_axis * norm_magnitude * max_wheel_value; } static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out) @@ -182,27 +197,9 @@ static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out) left = xi.Gamepad.sThumbLX; right = xi.Gamepad.sThumbRX; - if (!idz_xi_linear_steering) { - // Apply non-linear transform for both sticks - left = apply_non_linear_transform(left, idz_xi_left_stick_deadzone); - right = apply_non_linear_transform(right, idz_xi_right_stick_deadzone); - } else { - if (left < -idz_xi_left_stick_deadzone) { - left += idz_xi_left_stick_deadzone; - } else if (left > idz_xi_left_stick_deadzone) { - left -= idz_xi_left_stick_deadzone; - } else { - left = 0; - } - - if (right < -idz_xi_right_stick_deadzone) { - right += idz_xi_right_stick_deadzone; - } else if (right > idz_xi_right_stick_deadzone) { - right -= idz_xi_right_stick_deadzone; - } else { - right = 0; - } - } + // normalize the steering axis + left = calculate_norm_steering(left, idz_xi_left_stick_deadzone, idz_xi_linear_steering); + right = calculate_norm_steering(right, idz_xi_right_stick_deadzone, idz_xi_linear_steering); if(idz_xi_single_stick_steering) { out->wheel = left; diff --git a/swdcio/config.c b/swdcio/config.c index 6fdd659..0dce00e 100644 --- a/swdcio/config.c +++ b/swdcio/config.c @@ -24,6 +24,14 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename) _countof(cfg->device_name), filename); + GetPrivateProfileStringW( + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); + GetPrivateProfileStringW( L"dinput", L"brakeAxis", diff --git a/swdcio/config.h b/swdcio/config.h index c878d58..d6f9667 100644 --- a/swdcio/config.h +++ b/swdcio/config.h @@ -6,6 +6,7 @@ struct swdc_di_config { wchar_t device_name[64]; + wchar_t pedals_name[64]; wchar_t brake_axis[16]; wchar_t accel_axis[16]; uint8_t start; diff --git a/swdcio/di.c b/swdcio/di.c index 2fc7bc1..35c6463 100644 --- a/swdcio/di.c +++ b/swdcio/di.c @@ -25,6 +25,9 @@ static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name); static BOOL CALLBACK swdc_di_enum_callback( const DIDEVICEINSTANCEW *dev, void *ctx); +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); @@ -52,6 +55,7 @@ static const struct swdc_io_backend swdc_di_backend = { static HWND swdc_di_wnd; static IDirectInput8W *swdc_di_api; static IDirectInputDevice8W *swdc_di_dev; +static IDirectInputDevice8W *swdc_di_pedals; static IDirectInputEffect *swdc_di_fx; static size_t swdc_di_off_brake; static size_t swdc_di_off_accel; @@ -63,6 +67,7 @@ static uint8_t swdc_di_wheel_green; static uint8_t swdc_di_wheel_red; static uint8_t swdc_di_wheel_blue; static uint8_t swdc_di_wheel_yellow; +static bool swdc_di_use_pedals; static bool swdc_di_reverse_brake_axis; static bool swdc_di_reverse_accel_axis; @@ -165,6 +170,37 @@ HRESULT swdc_di_init( swdc_di_dev_start_fx(swdc_di_dev, &swdc_di_fx); + if (cfg->pedals_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + swdc_di_api, + DI8DEVCLASS_GAMECTRL, + swdc_di_enum_callback_pedals, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (swdc_di_dev == NULL) { + dprintf("Pedals: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = swdc_di_dev_start(swdc_di_pedals, swdc_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + swdc_di_use_pedals = true; + } else { + swdc_di_use_pedals = false; + } + dprintf("DirectInput: Controller initialized\n"); *backend = &swdc_di_backend; @@ -246,8 +282,10 @@ static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg) dprintf("Wheel: --- Begin configuration ---\n"); dprintf("Wheel: Device name . . . . : Contains \"%S\"\n", cfg->device_name); - dprintf("Wheel: Brake axis . . . . . . : %S\n", brake_axis->name); - dprintf("Wheel: Accel axis . . . . . . : %S\n", accel_axis->name); + if (cfg->pedals_name[0] == L'\0') { + dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + } dprintf("Wheel: Start button . . . . . : %i\n", cfg->start); dprintf("Wheel: View Change button . . : %i\n", cfg->view_chg); dprintf("Wheel: Paddle Left button . . : %i\n", cfg->paddle_left); @@ -260,6 +298,15 @@ static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg) dprintf("Wheel: Reverse Accel Axis . . : %i\n", cfg->reverse_accel_axis); dprintf("Wheel: --- End configuration ---\n"); + if (cfg->pedals_name[0] != L'\0') { + dprintf("Pedals: --- Begin configuration ---\n"); + dprintf("Pedals: Device name . . . : Contains \"%S\"\n", + cfg->pedals_name); + dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name); + dprintf("Pedals: --- End configuration ---\n"); + } + swdc_di_off_brake = brake_axis->off; swdc_di_off_accel = accel_axis->off; swdc_di_start = cfg->start; @@ -320,6 +367,34 @@ static BOOL CALLBACK swdc_di_enum_callback( return DIENUM_STOP; } +static BOOL CALLBACK swdc_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct swdc_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + swdc_di_api, + &dev->guidInstance, + &swdc_di_pedals, + NULL); + + if (FAILED(hr)) { + dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + static void swdc_di_get_buttons(uint16_t *gamebtn_out) { union swdc_di_state state; @@ -389,6 +464,7 @@ static uint8_t swdc_di_decode_pov(DWORD pov) static void swdc_di_get_analogs(struct swdc_io_analog_state *out) { union swdc_di_state state; + union swdc_di_state pedals_state; const LONG *brake; const LONG *accel; HRESULT hr; @@ -401,8 +477,19 @@ static void swdc_di_get_analogs(struct swdc_io_analog_state *out) return; } - brake = (LONG *) &state.bytes[swdc_di_off_brake]; - accel = (LONG *) &state.bytes[swdc_di_off_accel]; + if (swdc_di_use_pedals) { + hr = swdc_di_dev_poll(swdc_di_pedals, swdc_di_wnd, &pedals_state); + + if (FAILED(hr)) { + return; + } + + brake = (LONG *) &pedals_state.bytes[swdc_di_off_brake]; + accel = (LONG *) &pedals_state.bytes[swdc_di_off_accel]; + } else { + brake = (LONG *) &state.bytes[swdc_di_off_brake]; + accel = (LONG *) &state.bytes[swdc_di_off_accel]; + } out->wheel = state.st.lX - 32768; diff --git a/swdcio/xi.c b/swdcio/xi.c index 3067d3f..eb59ce6 100644 --- a/swdcio/xi.c +++ b/swdcio/xi.c @@ -28,6 +28,11 @@ static bool swdc_xi_linear_steering; static uint16_t swdc_xi_left_stick_deadzone; static uint16_t swdc_xi_right_stick_deadzone; +const uint16_t max_stick_value = 32767; +/* Apply steering wheel restriction. Real cabs only report about 76% of + the output value when the wheel is turned to either of its maximum positions. */ +const uint16_t max_wheel_value = 24831; + HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend) { HRESULT hr; @@ -143,29 +148,39 @@ static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out) *gamebtn_out = gamebtn; } -static int apply_non_linear_transform(int value, int deadzone_center) { - const int max_input = 32767; - const double power_factor = 3.0; +static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) { + // determine how far the controller is pushed + float magnitude = sqrt(axis*axis); - // Apply deadzone only after passing the center threshold - if (abs(value) < deadzone_center) { - return 0; + // determine the direction the controller is pushed + float norm_axis = axis / magnitude; + + float norm_magnitude = 0.0; + + // check if the controller is outside a circular dead zone + if (magnitude > deadzone) + { + // clip the magnitude at its expected maximum value + if (magnitude > max_stick_value) magnitude = max_stick_value; + + // adjust magnitude relative to the end of the dead zone + magnitude -= deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + norm_magnitude = magnitude / (max_stick_value - deadzone); + } else // if the controller is in the deadzone zero out the magnitude + { + magnitude = 0.0; + norm_magnitude = 0.0; } - // Scale the value to the range [-1.0, 1.0] - double scaled_value = (abs(value) - deadzone_center) / (double)(max_input - deadzone_center); + // apply non-linear transform to the axis + if (!linear_steering) { + return norm_axis * pow(norm_magnitude, 3.0) * max_wheel_value; + } - // Apply a non-linear transform (cubing in this case) and preserve the sign - double signed_value = copysign(pow(scaled_value, power_factor), value); - - // Scale the value back to the range [-32770, 32767] - int transformed_value = (int)(signed_value * max_input); - - // Clamp the value to the range [-32767, 32767] - transformed_value = (transformed_value > max_input) ? max_input : transformed_value; - transformed_value = (transformed_value < -max_input) ? -max_input : transformed_value; - - return transformed_value; + return norm_axis * norm_magnitude * max_wheel_value; } static void swdc_xi_get_analogs(struct swdc_io_analog_state *out) @@ -182,28 +197,10 @@ static void swdc_xi_get_analogs(struct swdc_io_analog_state *out) left = xi.Gamepad.sThumbLX; right = xi.Gamepad.sThumbRX; - if (!swdc_xi_linear_steering) { - // Apply non-linear transform for both sticks - left = apply_non_linear_transform(left, swdc_xi_left_stick_deadzone); - right = apply_non_linear_transform(right, swdc_xi_right_stick_deadzone); - } else { - if (left < -swdc_xi_left_stick_deadzone) { - left += swdc_xi_left_stick_deadzone; - } else if (left > swdc_xi_left_stick_deadzone) { - left -= swdc_xi_left_stick_deadzone; - } else { - left = 0; - } - - if (right < -swdc_xi_right_stick_deadzone) { - right += swdc_xi_right_stick_deadzone; - } else if (right > swdc_xi_right_stick_deadzone) { - right -= swdc_xi_right_stick_deadzone; - } else { - right = 0; - } - } - + // normalize the steering axis + left = calculate_norm_steering(left, swdc_xi_left_stick_deadzone, swdc_xi_linear_steering); + right = calculate_norm_steering(right, swdc_xi_right_stick_deadzone, swdc_xi_linear_steering); + if (swdc_xi_single_stick_steering) { out->wheel = left; } else {