From 6d9b9d44a1734c26cdca7ce3e213c5f987916200 Mon Sep 17 00:00:00 2001 From: badfontkeming <107527585+badfontkeming@users.noreply.github.com> Date: Wed, 6 Nov 2024 08:33:01 -0600 Subject: [PATCH] USB: add direct-to-directinput hack for windows for testing --- pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp | 53 ++++++++++++++++++++++++---- pcsx2/USB/usb-pad/usb-pad-sdl-ff.h | 45 +++++++++++++++++++++++ pcsx2/USB/usb-pad/usb-pad.cpp | 8 +++++ pcsx2/USB/usb-pad/usb-pad.h | 1 + 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp b/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp index 8d14f21be6..88fa0104db 100644 --- a/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp +++ b/pcsx2/USB/usb-pad/usb-pad-sdl-ff.cpp @@ -60,17 +60,17 @@ namespace usb_pad void SDLFFDevice::CreateEffects(const std::string_view device) { // Most games appear to assume that requested forces will be applied indefinitely. - // Gran Turismo 4 uses a single indefinite spring(?) force to center the wheel in menus, - // and both GT4 and the NFS games have been observed using only a single constant force + // Gran Turismo 4 uses a single indefinite spring(?) force to center the wheel in menus, + // and both GT4 and the NFS games have been observed using only a single constant force // command over long, consistent turns on smooth roads. - // + // // An infinite force is necessary as the normal mechanism for looping FFB effects, // the iteration count, isn't implemented by a large number of new wheels. This deficiency // exists at a firmware level and can only be dealt with by manually restarting forces. - // + // // Manually restarting forces causes problems on some wheels, however, so infinite forces // are preferred for the vast majority of wheels which do correctly handle them. - // + // // Known "Problem" wheels which don't implement effect iterations // - Moza series: DOES implement infinite durations // - Accuforce v2: DOES implement infinite durations (deduced from anecdote, not confirmed manually) @@ -195,18 +195,57 @@ namespace usb_pad if (m_constant_effect_id < 0) return; - const s16 new_level = static_cast(std::clamp(level, -32768, 32767)); + s16 new_level = static_cast(std::clamp(level, -32768, 32767)); if (m_constant_effect.constant.level != new_level) { +#ifdef __WIN32__ + if (bypass_sdl_when_updating) + { + new_level = new_level * 10000 / 32767; + // TODO: Other force types probably need the same type of attention, but constant forces are + // the most sensitive to this issue as they're generally going to be updated the most frequently. + + // DANGER! Reading this code may give you radiation poisoning. + // It's ugly, but it works. Ideally SDL would be patched to make this unnecessary, + // but if patching SDL proves to be too unwieldy, a cleaned-up version of this + // might be appropriate. + + // It's not easy to identify by hand if you're not highly experienced, so I'd recommend + // average joes use USBPcap via Wireshark in order to verify whether your approach works. + // Try this filter: + // usb.src=="host" && usb.data_len!=0 && usbhid.data + + // Steal the raw DirectInput references from SDL and update them directly. + // Allows us to set our own flags for SetParameters. This is important because + // SDL sends unnecessary flags with its updates, which causes unnecessary HID reports, + // which may be causing a loss in detail due to wheels unnecessarily reinitializing + // the force. + + // Yeah there's raw C casts, I couldn't figure out how to appease the C++ compiler + // when using stl casts before I had to stop working on this + _SDL_Haptic* real = (_SDL_Haptic*)(m_haptic); + auto ref = real->effects[m_constant_effect_id].hweffect->ref; + auto k = (DICONSTANTFORCE*)real->effects[m_constant_effect_id].hweffect->effect.lpvTypeSpecificParams; + k->lMagnitude = new_level; + ref->SetParameters(&real->effects[m_constant_effect_id].hweffect->effect, DIEP_TYPESPECIFICPARAMS); + } + else + { + m_constant_effect.constant.level = new_level; + if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0) + Console.Warning("SDL_HapticUpdateEffect() for constant failed: %s", SDL_GetError()); + } +#else m_constant_effect.constant.level = new_level; if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0) Console.Warning("SDL_HapticUpdateEffect() for constant failed: %s", SDL_GetError()); +#endif } // Avoid re-running already-running effects by default. Re-running a running effect // causes a variety of issues on different wheels, ranging from quality/detail loss, // to abrupt judders of the wheel's FFB rapidly cutting out and back in. - // + // // Known problem wheels: // Most common (Moza, Simagic, likely others): Loss of definition or quality // Accuforce v2: Split-second FFB drop with each update diff --git a/pcsx2/USB/usb-pad/usb-pad-sdl-ff.h b/pcsx2/USB/usb-pad/usb-pad-sdl-ff.h index 25d5c09978..500dbf466a 100644 --- a/pcsx2/USB/usb-pad/usb-pad-sdl-ff.h +++ b/pcsx2/USB/usb-pad/usb-pad-sdl-ff.h @@ -5,6 +5,51 @@ #include "USB/usb-pad/usb-pad.h" #include "Input/SDLInputSource.h" +#ifdef __WIN32__ +#include +#include +#endif + +#ifdef __WIN32__ +// Copied some internal structure definitions from SDL's source +// in order to scoop out their inner bits that are excluded from +// public-facing headers. + +// It's ugly, but it works for the purposes of this hack/PoC + +struct haptic_hweffect +{ + DIEFFECT effect; + LPDIRECTINPUTEFFECT ref; +}; + +struct haptic_effect +{ + SDL_HapticEffect effect; // The current event + struct haptic_hweffect* hweffect; // The hardware behind the event +}; + +struct _SDL_Haptic +{ + Uint8 index; /* Stores index it is attached to */ + + struct haptic_effect* effects; /* Allocated effects */ + int neffects; /* Maximum amount of effects */ + int nplaying; /* Maximum amount of effects to play at the same time */ + unsigned int supported; /* Supported effects */ + int naxes; /* Number of axes on the device. */ + + struct haptic_hwdata* hwdata; /* Driver dependent */ + int ref_count; /* Count for multiple opens */ + + int rumble_id; /* ID of rumble effect for simple rumble API. */ + SDL_HapticEffect rumble_effect; /* Rumble effect. */ + struct _SDL_Haptic* next; /* pointer to next haptic we have allocated */ +}; + +#endif + + namespace usb_pad { diff --git a/pcsx2/USB/usb-pad/usb-pad.cpp b/pcsx2/USB/usb-pad/usb-pad.cpp index 345ecfbd7f..a5254b9523 100644 --- a/pcsx2/USB/usb-pad/usb-pad.cpp +++ b/pcsx2/USB/usb-pad/usb-pad.cpp @@ -166,6 +166,9 @@ namespace usb_pad "Off", nullptr, nullptr, nullptr, nullptr, SteeringCurveExponentOptions}, {SettingInfo::Type::Boolean, "FfbDropoutWorkaround", TRANSLATE_NOOP("USB", "Workaround for Intermittent FFB Loss"), TRANSLATE_NOOP("USB", "Works around bugs in some wheels' firmware that result in brief interruptions in force. Leave this disabled unless you need it, as it has negative side effects on many wheels."), + "false"}, + {SettingInfo::Type::Boolean, "FfbDirectInputHack", TRANSLATE_NOOP("USB", "HACK: Bypass SDL for FFB updates on Windows"), + TRANSLATE_NOOP("USB", "Forgive me, SDL. It's not you, it's me. (It's you). Has no effect on non-Windows platforms."), "false"} }; @@ -234,6 +237,11 @@ namespace usb_pad const bool use_ffb_dropout_workaround = USB::GetConfigBool(si, port, devname, "FfbDropoutWorkaround", false); mFFdev->use_ffb_dropout_workaround = use_ffb_dropout_workaround; } + if (mFFdev != NULL) + { + const bool bypass_sdl_when_updating = USB::GetConfigBool(si, port, devname, "FfbDirectInputHack", false); + mFFdev->bypass_sdl_when_updating = bypass_sdl_when_updating; + } } } diff --git a/pcsx2/USB/usb-pad/usb-pad.h b/pcsx2/USB/usb-pad/usb-pad.h index 4a8da1f21a..2c7a14b7b0 100644 --- a/pcsx2/USB/usb-pad/usb-pad.h +++ b/pcsx2/USB/usb-pad/usb-pad.h @@ -289,6 +289,7 @@ namespace usb_pad virtual void DisableForce(EffectID force) = 0; bool use_ffb_dropout_workaround = false; + bool bypass_sdl_when_updating = false; }; struct PadState