XInputSource: Add support for SCP API extension

Allows to query pressure sensitive DualShock 3 buttons via
XInput when DsHidMini is used in SXS mode, with its xinput1_3.dll dropped
in the emulator directory.
This commit is contained in:
Silent 2022-04-13 21:32:56 +02:00 committed by refractionpcsx2
parent 5e913b0e9e
commit 502a44e9f5
2 changed files with 134 additions and 16 deletions

View File

@ -97,10 +97,12 @@ XInputSource::~XInputSource() = default;
bool XInputSource::Initialize(SettingsInterface& si)
{
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
m_xinput_module = LoadLibraryW(L"xinput1_4");
// xinput1_3.dll is flawed and obsolete, but it's also commonly used by wrappers.
// For this reason, try to load it *only* from the application directory, and not system32.
m_xinput_module = LoadLibraryExW(L"xinput1_3", nullptr, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
if (!m_xinput_module)
{
m_xinput_module = LoadLibraryW(L"xinput1_3");
m_xinput_module = LoadLibraryW(L"xinput1_4");
}
if (!m_xinput_module)
{
@ -121,10 +123,15 @@ bool XInputSource::Initialize(SettingsInterface& si)
reinterpret_cast<decltype(m_xinput_set_state)>(GetProcAddress(m_xinput_module, "XInputSetState"));
m_xinput_get_capabilities =
reinterpret_cast<decltype(m_xinput_get_capabilities)>(GetProcAddress(m_xinput_module, "XInputGetCapabilities"));
// SCP extension, only exists when the bridge xinput1_3.dll is in use
m_xinput_get_extended =
reinterpret_cast<decltype(m_xinput_get_extended)>(GetProcAddress(m_xinput_module, "XInputGetExtended"));
#else
m_xinput_get_state = XInputGetState;
m_xinput_set_state = XInputSetState;
m_xinput_get_capabilities = XInputGetCapabilities;
m_xinput_get_extended = nullptr;
#endif
if (!m_xinput_get_state || !m_xinput_set_state || !m_xinput_get_capabilities)
{
@ -158,29 +165,44 @@ void XInputSource::Shutdown()
m_xinput_get_state = nullptr;
m_xinput_set_state = nullptr;
m_xinput_get_capabilities = nullptr;
m_xinput_get_extended = nullptr;
}
void XInputSource::PollEvents()
{
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
XINPUT_STATE new_state;
const DWORD result = m_xinput_get_state(i, &new_state);
const bool was_connected = m_controllers[i].connected;
SCP_EXTN new_state_scp;
DWORD result = m_xinput_get_extended ? m_xinput_get_extended(i, &new_state_scp) : ERROR_NOT_SUPPORTED;
if (result == ERROR_SUCCESS)
{
if (!was_connected)
HandleControllerConnection(i);
CheckForStateChanges(i, new_state);
CheckForStateChangesSCP(i, new_state_scp);
}
else
{
if (result != ERROR_DEVICE_NOT_CONNECTED)
Console.Warning("XInputGetState(%u) failed: 0x%08X / 0x%08X", i, result, GetLastError());
XINPUT_STATE new_state;
result = m_xinput_get_state(i, &new_state);
if (was_connected)
HandleControllerDisconnection(i);
if (result == ERROR_SUCCESS)
{
if (!was_connected)
HandleControllerConnection(i);
CheckForStateChanges(i, new_state);
}
else
{
if (result != ERROR_DEVICE_NOT_CONNECTED)
Console.Warning("XInputGetState(%u) failed: 0x%08X / 0x%08X", i, result, GetLastError());
if (was_connected)
HandleControllerDisconnection(i);
}
}
}
}
@ -362,6 +384,8 @@ void XInputSource::HandleControllerConnection(u32 index)
cd.connected = true;
cd.has_large_motor = caps.Vibration.wLeftMotorSpeed != 0;
cd.has_small_motor = caps.Vibration.wRightMotorSpeed != 0;
cd.last_state = {};
cd.last_state_scp = {};
Host::OnInputDeviceConnected(StringUtil::StdStringFromFormat("XInput-%u", index),
StringUtil::StdStringFromFormat("XInput Controller %u", index));
@ -381,8 +405,6 @@ void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state
if (new_state.dwPacketNumber == cd.last_state.dwPacketNumber)
return;
cd.last_state.dwPacketNumber = new_state.dwPacketNumber;
XINPUT_GAMEPAD& ogp = cd.last_state.Gamepad;
const XINPUT_GAMEPAD& ngp = new_state.Gamepad;
@ -392,7 +414,6 @@ void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state
InputManager::InvokeEvents( \
MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), \
static_cast<float>(ngp.field) / ((ngp.field < 0) ? min_value : max_value)); \
ogp.field = ngp.field; \
}
// Y axes is inverted in XInput when compared to SDL.
@ -418,10 +439,68 @@ void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state
MakeGenericControllerButtonKey(InputSourceType::XInput, index, button),
(new_button_bits & button_mask) != 0);
}
ogp.wButtons = ngp.wButtons;
}
}
cd.last_state = new_state;
}
void XInputSource::CheckForStateChangesSCP(u32 index, const SCP_EXTN& new_state)
{
ControllerData& cd = m_controllers[index];
SCP_EXTN& ogp = cd.last_state_scp;
const SCP_EXTN& ngp = new_state;
s32 axis = 0, button = 0;
#define CHECK_AXIS(field) \
if (ogp.field != ngp.field) \
{ \
InputManager::InvokeEvents( \
MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), ngp.field); \
} \
axis++;
#define CHECK_BUTTON(field) \
if (ogp.field != ngp.field) \
{ \
InputManager::InvokeEvents( \
MakeGenericControllerButtonKey(InputSourceType::XInput, index, button), ngp.field); \
} \
button++;
CHECK_AXIS(SCP_LX);
CHECK_AXIS(SCP_LY);
CHECK_AXIS(SCP_RX);
CHECK_AXIS(SCP_RY);
CHECK_AXIS(SCP_L2);
CHECK_AXIS(SCP_R2);
CHECK_BUTTON(SCP_UP);
CHECK_BUTTON(SCP_DOWN);
CHECK_BUTTON(SCP_LEFT);
CHECK_BUTTON(SCP_RIGHT);
CHECK_BUTTON(SCP_START);
CHECK_BUTTON(SCP_SELECT);
CHECK_BUTTON(SCP_L3);
CHECK_BUTTON(SCP_R3);
CHECK_BUTTON(SCP_L1);
CHECK_BUTTON(SCP_R1);
CHECK_BUTTON(SCP_X);
CHECK_BUTTON(SCP_C);
CHECK_BUTTON(SCP_S);
CHECK_BUTTON(SCP_T);
CHECK_BUTTON(SCP_PS);
#undef CHECK_BUTTON
#undef CHECK_AXIS
cd.last_state_scp = new_state;
}
void XInputSource::UpdateMotorState(InputBindingKey key, float intensity)

