diff --git a/pcsx2-qt/Settings/ControllerBindingWidget.cpp b/pcsx2-qt/Settings/ControllerBindingWidget.cpp index e2fc7d2f66..0f4cb17024 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidget.cpp +++ b/pcsx2-qt/Settings/ControllerBindingWidget.cpp @@ -112,6 +112,14 @@ void ControllerBindingWidget::onTypeChanged() { m_bindings_widget = ControllerBindingWidget_Guitar::createInstance(this); } + else if (cinfo->type == Pad::ControllerType::Jogcon) + { + m_bindings_widget = ControllerBindingWidget_Jogcon::createInstance(this); + } + else if (cinfo->type == Pad::ControllerType::Negcon) + { + m_bindings_widget = ControllerBindingWidget_Negcon::createInstance(this); + } else if (cinfo->type == Pad::ControllerType::Popn) { m_bindings_widget = ControllerBindingWidget_Popn::createInstance(this); @@ -907,6 +915,48 @@ ControllerBindingWidget_Base* ControllerBindingWidget_Guitar::createInstance(Con return new ControllerBindingWidget_Guitar(parent); } +ControllerBindingWidget_Jogcon::ControllerBindingWidget_Jogcon(ControllerBindingWidget* parent) + : ControllerBindingWidget_Base(parent) +{ + m_ui.setupUi(this); + initBindingWidgets(); +} + +ControllerBindingWidget_Jogcon::~ControllerBindingWidget_Jogcon() +{ +} + +QIcon ControllerBindingWidget_Jogcon::getIcon() const +{ + return QIcon::fromTheme("controller-line"); +} + +ControllerBindingWidget_Base* ControllerBindingWidget_Jogcon::createInstance(ControllerBindingWidget* parent) +{ + return new ControllerBindingWidget_Jogcon(parent); +} + +ControllerBindingWidget_Negcon::ControllerBindingWidget_Negcon(ControllerBindingWidget* parent) + : ControllerBindingWidget_Base(parent) +{ + m_ui.setupUi(this); + initBindingWidgets(); +} + +ControllerBindingWidget_Negcon::~ControllerBindingWidget_Negcon() +{ +} + +QIcon ControllerBindingWidget_Negcon::getIcon() const +{ + return QIcon::fromTheme("controller-line"); +} + +ControllerBindingWidget_Base* ControllerBindingWidget_Negcon::createInstance(ControllerBindingWidget* parent) +{ + return new ControllerBindingWidget_Negcon(parent); +} + ControllerBindingWidget_Popn::ControllerBindingWidget_Popn(ControllerBindingWidget* parent) : ControllerBindingWidget_Base(parent) { diff --git a/pcsx2-qt/Settings/ControllerBindingWidget.h b/pcsx2-qt/Settings/ControllerBindingWidget.h index 17d62eb6db..747791639a 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidget.h +++ b/pcsx2-qt/Settings/ControllerBindingWidget.h @@ -12,6 +12,8 @@ #include "ui_ControllerBindingWidget.h" #include "ui_ControllerBindingWidget_DualShock2.h" #include "ui_ControllerBindingWidget_Guitar.h" +#include "ui_ControllerBindingWidget_Jogcon.h" +#include "ui_ControllerBindingWidget_Negcon.h" #include "ui_ControllerBindingWidget_Popn.h" #include "ui_ControllerMacroWidget.h" #include "ui_ControllerMacroEditWidget.h" @@ -200,6 +202,38 @@ private: Ui::ControllerBindingWidget_Guitar m_ui; }; +class ControllerBindingWidget_Jogcon final : public ControllerBindingWidget_Base +{ + Q_OBJECT + +public: + ControllerBindingWidget_Jogcon(ControllerBindingWidget* parent); + ~ControllerBindingWidget_Jogcon(); + + QIcon getIcon() const override; + + static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent); + +private: + Ui::ControllerBindingWidget_Jogcon m_ui; +}; + +class ControllerBindingWidget_Negcon final : public ControllerBindingWidget_Base +{ + Q_OBJECT + +public: + ControllerBindingWidget_Negcon(ControllerBindingWidget* parent); + ~ControllerBindingWidget_Negcon(); + + QIcon getIcon() const override; + + static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent); + +private: + Ui::ControllerBindingWidget_Negcon m_ui; +}; + class ControllerBindingWidget_Popn final : public ControllerBindingWidget_Base { Q_OBJECT diff --git a/pcsx2-qt/Settings/ControllerBindingWidget_Jogcon.ui b/pcsx2-qt/Settings/ControllerBindingWidget_Jogcon.ui new file mode 100644 index 0000000000..f00ad80360 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerBindingWidget_Jogcon.ui @@ -0,0 +1,832 @@ + + + ControllerBindingWidget_Jogcon + + + + 0 + 0 + 1232 + 644 + + + + + 0 + 0 + + + + + 1100 + 500 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + D-Pad + + + + + + Down + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Left + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Up + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Right + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Large Motor + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + L2 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + R2 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + L1 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + R1 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Start + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Select + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + + Face Buttons + + + + + + Cross + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Square + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Triangle + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Circle + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Small Motor + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 400 + 266 + + + + + + + :/images/Jogcon.svg + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Dial Left + + + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + Dial Right + + + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + InputBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+ + InputVibrationBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+
+ + + + +
diff --git a/pcsx2-qt/Settings/ControllerBindingWidget_Negcon.ui b/pcsx2-qt/Settings/ControllerBindingWidget_Negcon.ui new file mode 100644 index 0000000000..35e6bf526f --- /dev/null +++ b/pcsx2-qt/Settings/ControllerBindingWidget_Negcon.ui @@ -0,0 +1,730 @@ + + + ControllerBindingWidget_Negcon + + + + 0 + 0 + 1232 + 644 + + + + + 0 + 0 + + + + + 1100 + 500 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + D-Pad + + + + + + Down + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Left + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Up + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Right + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Large Motor + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + L + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Start + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + R + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + + Face Buttons + + + + + + I + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + II + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + B + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + A + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Small Motor + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 16777215 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 400 + 266 + + + + + + + :/images/Negcon.svg + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Twist Left + + + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + Twist Right + + + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + InputBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+ + InputVibrationBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+
+ + + + +
diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc index 36557a577f..5bf25c58c6 100644 --- a/pcsx2-qt/resources/resources.qrc +++ b/pcsx2-qt/resources/resources.qrc @@ -200,6 +200,8 @@ images/DualShock_2.svg images/GT_Force.svg images/Guitar.svg + images/Jogcon.svg + images/Negcon.svg images/Popn.svg diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 6177f31ed9..03d0197a29 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -436,6 +436,8 @@ set(pcsx2PADSources SIO/Pad/PadBase.cpp SIO/Pad/PadDualshock2.cpp SIO/Pad/PadGuitar.cpp + SIO/Pad/PadJogcon.cpp + SIO/Pad/PadNegcon.cpp SIO/Pad/PadPopn.cpp SIO/Pad/PadNotConnected.cpp ) @@ -444,6 +446,8 @@ set(pcsx2PADHeaders SIO/Pad/PadBase.h SIO/Pad/PadDualshock2.h SIO/Pad/PadGuitar.h + SIO/Pad/PadJogcon.h + SIO/Pad/PadNegcon.h SIO/Pad/PadPopn.h SIO/Pad/PadNotConnected.h SIO/Pad/PadTypes.h diff --git a/pcsx2/SIO/Pad/Pad.cpp b/pcsx2/SIO/Pad/Pad.cpp index bdac510cf3..5c1e64029b 100644 --- a/pcsx2/SIO/Pad/Pad.cpp +++ b/pcsx2/SIO/Pad/Pad.cpp @@ -6,6 +6,8 @@ #include "SIO/Pad/Pad.h" #include "SIO/Pad/PadDualshock2.h" #include "SIO/Pad/PadGuitar.h" +#include "SIO/Pad/PadJogcon.h" +#include "SIO/Pad/PadNegcon.h" #include "SIO/Pad/PadPopn.h" #include "SIO/Pad/PadNotConnected.h" #include "SIO/Sio.h" @@ -255,6 +257,8 @@ static const Pad::ControllerInfo* s_controller_info[] = { &PadNotConnected::ControllerInfo, &PadDualshock2::ControllerInfo, &PadGuitar::ControllerInfo, + &PadJogcon::ControllerInfo, + &PadNegcon::ControllerInfo, &PadPopn::ControllerInfo, }; @@ -495,6 +499,12 @@ PadBase* Pad::CreatePad(u8 unifiedSlot, ControllerType controllerType, size_t ej case ControllerType::Guitar: s_controllers[unifiedSlot] = std::make_unique(unifiedSlot, ejectTicks); break; + case ControllerType::Jogcon: + s_controllers[unifiedSlot] = std::make_unique(unifiedSlot, ejectTicks); + break; + case ControllerType::Negcon: + s_controllers[unifiedSlot] = std::make_unique(unifiedSlot, ejectTicks); + break; case ControllerType::Popn: s_controllers[unifiedSlot] = std::make_unique(unifiedSlot, ejectTicks); break; diff --git a/pcsx2/SIO/Pad/PadJogcon.cpp b/pcsx2/SIO/Pad/PadJogcon.cpp new file mode 100644 index 0000000000..320a2c51d7 --- /dev/null +++ b/pcsx2/SIO/Pad/PadJogcon.cpp @@ -0,0 +1,531 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "SIO/Pad/PadJogcon.h" +#include "SIO/Pad/Pad.h" +#include "SIO/Sio.h" + +#include "Common.h" +#include "Input/InputManager.h" +#include "Host.h" + +#include "IconsPromptFont.h" + +static const InputBindingInfo s_bindings[] = { + // clang-format off + {"Up", TRANSLATE_NOOP("Pad", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_UP, GenericInputBinding::DPadUp}, + {"Right", TRANSLATE_NOOP("Pad", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_RIGHT, GenericInputBinding::DPadRight}, + {"Down", TRANSLATE_NOOP("Pad", "D-Pad Down"), ICON_PF_DPAD_DOWN, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_DOWN, GenericInputBinding::DPadDown}, + {"Left", TRANSLATE_NOOP("Pad", "D-Pad Left"), ICON_PF_DPAD_LEFT, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_LEFT, GenericInputBinding::DPadLeft}, + {"Triangle", TRANSLATE_NOOP("Pad", "Triangle"), ICON_PF_BUTTON_TRIANGLE, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_TRIANGLE, GenericInputBinding::Triangle}, + {"Circle", TRANSLATE_NOOP("Pad", "Circle"), ICON_PF_BUTTON_CIRCLE, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_CIRCLE, GenericInputBinding::Circle}, + {"Cross", TRANSLATE_NOOP("Pad", "Cross"), ICON_PF_BUTTON_CROSS, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_CROSS, GenericInputBinding::Cross}, + {"Square", TRANSLATE_NOOP("Pad", "Square"), ICON_PF_BUTTON_SQUARE, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_SQUARE, GenericInputBinding::Square}, + {"Select", TRANSLATE_NOOP("Pad", "Select"), ICON_PF_SELECT_SHARE, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_SELECT, GenericInputBinding::Select}, + {"Start", TRANSLATE_NOOP("Pad", "Start"), ICON_PF_START, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_START, GenericInputBinding::Start}, + {"L1", TRANSLATE_NOOP("Pad", "L1 (Left Bumper)"), ICON_PF_LEFT_SHOULDER_L1, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_L1, GenericInputBinding::L1}, + {"L2", TRANSLATE_NOOP("Pad", "L2 (Left Trigger)"), ICON_PF_LEFT_TRIGGER_L2, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_L2, GenericInputBinding::L2}, + {"R1", TRANSLATE_NOOP("Pad", "R1 (Right Bumper)"), ICON_PF_RIGHT_SHOULDER_R1, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_R1, GenericInputBinding::R1}, + {"R2", TRANSLATE_NOOP("Pad", "R2 (Right Trigger)"), ICON_PF_RIGHT_TRIGGER_R2, InputBindingInfo::Type::Button, PadJogcon::Inputs::PAD_R2, GenericInputBinding::R2}, + {}, {}, {}, {}, + {"DialLeft", TRANSLATE_NOOP("Pad", "Dial (Left)"), nullptr, InputBindingInfo::Type::HalfAxis, PadJogcon::Inputs::PAD_DIAL_LEFT, GenericInputBinding::LeftStickLeft}, + {"DialRight", TRANSLATE_NOOP("Pad", "Dial (Right)"), nullptr, InputBindingInfo::Type::HalfAxis, PadJogcon::Inputs::PAD_DIAL_RIGHT, GenericInputBinding::LeftStickRight}, + {"LargeMotor", TRANSLATE_NOOP("Pad", "Large (Low Frequency) Motor"), nullptr, InputBindingInfo::Type::Motor, 0, GenericInputBinding::LargeMotor}, + {"SmallMotor", TRANSLATE_NOOP("Pad", "Small (High Frequency) Motor"), nullptr, InputBindingInfo::Type::Motor, 0, GenericInputBinding::SmallMotor}, + // clang-format on +}; + +static const SettingInfo s_settings[] = { + {SettingInfo::Type::Float, "Deadzone", TRANSLATE_NOOP("Pad", "Dial Deadzone"), + TRANSLATE_NOOP("Pad", "Sets the dial deadzone. Inputs below this value will not be sent to the PS2."), + "0.00", "0.00", "1.00", "0.01", TRANSLATE_NOOP("Pad", "%.0f%%"), nullptr, nullptr, 100.0f}, + {SettingInfo::Type::Float, "AxisScale", TRANSLATE_NOOP("Pad", "Dial Sensitivity"), + TRANSLATE_NOOP("Pad", "Sets the dial scaling factor."), + "1.0", "0.01", "2.00", "0.01", TRANSLATE_NOOP("Pad", "%.0f%%"), nullptr, nullptr, 100.0f}, +}; + +const Pad::ControllerInfo PadJogcon::ControllerInfo = {Pad::ControllerType::Jogcon, "Jogcon", + TRANSLATE_NOOP("Pad", "Jogcon"), ICON_PF_GAMEPAD_ALT, s_bindings, s_settings, Pad::VibrationCapabilities::LargeSmallMotors}; + +void PadJogcon::ConfigLog() +{ + const auto [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot); + + // AL: Analog Light (is it turned on right now) + // AB: Analog Button (is it useable or is it locked in its current state) + Console.WriteLn(fmt::format("[Pad] Jogcon Config Finished - P{0}/S{1} - AL: {2} - AB: {3}", + port + 1, + slot + 1, + (this->analogLight ? "On" : "Off"), + (this->analogLocked ? "Locked" : "Usable"))); +} + +u8 PadJogcon::Mystery(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 5: + return 0x02; + case 8: + return 0x5a; + default: + return 0x00; + } +} + +u8 PadJogcon::ButtonQuery(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + case 4: + return 0xff; + case 5: + return 0x03; + case 8: + return 0x5a; + default: + return 0x00; + } +} + +u8 PadJogcon::Poll(u8 commandByte) +{ + const u32 buttons = GetButtons(); + u8 largeMotor = 0x00; + u8 smallMotor = 0x00; + + switch (this->commandBytesReceived) + { + case 3: + this->vibrationMotors[0] = commandByte; + return (buttons >> 8) & 0xff; + case 4: + this->vibrationMotors[1] = commandByte; + + // Apply the vibration mapping to the motors + switch (this->largeMotorLastConfig) + { + case 0x00: + largeMotor = this->vibrationMotors[0]; + break; + case 0x01: + largeMotor = this->vibrationMotors[1]; + break; + default: + break; + } + + // Small motor on the controller is only controlled by the LSB. + // Any value can be sent by the software, but only odd numbers + // (LSB set) will turn on the motor. + switch (this->smallMotorLastConfig) + { + case 0x00: + smallMotor = this->vibrationMotors[0] & 0x01; + break; + case 0x01: + smallMotor = this->vibrationMotors[1] & 0x01; + break; + default: + break; + } + + // Order is reversed here - SetPadVibrationIntensity takes large motor first, then small. PS2 orders small motor first, large motor second. + InputManager::SetPadVibrationIntensity(this->unifiedSlot, + std::min(static_cast(largeMotor) * GetVibrationScale(1) * (1.0f / 255.0f), 1.0f), + // Small motor on the PS2 is either on full power or zero power, it has no variable speed. If the game supplies any value here at all, + // the pad in turn supplies full power to the motor, or no power at all if zero. + std::min(static_cast((smallMotor ? 0xff : 0)) * GetVibrationScale(0) * (1.0f / 255.0f), 1.0f)); + + return buttons & 0xff; + case 5: + return dial & 0xff; + case 6: + return (dial >> 8) & 0xff; + case 7: + { + const u8 b7 = this->dial > this->lastdial ? 1 : this->dial < this->lastdial ? 2 : 0; + this->lastdial = this->dial; + return b7; + } + case 8: + return 0x00; + } + + Console.Warning("%s(%02X) Did not reach a valid return path! Returning zero as a failsafe!", __FUNCTION__, commandByte); + return 0x00; +} + +u8 PadJogcon::Config(u8 commandByte) +{ + if (this->commandBytesReceived == 3) + { + if (commandByte) + { + if (!this->isInConfig) + { + this->isInConfig = true; + } + else + { + Console.Warning("%s(%02X) Unexpected enter while already in config mode", __FUNCTION__, commandByte); + } + } + else + { + if (this->isInConfig) + { + this->isInConfig = false; + this->ConfigLog(); + } + else + { + Console.Warning("%s(%02X) Unexpected exit while not in config mode", __FUNCTION__, commandByte); + } + } + } + + return 0x00; +} + +// Changes the mode of the controller between digital and analog, and adjusts the analog LED accordingly. +u8 PadJogcon::ModeSwitch(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + this->analogLight = commandByte; + + if (this->analogLight) + { + this->currentMode = Pad::Mode::ANALOG; + } + else + { + this->currentMode = Pad::Mode::DIGITAL; + } + + break; + case 4: + this->analogLocked = (commandByte == 0x03); + break; + default: + break; + } + + return 0x00; +} + +u8 PadJogcon::StatusInfo(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + return static_cast(Pad::PhysicalType::STANDARD); + case 4: + return 0x02; + case 5: + return this->analogLight; + case 6: + return 0x02; + case 7: + return 0x01; + default: + return 0x00; + } +} + +u8 PadJogcon::Constant1(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + commandStage = (commandByte != 0); + return 0x00; + case 5: + return 0x01; + case 6: + return (!commandStage ? 0x02 : 0x01); + case 7: + return (!commandStage ? 0x00 : 0x01); + case 8: + return (commandStage ? 0x0a : 0x14); + default: + return 0x00; + } +} + +u8 PadJogcon::Constant2(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 5: + return 0x02; + case 7: + return 0x01; + default: + return 0x00; + } +} + +u8 PadJogcon::Constant3(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + commandStage = (commandByte != 0); + return 0x00; + case 6: + return (!commandStage ? 0x04 : 0x07); + default: + return 0x00; + } +} + +u8 PadJogcon::VibrationMap(u8 commandByte) +{ + u8 ret = 0xff; + + switch (commandBytesReceived) + { + case 3: + ret = this->smallMotorLastConfig; + this->smallMotorLastConfig = commandByte; + return ret; + case 4: + ret = this->largeMotorLastConfig; + this->largeMotorLastConfig = commandByte; + return ret; + case 8: + return 0xff; + default: + return 0xff; + } +} + +PadJogcon::PadJogcon(u8 unifiedSlot, size_t ejectTicks) + : PadBase(unifiedSlot, ejectTicks) +{ + currentMode = Pad::Mode::PS1_JOGCON; +} + +PadJogcon::~PadJogcon() = default; + +Pad::ControllerType PadJogcon::GetType() const +{ + return Pad::ControllerType::Jogcon; +} + +const Pad::ControllerInfo& PadJogcon::GetInfo() const +{ + return ControllerInfo; +} + +void PadJogcon::Set(u32 index, float value) +{ + if (index > Inputs::LENGTH) + { + return; + } + + if (IsAnalogKey(index)) + { + const float dz_value = (this->dialDeadzone > 0.0f && value < this->dialDeadzone) ? 0.0f : value; + this->rawInputs[index] = static_cast(std::clamp(dz_value * this->dialScale * 255.0f, 0.0f, 255.0f)); + + this->dial = this->rawInputs[Inputs::PAD_DIAL_RIGHT] != 0 + ? this->rawInputs[Inputs::PAD_DIAL_RIGHT] + : -this->rawInputs[Inputs::PAD_DIAL_LEFT]; + return; + } + + this->rawInputs[index] = static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); + if (this->rawInputs[index] > 0.0f) + { + this->buttons &= ~(1u << bitmaskMapping[index]); + } + else + { + this->buttons |= (1u << bitmaskMapping[index]); + } +} + +void PadJogcon::SetRawAnalogs(const std::tuple left, const std::tuple right) +{ + this->dial = (std::get<0>(left) << 8) | std::get<1>(left); +} + +void PadJogcon::SetRawPressureButton(u32 index, const std::tuple value) +{ + this->rawInputs[index] = std::get<1>(value); + + if (std::get<0>(value)) + { + this->buttons &= ~(1u << bitmaskMapping[index]); + } + else + { + this->buttons |= (1u << bitmaskMapping[index]); + } +} + +void PadJogcon::SetAxisScale(float deadzone, float scale) +{ + this->dialDeadzone = deadzone; + this->dialScale = scale; +} + +float PadJogcon::GetVibrationScale(u32 motor) const +{ + return this->vibrationScale[motor]; +} + +void PadJogcon::SetVibrationScale(u32 motor, float scale) +{ + this->vibrationScale[motor] = scale; +} + +float PadJogcon::GetPressureModifier() const +{ + return 0; +} + +void PadJogcon::SetPressureModifier(float mod) +{ +} + +void PadJogcon::SetButtonDeadzone(float deadzone) +{ +} + +void PadJogcon::SetAnalogInvertL(bool x, bool y) +{ +} + +void PadJogcon::SetAnalogInvertR(bool x, bool y) +{ +} + +float PadJogcon::GetEffectiveInput(u32 index) const +{ + if (!IsAnalogKey(index)) + return GetRawInput(index); + + switch (index) + { + case Inputs::PAD_DIAL_LEFT: + return (dial < 0) ? -(dial / 255.0f) : 0; + + case Inputs::PAD_DIAL_RIGHT: + return (dial > 0) ? (dial / 255.0f) : 0; + + default: + return 0; + } +} + +u8 PadJogcon::GetRawInput(u32 index) const +{ + return rawInputs[index]; +} + +std::tuple PadJogcon::GetRawLeftAnalog() const +{ + return std::tuple{(dial >> 8) & 0xff, dial & 0xff}; +} + +std::tuple PadJogcon::GetRawRightAnalog() const +{ + return std::tuple{0x7f, 0x7f}; +} + +u32 PadJogcon::GetButtons() const +{ + return buttons; +} + +u8 PadJogcon::GetPressure(u32 index) const +{ + return 0; +} + +bool PadJogcon::Freeze(StateWrapper& sw) +{ + if (!PadBase::Freeze(sw) || !sw.DoMarker("PadJogcon")) + return false; + + // Private PadJogcon members + sw.Do(&analogLight); + sw.Do(&analogLocked); + sw.Do(&commandStage); + sw.Do(&vibrationMotors); + sw.Do(&smallMotorLastConfig); + sw.Do(&largeMotorLastConfig); + return !sw.HasError(); +} + +u8 PadJogcon::SendCommandByte(u8 commandByte) +{ + u8 ret = 0; + + switch (this->commandBytesReceived) + { + case 0: + ret = 0x00; + break; + case 1: + this->currentCommand = static_cast(commandByte); + + if (this->currentCommand != Pad::Command::POLL && this->currentCommand != Pad::Command::CONFIG && !this->isInConfig) + { + Console.Warning("%s(%02X) Config-only command was sent to a pad outside of config mode!", __FUNCTION__, commandByte); + } + + ret = this->isInConfig ? static_cast(Pad::Mode::CONFIG) : static_cast(this->currentMode); + break; + case 2: + ret = 0x5a; + break; + default: + switch (this->currentCommand) + { + case Pad::Command::MYSTERY: + ret = Mystery(commandByte); + break; + case Pad::Command::BUTTON_QUERY: + ret = ButtonQuery(commandByte); + break; + case Pad::Command::POLL: + ret = Poll(commandByte); + break; + case Pad::Command::CONFIG: + ret = Config(commandByte); + break; + case Pad::Command::MODE_SWITCH: + ret = ModeSwitch(commandByte); + break; + case Pad::Command::STATUS_INFO: + ret = StatusInfo(commandByte); + break; + case Pad::Command::CONST_1: + ret = Constant1(commandByte); + break; + case Pad::Command::CONST_2: + ret = Constant2(commandByte); + break; + case Pad::Command::CONST_3: + ret = Constant3(commandByte); + break; + case Pad::Command::VIBRATION_MAP: + ret = VibrationMap(commandByte); + break; + default: + ret = 0x00; + break; + } + } + + this->commandBytesReceived++; + return ret; +} diff --git a/pcsx2/SIO/Pad/PadJogcon.h b/pcsx2/SIO/Pad/PadJogcon.h new file mode 100644 index 0000000000..a0367cb8e7 --- /dev/null +++ b/pcsx2/SIO/Pad/PadJogcon.h @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "SIO/Pad/PadBase.h" + +class PadJogcon final : public PadBase +{ +public: + enum Inputs + { + PAD_UP, // Directional pad up + PAD_RIGHT, // Directional pad right + PAD_DOWN, // Directional pad down + PAD_LEFT, // Directional pad left + PAD_TRIANGLE, // Triangle button + PAD_CIRCLE, // Circle button + PAD_CROSS, // Cross button + PAD_SQUARE, // Square button + PAD_SELECT, // Select button + PAD_START, // Start button + PAD_L1, // L1 button + PAD_L2, // L2 button + PAD_R1, // R1 button + PAD_R2, // R2 button + + // This workaround is necessary because InputRecorder doesn't support custom Pads beside DS2: + // https://github.com/PCSX2/pcsx2/blob/ded55635c105c547756f5369b998297f602ada2f/pcsx2/Recording/PadData.cpp#L42-L61 + // We need to consider and avoid the DS2's indexes that aren't saved in InputRecorder + // and we also have to use DS2's analog indexes for our analog axes. + PADDING1, PADDING2, PADDING3, PADDING4, + + PAD_DIAL_LEFT, // Dial (Left) + PAD_DIAL_RIGHT, // Dial (Right) + LENGTH, + }; + + static constexpr u8 VIBRATION_MOTORS = 2; + +private: + u32 buttons = 0xffffffffu; + s16 dial = 0x0000; + s16 lastdial = 0x0000; + + bool analogLight = false; + bool analogLocked = false; + bool commandStage = false; + std::array vibrationMotors = {}; + std::array vibrationScale = {1.0f, 1.0f}; + float dialDeadzone = 0.0f; + float dialScale = 1.0f; + // Used to store the last vibration mapping request the PS2 made for the small motor. + u8 smallMotorLastConfig = 0xff; + // Used to store the last vibration mapping request the PS2 made for the large motor. + u8 largeMotorLastConfig = 0xff; + + // Since we reordered the buttons for better UI, we need to remap them here. + static constexpr std::array bitmaskMapping = {{ + 12, // PAD_UP + 13, // PAD_RIGHT + 14, // PAD_DOWN + 15, // PAD_LEFT + 4, // PAD_TRIANGLE + 5, // PAD_CIRCLE + 6, // PAD_CROSS + 7, // PAD_SQUARE + 8, // PAD_SELECT + 11, // PAD_START + 2, // PAD_L1 + 0, // PAD_L2 + 3, // PAD_R1 + 1, // PAD_R2 + }}; + + void ConfigLog(); + + u8 Mystery(u8 commandByte); + u8 ButtonQuery(u8 commandByte); + u8 Poll(u8 commandByte); + u8 Config(u8 commandByte); + u8 ModeSwitch(u8 commandByte); + u8 StatusInfo(u8 commandByte); + u8 Constant1(u8 commandByte); + u8 Constant2(u8 commandByte); + u8 Constant3(u8 commandByte); + u8 VibrationMap(u8 commandByte); + +public: + PadJogcon(u8 unifiedSlot, size_t ejectTicks); + ~PadJogcon() override; + + static inline bool IsAnalogKey(int index) + { + return index == Inputs::PAD_DIAL_LEFT || index == Inputs::PAD_DIAL_RIGHT; + } + + Pad::ControllerType GetType() const override; + const Pad::ControllerInfo& GetInfo() const override; + void Set(u32 index, float value) override; + void SetRawAnalogs(const std::tuple left, const std::tuple right) override; + void SetRawPressureButton(u32 index, const std::tuple value) override; + void SetAxisScale(float deadzone, float scale) override; + float GetVibrationScale(u32 motor) const override; + void SetVibrationScale(u32 motor, float scale) override; + float GetPressureModifier() const override; + void SetPressureModifier(float mod) override; + void SetButtonDeadzone(float deadzone) override; + void SetAnalogInvertL(bool x, bool y) override; + void SetAnalogInvertR(bool x, bool y) override; + float GetEffectiveInput(u32 index) const override; + u8 GetRawInput(u32 index) const override; + std::tuple GetRawLeftAnalog() const override; + std::tuple GetRawRightAnalog() const override; + u32 GetButtons() const override; + u8 GetPressure(u32 index) const override; + + bool Freeze(StateWrapper& sw) override; + + u8 SendCommandByte(u8 commandByte) override; + + static const Pad::ControllerInfo ControllerInfo; +}; diff --git a/pcsx2/SIO/Pad/PadNegcon.cpp b/pcsx2/SIO/Pad/PadNegcon.cpp new file mode 100644 index 0000000000..a62fd56c20 --- /dev/null +++ b/pcsx2/SIO/Pad/PadNegcon.cpp @@ -0,0 +1,549 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "SIO/Pad/PadNegcon.h" +#include "SIO/Pad/Pad.h" +#include "SIO/Sio.h" + +#include "Common.h" +#include "Input/InputManager.h" +#include "Host.h" + +#include "IconsPromptFont.h" + +static const InputBindingInfo s_bindings[] = { + // clang-format off + {"Up", TRANSLATE_NOOP("Pad", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_UP, GenericInputBinding::DPadUp}, + {"Right", TRANSLATE_NOOP("Pad", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_RIGHT, GenericInputBinding::DPadRight}, + {"Down", TRANSLATE_NOOP("Pad", "D-Pad Down"), ICON_PF_DPAD_DOWN, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_DOWN, GenericInputBinding::DPadDown}, + {"Left", TRANSLATE_NOOP("Pad", "D-Pad Left"), ICON_PF_DPAD_LEFT, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_LEFT, GenericInputBinding::DPadLeft}, + {"B", TRANSLATE_NOOP("Pad", "B Button"), ICON_PF_BUTTON_B, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_B, GenericInputBinding::Triangle}, + {"A", TRANSLATE_NOOP("Pad", "A Button"), ICON_PF_BUTTON_A, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_A, GenericInputBinding::Circle}, + {"I", TRANSLATE_NOOP("Pad", "I Button"), nullptr, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_I, GenericInputBinding::Cross}, + {"II", TRANSLATE_NOOP("Pad", "II Button"), nullptr, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_II, GenericInputBinding::Square}, + {}, + {"Start", TRANSLATE_NOOP("Pad", "Start"), ICON_PF_START, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_START, GenericInputBinding::Start}, + {"L", TRANSLATE_NOOP("Pad", "L (Left Bumper)"), ICON_PF_LEFT_SHOULDER_L1, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_L, GenericInputBinding::L2}, + {"R", TRANSLATE_NOOP("Pad", "R (Right Bumper)"), ICON_PF_RIGHT_SHOULDER_R1, InputBindingInfo::Type::Button, PadNegcon::Inputs::PAD_R, GenericInputBinding::R2}, + {"TwistLeft", TRANSLATE_NOOP("Pad", "Twist (Left)"), nullptr, InputBindingInfo::Type::HalfAxis, PadNegcon::Inputs::PAD_TWIST_LEFT, GenericInputBinding::LeftStickLeft}, + {"TwistRight", TRANSLATE_NOOP("Pad", "Twist (Right)"), nullptr, InputBindingInfo::Type::HalfAxis, PadNegcon::Inputs::PAD_TWIST_RIGHT, GenericInputBinding::LeftStickRight}, + {"LargeMotor", TRANSLATE_NOOP("Pad", "Large (Low Frequency) Motor"), nullptr, InputBindingInfo::Type::Motor, 0, GenericInputBinding::LargeMotor}, + {"SmallMotor", TRANSLATE_NOOP("Pad", "Small (High Frequency) Motor"), nullptr, InputBindingInfo::Type::Motor, 0, GenericInputBinding::SmallMotor}, + // clang-format on +}; + +static const SettingInfo s_settings[] = { + {SettingInfo::Type::Float, "Deadzone", TRANSLATE_NOOP("Pad", "Twist Deadzone"), + TRANSLATE_NOOP("Pad", "Sets the twist deadzone. Inputs below this value will not be sent to the PS2."), + "0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f}, + {SettingInfo::Type::Float, "AxisScale", TRANSLATE_NOOP("Pad", "Twist Sensitivity"), + TRANSLATE_NOOP("Pad", "Sets the twist scaling factor."), + "1.0", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f}, +}; + +const Pad::ControllerInfo PadNegcon::ControllerInfo = {Pad::ControllerType::Negcon, "Negcon", + TRANSLATE_NOOP("Pad", "Negcon"), ICON_PF_GAMEPAD_ALT, s_bindings, s_settings, Pad::VibrationCapabilities::LargeSmallMotors}; + +void PadNegcon::ConfigLog() +{ + const auto [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot); + + // AL: Analog Light (is it turned on right now) + // AB: Analog Button (is it useable or is it locked in its current state) + Console.WriteLn(fmt::format("[Pad] Negcon Config Finished - P{0}/S{1} - AL: {2} - AB: {3}", + port + 1, + slot + 1, + (this->analogLight ? "On" : "Off"), + (this->analogLocked ? "Locked" : "Usable"))); +} + +u8 PadNegcon::Mystery(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 5: + return 0x02; + case 8: + return 0x5a; + default: + return 0x00; + } +} + +u8 PadNegcon::ButtonQuery(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + case 4: + return 0xff; + case 5: + return 0x03; + case 8: + return 0x5a; + default: + return 0x00; + } +} + +u8 PadNegcon::Poll(u8 commandByte) +{ + const u32 buttons = GetButtons(); + u8 largeMotor = 0x00; + u8 smallMotor = 0x00; + + switch (this->commandBytesReceived) + { + case 3: + this->vibrationMotors[0] = commandByte; + return (buttons >> 8) & 0xff; + case 4: + this->vibrationMotors[1] = commandByte; + + // Apply the vibration mapping to the motors + switch (this->largeMotorLastConfig) + { + case 0x00: + largeMotor = this->vibrationMotors[0]; + break; + case 0x01: + largeMotor = this->vibrationMotors[1]; + break; + default: + break; + } + + // Small motor on the controller is only controlled by the LSB. + // Any value can be sent by the software, but only odd numbers + // (LSB set) will turn on the motor. + switch (this->smallMotorLastConfig) + { + case 0x00: + smallMotor = this->vibrationMotors[0] & 0x01; + break; + case 0x01: + smallMotor = this->vibrationMotors[1] & 0x01; + break; + default: + break; + } + + // Order is reversed here - SetPadVibrationIntensity takes large motor first, then small. PS2 orders small motor first, large motor second. + InputManager::SetPadVibrationIntensity(this->unifiedSlot, + std::min(static_cast(largeMotor) * GetVibrationScale(1) * (1.0f / 255.0f), 1.0f), + // Small motor on the PS2 is either on full power or zero power, it has no variable speed. If the game supplies any value here at all, + // the pad in turn supplies full power to the motor, or no power at all if zero. + std::min(static_cast((smallMotor ? 0xff : 0)) * GetVibrationScale(0) * (1.0f / 255.0f), 1.0f)); + + return buttons & 0xff; + case 5: + return this->analogs.twist; + case 6: + return this->analogs.i; + case 7: + return this->analogs.ii; + case 8: + return this->analogs.l; + } + + Console.Warning("%s(%02X) Did not reach a valid return path! Returning zero as a failsafe!", __FUNCTION__, commandByte); + return 0x00; +} + +u8 PadNegcon::Config(u8 commandByte) +{ + if (this->commandBytesReceived == 3) + { + if (commandByte) + { + if (!this->isInConfig) + { + this->isInConfig = true; + } + else + { + Console.Warning("%s(%02X) Unexpected enter while already in config mode", __FUNCTION__, commandByte); + } + } + else + { + if (this->isInConfig) + { + this->isInConfig = false; + this->ConfigLog(); + } + else + { + Console.Warning("%s(%02X) Unexpected exit while not in config mode", __FUNCTION__, commandByte); + } + } + } + + return 0x00; +} + +// Changes the mode of the controller between digital and analog, and adjusts the analog LED accordingly. +u8 PadNegcon::ModeSwitch(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + this->analogLight = commandByte; + + if (this->analogLight) + { + this->currentMode = Pad::Mode::ANALOG; + } + else + { + this->currentMode = Pad::Mode::DIGITAL; + } + + break; + case 4: + this->analogLocked = (commandByte == 0x03); + break; + default: + break; + } + + return 0x00; +} + +u8 PadNegcon::StatusInfo(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + return static_cast(Pad::PhysicalType::STANDARD); + case 4: + return 0x02; + case 5: + return this->analogLight; + case 6: + return 0x02; + case 7: + return 0x01; + default: + return 0x00; + } +} + +u8 PadNegcon::Constant1(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + commandStage = (commandByte != 0); + return 0x00; + case 5: + return 0x01; + case 6: + return (!commandStage ? 0x02 : 0x01); + case 7: + return (!commandStage ? 0x00 : 0x01); + case 8: + return (commandStage ? 0x0a : 0x14); + default: + return 0x00; + } +} + +u8 PadNegcon::Constant2(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 5: + return 0x02; + case 7: + return 0x01; + default: + return 0x00; + } +} + +u8 PadNegcon::Constant3(u8 commandByte) +{ + switch (this->commandBytesReceived) + { + case 3: + commandStage = (commandByte != 0); + return 0x00; + case 6: + return (!commandStage ? 0x04 : 0x07); + default: + return 0x00; + } +} + +u8 PadNegcon::VibrationMap(u8 commandByte) +{ + u8 ret = 0xff; + + switch (commandBytesReceived) + { + case 3: + ret = this->smallMotorLastConfig; + this->smallMotorLastConfig = commandByte; + return ret; + case 4: + ret = this->largeMotorLastConfig; + this->largeMotorLastConfig = commandByte; + return ret; + case 8: + return 0xff; + default: + return 0xff; + } +} + +PadNegcon::PadNegcon(u8 unifiedSlot, size_t ejectTicks) + : PadBase(unifiedSlot, ejectTicks) +{ + currentMode = Pad::Mode::NEGCON; +} + +PadNegcon::~PadNegcon() = default; + +Pad::ControllerType PadNegcon::GetType() const +{ + return Pad::ControllerType::Negcon; +} + +const Pad::ControllerInfo& PadNegcon::GetInfo() const +{ + return ControllerInfo; +} + +void PadNegcon::Set(u32 index, float value) +{ + if (index > Inputs::LENGTH) + { + return; + } + + if (IsTwistKey(index)) + { + const float dz_value = (this->twistDeadzone > 0.0f && value < this->twistDeadzone) ? 0.0f : value; + this->rawInputs[index] = static_cast(std::clamp(dz_value * this->twistScale * 255.0f, 0.0f, 255.0f)); + + this->analogs.twist = this->rawInputs[Inputs::PAD_TWIST_RIGHT] != 0 ? 127u + (this->rawInputs[Inputs::PAD_TWIST_RIGHT] + 1u) / 2u : 127u - (this->rawInputs[Inputs::PAD_TWIST_LEFT] - 1u) / 2; + return; + } + + this->rawInputs[index] = static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); + if (IsAnalogKey(index)) + { + switch (index) + { + case PAD_I: + this->analogs.i = this->rawInputs[index]; + break; + case PAD_II: + this->analogs.ii = this->rawInputs[index]; + break; + case PAD_L: + this->analogs.l = this->rawInputs[index]; + break; + } + } + + if (this->rawInputs[index] > 0.0f) + { + this->buttons &= ~(1u << bitmaskMapping[index]); + } + else + { + this->buttons |= (1u << bitmaskMapping[index]); + } +} + +void PadNegcon::SetRawAnalogs(const std::tuple left, const std::tuple right) +{ + this->analogs.i = std::get<0>(left); + this->analogs.ii = std::get<1>(left); + this->analogs.l = std::get<0>(right); + this->analogs.twist = std::get<1>(right); +} + +void PadNegcon::SetRawPressureButton(u32 index, const std::tuple value) +{ + this->rawInputs[index] = std::get<1>(value); + if (std::get<0>(value)) + { + this->buttons &= ~(1u << bitmaskMapping[index]); + } + else + { + this->buttons |= (1u << bitmaskMapping[index]); + } +} + +void PadNegcon::SetAxisScale(float deadzone, float scale) +{ + this->twistDeadzone = deadzone; + this->twistScale = scale; +} + +float PadNegcon::GetVibrationScale(u32 motor) const +{ + return this->vibrationScale[motor]; +} + +void PadNegcon::SetVibrationScale(u32 motor, float scale) +{ + this->vibrationScale[motor] = scale; +} + +float PadNegcon::GetPressureModifier() const +{ + return 0; +} + +void PadNegcon::SetPressureModifier(float mod) +{ +} + +void PadNegcon::SetButtonDeadzone(float deadzone) +{ +} + +void PadNegcon::SetAnalogInvertL(bool x, bool y) +{ +} + +void PadNegcon::SetAnalogInvertR(bool x, bool y) +{ +} + +float PadNegcon::GetEffectiveInput(u32 index) const +{ + if (!IsAnalogKey(index)) + return GetRawInput(index); + + switch (index) + { + case Inputs::PAD_I: + return analogs.i; + + case Inputs::PAD_II: + return analogs.ii; + + case Inputs::PAD_L: + return analogs.l; + + case Inputs::PAD_TWIST_LEFT: + return (analogs.twist < 127) ? ((127 - analogs.twist) / 127.0f) : 0; + + case Inputs::PAD_TWIST_RIGHT: + return (analogs.twist > 128) ? ((analogs.twist - 128) / 127.0f) : 0; + + default: + return 0; + } +} + +u8 PadNegcon::GetRawInput(u32 index) const +{ + return rawInputs[index]; +} + +std::tuple PadNegcon::GetRawLeftAnalog() const +{ + return std::tuple{analogs.i, analogs.ii}; +} + +std::tuple PadNegcon::GetRawRightAnalog() const +{ + return std::tuple{analogs.l, analogs.twist}; +} + +u32 PadNegcon::GetButtons() const +{ + return buttons; +} + +u8 PadNegcon::GetPressure(u32 index) const +{ + return 0; +} + +bool PadNegcon::Freeze(StateWrapper& sw) +{ + if (!PadBase::Freeze(sw) || !sw.DoMarker("PadNegcon")) + return false; + + // Private PadNegcon members + sw.Do(&analogLight); + sw.Do(&analogLocked); + sw.Do(&commandStage); + sw.Do(&vibrationMotors); + sw.Do(&smallMotorLastConfig); + sw.Do(&largeMotorLastConfig); + return !sw.HasError(); +} + +u8 PadNegcon::SendCommandByte(u8 commandByte) +{ + u8 ret = 0; + + switch (this->commandBytesReceived) + { + case 0: + ret = 0x00; + break; + case 1: + this->currentCommand = static_cast(commandByte); + + if (this->currentCommand != Pad::Command::POLL && this->currentCommand != Pad::Command::CONFIG && !this->isInConfig) + { + Console.Warning("%s(%02X) Config-only command was sent to a pad outside of config mode!", __FUNCTION__, commandByte); + } + + ret = this->isInConfig ? static_cast(Pad::Mode::CONFIG) : static_cast(this->currentMode); + break; + case 2: + ret = 0x5a; + break; + default: + switch (this->currentCommand) + { + case Pad::Command::MYSTERY: + ret = Mystery(commandByte); + break; + case Pad::Command::BUTTON_QUERY: + ret = ButtonQuery(commandByte); + break; + case Pad::Command::POLL: + ret = Poll(commandByte); + break; + case Pad::Command::CONFIG: + ret = Config(commandByte); + break; + case Pad::Command::MODE_SWITCH: + ret = ModeSwitch(commandByte); + break; + case Pad::Command::STATUS_INFO: + ret = StatusInfo(commandByte); + break; + case Pad::Command::CONST_1: + ret = Constant1(commandByte); + break; + case Pad::Command::CONST_2: + ret = Constant2(commandByte); + break; + case Pad::Command::CONST_3: + ret = Constant3(commandByte); + break; + case Pad::Command::VIBRATION_MAP: + ret = VibrationMap(commandByte); + break; + default: + ret = 0x00; + break; + } + } + + this->commandBytesReceived++; + return ret; +} diff --git a/pcsx2/SIO/Pad/PadNegcon.h b/pcsx2/SIO/Pad/PadNegcon.h new file mode 100644 index 0000000000..d91f86f2d4 --- /dev/null +++ b/pcsx2/SIO/Pad/PadNegcon.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "SIO/Pad/PadBase.h" + +class PadNegcon final : public PadBase +{ +public: + enum Inputs + { + PAD_UP, // Directional pad up + PAD_RIGHT, // Directional pad right + PAD_DOWN, // Directional pad down + PAD_LEFT, // Directional pad left + PAD_B, // B button + PAD_A, // A button + PAD_I, // I button + PAD_II, // II button + + // This workaround is necessary because InputRecorder doesn't support custom Pads beside DS2: + // https://github.com/PCSX2/pcsx2/blob/ded55635c105c547756f5369b998297f602ada2f/pcsx2/Recording/PadData.cpp#L42-L61 + // We need to consider and avoid the DS2's indexes that aren't saved in InputRecorder + // and we also have to use DS2's analog indexes for our analog axes. + PAD_PADDING1, + + PAD_START, // Start button + PAD_L, // L button + PAD_R, // R button + PAD_TWIST_LEFT, // Twist (Left) + PAD_TWIST_RIGHT, // Twist (Right) + LENGTH, + }; + + static constexpr u8 VIBRATION_MOTORS = 2; + +private: + struct Analogs + { + u8 i = 0x00; + u8 ii = 0x00; + u8 l = 0x00; + u8 twist = 0x80; + }; + + u32 buttons = 0xffffffffu; + Analogs analogs; + + bool analogLight = false; + bool analogLocked = false; + bool commandStage = false; + std::array vibrationMotors = {}; + std::array vibrationScale = {1.0f, 1.0f}; + float twistDeadzone = 0.0f; + float twistScale = 1.0f; + // Used to store the last vibration mapping request the PS2 made for the small motor. + u8 smallMotorLastConfig = 0xff; + // Used to store the last vibration mapping request the PS2 made for the large motor. + u8 largeMotorLastConfig = 0xff; + + // Since we reordered the buttons for better UI, we need to remap them here. + static constexpr std::array bitmaskMapping = {{ + 12, // PAD_UP + 13, // PAD_RIGHT + 14, // PAD_DOWN + 15, // PAD_LEFT + 4, // PAD_B + 5, // PAD_A + 6, // PAD_I + 7, // PAD_II + 0, + 11, // PAD_START + 2, // PAD_L + 3, // PAD_R + }}; + + void ConfigLog(); + + u8 Mystery(u8 commandByte); + u8 ButtonQuery(u8 commandByte); + u8 Poll(u8 commandByte); + u8 Config(u8 commandByte); + u8 ModeSwitch(u8 commandByte); + u8 StatusInfo(u8 commandByte); + u8 Constant1(u8 commandByte); + u8 Constant2(u8 commandByte); + u8 Constant3(u8 commandByte); + u8 VibrationMap(u8 commandByte); + +public: + PadNegcon(u8 unifiedSlot, size_t ejectTicks); + ~PadNegcon() override; + + static inline bool IsAnalogKey(int index) + { + return index == Inputs::PAD_I + || index == Inputs::PAD_II + || index == Inputs::PAD_L + || index == Inputs::PAD_TWIST_LEFT + || index == Inputs::PAD_TWIST_RIGHT; + } + + static inline bool IsTwistKey(int index) + { + return index == Inputs::PAD_TWIST_LEFT + || index == Inputs::PAD_TWIST_RIGHT; + } + + Pad::ControllerType GetType() const override; + const Pad::ControllerInfo& GetInfo() const override; + void Set(u32 index, float value) override; + void SetRawAnalogs(const std::tuple left, const std::tuple right) override; + void SetRawPressureButton(u32 index, const std::tuple value) override; + void SetAxisScale(float deadzone, float scale) override; + float GetVibrationScale(u32 motor) const override; + void SetVibrationScale(u32 motor, float scale) override; + float GetPressureModifier() const override; + void SetPressureModifier(float mod) override; + void SetButtonDeadzone(float deadzone) override; + void SetAnalogInvertL(bool x, bool y) override; + void SetAnalogInvertR(bool x, bool y) override; + float GetEffectiveInput(u32 index) const override; + u8 GetRawInput(u32 index) const override; + std::tuple GetRawLeftAnalog() const override; + std::tuple GetRawRightAnalog() const override; + u32 GetButtons() const override; + u8 GetPressure(u32 index) const override; + + bool Freeze(StateWrapper& sw) override; + + u8 SendCommandByte(u8 commandByte) override; + + static const Pad::ControllerInfo ControllerInfo; +}; diff --git a/pcsx2/SIO/Pad/PadTypes.h b/pcsx2/SIO/Pad/PadTypes.h index e9a9baaff5..cf02d6236c 100644 --- a/pcsx2/SIO/Pad/PadTypes.h +++ b/pcsx2/SIO/Pad/PadTypes.h @@ -64,6 +64,8 @@ namespace Pad NotConnected, DualShock2, Guitar, + Jogcon, + Negcon, Popn, Count }; diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index a0c70b5e14..cbe7dac10c 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -291,6 +291,8 @@ + + @@ -733,6 +735,8 @@ + + diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index bc919af5c5..4964728abf 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -1431,6 +1431,12 @@ System\Ps2\EmotionEngine\EE\Dynarec\arm64 + + System\Ps2\Iop\SIO\PAD + + + System\Ps2\Iop\SIO\PAD + @@ -2372,6 +2378,12 @@ Tools\arm64 + + System\Ps2\Iop\SIO\PAD + + + System\Ps2\Iop\SIO\PAD +