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