View File

@ -21,6 +21,39 @@
#include <mutex>
#include <vector>
// SCP XInput extension
typedef struct
{
float SCP_UP;
float SCP_RIGHT;
float SCP_DOWN;
float SCP_LEFT;
float SCP_LX;
float SCP_LY;
float SCP_L1;
float SCP_L2;
float SCP_L3;
float SCP_RX;
float SCP_RY;
float SCP_R1;
float SCP_R2;
float SCP_R3;
float SCP_T;
float SCP_C;
float SCP_X;
float SCP_S;
float SCP_SELECT;
float SCP_START;
float SCP_PS;
} SCP_EXTN;
class SettingsInterface;
class XInputSource final : public InputSource
@ -63,9 +96,13 @@ private:
struct ControllerData
{
XINPUT_STATE last_state = {};
union
{
XINPUT_STATE last_state;
SCP_EXTN last_state_scp;
};
XINPUT_VIBRATION last_vibration = {};
bool connected = false;
bool connected = false;
bool has_large_motor = false;
bool has_small_motor = false;
};
@ -73,6 +110,7 @@ private:
using ControllerDataArray = std::array<ControllerData, NUM_CONTROLLERS>;
void CheckForStateChanges(u32 index, const XINPUT_STATE& new_state);
void CheckForStateChangesSCP(u32 index, const SCP_EXTN& new_state);
void HandleControllerConnection(u32 index);
void HandleControllerDisconnection(u32 index);
@ -82,6 +120,7 @@ private:
DWORD(WINAPI* m_xinput_get_state)(DWORD, XINPUT_STATE*);
DWORD(WINAPI* m_xinput_set_state)(DWORD, XINPUT_VIBRATION*);
DWORD(WINAPI* m_xinput_get_capabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*);
DWORD(WINAPI* m_xinput_get_extended)(DWORD, SCP_EXTN*);
static const char* s_axis_names[NUM_AXES];
static const char* s_button_names[NUM_BUTTONS];