diff --git a/rpcs3/Json/pad_settings.json b/rpcs3/Json/pad_settings.json
index f222db6052..4b93cc96f0 100644
--- a/rpcs3/Json/pad_settings.json
+++ b/rpcs3/Json/pad_settings.json
@@ -8,7 +8,7 @@
"ds4_windows": "If you have any issues with the DualShock 4 handler, it might be caused by third-party tools such as DS4Windows. It's recommended that you disable them while using this handler.",
"ds4_linux": "In order to use the DualShock 4 handler, you might need to add udev rules to let RPCS3 access the controller.\nSee the RPCS3 Wiki for instructions.",
"ds4_other": "The DualShock 4 handler is recommended for official DualShock 4 controllers.",
- "xinput": "The XInput handler will work with Xbox controllers and many third-party PC-compatible controllers.",
+ "xinput": "The XInput handler will work with Xbox controllers and many third-party PC-compatible controllers. Pressure sensitive buttons from SCP are supported when SCP's XInput1_3.dll is placed in the main RPCS3 directory. For more details, see the RPCS3 Wiki.",
"evdev": "The evdev handler should work with any controller that has linux support.\nIf your joystick is not being centered properly, read the RPCS3 Wiki for instructions.",
"mmjoy": "The MMJoystick handler should work with almost any controller recognized by Windows. However, it is recommended that you use the more specific handlers if you have a controller that supports them."
}
diff --git a/rpcs3/xinput_pad_handler.cpp b/rpcs3/xinput_pad_handler.cpp
index a099d679ff..484dfb1ec5 100644
--- a/rpcs3/xinput_pad_handler.cpp
+++ b/rpcs3/xinput_pad_handler.cpp
@@ -2,6 +2,49 @@
#ifdef _WIN32
#include "xinput_pad_handler.h"
+namespace XINPUT_INFO
+{
+ const DWORD GUIDE_BUTTON = 0x0400;
+ const LPCWSTR LIBRARY_FILENAMES[] = {
+ L"xinput1_3.dll", // Prioritizing 1_3 because of SCP
+ L"xinput1_4.dll",
+ L"xinput9_1_0.dll"
+ };
+} // namespace XINPUT_INFO
+
+// ScpToolkit defined structure for pressure sensitive button query
+struct SCP_EXTN
+{
+ 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;
+};
+
xinput_pad_handler::xinput_pad_handler() : PadHandlerBase(pad_handler::xinput)
{
init_configs();
@@ -83,20 +126,17 @@ void xinput_pad_handler::GetNextButtonPress(const std::string& padId, const std:
if (device_number < 0)
return fail_callback(padId);
- DWORD dwResult;
- XINPUT_STATE state;
- ZeroMemory(&state, sizeof(XINPUT_STATE));
-
// Simply get the state of the controller from XInput.
- dwResult = (*xinputGetState)(static_cast(device_number), &state);
- if (dwResult != ERROR_SUCCESS)
+ const auto state = GetState(static_cast(device_number));
+ if (std::get(state) != ERROR_SUCCESS)
return fail_callback(padId);
// Check for each button in our list if its corresponding (maybe remapped) button or axis was pressed.
// Return the new value if the button was pressed (aka. its value was bigger than 0 or the defined threshold)
// Use a pair to get all the legally pressed buttons and use the one with highest value (prioritize first)
- std::pair pressed_button = { 0, "" };
- auto data = GetButtonValues(state);
+ ASSERT(std::get>(state).has_value());
+ std::pair pressed_button = {0, ""};
+ const PadButtonValues& data = *std::get>(state);
for (const auto& button : button_list)
{
u32 keycode = button.first;
@@ -204,7 +244,7 @@ int xinput_pad_handler::GetDeviceNumber(const std::string& padId)
return device_number;
}
-std::array xinput_pad_handler::GetButtonValues(const XINPUT_STATE& state)
+std::array xinput_pad_handler::GetButtonValues_Base(const XINPUT_STATE& state)
{
std::array values;
@@ -263,6 +303,91 @@ std::array xinput_pad_han
return values;
}
+std::array xinput_pad_handler::GetButtonValues_SCP(const SCP_EXTN& state)
+{
+ std::array values;
+
+ // Triggers
+ values[xinput_pad_handler::XInputKeyCodes::LT] = static_cast(state.SCP_L2 * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::RT] = static_cast(state.SCP_R2 * 255.0f);
+
+ // Sticks
+ float lx = state.SCP_LX;
+ float ly = state.SCP_LY;
+ float rx = state.SCP_RX;
+ float ry = state.SCP_RY;
+
+ // Left Stick X Axis
+ values[xinput_pad_handler::XInputKeyCodes::LSXNeg] = lx < 0.0f ? static_cast(lx * -32768.0f) : 0;
+ values[xinput_pad_handler::XInputKeyCodes::LSXPos] = lx > 0.0f ? static_cast(lx * 32767.0f) : 0;
+
+ // Left Stick Y Axis
+ values[xinput_pad_handler::XInputKeyCodes::LSYNeg] = ly < 0.0f ? static_cast(ly * -32768.0f) : 0;
+ values[xinput_pad_handler::XInputKeyCodes::LSYPos] = ly > 0.0f ? static_cast(ly * 32767.0f) : 0;
+
+ // Right Stick X Axis
+ values[xinput_pad_handler::XInputKeyCodes::RSXNeg] = rx < 0.0f ? static_cast(rx * -32768.0f) : 0;
+ values[xinput_pad_handler::XInputKeyCodes::RSXPos] = rx > 0.0f ? static_cast(rx * 32767.0f) : 0;
+
+ // Right Stick Y Axis
+ values[xinput_pad_handler::XInputKeyCodes::RSYNeg] = ry < 0.0f ? static_cast(ry * -32768.0f) : 0;
+ values[xinput_pad_handler::XInputKeyCodes::RSYPos] = ry > 0.0f ? static_cast(ry * 32767.0f) : 0;
+
+ // A, B, X, Y
+ values[xinput_pad_handler::XInputKeyCodes::A] = static_cast(state.SCP_X * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::B] = static_cast(state.SCP_C * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::X] = static_cast(state.SCP_S * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::Y] = static_cast(state.SCP_T * 255.0f);
+
+ // D-Pad
+ values[xinput_pad_handler::XInputKeyCodes::Left] = static_cast(state.SCP_LEFT * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::Right] = static_cast(state.SCP_RIGHT * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::Up] = static_cast(state.SCP_UP * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::Down] = static_cast(state.SCP_DOWN * 255.0f);
+
+ // LB, RB, LS, RS
+ values[xinput_pad_handler::XInputKeyCodes::LB] = static_cast(state.SCP_L1 * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::RB] = static_cast(state.SCP_R1 * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::LS] = static_cast(state.SCP_L3 * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::RS] = static_cast(state.SCP_R3 * 255.0f);
+
+ // Start, Back, Guide
+ values[xinput_pad_handler::XInputKeyCodes::Start] = static_cast(state.SCP_START * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::Back] = static_cast(state.SCP_SELECT * 255.0f);
+ values[xinput_pad_handler::XInputKeyCodes::Guide] = static_cast(state.SCP_PS * 255.0f);
+
+ return values;
+}
+
+auto xinput_pad_handler::GetState(u32 device_number) -> std::tuple>
+{
+ std::tuple> result;
+ std::get(result) = ERROR_NOT_CONNECTED;
+
+ // Try SCP first, if it fails for that pad then try normal XInput
+ if (xinputGetExtended)
+ {
+ SCP_EXTN stateExtn;
+ std::get(result) = xinputGetExtended(device_number, &stateExtn);
+ if (std::get(result) == ERROR_SUCCESS)
+ {
+ std::get>(result) = GetButtonValues_SCP(stateExtn);
+ }
+ }
+
+ if (std::get(result) != ERROR_SUCCESS)
+ {
+ XINPUT_STATE stateBase;
+ std::get(result) = xinputGetState(device_number, &stateBase);
+ if (std::get(result) == ERROR_SUCCESS)
+ {
+ std::get>(result) = GetButtonValues_Base(stateBase);
+ }
+ }
+
+ return result;
+}
+
bool xinput_pad_handler::Init()
{
if (is_init)
@@ -273,7 +398,7 @@ bool xinput_pad_handler::Init()
library = LoadLibrary(it);
if (library)
{
- xinputEnable = reinterpret_cast(GetProcAddress(library, "XInputEnable"));
+ xinputGetExtended = reinterpret_cast(GetProcAddress(library, "XInputGetExtended")); // Optional
xinputGetState = reinterpret_cast(GetProcAddress(library, reinterpret_cast(100)));
if (!xinputGetState)
xinputGetState = reinterpret_cast(GetProcAddress(library, "XInputGetState"));
@@ -281,7 +406,7 @@ bool xinput_pad_handler::Init()
xinputSetState = reinterpret_cast(GetProcAddress(library, "XInputSetState"));
xinputGetBatteryInformation = reinterpret_cast(GetProcAddress(library, "XInputGetBatteryInformation"));
- if (xinputEnable && xinputGetState && xinputSetState && xinputGetBatteryInformation)
+ if (xinputGetState && xinputSetState && xinputGetBatteryInformation)
{
is_init = true;
break;
@@ -289,8 +414,9 @@ bool xinput_pad_handler::Init()
FreeLibrary(library);
library = nullptr;
- xinputEnable = nullptr;
+ xinputGetExtended = nullptr;
xinputGetState = nullptr;
+ xinputSetState = nullptr;
xinputGetBatteryInformation = nullptr;
}
}
@@ -307,8 +433,9 @@ void xinput_pad_handler::Close()
{
FreeLibrary(library);
library = nullptr;
+ xinputGetExtended = nullptr;
xinputGetState = nullptr;
- xinputEnable = nullptr;
+ xinputSetState = nullptr;
xinputGetBatteryInformation = nullptr;
}
}
@@ -323,9 +450,9 @@ void xinput_pad_handler::ThreadProc()
auto profile = m_dev->config;
auto pad = bind.second;
- result = (*xinputGetState)(padnum, &state);
+ const auto state = GetState(padnum);
- switch (result)
+ switch (std::get(state))
{
case ERROR_DEVICE_NOT_CONNECTED:
{
@@ -350,7 +477,8 @@ void xinput_pad_handler::ThreadProc()
connected++;
}
- std::array button_values = GetButtonValues(state);
+ ASSERT(std::get>(state).has_value());
+ const PadButtonValues& button_values = *std::get>(state);
// Translate any corresponding keycodes to our normal DS3 buttons and triggers
for (auto& btn : pad->m_buttons)
diff --git a/rpcs3/xinput_pad_handler.h b/rpcs3/xinput_pad_handler.h
index ad94d9222b..3c3d334698 100644
--- a/rpcs3/xinput_pad_handler.h
+++ b/rpcs3/xinput_pad_handler.h
@@ -6,20 +6,9 @@
#include
#include
#include
+#include
-namespace XINPUT_INFO
-{
- const DWORD THREAD_TIMEOUT = 1000;
- const DWORD THREAD_SLEEP = 10;
- const DWORD THREAD_SLEEP_INACTIVE = 100;
- const DWORD GUIDE_BUTTON = 0x0400;
- const LPCWSTR LIBRARY_FILENAMES[] = {
- L"xinput1_4.dll",
- L"xinput1_3.dll",
- L"xinput1_2.dll",
- L"xinput9_1_0.dll"
- };
-}
+struct SCP_EXTN;
class xinput_pad_handler final : public PadHandlerBase
{
@@ -112,28 +101,28 @@ public:
void init_config(pad_config* cfg, const std::string& name) override;
private:
- typedef void (WINAPI * PFN_XINPUTENABLE)(BOOL);
+ typedef DWORD (WINAPI * PFN_XINPUTGETEXTENDED)(DWORD, SCP_EXTN *);
typedef DWORD (WINAPI * PFN_XINPUTGETSTATE)(DWORD, XINPUT_STATE *);
typedef DWORD (WINAPI * PFN_XINPUTSETSTATE)(DWORD, XINPUT_VIBRATION *);
typedef DWORD (WINAPI * PFN_XINPUTGETBATTERYINFORMATION)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *);
+ using PadButtonValues = std::array;
+
private:
int GetDeviceNumber(const std::string& padId);
- std::array GetButtonValues(const XINPUT_STATE& state);
+ std::tuple> GetState(u32 device_number);
+ PadButtonValues GetButtonValues_Base(const XINPUT_STATE& state);
+ PadButtonValues GetButtonValues_SCP(const SCP_EXTN& state);
void TranslateButtonPress(u64 keyCode, bool& pressed, u16& val, bool ignore_threshold = false) override;
bool is_init{ false };
HMODULE library{ nullptr };
+ PFN_XINPUTGETEXTENDED xinputGetExtended{ nullptr };
PFN_XINPUTGETSTATE xinputGetState{ nullptr };
PFN_XINPUTSETSTATE xinputSetState{ nullptr };
- PFN_XINPUTENABLE xinputEnable{ nullptr };
PFN_XINPUTGETBATTERYINFORMATION xinputGetBatteryInformation{ nullptr };
std::vector blacklist;
std::vector, std::shared_ptr>> bindings;
std::shared_ptr m_dev;
-
- // holds internal controller state change
- XINPUT_STATE state;
- DWORD result{ 0 };
};