diff --git a/dist/idac/segatools.ini b/dist/idac/segatools.ini index f596979..7cf57fc 100644 --- a/dist/idac/segatools.ini +++ b/dist/idac/segatools.ini @@ -271,3 +271,14 @@ damperStrength=100 rumbleStrength=100 ; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. rumbleDuration=1000 +; Sets the minimum amount of "weight" or stiffness in the wheel, +; even when the car is stationary or moving slowly. +; Higher values make the wheel feel heavier and less "springy." +baseDamperFraction = 20 + +; Deadband (Granularity: 0.1% per unit, max 20.0%) +; Creates a small "no-force" zone at the wheel's center. +; This prevents the wheel from oscillating or "shaking" when driving straight. +; Recommended: 10 (1.0%) to 100 (10.0%). +; Default: 20 = 2.0% +deadband = 20 diff --git a/dist/idz/segatools.ini b/dist/idz/segatools.ini index fc2ec69..d27d992 100644 --- a/dist/idz/segatools.ini +++ b/dist/idz/segatools.ini @@ -266,8 +266,18 @@ reverseBrakeAxis=0 constantForceStrength=100 ; Damper strength, used for steering wheel damper effect. damperStrength=100 - ; Rumble strength, used for road surface effects. rumbleStrength=100 ; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. -rumbleDuration=1000 \ No newline at end of file +rumbleDuration=1000 +; Sets the minimum amount of "weight" or stiffness in the wheel, +; even when the car is stationary or moving slowly. +; Higher values make the wheel feel heavier and less "springy." +baseDamperFraction = 20 + +; Deadband (Granularity: 0.1% per unit, max 20.0%) +; Creates a small "no-force" zone at the wheel's center. +; This prevents the wheel from oscillating or "shaking" when driving straight. +; Recommended: 10 (1.0%) to 100 (10.0%). +; Default: 20 = 2.0% +deadband = 20 diff --git a/dist/swdc/segatools.ini b/dist/swdc/segatools.ini index 6c93ae7..1d43e50 100644 --- a/dist/swdc/segatools.ini +++ b/dist/swdc/segatools.ini @@ -218,8 +218,18 @@ reverseBrakeAxis=0 constantForceStrength=100 ; Damper strength, used for steering wheel damper effect. damperStrength=100 - ; Rumble strength, used for road surface effects. rumbleStrength=100 ; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. rumbleDuration=1000 +; Sets the minimum amount of "weight" or stiffness in the wheel, +; even when the car is stationary or moving slowly. +; Higher values make the wheel feel heavier and less "springy." +baseDamperFraction = 20 + +; Deadband (Granularity: 0.1% per unit, max 20.0%) +; Creates a small "no-force" zone at the wheel's center. +; This prevents the wheel from oscillating or "shaking" when driving straight. +; Recommended: 10 (1.0%) to 100 (10.0%). +; Default: 20 = 2.0% +deadband = 20 diff --git a/games/idacio/config.c b/games/idacio/config.c index a5c1769..8b85c54 100644 --- a/games/idacio/config.c +++ b/games/idacio/config.c @@ -105,6 +105,18 @@ void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename) L"rumbleDuration", 1000, filename); + + cfg->ffb_base_damper_fraction = GetPrivateProfileIntW( + L"dinput", + L"baseDamperFraction", + 20, + filename); + + cfg->ffb_deadband = GetPrivateProfileIntW( + L"dinput", + L"deadband", + 2, + filename); } void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename) diff --git a/games/idacio/config.h b/games/idacio/config.h index 5b7e736..698fb7c 100644 --- a/games/idacio/config.h +++ b/games/idacio/config.h @@ -30,7 +30,8 @@ struct idac_di_config { uint8_t ffb_constant_force_strength; uint8_t ffb_rumble_strength; uint8_t ffb_damper_strength; - + uint8_t ffb_deadband; + uint8_t ffb_base_damper_fraction; uint32_t ffb_rumble_duration; }; diff --git a/games/idacio/di-dev.c b/games/idacio/di-dev.c index 5f959f6..1bc714a 100644 --- a/games/idacio/di-dev.c +++ b/games/idacio/di-dev.c @@ -1,46 +1,102 @@ -#include +#include #include #include -#include +#include #include "idacio/di-dev.h" + #include "util/dprintf.h" -const struct idac_di_config *idac_di_cfg; -static HWND idac_di_wnd; -static IDirectInputDevice8W *idac_di_dev; +/* Globals */ +static HWND idac_di_wnd = NULL; +static IDirectInputDevice8W* idac_di_dev = NULL; -/* Individual DI Effects */ -static IDirectInputEffect *idac_di_fx; -static IDirectInputEffect *idac_di_fx_rumble; -static IDirectInputEffect *idac_di_fx_damper; +static void update_or_create_effect(LPDIRECTINPUTEFFECT* effect_ptr, REFGUID guid, DIEFFECT* fx); + +/* Effect handles */ +static IDirectInputEffect* idac_di_fx_constant = NULL; +static IDirectInputEffect* idac_di_fx_rumble = NULL; +static IDirectInputEffect* idac_di_fx_spring = NULL; +static IDirectInputEffect* idac_di_fx_damper = NULL; /* Max FFB Board value is 127 */ static const double idac_di_ffb_scale = 127.0; +static const double idac_di_damper_spring_ratio = 0.5; -HRESULT idac_di_dev_init( - const struct idac_di_config *cfg, - IDirectInputDevice8W *dev, - HWND wnd) -{ +/* Clamped config settings */ +static uint8_t idac_di_constant_force_strength; +static uint8_t idac_di_rumble_strength; +static uint8_t idac_di_damper_strength; +static uint8_t idac_di_rumble_duration; +static uint8_t idac_di_base_damper; +static uint8_t idac_di_deadband; + +HRESULT idac_di_dev_init(const struct idac_di_config* cfg, + IDirectInputDevice8W* dev, HWND wnd) { + assert(cfg != NULL); + assert(dev != NULL); + assert(wnd != NULL); + + idac_di_dev = dev; + idac_di_wnd = wnd; + + idac_di_rumble_duration = cfg->ffb_rumble_duration; + + idac_di_constant_force_strength = cfg->ffb_constant_force_strength > 100 + ? 100 + : cfg->ffb_constant_force_strength; + + idac_di_rumble_strength = cfg->ffb_rumble_strength > 100 + ? 100 + : cfg->ffb_rumble_strength; + + idac_di_damper_strength = cfg->ffb_damper_strength > 100 + ? 100 + : cfg->ffb_damper_strength; + + idac_di_base_damper = cfg->ffb_base_damper_fraction > 100 + ? 100 + : cfg->ffb_base_damper_fraction; + + /* Deadband is the only setting from 0.0% to 20.0%*/ + idac_di_deadband = cfg->ffb_deadband > 200 + ? 200 + : cfg->ffb_deadband; + + return S_OK; +} + +HRESULT idac_di_dev_start(IDirectInputDevice8W* dev, HWND wnd) { HRESULT hr; assert(dev != NULL); assert(wnd != NULL); - idac_di_cfg = cfg; - idac_di_dev = dev; - idac_di_wnd = wnd; + hr = IDirectInputDevice8_SetCooperativeLevel( + dev, wnd, DISCL_BACKGROUND | DISCL_EXCLUSIVE); + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); + return hr; + } return S_OK; } -HRESULT idac_di_dev_poll( - IDirectInputDevice8W *dev, - HWND wnd, - union idac_di_state *out) -{ +HRESULT idac_di_dev_poll(IDirectInputDevice8W* dev, HWND wnd, + union idac_di_state* out) { HRESULT hr; MSG msg; @@ -50,19 +106,12 @@ HRESULT idac_di_dev_poll( memset(out, 0, sizeof(*out)); - /* Pump our dummy window's message queue just in case DirectInput or an - IHV DirectInput driver somehow relies on it */ - while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } - hr = IDirectInputDevice8_GetDeviceState( - dev, - sizeof(out->st), - &out->st); - + hr = IDirectInputDevice8_GetDeviceState(dev, sizeof(out->st), &out->st); if (FAILED(hr)) { dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); } @@ -70,100 +119,56 @@ HRESULT idac_di_dev_poll( return hr; } -HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { - HRESULT hr; - - assert(dev != NULL); - assert(wnd != NULL); - - hr = IDirectInputDevice8_SetCooperativeLevel( - dev, - wnd, - DISCL_BACKGROUND | DISCL_EXCLUSIVE); - - if (FAILED(hr)) { - dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); - return hr; +HRESULT idac_di_ffb_init(void) { + if (!idac_di_dev || !idac_di_wnd) { + return E_FAIL; } - hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); - - if (FAILED(hr)) { - dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); - return hr; - } - - hr = IDirectInputDevice8_Acquire(dev); - - if (FAILED(hr)) { - dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); - return hr; - } - - return hr; + return idac_di_dev_start(idac_di_dev, idac_di_wnd); } -HRESULT idac_di_ffb_init(void) -{ - HRESULT hr; - - hr = idac_di_dev_start(idac_di_dev, idac_di_wnd); - - if (FAILED(hr)) { - return hr; - } - - return S_OK; -} - -void idac_di_ffb_toggle(bool active) -{ +void idac_di_ffb_toggle(bool active) { if (active) { return; } - /* Stop and release all effects */ - /* I never programmed DirectInput Effects, so this might be bad practice. */ - if (idac_di_fx != NULL) { - IDirectInputEffect_Stop(idac_di_fx); - IDirectInputEffect_Release(idac_di_fx); - idac_di_fx = NULL; + if (idac_di_fx_constant) { + IDirectInputEffect_Stop(idac_di_fx_constant); + IDirectInputEffect_Release(idac_di_fx_constant); + idac_di_fx_constant = NULL; } - - if (idac_di_fx_rumble != NULL) { + if (idac_di_fx_rumble) { IDirectInputEffect_Stop(idac_di_fx_rumble); IDirectInputEffect_Release(idac_di_fx_rumble); idac_di_fx_rumble = NULL; } - - if (idac_di_fx_damper != NULL) { + if (idac_di_fx_spring) { + IDirectInputEffect_Stop(idac_di_fx_spring); + IDirectInputEffect_Release(idac_di_fx_spring); + idac_di_fx_spring = NULL; + } + if (idac_di_fx_damper) { IDirectInputEffect_Stop(idac_di_fx_damper); IDirectInputEffect_Release(idac_di_fx_damper); idac_di_fx_damper = NULL; } } -void idac_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) -{ - /* DI expects a magnitude in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = idac_di_cfg->ffb_constant_force_strength * 100; +void idac_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) { + uint16_t ffb_strength = idac_di_constant_force_strength * 100; if (ffb_strength == 0) { return; } - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DICONSTANTFORCE cf; HRESULT hr; - /* Direction 0: move to the right, 1: move to the left */ LONG magnitude = (LONG)(((double)force / idac_di_ffb_scale) * ffb_strength); cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; - axis = DIJOFS_X; - direction = 0; - memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; @@ -177,71 +182,24 @@ void idac_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) fx.cbTypeSpecificParams = sizeof(cf); fx.lpvTypeSpecificParams = &cf; - /* Check if the effect already exists */ - if (idac_di_fx != NULL) { - hr = IDirectInputEffect_SetParameters(idac_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); - if (SUCCEEDED(hr)) { - return; // Successfully updated existing effect - } - else { - dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); - IDirectInputEffect_Stop(idac_di_fx); - IDirectInputEffect_Release(idac_di_fx); - idac_di_fx = NULL; // Reset the pointer - } - } - - /* Create a new constant force effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - idac_di_dev, - &GUID_ConstantForce, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr); - return; - } - - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - if (FAILED(hr)) { - dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr); - IDirectInputEffect_Release(obj); - return; - } - - idac_di_fx = obj; + update_or_create_effect(&idac_di_fx_constant, &GUID_ConstantForce, &fx); } -void idac_di_ffb_rumble(uint8_t force, uint8_t period) -{ - /* DI expects a magnitude in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = idac_di_cfg->ffb_rumble_strength * 100; +void idac_di_ffb_rumble(uint8_t force, uint8_t period) { + uint16_t ffb_strength = idac_di_rumble_strength * 100; if (ffb_strength == 0) { return; } - uint32_t ffb_duration = idac_di_cfg->ffb_rumble_duration; - - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DIPERIODIC pe; HRESULT hr; - DWORD duration = (DWORD)((double)force * ffb_duration); - memset(&pe, 0, sizeof(pe)); pe.dwMagnitude = (DWORD)(((double)force / idac_di_ffb_scale) * ffb_strength); - pe.lOffset = 0; - pe.dwPhase = 0; - pe.dwPeriod = duration; - - axis = DIJOFS_X; - direction = 0; + pe.dwPeriod = (DWORD)force * idac_di_rumble_duration; memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); @@ -256,70 +214,41 @@ void idac_di_ffb_rumble(uint8_t force, uint8_t period) fx.cbTypeSpecificParams = sizeof(pe); fx.lpvTypeSpecificParams = &pe; - /* Check if the effect already exists */ - if (idac_di_fx_rumble != NULL) { - hr = IDirectInputEffect_SetParameters(idac_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); - if (SUCCEEDED(hr)) { - return; - } - else { - dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr); - IDirectInputEffect_Stop(idac_di_fx_rumble); - IDirectInputEffect_Release(idac_di_fx_rumble); - idac_di_fx_rumble = NULL; - } - } - - /* Create a new rumble effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - idac_di_dev, - &GUID_Sine, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr); - return; - } - - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - if (FAILED(hr)) { - dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr); - IDirectInputEffect_Release(obj); - return; - } - - idac_di_fx_rumble = obj; + update_or_create_effect(&idac_di_fx_rumble, &GUID_Sine, &fx); } -void idac_di_ffb_damper(uint8_t force) -{ - /* DI expects a coefficient in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = idac_di_cfg->ffb_damper_strength * 100; +void idac_di_ffb_damper(uint8_t force) { + HRESULT hr; + uint16_t ffb_strength = idac_di_damper_strength * 100; + if (ffb_strength == 0) { + if (idac_di_fx_spring) { + IDirectInputEffect_Stop(idac_di_fx_spring); + IDirectInputEffect_Release(idac_di_fx_spring); + idac_di_fx_spring = NULL; + } + if (idac_di_fx_damper) { + IDirectInputEffect_Stop(idac_di_fx_damper); + IDirectInputEffect_Release(idac_di_fx_damper); + idac_di_fx_damper = NULL; + } return; } - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DICONDITION cond; - HRESULT hr; + /* SPRING (centering) */ memset(&cond, 0, sizeof(cond)); - cond.lOffset = 0; - cond.lPositiveCoefficient = (LONG)(((double)force / idac_di_ffb_scale) * ffb_strength); - cond.lNegativeCoefficient = (LONG)(((double)force / idac_di_ffb_scale) * ffb_strength); - /* Not sure on this one */ + cond.lPositiveCoefficient = (LONG)(((uint32_t)force * ffb_strength) / idac_di_ffb_scale); + cond.lNegativeCoefficient = cond.lPositiveCoefficient; cond.dwPositiveSaturation = DI_FFNOMINALMAX; cond.dwNegativeSaturation = DI_FFNOMINALMAX; - cond.lDeadBand = 0; - - axis = DIJOFS_X; - direction = 0; + + /* If user enters 25, result is 0.025 * DI_FFNOMINALMAX */ + cond.lDeadBand = (DI_FFNOMINALMAX * (LONG)idac_di_deadband) / 1000; memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); @@ -334,38 +263,64 @@ void idac_di_ffb_damper(uint8_t force) fx.cbTypeSpecificParams = sizeof(cond); fx.lpvTypeSpecificParams = &cond; - /* Check if the damper effect already exists */ - if (idac_di_fx_damper != NULL) { - hr = IDirectInputEffect_SetParameters(idac_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + update_or_create_effect(&idac_di_fx_spring, &GUID_Spring, &fx); + + /* DAMPER (resistance with baseline) */ + memset(&cond, 0, sizeof(cond)); + + LONG nominal = (LONG)(( (LONG)force * ffb_strength * idac_di_damper_spring_ratio ) / idac_di_ffb_scale); + LONG min_baseline = (LONG)(DI_FFNOMINALMAX * (idac_di_base_damper / 100.0)); + + if (nominal < min_baseline) { + nominal = min_baseline; + } + + cond.lPositiveCoefficient = nominal; + cond.lNegativeCoefficient = nominal; + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + update_or_create_effect(&idac_di_fx_damper, &GUID_Damper, &fx); +} + +static void update_or_create_effect(LPDIRECTINPUTEFFECT* effect_ptr, REFGUID guid, DIEFFECT* fx) { + HRESULT hr; + + if (*effect_ptr != NULL) { + hr = IDirectInputEffect_SetParameters(*effect_ptr, fx, DIEP_TYPESPECIFICPARAMS); if (SUCCEEDED(hr)) { return; - } - else { - IDirectInputEffect_Stop(idac_di_fx_damper); - IDirectInputEffect_Release(idac_di_fx_damper); - idac_di_fx_damper = NULL; + } else { + IDirectInputEffect_Stop(*effect_ptr); + IDirectInputEffect_Release(*effect_ptr); + *effect_ptr = NULL; } } - /* Create a new damper effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - idac_di_dev, - &GUID_Damper, - &fx, - &obj, - NULL); - + IDirectInputEffect* obj = NULL; + hr = IDirectInputDevice8_CreateEffect(idac_di_dev, guid, fx, &obj, NULL); if (FAILED(hr)) { return; } - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0); + hr = IDirectInputEffect_Start(obj, fx->dwDuration, 0); if (FAILED(hr)) { IDirectInputEffect_Release(obj); return; } - idac_di_fx_damper = obj; + *effect_ptr = obj; } diff --git a/games/idzio/config.c b/games/idzio/config.c index 81b3a70..215934f 100644 --- a/games/idzio/config.c +++ b/games/idzio/config.c @@ -105,6 +105,18 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename) L"rumbleDuration", 1000, filename); + + cfg->ffb_base_damper_fraction = GetPrivateProfileIntW( + L"dinput", + L"baseDamperFraction", + 20, + filename); + + cfg->ffb_deadband = GetPrivateProfileIntW( + L"dinput", + L"deadband", + 2, + filename); } void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename) diff --git a/games/idzio/config.h b/games/idzio/config.h index 78b4b50..acb8f0d 100644 --- a/games/idzio/config.h +++ b/games/idzio/config.h @@ -30,7 +30,8 @@ struct idz_di_config { uint8_t ffb_constant_force_strength; uint8_t ffb_rumble_strength; uint8_t ffb_damper_strength; - + uint8_t ffb_base_damper_fraction; + uint8_t ffb_deadband; uint32_t ffb_rumble_duration; }; diff --git a/games/idzio/di-dev.c b/games/idzio/di-dev.c index 10b67d6..25d2b79 100644 --- a/games/idzio/di-dev.c +++ b/games/idzio/di-dev.c @@ -1,85 +1,79 @@ -#include +#include #include #include -#include +#include #include "idzio/di-dev.h" #include "util/dprintf.h" -const struct idz_di_config *idz_di_cfg; -static HWND idz_di_wnd; -static IDirectInputDevice8W *idz_di_dev; +/* Globals */ +static HWND idz_di_wnd = NULL; +static IDirectInputDevice8W* idz_di_dev = NULL; + +static void update_or_create_effect(LPDIRECTINPUTEFFECT* effect_ptr, REFGUID guid, DIEFFECT* fx); /* Individual DI Effects */ -static IDirectInputEffect *idz_di_fx; -static IDirectInputEffect *idz_di_fx_rumble; -static IDirectInputEffect *idz_di_fx_damper; +static IDirectInputEffect* idz_di_fx_constant = NULL; +static IDirectInputEffect* idz_di_fx_rumble = NULL; +static IDirectInputEffect* idz_di_fx_spring = NULL; +static IDirectInputEffect* idz_di_fx_damper = NULL; /* Max FFB Board value is 127 */ static const double idz_di_ffb_scale = 127.0; +static const double idz_di_damper_spring_ratio = 0.5; -HRESULT idz_di_dev_init( - const struct idz_di_config *cfg, - IDirectInputDevice8W *dev, - HWND wnd) -{ - HRESULT hr; +/* Clamped config settings */ +static uint8_t idz_di_constant_force_strength; +static uint8_t idz_di_rumble_strength; +static uint8_t idz_di_damper_strength; +static uint8_t idz_di_rumble_duration; +static uint8_t idz_di_base_damper; +static uint8_t idz_di_deadband; +HRESULT idz_di_dev_init(const struct idz_di_config* cfg, + IDirectInputDevice8W* dev, HWND wnd) { + assert(cfg != NULL); assert(dev != NULL); assert(wnd != NULL); - idz_di_cfg = cfg; idz_di_dev = dev; idz_di_wnd = wnd; + idz_di_rumble_duration = cfg->ffb_rumble_duration; + + idz_di_constant_force_strength = cfg->ffb_constant_force_strength > 100 + ? 100 + : cfg->ffb_constant_force_strength; + + idz_di_rumble_strength = cfg->ffb_rumble_strength > 100 + ? 100 + : cfg->ffb_rumble_strength; + + idz_di_damper_strength = cfg->ffb_damper_strength > 100 + ? 100 + : cfg->ffb_damper_strength; + + idz_di_base_damper = cfg->ffb_base_damper_fraction > 100 + ? 100 + : cfg->ffb_base_damper_fraction; + + /* Deadband is the only setting from 0.0% to 20.0%*/ + idz_di_deadband = cfg->ffb_deadband > 200 + ? 200 + : cfg->ffb_deadband; + return S_OK; } -HRESULT idz_di_dev_poll( - IDirectInputDevice8W *dev, - HWND wnd, - union idz_di_state *out) -{ - HRESULT hr; - MSG msg; - - assert(dev != NULL); - assert(wnd != NULL); - assert(out != NULL); - - memset(out, 0, sizeof(*out)); - - /* Pump our dummy window's message queue just in case DirectInput or an - IHV DirectInput driver somehow relies on it */ - - while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - hr = IDirectInputDevice8_GetDeviceState( - dev, - sizeof(out->st), - &out->st); - - if (FAILED(hr)) { - dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); - } - - return hr; -} - -HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { +HRESULT idz_di_dev_start(IDirectInputDevice8W* dev, HWND wnd) { HRESULT hr; assert(dev != NULL); assert(wnd != NULL); hr = IDirectInputDevice8_SetCooperativeLevel( - dev, - wnd, - DISCL_BACKGROUND | DISCL_EXCLUSIVE); + dev, wnd, DISCL_BACKGROUND | DISCL_EXCLUSIVE); if (FAILED(hr)) { dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); @@ -100,70 +94,90 @@ HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { return hr; } - return hr; -} - -HRESULT idz_di_ffb_init(void) -{ - HRESULT hr; - - hr = idz_di_dev_start(idz_di_dev, idz_di_wnd); - - if (FAILED(hr)) { - return hr; - } - return S_OK; } -void idz_di_ffb_toggle(bool active) -{ +HRESULT idz_di_dev_poll(IDirectInputDevice8W* dev, HWND wnd, + union idz_di_state* out) { + HRESULT hr; + MSG msg; + + assert(dev != NULL); + assert(wnd != NULL); + assert(out != NULL); + + memset(out, 0, sizeof(*out)); + + /* Pump our dummy window's message queue just in case DirectInput or an + IHV DirectInput driver somehow relies on it */ + + while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + hr = IDirectInputDevice8_GetDeviceState(dev, sizeof(out->st), &out->st); + + if (FAILED(hr)) { + dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); + } + + return hr; +} + +HRESULT idz_di_ffb_init(void) { + if (!idz_di_dev || !idz_di_wnd) { + return E_FAIL; + } + + return idz_di_dev_start(idz_di_dev, idz_di_wnd); +} + +void idz_di_ffb_toggle(bool active) { if (active) { return; } - /* Stop and release all effects */ - /* I never programmed DirectInput Effects, so this might be bad practice. */ - if (idz_di_fx != NULL) { - IDirectInputEffect_Stop(idz_di_fx); - IDirectInputEffect_Release(idz_di_fx); - idz_di_fx = NULL; + if (idz_di_fx_constant) { + IDirectInputEffect_Stop(idz_di_fx_constant); + IDirectInputEffect_Release(idz_di_fx_constant); + idz_di_fx_constant = NULL; } - if (idz_di_fx_rumble != NULL) { + if (idz_di_fx_rumble) { IDirectInputEffect_Stop(idz_di_fx_rumble); IDirectInputEffect_Release(idz_di_fx_rumble); idz_di_fx_rumble = NULL; } - if (idz_di_fx_damper != NULL) { + if (idz_di_fx_spring) { + IDirectInputEffect_Stop(idz_di_fx_spring); + IDirectInputEffect_Release(idz_di_fx_spring); + idz_di_fx_spring = NULL; + } + + if (idz_di_fx_damper) { IDirectInputEffect_Stop(idz_di_fx_damper); IDirectInputEffect_Release(idz_di_fx_damper); idz_di_fx_damper = NULL; } } -void idz_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) -{ - /* DI expects a magnitude in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = idz_di_cfg->ffb_constant_force_strength * 100; +void idz_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) { + uint16_t ffb_strength = idz_di_constant_force_strength * 100; if (ffb_strength == 0) { return; } - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DICONSTANTFORCE cf; HRESULT hr; - /* Direction 0: move to the right, 1: move to the left */ LONG magnitude = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; - axis = DIJOFS_X; - direction = 0; - memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; @@ -177,71 +191,24 @@ void idz_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) fx.cbTypeSpecificParams = sizeof(cf); fx.lpvTypeSpecificParams = &cf; - /* Check if the effect already exists */ - if (idz_di_fx != NULL) { - hr = IDirectInputEffect_SetParameters(idz_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); - if (SUCCEEDED(hr)) { - return; // Successfully updated existing effect - } - else { - dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); - IDirectInputEffect_Stop(idz_di_fx); - IDirectInputEffect_Release(idz_di_fx); - idz_di_fx = NULL; // Reset the pointer - } - } - - /* Create a new constant force effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - idz_di_dev, - &GUID_ConstantForce, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr); - return; - } - - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - if (FAILED(hr)) { - dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr); - IDirectInputEffect_Release(obj); - return; - } - - idz_di_fx = obj; + update_or_create_effect(&idz_di_fx_constant, &GUID_ConstantForce, &fx); } -void idz_di_ffb_rumble(uint8_t force, uint8_t period) -{ - /* DI expects a magnitude in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = idz_di_cfg->ffb_rumble_strength * 100; +void idz_di_ffb_rumble(uint8_t force, uint8_t period) { + uint16_t ffb_strength = idz_di_rumble_strength * 100; if (ffb_strength == 0) { return; } - uint32_t ffb_duration = idz_di_cfg->ffb_rumble_duration; - - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DIPERIODIC pe; HRESULT hr; - DWORD duration = (DWORD)((double)force * ffb_duration); - memset(&pe, 0, sizeof(pe)); pe.dwMagnitude = (DWORD)(((double)force / idz_di_ffb_scale) * ffb_strength); - pe.lOffset = 0; - pe.dwPhase = 0; - pe.dwPeriod = duration; - - axis = DIJOFS_X; - direction = 0; + pe.dwPeriod = (DWORD)force * idz_di_rumble_duration; memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); @@ -256,70 +223,41 @@ void idz_di_ffb_rumble(uint8_t force, uint8_t period) fx.cbTypeSpecificParams = sizeof(pe); fx.lpvTypeSpecificParams = &pe; - /* Check if the effect already exists */ - if (idz_di_fx_rumble != NULL) { - hr = IDirectInputEffect_SetParameters(idz_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); - if (SUCCEEDED(hr)) { - return; - } - else { - dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr); - IDirectInputEffect_Stop(idz_di_fx_rumble); - IDirectInputEffect_Release(idz_di_fx_rumble); - idz_di_fx_rumble = NULL; - } - } - - /* Create a new rumble effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - idz_di_dev, - &GUID_Sine, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr); - return; - } - - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - if (FAILED(hr)) { - dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr); - IDirectInputEffect_Release(obj); - return; - } - - idz_di_fx_rumble = obj; + update_or_create_effect(&idz_di_fx_rumble, &GUID_Sine, &fx); } -void idz_di_ffb_damper(uint8_t force) -{ - /* DI expects a coefficient in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = idz_di_cfg->ffb_damper_strength * 100; +void idz_di_ffb_damper(uint8_t force) { + HRESULT hr; + uint16_t ffb_strength = idz_di_damper_strength * 100; + if (ffb_strength == 0) { + if (idz_di_fx_spring) { + IDirectInputEffect_Stop(idz_di_fx_spring); + IDirectInputEffect_Release(idz_di_fx_spring); + idz_di_fx_spring = NULL; + } + if (idz_di_fx_damper) { + IDirectInputEffect_Stop(idz_di_fx_damper); + IDirectInputEffect_Release(idz_di_fx_damper); + idz_di_fx_damper = NULL; + } return; } - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DICONDITION cond; - HRESULT hr; + /* SPRING (centering) */ memset(&cond, 0, sizeof(cond)); - cond.lOffset = 0; - cond.lPositiveCoefficient = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); - cond.lNegativeCoefficient = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); - /* Not sure on this one */ + cond.lPositiveCoefficient = (LONG)(((uint32_t)force * ffb_strength) / idz_di_ffb_scale); + cond.lNegativeCoefficient = cond.lPositiveCoefficient; cond.dwPositiveSaturation = DI_FFNOMINALMAX; cond.dwNegativeSaturation = DI_FFNOMINALMAX; - cond.lDeadBand = 0; - - axis = DIJOFS_X; - direction = 0; + + /* If user enters 25, result is 0.025 * DI_FFNOMINALMAX */ + cond.lDeadBand = (DI_FFNOMINALMAX * (LONG)idz_di_deadband) / 1000; memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); @@ -334,38 +272,64 @@ void idz_di_ffb_damper(uint8_t force) fx.cbTypeSpecificParams = sizeof(cond); fx.lpvTypeSpecificParams = &cond; - /* Check if the damper effect already exists */ - if (idz_di_fx_damper != NULL) { - hr = IDirectInputEffect_SetParameters(idz_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + update_or_create_effect(&idz_di_fx_spring, &GUID_Spring, &fx); + + /* DAMPER (resistance with baseline) */ + memset(&cond, 0, sizeof(cond)); + + LONG nominal = (LONG)(( (LONG)force * ffb_strength * idz_di_damper_spring_ratio ) / idz_di_ffb_scale); + LONG min_baseline = (LONG)(DI_FFNOMINALMAX * (idz_di_base_damper / 100.0)); + + if (nominal < min_baseline) { + nominal = min_baseline; + } + + cond.lPositiveCoefficient = nominal; + cond.lNegativeCoefficient = nominal; + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + update_or_create_effect(&idz_di_fx_damper, &GUID_Damper, &fx); +} + +static void update_or_create_effect(LPDIRECTINPUTEFFECT* effect_ptr, REFGUID guid, DIEFFECT* fx) { + HRESULT hr; + + if (*effect_ptr != NULL) { + hr = IDirectInputEffect_SetParameters(*effect_ptr, fx, DIEP_TYPESPECIFICPARAMS); if (SUCCEEDED(hr)) { return; - } - else { - IDirectInputEffect_Stop(idz_di_fx_damper); - IDirectInputEffect_Release(idz_di_fx_damper); - idz_di_fx_damper = NULL; + } else { + IDirectInputEffect_Stop(*effect_ptr); + IDirectInputEffect_Release(*effect_ptr); + *effect_ptr = NULL; } } - /* Create a new damper effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - idz_di_dev, - &GUID_Damper, - &fx, - &obj, - NULL); - + IDirectInputEffect* obj = NULL; + hr = IDirectInputDevice8_CreateEffect(idz_di_dev, guid, fx, &obj, NULL); if (FAILED(hr)) { return; } - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0); + hr = IDirectInputEffect_Start(obj, fx->dwDuration, 0); if (FAILED(hr)) { IDirectInputEffect_Release(obj); return; } - idz_di_fx_damper = obj; + *effect_ptr = obj; } diff --git a/games/swdcio/config.c b/games/swdcio/config.c index 9bf45a8..aedc0cc 100644 --- a/games/swdcio/config.c +++ b/games/swdcio/config.c @@ -18,35 +18,32 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename) GetPrivateProfileStringW( L"dinput", - L"deviceName", - L"", - cfg->device_name, - _countof(cfg->device_name), - filename); - + L"deviceName", + L"", + cfg->device_name, + _countof(cfg->device_name), + filename); GetPrivateProfileStringW( - L"dinput", - L"pedalsName", - L"", - cfg->pedals_name, - _countof(cfg->pedals_name), - filename); - + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); GetPrivateProfileStringW( - L"dinput", - L"brakeAxis", - L"RZ", - cfg->brake_axis, - _countof(cfg->brake_axis), - filename); - + L"dinput", + L"brakeAxis", + L"RZ", + cfg->brake_axis, + _countof(cfg->brake_axis), + filename); GetPrivateProfileStringW( - L"dinput", - L"accelAxis", - L"Y", - cfg->accel_axis, - _countof(cfg->accel_axis), - filename); + L"dinput", + L"accelAxis", + L"Y", + cfg->accel_axis, + _countof(cfg->accel_axis), + filename); cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename); cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename); @@ -62,15 +59,15 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename) cfg->wheel_yellow = GetPrivateProfileIntW(L"dinput", L"wheelYellow", 0, filename); cfg->reverse_brake_axis = GetPrivateProfileIntW( - L"dinput", - L"reverseBrakeAxis", - 0, - filename); + L"dinput", + L"reverseBrakeAxis", + 0, + filename); cfg->reverse_accel_axis = GetPrivateProfileIntW( - L"dinput", - L"reverseAccelAxis", - 0, - filename); + L"dinput", + L"reverseAccelAxis", + 0, + filename); // FFB configuration cfg->ffb_constant_force_strength = GetPrivateProfileIntW( @@ -96,6 +93,18 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename) L"rumbleDuration", 1000, filename); + + cfg->ffb_base_damper_fraction = GetPrivateProfileIntW( + L"dinput", + L"baseDamperFraction", + 20, + filename); + + cfg->ffb_deadband = GetPrivateProfileIntW( + L"dinput", + L"deadband", + 2, + filename); } void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename) @@ -114,13 +123,13 @@ void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename) L"linearSteering", 0, filename); - + cfg->left_stick_deadzone = GetPrivateProfileIntW( - L"xinput", - L"leftStickDeadzone", - 7849, - filename); - + L"xinput", + L"leftStickDeadzone", + 7849, + filename); + cfg->right_stick_deadzone = GetPrivateProfileIntW( L"xinput", L"rightStickDeadzone", @@ -133,18 +142,34 @@ void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename) assert(cfg != NULL); assert(filename != NULL); - cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); - cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); - cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); - cfg->restrict_ = GetPrivateProfileIntW(L"io4", L"restrict", 128, filename); + cfg->vk_test = GetPrivateProfileIntW( + L"io4", + L"test", + VK_F1, + filename); + cfg->vk_service = GetPrivateProfileIntW( + L"io4", + L"service", + VK_F2, + filename); + cfg->vk_coin = GetPrivateProfileIntW( + L"io4", + L"coin", + VK_F3, + filename); + cfg->restrict_ = GetPrivateProfileIntW( + L"io4", + L"restrict", + 128, + filename); GetPrivateProfileStringW( - L"io4", - L"mode", - L"xinput", - cfg->mode, - _countof(cfg->mode), - filename); + L"io4", + L"mode", + L"xinput", + cfg->mode, + _countof(cfg->mode), + filename); swdc_di_config_load(&cfg->di, filename); swdc_xi_config_load(&cfg->xi, filename); diff --git a/games/swdcio/config.h b/games/swdcio/config.h index c4e2dc3..26b3157 100644 --- a/games/swdcio/config.h +++ b/games/swdcio/config.h @@ -28,7 +28,8 @@ struct swdc_di_config { uint8_t ffb_constant_force_strength; uint8_t ffb_rumble_strength; uint8_t ffb_damper_strength; - + uint8_t ffb_deadband; + uint8_t ffb_base_damper_fraction; uint32_t ffb_rumble_duration; }; diff --git a/games/swdcio/di-dev.c b/games/swdcio/di-dev.c index 4f099cc..e913927 100644 --- a/games/swdcio/di-dev.c +++ b/games/swdcio/di-dev.c @@ -1,46 +1,102 @@ -#include +#include "swdcio/di-dev.h" + +#include #include #include -#include - -#include "swdcio/di-dev.h" +#include #include "util/dprintf.h" -const struct swdc_di_config *swdc_di_cfg; -static HWND swdc_di_wnd; -static IDirectInputDevice8W *swdc_di_dev; +/* Globals */ +static HWND swdc_di_wnd = NULL; +static IDirectInputDevice8W* swdc_di_dev = NULL; + +static void update_or_create_effect(LPDIRECTINPUTEFFECT* effect_ptr, + REFGUID guid, DIEFFECT* fx); /* Individual DI Effects */ -static IDirectInputEffect *swdc_di_fx; -static IDirectInputEffect *swdc_di_fx_rumble; -static IDirectInputEffect *swdc_di_fx_damper; +static IDirectInputEffect* swdc_di_fx_constant = NULL; +static IDirectInputEffect* swdc_di_fx_rumble = NULL; +static IDirectInputEffect* swdc_di_fx_spring = NULL; +static IDirectInputEffect* swdc_di_fx_damper = NULL; /* Max FFB Board value is 127 */ static const double swdc_di_ffb_scale = 127.0; +static const double swdc_di_damper_spring_ratio = 0.5; -HRESULT swdc_di_dev_init( - const struct swdc_di_config *cfg, - IDirectInputDevice8W *dev, - HWND wnd) -{ +/* Clamped config settings */ +static uint8_t swdc_di_constant_force_strength; +static uint8_t swdc_di_rumble_strength; +static uint8_t swdc_di_damper_strength; +static uint8_t swdc_di_rumble_duration; +static uint8_t swdc_di_base_damper; +static uint8_t swdc_di_deadband; + +HRESULT swdc_di_dev_init(const struct swdc_di_config* cfg, + IDirectInputDevice8W* dev, HWND wnd) { + assert(cfg != NULL); + assert(dev != NULL); + assert(wnd != NULL); + + swdc_di_dev = dev; + swdc_di_wnd = wnd; + + swdc_di_rumble_duration = cfg->ffb_rumble_duration; + + swdc_di_constant_force_strength = cfg->ffb_constant_force_strength > 100 + ? 100 + : cfg->ffb_constant_force_strength; + + swdc_di_rumble_strength = cfg->ffb_rumble_strength > 100 + ? 100 + : cfg->ffb_rumble_strength; + + swdc_di_damper_strength = cfg->ffb_damper_strength > 100 + ? 100 + : cfg->ffb_damper_strength; + + swdc_di_base_damper = cfg->ffb_base_damper_fraction > 100 + ? 100 + : cfg->ffb_base_damper_fraction; + + /* Deadband is the only setting from 0.0% to 20.0%*/ + swdc_di_deadband = cfg->ffb_deadband > 200 + ? 200 + : cfg->ffb_deadband; + + return S_OK; +} + +HRESULT swdc_di_dev_start(IDirectInputDevice8W* dev, HWND wnd) { HRESULT hr; assert(dev != NULL); assert(wnd != NULL); - swdc_di_cfg = cfg; - swdc_di_dev = dev; - swdc_di_wnd = wnd; + hr = IDirectInputDevice8_SetCooperativeLevel( + dev, wnd, DISCL_BACKGROUND | DISCL_EXCLUSIVE); + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); + return hr; + } return S_OK; } -HRESULT swdc_di_dev_poll( - IDirectInputDevice8W *dev, - HWND wnd, - union swdc_di_state *out) -{ +HRESULT swdc_di_dev_poll(IDirectInputDevice8W* dev, HWND wnd, + union swdc_di_state* out) { HRESULT hr; MSG msg; @@ -50,19 +106,12 @@ HRESULT swdc_di_dev_poll( memset(out, 0, sizeof(*out)); - /* Pump our dummy window's message queue just in case DirectInput or an - IHV DirectInput driver somehow relies on it */ - while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } - hr = IDirectInputDevice8_GetDeviceState( - dev, - sizeof(out->st), - &out->st); - + hr = IDirectInputDevice8_GetDeviceState(dev, sizeof(out->st), &out->st); if (FAILED(hr)) { dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); } @@ -70,100 +119,56 @@ HRESULT swdc_di_dev_poll( return hr; } -HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { - HRESULT hr; - - assert(dev != NULL); - assert(wnd != NULL); - - hr = IDirectInputDevice8_SetCooperativeLevel( - dev, - wnd, - DISCL_BACKGROUND | DISCL_EXCLUSIVE); - - if (FAILED(hr)) { - dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); - return hr; +HRESULT swdc_di_ffb_init(void) { + if (!swdc_di_dev || !swdc_di_wnd) { + return E_FAIL; } - hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); - - if (FAILED(hr)) { - dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); - return hr; - } - - hr = IDirectInputDevice8_Acquire(dev); - - if (FAILED(hr)) { - dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); - return hr; - } - - return hr; + return swdc_di_dev_start(swdc_di_dev, swdc_di_wnd); } -HRESULT swdc_di_ffb_init(void) -{ - HRESULT hr; - - hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd); - - if (FAILED(hr)) { - return hr; - } - - return S_OK; -} - -void swdc_di_ffb_toggle(bool active) -{ +void swdc_di_ffb_toggle(bool active) { if (active) { return; } - /* Stop and release all effects */ - /* I never programmed DirectInput Effects, so this might be bad practice. */ - if (swdc_di_fx != NULL) { - IDirectInputEffect_Stop(swdc_di_fx); - IDirectInputEffect_Release(swdc_di_fx); - swdc_di_fx = NULL; + if (swdc_di_fx_constant) { + IDirectInputEffect_Stop(swdc_di_fx_constant); + IDirectInputEffect_Release(swdc_di_fx_constant); + swdc_di_fx_constant = NULL; } - - if (swdc_di_fx_rumble != NULL) { + if (swdc_di_fx_rumble) { IDirectInputEffect_Stop(swdc_di_fx_rumble); IDirectInputEffect_Release(swdc_di_fx_rumble); swdc_di_fx_rumble = NULL; } - - if (swdc_di_fx_damper != NULL) { + if (swdc_di_fx_spring) { + IDirectInputEffect_Stop(swdc_di_fx_spring); + IDirectInputEffect_Release(swdc_di_fx_spring); + swdc_di_fx_spring = NULL; + } + if (swdc_di_fx_damper) { IDirectInputEffect_Stop(swdc_di_fx_damper); IDirectInputEffect_Release(swdc_di_fx_damper); swdc_di_fx_damper = NULL; } } -void swdc_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) -{ - /* DI expects a magnitude in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = swdc_di_cfg->ffb_constant_force_strength * 100; +void swdc_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) { + uint16_t ffb_strength = swdc_di_constant_force_strength * 100; if (ffb_strength == 0) { return; } - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DICONSTANTFORCE cf; HRESULT hr; - /* Direction 0: move to the right, 1: move to the left */ LONG magnitude = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength); cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; - axis = DIJOFS_X; - direction = 0; - memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; @@ -177,71 +182,24 @@ void swdc_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) fx.cbTypeSpecificParams = sizeof(cf); fx.lpvTypeSpecificParams = &cf; - /* Check if the effect already exists */ - if (swdc_di_fx != NULL) { - hr = IDirectInputEffect_SetParameters(swdc_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); - if (SUCCEEDED(hr)) { - return; // Successfully updated existing effect - } - else { - dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); - IDirectInputEffect_Stop(swdc_di_fx); - IDirectInputEffect_Release(swdc_di_fx); - swdc_di_fx = NULL; // Reset the pointer - } - } - - /* Create a new constant force effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - swdc_di_dev, - &GUID_ConstantForce, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr); - return; - } - - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - if (FAILED(hr)) { - dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr); - IDirectInputEffect_Release(obj); - return; - } - - swdc_di_fx = obj; + update_or_create_effect(&swdc_di_fx_constant, &GUID_ConstantForce, &fx); } -void swdc_di_ffb_rumble(uint8_t force, uint8_t period) -{ - /* DI expects a magnitude in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = swdc_di_cfg->ffb_rumble_strength * 100; +void swdc_di_ffb_rumble(uint8_t force, uint8_t period) { + uint16_t ffb_strength = swdc_di_rumble_strength * 100; if (ffb_strength == 0) { return; } - uint32_t ffb_duration = swdc_di_cfg->ffb_rumble_duration; - - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DIPERIODIC pe; HRESULT hr; - DWORD duration = (DWORD)((double)force * ffb_duration); - memset(&pe, 0, sizeof(pe)); pe.dwMagnitude = (DWORD)(((double)force / swdc_di_ffb_scale) * ffb_strength); - pe.lOffset = 0; - pe.dwPhase = 0; - pe.dwPeriod = duration; - - axis = DIJOFS_X; - direction = 0; + pe.dwPeriod = (DWORD)force * swdc_di_rumble_duration; memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); @@ -256,70 +214,41 @@ void swdc_di_ffb_rumble(uint8_t force, uint8_t period) fx.cbTypeSpecificParams = sizeof(pe); fx.lpvTypeSpecificParams = &pe; - /* Check if the effect already exists */ - if (swdc_di_fx_rumble != NULL) { - hr = IDirectInputEffect_SetParameters(swdc_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); - if (SUCCEEDED(hr)) { - return; - } - else { - dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr); - IDirectInputEffect_Stop(swdc_di_fx_rumble); - IDirectInputEffect_Release(swdc_di_fx_rumble); - swdc_di_fx_rumble = NULL; - } - } - - /* Create a new rumble effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - swdc_di_dev, - &GUID_Sine, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr); - return; - } - - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - if (FAILED(hr)) { - dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr); - IDirectInputEffect_Release(obj); - return; - } - - swdc_di_fx_rumble = obj; + update_or_create_effect(&swdc_di_fx_rumble, &GUID_Sine, &fx); } -void swdc_di_ffb_damper(uint8_t force) -{ - /* DI expects a coefficient in the range of -10.000 to 10.000 */ - uint16_t ffb_strength = swdc_di_cfg->ffb_damper_strength * 100; +void swdc_di_ffb_damper(uint8_t force) { + HRESULT hr; + uint16_t ffb_strength = swdc_di_damper_strength * 100; + if (ffb_strength == 0) { + if (swdc_di_fx_spring) { + IDirectInputEffect_Stop(swdc_di_fx_spring); + IDirectInputEffect_Release(swdc_di_fx_spring); + swdc_di_fx_spring = NULL; + } + if (swdc_di_fx_damper) { + IDirectInputEffect_Stop(swdc_di_fx_damper); + IDirectInputEffect_Release(swdc_di_fx_damper); + swdc_di_fx_damper = NULL; + } return; } - DWORD axis; - LONG direction; + DWORD axis = DIJOFS_X; + LONG direction = 0; DIEFFECT fx; DICONDITION cond; - HRESULT hr; + /* SPRING (centering) */ memset(&cond, 0, sizeof(cond)); - cond.lOffset = 0; - cond.lPositiveCoefficient = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength); - cond.lNegativeCoefficient = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength); - /* Not sure on this one */ + cond.lPositiveCoefficient = (LONG)(((uint32_t)force * ffb_strength) / swdc_di_ffb_scale); + cond.lNegativeCoefficient = cond.lPositiveCoefficient; cond.dwPositiveSaturation = DI_FFNOMINALMAX; cond.dwNegativeSaturation = DI_FFNOMINALMAX; - cond.lDeadBand = 0; - - axis = DIJOFS_X; - direction = 0; + + /* If user enters 25, result is 0.025 * DI_FFNOMINALMAX */ + cond.lDeadBand = (DI_FFNOMINALMAX * (LONG)swdc_di_deadband) / 1000; memset(&fx, 0, sizeof(fx)); fx.dwSize = sizeof(fx); @@ -334,38 +263,66 @@ void swdc_di_ffb_damper(uint8_t force) fx.cbTypeSpecificParams = sizeof(cond); fx.lpvTypeSpecificParams = &cond; - /* Check if the damper effect already exists */ - if (swdc_di_fx_damper != NULL) { - hr = IDirectInputEffect_SetParameters(swdc_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + update_or_create_effect(&swdc_di_fx_spring, &GUID_Spring, &fx); + + /* DAMPER (resistance with baseline) */ + memset(&cond, 0, sizeof(cond)); + + LONG nominal = (LONG)(( (LONG)force * ffb_strength * swdc_di_damper_spring_ratio ) / swdc_di_ffb_scale); + LONG min_baseline = (LONG)(DI_FFNOMINALMAX * (swdc_di_base_damper / 100.0)); + + if (nominal < min_baseline) { + nominal = min_baseline; + } + + cond.lPositiveCoefficient = nominal; + cond.lNegativeCoefficient = nominal; + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + update_or_create_effect(&swdc_di_fx_damper, &GUID_Damper, &fx); +} + +static void update_or_create_effect(LPDIRECTINPUTEFFECT* effect_ptr, + REFGUID guid, DIEFFECT* fx) { + HRESULT hr; + + if (*effect_ptr != NULL) { + hr = IDirectInputEffect_SetParameters(*effect_ptr, fx, + DIEP_TYPESPECIFICPARAMS); if (SUCCEEDED(hr)) { return; - } - else { - IDirectInputEffect_Stop(swdc_di_fx_damper); - IDirectInputEffect_Release(swdc_di_fx_damper); - swdc_di_fx_damper = NULL; + } else { + IDirectInputEffect_Stop(*effect_ptr); + IDirectInputEffect_Release(*effect_ptr); + *effect_ptr = NULL; } } - /* Create a new damper effect */ - IDirectInputEffect *obj; - hr = IDirectInputDevice8_CreateEffect( - swdc_di_dev, - &GUID_Damper, - &fx, - &obj, - NULL); - + IDirectInputEffect* obj = NULL; + hr = IDirectInputDevice8_CreateEffect(swdc_di_dev, guid, fx, &obj, NULL); if (FAILED(hr)) { return; } - /* Start the effect */ - hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0); + hr = IDirectInputEffect_Start(obj, fx->dwDuration, 0); if (FAILED(hr)) { IDirectInputEffect_Release(obj); return; } - swdc_di_fx_damper = obj; + *effect_ptr = obj; }