USB: add direct-to-directinput hack for windows for testing

This commit is contained in:
badfontkeming 2024-11-06 08:33:01 -06:00
parent 2d5faa627f
commit 6d9b9d44a1
4 changed files with 100 additions and 7 deletions

View File

@ -60,17 +60,17 @@ namespace usb_pad
void SDLFFDevice::CreateEffects(const std::string_view device) void SDLFFDevice::CreateEffects(const std::string_view device)
{ {
// Most games appear to assume that requested forces will be applied indefinitely. // 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, // 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 // and both GT4 and the NFS games have been observed using only a single constant force
// command over long, consistent turns on smooth roads. // command over long, consistent turns on smooth roads.
// //
// An infinite force is necessary as the normal mechanism for looping FFB effects, // 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 // 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. // 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 // 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. // are preferred for the vast majority of wheels which do correctly handle them.
// //
// Known "Problem" wheels which don't implement effect iterations // Known "Problem" wheels which don't implement effect iterations
// - Moza series: DOES implement infinite durations // - Moza series: DOES implement infinite durations
// - Accuforce v2: DOES implement infinite durations (deduced from anecdote, not confirmed manually) // - 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) if (m_constant_effect_id < 0)
return; return;
const s16 new_level = static_cast<s16>(std::clamp(level, -32768, 32767)); s16 new_level = static_cast<s16>(std::clamp(level, -32768, 32767));
if (m_constant_effect.constant.level != new_level) 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; m_constant_effect.constant.level = new_level;
if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0) if (SDL_HapticUpdateEffect(m_haptic, m_constant_effect_id, &m_constant_effect) != 0)
Console.Warning("SDL_HapticUpdateEffect() for constant failed: %s", SDL_GetError()); Console.Warning("SDL_HapticUpdateEffect() for constant failed: %s", SDL_GetError());
#endif
} }
// Avoid re-running already-running effects by default. Re-running a running effect // 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, // 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. // to abrupt judders of the wheel's FFB rapidly cutting out and back in.
// //
// Known problem wheels: // Known problem wheels:
// Most common (Moza, Simagic, likely others): Loss of definition or quality // Most common (Moza, Simagic, likely others): Loss of definition or quality
// Accuforce v2: Split-second FFB drop with each update // Accuforce v2: Split-second FFB drop with each update

View File

@ -5,6 +5,51 @@
#include "USB/usb-pad/usb-pad.h" #include "USB/usb-pad/usb-pad.h"
#include "Input/SDLInputSource.h" #include "Input/SDLInputSource.h"
#ifdef __WIN32__
#include <sdl_haptic.h>
#include <dinput.h>
#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 namespace usb_pad
{ {

View File

@ -166,6 +166,9 @@ namespace usb_pad
"Off", nullptr, nullptr, nullptr, nullptr, SteeringCurveExponentOptions}, "Off", nullptr, nullptr, nullptr, nullptr, SteeringCurveExponentOptions},
{SettingInfo::Type::Boolean, "FfbDropoutWorkaround", TRANSLATE_NOOP("USB", "Workaround for Intermittent FFB Loss"), {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."), 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"} "false"}
}; };
@ -234,6 +237,11 @@ namespace usb_pad
const bool use_ffb_dropout_workaround = USB::GetConfigBool(si, port, devname, "FfbDropoutWorkaround", false); const bool use_ffb_dropout_workaround = USB::GetConfigBool(si, port, devname, "FfbDropoutWorkaround", false);
mFFdev->use_ffb_dropout_workaround = use_ffb_dropout_workaround; 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;
}
} }
} }

View File

@ -289,6 +289,7 @@ namespace usb_pad
virtual void DisableForce(EffectID force) = 0; virtual void DisableForce(EffectID force) = 0;
bool use_ffb_dropout_workaround = false; bool use_ffb_dropout_workaround = false;
bool bypass_sdl_when_updating = false;
}; };
struct PadState struct PadState