diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp
index 7ab12e94db33b..2a7192c38057b 100644
--- a/pcsx2-gsrunner/Main.cpp
+++ b/pcsx2-gsrunner/Main.cpp
@@ -96,6 +96,7 @@ bool GSRunner::InitializeConfig()
// ensure all input sources are disabled, we're not using them
si.SetBoolValue("InputSources", "SDL", false);
si.SetBoolValue("InputSources", "XInput", false);
+ si.SetBoolValue("InputSources", "DS3Input", false);
// we don't need any sound output
si.SetStringValue("SPU2/Output", "OutputModule", "nullout");
diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp
index a991aa2183795..56b30f073ad7c 100644
--- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp
@@ -62,6 +62,7 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
#ifdef _WIN32
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDInputSource, "InputSources", "DInput", false);
+ SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDS3Source, "InputSources", "DS3Input", false);
#else
m_ui.mainLayout->removeWidget(m_ui.xinputGroup);
m_ui.xinputGroup->deleteLater();
@@ -69,6 +70,9 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
m_ui.mainLayout->removeWidget(m_ui.dinputGroup);
m_ui.dinputGroup->deleteLater();
m_ui.dinputGroup = nullptr;
+ m_ui.mainLayout->removeWidget(m_ui.ds3inputGroup);
+ m_ui.ds3inputGroup->deleteLater();
+ m_ui.ds3inputGroup = nullptr;
#endif
if (dialog->isEditingProfile())
diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui
index 085760544b329..d3ae42670593f 100644
--- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui
+++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui
@@ -26,7 +26,7 @@
0
- -
+
-
Profile Settings
@@ -146,7 +146,7 @@
- -
+
-
Controller Multitap
@@ -231,6 +231,32 @@
-
+
+
+ DualShock 3 Input Source
+
+
+
-
+
+
+ The DualShock 3 input source provides native support for DualShock 3 controllers. (Requires Official Sony DualShock 3 Driver)
+
+
+ true
+
+
+
+ -
+
+
+ Enable DualShock 3 Input Source
+
+
+
+
+
+
+ -
Mouse/Pointer Source
diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index 18c6b4cec0902..db39a3a9ea4bf 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -3814,6 +3814,8 @@ void FullscreenUI::DrawControllerSettingsPage()
#ifdef _WIN32
DrawToggleSetting(bsi, ICON_FA_COG " Enable XInput Input Source",
"The XInput source provides support for XBox 360/XBox One/XBox Series controllers.", "InputSources", "XInput", false, true, false);
+ DrawToggleSetting(bsi, ICON_FA_COG " Enable DualShock 3 Input Source",
+ "The DualShock 3 input source provides native support for DualShock 3 controllers. (Requires Official Sony DualShock 3 Driver)", "InputSources", "DS3Input", false, true, false);
#endif
MenuHeading("Multitap");
diff --git a/pcsx2/Input/DualShock3InputSource.cpp b/pcsx2/Input/DualShock3InputSource.cpp
new file mode 100644
index 0000000000000..c1408d0c49b2f
--- /dev/null
+++ b/pcsx2/Input/DualShock3InputSource.cpp
@@ -0,0 +1,452 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "Input/DualShock3InputSource.h"
+#include "Input/InputManager.h"
+
+#include "Input/WindowsHIDUtility.h"
+#include "common/Assertions.h"
+#include "common/Console.h"
+#include "common/StringUtil.h"
+
+DualShock3InputSource::DualShock3InputSource() noexcept = default;
+
+DualShock3InputSource::~DualShock3InputSource() = default;
+
+bool DualShock3InputSource::Initialize(SettingsInterface& si, std::unique_lock& settings_lock)
+{
+ ReloadDevices();
+ return true;
+}
+
+void DualShock3InputSource::UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock)
+{
+}
+
+bool DualShock3InputSource::ReloadDevices()
+{
+ bool changed = false;
+
+ const auto foundDS3Hids = WindowsHIDUtility::FindHids(DS3_VID, DS3_PID);
+
+ if (std::empty(foundDS3Hids))
+ {
+ return changed;
+ }
+
+ for (std::size_t i = 0; i < std::size(foundDS3Hids); ++i)
+ {
+ //extra check to see if it really is a DualShock 3 controller
+ if (foundDS3Hids[i].caps.FeatureReportByteLength == 50 &&
+ foundDS3Hids[i].caps.OutputReportByteLength == 49)
+ {
+ //check we don't already have the DS3 controller using unique DevicePath
+ if (!std::empty(m_controllers))
+ {
+ if (std::any_of(m_controllers.begin(), m_controllers.end(),
+ [&foundDS3HidPath = std::as_const(foundDS3Hids[i].device_path)](const DS3ControllerData& cd) { return cd.device_path == foundDS3HidPath; }))
+ {
+ continue;
+ }
+ }
+
+ DS3ControllerData ds3 = {GetFreePlayerId(), foundDS3Hids[i].device_path};
+
+ const bool ds3_activated = ds3.Activate();
+
+ if (ds3_activated)
+ {
+ InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("DS3-%d", ds3.player_id),
+ StringUtil::StdStringFromFormat("DualShock 3 Controller %d", ds3.player_id));
+
+ m_controllers.push_back(ds3);
+
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+}
+
+void DualShock3InputSource::Shutdown()
+{
+ while (!std::empty(m_controllers))
+ {
+ m_controllers.back().Deactivate();
+ InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("DS3-%d", m_controllers.back().player_id));
+ m_controllers.pop_back();
+ }
+}
+
+void DualShock3InputSource::PollEvents()
+{
+ size_t controllerindex = 0;
+ while (controllerindex < m_controllers.size())
+ {
+ if (!m_controllers[controllerindex].active)
+ {
+ ++controllerindex;
+ continue;
+ }
+
+ u8 Buffer[50] = {};
+
+ //Buffer[0] = 0; //reportId
+
+ if (!HidD_GetFeature(m_controllers[controllerindex].hFile, Buffer, sizeof(Buffer)))
+ {
+ //looks like DS3 controller has been disconnected - deactivate and erase from m_controllers
+ m_controllers[controllerindex].Deactivate();
+ InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("DS3-%d", m_controllers[controllerindex].player_id));
+ m_controllers.erase(m_controllers.begin() + controllerindex);
+ continue;
+ }
+
+ const u8* getState = &Buffer[1];
+
+ //Buttons
+ m_controllers[controllerindex].physicalButtonState[0] = getState[2] & 0x01; //SELECT
+ m_controllers[controllerindex].physicalButtonState[1] = getState[2] & 0x02; //L3
+ m_controllers[controllerindex].physicalButtonState[2] = getState[2] & 0x04; //R3
+ m_controllers[controllerindex].physicalButtonState[3] = getState[2] & 0x08; //START
+ m_controllers[controllerindex].physicalButtonState[4] = getState[2] & 0x10; //Dpad Up
+ m_controllers[controllerindex].physicalButtonState[5] = getState[2] & 0x20; //Dpad Right
+ m_controllers[controllerindex].physicalButtonState[6] = getState[2] & 0x40; //Dpad Down
+ m_controllers[controllerindex].physicalButtonState[7] = getState[2] & 0x80; //Dpad Left
+ m_controllers[controllerindex].physicalButtonState[8] = getState[3] & 0x01; //L2
+ m_controllers[controllerindex].physicalButtonState[9] = getState[3] & 0x02; //R2
+ m_controllers[controllerindex].physicalButtonState[10] = getState[3] & 0x04; //L1
+ m_controllers[controllerindex].physicalButtonState[11] = getState[3] & 0x08; //R1
+ m_controllers[controllerindex].physicalButtonState[12] = getState[3] & 0x10; //Triangle
+ m_controllers[controllerindex].physicalButtonState[13] = getState[3] & 0x20; //Circle
+ m_controllers[controllerindex].physicalButtonState[14] = getState[3] & 0x40; //Cross
+ m_controllers[controllerindex].physicalButtonState[15] = getState[3] & 0x80; //Square
+ m_controllers[controllerindex].physicalButtonState[16] = getState[4] & 0x01; //PS Button
+
+ //If we ever want to support pressure sensitive buttons
+ //m_controllers[controllerindex].physicalButtonPressureState[0] = getState[14]; //Dpad Up
+ //m_controllers[controllerindex].physicalButtonPressureState[1] = getState[15]; //Dpad Right
+ //m_controllers[controllerindex].physicalButtonPressureState[2] = getState[16]; //Dpad Down
+ //m_controllers[controllerindex].physicalButtonPressureState[3] = getState[17]; //Dpad Left
+ //m_controllers[controllerindex].physicalButtonPressureState[4] = getState[18]; //L2
+ //m_controllers[controllerindex].physicalButtonPressureState[5] = getState[19]; //R2
+ //m_controllers[controllerindex].physicalButtonPressureState[6] = getState[20]; //L1
+ //m_controllers[controllerindex].physicalButtonPressureState[7] = getState[21]; //R1
+ //m_controllers[controllerindex].physicalButtonPressureState[8] = getState[22]; //Triangle
+ //m_controllers[controllerindex].physicalButtonPressureState[9] = getState[23]; //Circle
+ //m_controllers[controllerindex].physicalButtonPressureState[10] = getState[24]; //Cross
+ //m_controllers[controllerindex].physicalButtonPressureState[11] = getState[25]; //Square
+
+ //Axis
+ m_controllers[controllerindex].physicalAxisState[0] = getState[6]; //Left Stick X
+ m_controllers[controllerindex].physicalAxisState[1] = getState[7]; //Left Stick Y
+ m_controllers[controllerindex].physicalAxisState[2] = getState[8]; //Right Stick X
+ m_controllers[controllerindex].physicalAxisState[3] = getState[9]; //Right Stick Y
+
+ //Buttons
+ for (size_t btnindex = 0; btnindex < std::size(m_controllers[controllerindex].physicalButtonState); ++btnindex)
+ {
+ if (m_controllers[controllerindex].lastPhysicalButtonState[btnindex] != m_controllers[controllerindex].physicalButtonState[btnindex])
+ {
+ m_controllers[controllerindex].lastPhysicalButtonState[btnindex] = m_controllers[controllerindex].physicalButtonState[btnindex];
+
+ const float button_value = m_controllers[controllerindex].physicalButtonState[btnindex];
+
+ InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DS3Input, static_cast(controllerindex), btnindex), button_value, GenericInputBinding::Unknown);
+ }
+ }
+
+ //Axis
+ for (size_t axisindex = 0; axisindex < std::size(m_controllers[controllerindex].physicalAxisState); ++axisindex)
+ {
+ if (m_controllers[controllerindex].lastPhysicalAxisState[axisindex] != m_controllers[controllerindex].physicalAxisState[axisindex])
+ {
+ m_controllers[controllerindex].lastPhysicalAxisState[axisindex] = m_controllers[controllerindex].physicalAxisState[axisindex];
+
+ const float axis_value = ConvertDS3Axis(m_controllers[controllerindex].physicalAxisState[axisindex]);
+
+ InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::DS3Input, static_cast(controllerindex), axisindex), axis_value, GenericInputBinding::Unknown);
+ }
+
+ }
+
+ ++controllerindex;
+ }
+}
+
+std::vector> DualShock3InputSource::EnumerateDevices()
+{
+ std::vector> ret;
+
+ for (const auto& controller : m_controllers)
+ {
+ if (!controller.active)
+ continue;
+
+ ret.emplace_back(StringUtil::StdStringFromFormat("DS3-%d", controller.player_id),
+ StringUtil::StdStringFromFormat("DualShock 3 Controller %d", controller.player_id));
+ }
+
+ return ret;
+}
+
+std::vector DualShock3InputSource::EnumerateMotors()
+{
+ std::vector ret;
+
+ for (const auto& controller : m_controllers)
+ {
+ if (!controller.active)
+ continue;
+
+ ret.emplace_back(MakeGenericControllerMotorKey(InputSourceType::DS3Input, controller.player_id, 0)); //large motor
+ ret.emplace_back(MakeGenericControllerMotorKey(InputSourceType::DS3Input, controller.player_id, 1)); //small motor
+ }
+
+ return ret;
+}
+
+bool DualShock3InputSource::GetGenericBindingMapping(const std::string_view& device, InputManager::GenericInputBindingMapping* mapping)
+{
+
+ if (!StringUtil::StartsWith(device, "DS3-"))
+ return false;
+
+ const std::optional player_id = StringUtil::FromChars(device.substr(4));
+ if (!player_id.has_value() || player_id.value() < 0)
+ return false;
+
+ const s32 pid = player_id.value();
+
+ for (u32 i = 0; i < std::size(DualShock3GenericAxisMapping); ++i)
+ {
+ const GenericInputBinding negative = DualShock3GenericAxisMapping[i][0];
+ const GenericInputBinding positive = DualShock3GenericAxisMapping[i][1];
+ if (negative != GenericInputBinding::Unknown)
+ mapping->emplace_back(negative, StringUtil::StdStringFromFormat("DS3-%d/-%s", pid, DualShock3AxisNames[i]));
+
+ if (positive != GenericInputBinding::Unknown)
+ mapping->emplace_back(positive, StringUtil::StdStringFromFormat("DS3-%d/+%s", pid, DualShock3AxisNames[i]));
+ }
+ for (u32 i = 0; i < std::size(DualShock3GenericButtonMapping); ++i)
+ {
+ const GenericInputBinding binding = DualShock3GenericButtonMapping[i];
+ if (binding != GenericInputBinding::Unknown)
+ mapping->emplace_back(binding, StringUtil::StdStringFromFormat("DS3-%d/%s", pid, DualShock3ButtonNames[i]));
+ }
+
+ mapping->emplace_back(GenericInputBinding::SmallMotor, StringUtil::StdStringFromFormat("DS3-%d/SmallMotor", pid));
+ mapping->emplace_back(GenericInputBinding::LargeMotor, StringUtil::StdStringFromFormat("DS3-%d/LargeMotor", pid));
+
+ return true;
+}
+
+void DualShock3InputSource::UpdateMotorState(InputBindingKey key, float intensity)
+{
+}
+
+void DualShock3InputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity)
+{
+ if (large_key.source_index != small_key.source_index || large_key.source_subtype != InputSubclass::ControllerMotor ||
+ small_key.source_subtype != InputSubclass::ControllerMotor)
+ {
+ return;
+ }
+
+ const auto ctrlr_iter = GetDS3ControllerDataForPlayerId(large_key.source_index);
+ if (ctrlr_iter == m_controllers.end() || !ctrlr_iter->active)
+ {
+ return;
+ }
+
+ ctrlr_iter->SmallMotorOn = static_cast(small_intensity);
+ ctrlr_iter->LargeMotorForce = static_cast(std::round(large_intensity * 255.0f)); //scale intensity to range [0, 255]
+
+ u8 outputReport[49] = {};
+
+ //outputReport[0] = 0; //reportId
+
+ //This is command for sixaxis driver to set motors
+ outputReport[1] = SIXASIS_DRIVER_COMMANDS::SIXAXIS_COMMAND_SET_MOTORS;
+
+ outputReport[5] = 0xFF; //small motor duration - 0xFF is forever
+ outputReport[6] = ctrlr_iter->SmallMotorOn;
+
+ outputReport[7] = 0xFF; //large motor duration - 0xFF is forever
+ outputReport[8] = ctrlr_iter->LargeMotorForce;
+
+ DWORD lpNumberOfBytesWritten = 0;
+ const BOOL wfRes = WriteFile(ctrlr_iter->hFile, outputReport, sizeof(outputReport), &lpNumberOfBytesWritten, NULL);
+ if (!wfRes)
+ {
+ const DWORD wfError = GetLastError();
+ DevCon.WriteLn("DS3: WriteFile failed with error code: %lu", wfError);
+ }
+}
+
+std::optional DualShock3InputSource::ParseKeyString(const std::string_view& device, const std::string_view& binding)
+{
+ if (!StringUtil::StartsWith(device, "DS3-") || binding.empty())
+ return std::nullopt;
+
+ const std::optional player_id = StringUtil::FromChars(device.substr(4));
+ if (!player_id.has_value() || player_id.value() < 0)
+ return std::nullopt;
+
+ InputBindingKey key = {};
+ key.source_type = InputSourceType::DS3Input;
+ key.source_index = static_cast(player_id.value());
+
+ if (StringUtil::EndsWith(binding, "Motor"))
+ {
+ key.source_subtype = InputSubclass::ControllerMotor;
+ if (binding == "LargeMotor")
+ {
+ key.data = 0;
+ return key;
+ }
+ else if (binding == "SmallMotor")
+ {
+ key.data = 1;
+ return key;
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }
+ else if (binding[0] == '+' || binding[0] == '-') //Axes
+ {
+ const std::string_view axis_name(binding.substr(1));
+
+ for (std::size_t i = 0; i < std::size(DualShock3AxisNames); ++i)
+ {
+ if (axis_name == DualShock3AxisNames[i])
+ {
+ // found an axis!
+ key.source_subtype = InputSubclass::ControllerAxis;
+ key.data = i;
+ key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
+ return key;
+ }
+ }
+ }
+ else //Buttons
+ {
+ for (std::size_t i = 0; i < std::size(DualShock3ButtonNames); ++i)
+ {
+ if (binding == DualShock3ButtonNames[i])
+ {
+ key.source_subtype = InputSubclass::ControllerButton;
+ key.data = i;
+ return key;
+ }
+ }
+ }
+
+ // unknown axis/button - should not reach here
+ return std::nullopt;
+}
+
+std::string DualShock3InputSource::ConvertKeyToString(InputBindingKey key)
+{
+ std::string ret;
+
+ if (key.source_type == InputSourceType::DS3Input)
+ {
+ if (key.source_subtype == InputSubclass::ControllerAxis)
+ {
+ const char modifier = key.modifier == InputModifier::Negate ? '-' : '+';
+
+ if (key.data < std::size(DualShock3AxisNames))
+ ret = StringUtil::StdStringFromFormat("DS3-%u/%c%s", key.source_index, modifier, DualShock3AxisNames[key.data]);
+ else
+ ret = StringUtil::StdStringFromFormat("DS3-%u/%cAxis%u%s", key.source_index, modifier, key.data, key.invert ? "~" : ""); // shouldn't be called
+ }
+ else if (key.source_subtype == InputSubclass::ControllerButton)
+ {
+ if (key.data < std::size(DualShock3ButtonNames))
+ ret = StringUtil::StdStringFromFormat("DS3-%u/%s", key.source_index, DualShock3ButtonNames[key.data]);
+ else
+ ret = StringUtil::StdStringFromFormat("DS3-%u/Button%u", key.source_index, key.data); // shouldn't be called
+ }
+ else if (key.source_subtype == InputSubclass::ControllerMotor)
+ {
+ ret = StringUtil::StdStringFromFormat("DS3-%u/%sMotor", key.source_index, key.data ? "Large" : "Small");
+ }
+ }
+
+ return ret;
+}
+
+//convert raw DS3 axis value in range [0, 255] to PCSX2 range [-1.0, 1.0]
+float DualShock3InputSource::ConvertDS3Axis(u8 axis_val) const
+{
+ const float adjusted_axis_val = axis_val - 128.0f;
+ return adjusted_axis_val / (adjusted_axis_val < 0.0f ? 128.0f : 127.0f);
+}
+
+DualShock3InputSource::DS3ControllerDataVector::iterator DualShock3InputSource::GetDS3ControllerDataForPlayerId(int id)
+{
+ return std::find_if(m_controllers.begin(), m_controllers.end(), [id](const DS3ControllerData& cd) { return cd.player_id == id; });
+}
+
+int DualShock3InputSource::GetFreePlayerId() const
+{
+ for (int player_id = 0;; ++player_id)
+ {
+ size_t i = 0;
+ for (; i < m_controllers.size(); ++i)
+ {
+ if (m_controllers[i].player_id == player_id)
+ break;
+ }
+ if (i == m_controllers.size())
+ return player_id;
+ }
+
+ return 0;
+}
+
+bool DualShock3InputSource::DS3ControllerData::Activate()
+{
+ hFile = CreateFileW(device_path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ const DWORD cfError = GetLastError();
+ DevCon.WriteLn("DS3: CreateFileW failed with error code: %lu", cfError);
+ return false;
+ }
+ active = true;
+ return true;
+}
+
+void DualShock3InputSource::DS3ControllerData::Deactivate()
+{
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ CancelIo(hFile);
+ CloseHandle(hFile);
+ hFile = INVALID_HANDLE_VALUE;
+ }
+
+ SmallMotorOn = 0;
+ LargeMotorForce = 0;
+
+ active = false;
+}
diff --git a/pcsx2/Input/DualShock3InputSource.h b/pcsx2/Input/DualShock3InputSource.h
new file mode 100644
index 0000000000000..c70bb072cca03
--- /dev/null
+++ b/pcsx2/Input/DualShock3InputSource.h
@@ -0,0 +1,168 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "Input/InputSource.h"
+
+#include "common/RedtapeWindows.h"
+
+#include
+#include
+#include
+
+class DualShock3InputSource final : public InputSource
+{
+public:
+ DualShock3InputSource() noexcept;
+ ~DualShock3InputSource() override;
+
+ bool Initialize(SettingsInterface& si, std::unique_lock& settings_lock) override;
+ void UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) override;
+ bool ReloadDevices() override;
+ void Shutdown() override;
+
+ void PollEvents() override;
+ std::vector> EnumerateDevices() override;
+ std::vector EnumerateMotors() override;
+ bool GetGenericBindingMapping(const std::string_view& device, InputManager::GenericInputBindingMapping* mapping) override;
+ void UpdateMotorState(InputBindingKey key, float intensity) override;
+ void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override;
+
+ std::optional ParseKeyString(const std::string_view& device, const std::string_view& binding) override;
+ std::string ConvertKeyToString(InputBindingKey key) override;
+
+private:
+
+ //Sixaxis driver commands.
+ //All commands must be sent via WriteFile with 49-byte buffer containing output report.
+ //Byte 0 indicates reportId and must always be 0.
+ //Byte 1 indicates some command, supported values are specified below.
+ enum SIXASIS_DRIVER_COMMANDS : u8
+ {
+ //This command allows to set user LEDs.
+ //Bytes 5,6.7.8 contain mode for corresponding LED: 0 value means LED is OFF, 1 means LEDs in ON and 2 means LEDs is flashing.
+ //Bytes 9-16 specify 64-bit LED flash period in 100 ns units if some LED is flashing, otherwise not used.
+ SIXAXIS_COMMAND_SET_LEDS = 1,
+
+ //This command allows to set left and right motors.
+ //Byte 5 is right motor duration (0-255) and byte 6, if not zero, activates right motor. Zero value disables right motor.
+ //Byte 7 is left motor duration (0-255) and byte 8 is left motor amplitude (0-255).
+ SIXAXIS_COMMAND_SET_MOTORS = 2,
+
+ //This command allows to block/unblock setting device LEDs by applications.
+ //Byte 5 is used as parameter - any non-zero value blocks LEDs, zero value will unblock LEDs.
+ SIXAXIS_COMMAND_BLOCK_LEDS = 3,
+
+ //This command refreshes driver settings. No parameters used.
+ //When sixaxis driver loads it reads 'CurrentDriverSetting' binary value from 'HKLM\System\CurrentControlSet\Services\sixaxis\Parameters' registry key.
+ //If the key is not present then default values are used. Sending this command forces sixaxis driver to re-read the registry and update driver settings.
+ SIXAXIS_COMMAND_REFRESH_DRIVER_SETTING = 9,
+
+ //This command clears current bluetooth pairing. No parameters used.
+ SIXAXIS_COMMAND_CLEAR_PAIRING = 10
+ };
+
+ struct DS3ControllerData
+ {
+ int player_id;
+ std::wstring device_path;
+
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+
+ bool active = false;
+
+ u8 SmallMotorOn = 0; // 0 or 1 (off/on)
+ u8 LargeMotorForce = 0; // range [0, 255]
+
+ std::array physicalButtonState = {};
+ std::array lastPhysicalButtonState = {};
+
+ //If we wever want to support pressure sensitive buttons
+ //std::array physicalButtonPressureState = {};
+ //std::array lastPhysicalButtonPressureState = {};
+
+ std::array physicalAxisState = {};
+ std::array lastPhysicalAxisState = {};
+
+ public:
+ bool Activate();
+ void Deactivate();
+ };
+
+ using DS3ControllerDataVector = std::vector;
+ DS3ControllerDataVector m_controllers;
+
+ float ConvertDS3Axis(u8 axis_val) const;
+
+ DS3ControllerDataVector::iterator GetDS3ControllerDataForPlayerId(int id);
+ int GetFreePlayerId() const; //borrowed from SDLInputSource
+
+ static constexpr u16 DS3_VID = 0x054c; // Sony Corp.
+ static constexpr u16 DS3_PID = 0x0268; // PlayStation 3 Controller
+
+ static constexpr const char* DualShock3AxisNames[] = {
+ "LeftX",
+ "LeftY",
+ "RightX",
+ "RightY"
+ };
+
+ static constexpr GenericInputBinding DualShock3GenericAxisMapping[][2] = {
+ {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight},
+ {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown},
+ {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight},
+ {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}
+ };
+
+ static constexpr const char* DualShock3ButtonNames[] = {
+ "Select",
+ "L3",
+ "R3",
+ "Start",
+ "Up",
+ "Right",
+ "Down",
+ "Left",
+ "L2",
+ "R2",
+ "L1",
+ "R1",
+ "Triangle",
+ "Circle",
+ "Cross",
+ "Square",
+ "PS Button"
+ };
+
+ static constexpr GenericInputBinding DualShock3GenericButtonMapping[] = {
+ GenericInputBinding::Select,
+ GenericInputBinding::L3,
+ GenericInputBinding::R3,
+ GenericInputBinding::Start,
+ GenericInputBinding::DPadUp,
+ GenericInputBinding::DPadRight,
+ GenericInputBinding::DPadDown,
+ GenericInputBinding::DPadLeft,
+ GenericInputBinding::L2,
+ GenericInputBinding::R2,
+ GenericInputBinding::L1,
+ GenericInputBinding::R1,
+ GenericInputBinding::Triangle,
+ GenericInputBinding::Circle,
+ GenericInputBinding::Cross,
+ GenericInputBinding::Square,
+ GenericInputBinding::System
+ };
+};
diff --git a/pcsx2/Input/InputManager.cpp b/pcsx2/Input/InputManager.cpp
index a77729b962902..1e2b2336c0cd6 100644
--- a/pcsx2/Input/InputManager.cpp
+++ b/pcsx2/Input/InputManager.cpp
@@ -445,6 +445,7 @@ static std::array(InputSourceType::Count)> s_input
#ifdef _WIN32
"DInput",
"XInput",
+ "DS3Input",
#endif
#ifdef SDL_BUILD
"SDL",
@@ -472,6 +473,8 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
#ifdef _WIN32
case InputSourceType::DInput:
return false;
+ case InputSourceType::DS3Input:
+ return false;
case InputSourceType::XInput:
// Disable xinput by default if we have SDL.
@@ -1506,6 +1509,7 @@ void InputManager::UpdateInputSourceState(SettingsInterface& si, std::unique_loc
#ifdef _WIN32
#include "Input/DInputSource.h"
#include "Input/XInputSource.h"
+#include "Input/DualShock3InputSource.h"
#endif
#ifdef SDL_BUILD
@@ -1517,6 +1521,7 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock(si, settings_lock, InputSourceType::DInput);
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput);
+ UpdateInputSourceState(si, settings_lock, InputSourceType::DS3Input);
#endif
#ifdef SDL_BUILD
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL);
diff --git a/pcsx2/Input/InputManager.h b/pcsx2/Input/InputManager.h
index 894af7e650c26..b28f0bec72fac 100644
--- a/pcsx2/Input/InputManager.h
+++ b/pcsx2/Input/InputManager.h
@@ -36,6 +36,7 @@ enum class InputSourceType : u32
#ifdef _WIN32
DInput,
XInput,
+ DS3Input,
#endif
#ifdef SDL_BUILD
SDL,
diff --git a/pcsx2/Input/WindowsHIDUtility.cpp b/pcsx2/Input/WindowsHIDUtility.cpp
new file mode 100644
index 0000000000000..40623e1ca7094
--- /dev/null
+++ b/pcsx2/Input/WindowsHIDUtility.cpp
@@ -0,0 +1,82 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "Input/WindowsHIDUtility.h"
+
+namespace WindowsHIDUtility
+{
+ std::vector FindHids(u16 vid, u16 pid)
+ {
+ std::vector foundDevs;
+
+ GUID GUID_DEVINTERFACE_HID;
+ HidD_GetHidGuid(&GUID_DEVINTERFACE_HID);
+
+ HDEVINFO hdev = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_HID, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (hdev != INVALID_HANDLE_VALUE)
+ {
+ SP_DEVICE_INTERFACE_DATA devInterfaceData;
+ devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+ for (int i = 0; SetupDiEnumDeviceInterfaces(hdev, 0, &GUID_DEVINTERFACE_HID, i, &devInterfaceData); ++i)
+ {
+ DWORD size = 0;
+ SetupDiGetDeviceInterfaceDetailW(hdev, &devInterfaceData, 0, 0, &size, 0);
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || !size)
+ continue;
+
+ SP_DEVICE_INTERFACE_DETAIL_DATA_W* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)malloc(size);
+
+ if (!devInterfaceDetails)
+ continue;
+
+ devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
+
+ SP_DEVINFO_DATA devInfoData;
+ devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
+
+ if (!SetupDiGetDeviceInterfaceDetailW(hdev, &devInterfaceData, devInterfaceDetails, size, &size, &devInfoData))
+ continue;
+
+ HANDLE hfile = CreateFileW(devInterfaceDetails->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
+ if (hfile != INVALID_HANDLE_VALUE)
+ {
+ HIDD_ATTRIBUTES attributes;
+ attributes.Size = sizeof(attributes);
+ if (HidD_GetAttributes(hfile, &attributes))
+ {
+ if (attributes.VendorID == vid && attributes.ProductID == pid)
+ {
+ PHIDP_PREPARSED_DATA pData;
+ HIDP_CAPS caps;
+ if (HidD_GetPreparsedData(hfile, &pData))
+ {
+ if (HidP_GetCaps(pData, &caps) == HIDP_STATUS_SUCCESS)
+ {
+ foundDevs.push_back({caps, attributes.VendorID, attributes.ProductID, devInterfaceDetails->DevicePath});
+ }
+ HidD_FreePreparsedData(pData);
+ }
+ }
+ }
+ CloseHandle(hfile);
+ }
+ free(devInterfaceDetails);
+ }
+ SetupDiDestroyDeviceInfoList(hdev);
+ }
+ return foundDevs;
+ }
+
+} // namespace WindowsHIDUtility
\ No newline at end of file
diff --git a/pcsx2/Input/WindowsHIDUtility.h b/pcsx2/Input/WindowsHIDUtility.h
new file mode 100644
index 0000000000000..79054855a27c1
--- /dev/null
+++ b/pcsx2/Input/WindowsHIDUtility.h
@@ -0,0 +1,38 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include "common/RedtapeWindows.h"
+
+#include
+#include
+
+#include
+#include
+
+namespace WindowsHIDUtility
+{
+ struct HidDeviceInfo
+ {
+ HIDP_CAPS caps;
+ u16 vid;
+ u16 pid;
+ std::wstring device_path;
+ };
+
+ std::vector FindHids(u16 vid, u16 pid);
+
+} // namespace WindowsHIDUtility
\ No newline at end of file
diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj
index 455cfd692578f..436d6c168e301 100644
--- a/pcsx2/pcsx2.vcxproj
+++ b/pcsx2/pcsx2.vcxproj
@@ -217,9 +217,11 @@
+
+
@@ -570,9 +572,11 @@
+
+
diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters
index 06e5aacba5101..542cdd8f4692c 100644
--- a/pcsx2/pcsx2.vcxproj.filters
+++ b/pcsx2/pcsx2.vcxproj.filters
@@ -1367,6 +1367,12 @@
Misc\Input
+
+ Misc\Input
+
+
+ Misc\Input
+
Misc\Input
@@ -2324,6 +2330,12 @@
Misc\Input
+
+ Misc\Input
+
+
+ Misc\Input
+
Misc\Input