| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h" | ||
|
|
||
| #include <QDialogButtonBox> | ||
| #include <QLabel> | ||
| #include <QTabWidget> | ||
| #include <QVBoxLayout> | ||
|
|
||
| #if defined(CIFACE_USE_DUALSHOCKUDPCLIENT) | ||
| #include "DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h" | ||
| #endif | ||
|
|
||
| ControllerInterfaceWindow::ControllerInterfaceWindow(QWidget* parent) : QDialog(parent) | ||
| { | ||
| CreateMainLayout(); | ||
|
|
||
| setWindowTitle(tr("Alternate Input Sources")); | ||
| setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| } | ||
|
|
||
| void ControllerInterfaceWindow::CreateMainLayout() | ||
| { | ||
| m_button_box = new QDialogButtonBox(QDialogButtonBox::Close); | ||
| connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); | ||
|
|
||
| m_tab_widget = new QTabWidget(); | ||
| #if defined(CIFACE_USE_DUALSHOCKUDPCLIENT) | ||
| m_dsuclient_widget = new DualShockUDPClientWidget(); | ||
| m_tab_widget->addTab(m_dsuclient_widget, tr("DSU Client")); // TODO: use GetWrappedWidget()? | ||
| #endif | ||
|
|
||
| auto* main_layout = new QVBoxLayout(); | ||
| if (m_tab_widget->count() > 0) | ||
| { | ||
| main_layout->addWidget(m_tab_widget); | ||
| } | ||
| else | ||
| { | ||
| main_layout->addWidget(new QLabel(tr("Nothing to configure")), 0, | ||
| Qt::AlignVCenter | Qt::AlignHCenter); | ||
| } | ||
| main_layout->addWidget(m_button_box); | ||
| setLayout(main_layout); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <QDialog> | ||
|
|
||
| #include "InputCommon/ControllerInterface/ControllerInterface.h" | ||
|
|
||
| #if defined(CIFACE_USE_DUALSHOCKUDPCLIENT) | ||
| class DualShockUDPClientWidget; | ||
| #endif | ||
| class QTabWidget; | ||
| class QDialogButtonBox; | ||
|
|
||
| class ControllerInterfaceWindow final : public QDialog | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| explicit ControllerInterfaceWindow(QWidget* parent); | ||
|
|
||
| private: | ||
| void CreateMainLayout(); | ||
|
|
||
| QTabWidget* m_tab_widget; | ||
| QDialogButtonBox* m_button_box; | ||
|
|
||
| #if defined(CIFACE_USE_DUALSHOCKUDPCLIENT) | ||
| DualShockUDPClientWidget* m_dsuclient_widget; | ||
| #endif | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // Copyright 2019 Dolphin Emulator Project5~5~5~ | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h" | ||
|
|
||
| #include <QCheckBox> | ||
| #include <QGridLayout> | ||
| #include <QGroupBox> | ||
| #include <QLabel> | ||
| #include <QLineEdit> | ||
| #include <QSpinBox> | ||
|
|
||
| #include "Common/Config/Config.h" | ||
| #include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h" | ||
|
|
||
| DualShockUDPClientWidget::DualShockUDPClientWidget() | ||
| { | ||
| CreateWidgets(); | ||
| ConnectWidgets(); | ||
| } | ||
|
|
||
| void DualShockUDPClientWidget::CreateWidgets() | ||
| { | ||
| auto* main_layout = new QGridLayout; | ||
|
|
||
| m_server_enabled = new QCheckBox(tr("Enable")); | ||
| m_server_enabled->setChecked(Config::Get(ciface::DualShockUDPClient::Settings::SERVER_ENABLED)); | ||
|
|
||
| m_server_address = new QLineEdit( | ||
| QString::fromStdString(Config::Get(ciface::DualShockUDPClient::Settings::SERVER_ADDRESS))); | ||
|
|
||
| m_server_port = new QSpinBox(); | ||
| m_server_port->setMaximum(65535); | ||
| m_server_port->setValue(Config::Get(ciface::DualShockUDPClient::Settings::SERVER_PORT)); | ||
|
|
||
| auto* description = | ||
| new QLabel(tr("DSU protocol enables the use of input and motion data from compatible " | ||
| "sources, like PlayStation, Nintendo Switch and Steam controllers.<br><br>" | ||
| "For setup instructions, " | ||
| "<a href=\"https://wiki.dolphin-emu.org/index.php?title=DSU_Client\">" | ||
| "refer to this page</a>.")); | ||
| description->setTextFormat(Qt::RichText); | ||
| description->setWordWrap(true); | ||
| description->setTextInteractionFlags(Qt::TextBrowserInteraction); | ||
| description->setOpenExternalLinks(true); | ||
|
|
||
| main_layout->addWidget(m_server_enabled, 1, 1); | ||
| main_layout->addWidget(new QLabel(tr("Server IP Address")), 2, 1); | ||
| main_layout->addWidget(m_server_address, 2, 2); | ||
| main_layout->addWidget(new QLabel(tr("Server Port")), 3, 1); | ||
| main_layout->addWidget(m_server_port, 3, 2); | ||
| main_layout->addWidget(description, 4, 1, 1, 2); | ||
|
|
||
| setLayout(main_layout); | ||
| } | ||
|
|
||
| void DualShockUDPClientWidget::ConnectWidgets() | ||
| { | ||
| connect(m_server_enabled, &QCheckBox::toggled, this, [this] { | ||
| Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVER_ENABLED, | ||
| m_server_enabled->isChecked()); | ||
| }); | ||
|
|
||
| connect(m_server_address, &QLineEdit::editingFinished, this, [this] { | ||
| Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVER_ADDRESS, | ||
| m_server_address->text().toStdString()); | ||
| }); | ||
|
|
||
| connect(m_server_port, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, | ||
| [this] { | ||
| Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVER_PORT, | ||
| static_cast<u16>(m_server_port->value())); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <QWidget> | ||
|
|
||
| class QCheckBox; | ||
| class QLineEdit; | ||
| class QSpinBox; | ||
|
|
||
| class DualShockUDPClientWidget final : public QWidget | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| explicit DualShockUDPClientWidget(); | ||
|
|
||
| private: | ||
| void CreateWidgets(); | ||
| void ConnectWidgets(); | ||
|
|
||
| QCheckBox* m_server_enabled; | ||
| QLineEdit* m_server_address; | ||
| QSpinBox* m_server_port; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h" | ||
|
|
||
| #include <QFormLayout> | ||
| #include <QGroupBox> | ||
| #include <QHBoxLayout> | ||
| #include <QLabel> | ||
| #include <QPushButton> | ||
| #include <QString> | ||
| #include <QVBoxLayout> | ||
|
|
||
| #include "Core/HW/Wiimote.h" | ||
| #include "Core/HW/WiimoteEmu/WiimoteEmu.h" | ||
|
|
||
| #include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h" | ||
|
|
||
| #include "InputCommon/InputConfig.h" | ||
|
|
||
| WiimoteEmuMotionControlIMU::WiimoteEmuMotionControlIMU(MappingWindow* window) | ||
| : MappingWidget(window) | ||
| { | ||
| CreateMainLayout(); | ||
| } | ||
|
|
||
| void WiimoteEmuMotionControlIMU::CreateMainLayout() | ||
| { | ||
| auto* warning_layout = new QHBoxLayout(); | ||
| auto* warning_label = | ||
| new QLabel(tr("WARNING: The controls under Accelerometer and Gyroscope are designed to " | ||
| "interface directly with motion sensor hardware. They are not intended for " | ||
| "mapping traditional buttons, triggers or axes. You might need to configure " | ||
| "alternate input sources before using these controls.")); | ||
| warning_label->setWordWrap(true); | ||
| auto* warning_input_sources_button = new QPushButton(tr("Alternate Input Sources")); | ||
| warning_layout->addWidget(warning_label, 1); | ||
| warning_layout->addWidget(warning_input_sources_button, 0, Qt::AlignRight); | ||
| connect(warning_input_sources_button, &QPushButton::clicked, this, [this] { | ||
| ControllerInterfaceWindow* window = new ControllerInterfaceWindow(this); | ||
| window->setAttribute(Qt::WA_DeleteOnClose, true); | ||
| window->setWindowModality(Qt::WindowModality::WindowModal); | ||
| window->show(); | ||
| }); | ||
|
|
||
| auto* groups_layout = new QHBoxLayout(); | ||
| groups_layout->addWidget( | ||
| CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUPoint))); | ||
| groups_layout->addWidget(CreateGroupBox( | ||
| Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUAccelerometer))); | ||
| groups_layout->addWidget( | ||
| CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUGyroscope))); | ||
|
|
||
| m_main_layout = new QVBoxLayout(); | ||
| m_main_layout->addLayout(warning_layout); | ||
| m_main_layout->addLayout(groups_layout); | ||
|
|
||
| setLayout(m_main_layout); | ||
| } | ||
|
|
||
| void WiimoteEmuMotionControlIMU::LoadSettings() | ||
| { | ||
| Wiimote::LoadConfig(); | ||
| } | ||
|
|
||
| void WiimoteEmuMotionControlIMU::SaveSettings() | ||
| { | ||
| Wiimote::GetConfig()->SaveConfig(); | ||
| } | ||
|
|
||
| InputConfig* WiimoteEmuMotionControlIMU::GetConfig() | ||
| { | ||
| return Wiimote::GetConfig(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "DolphinQt/Config/Mapping/MappingWidget.h" | ||
|
|
||
| class QCheckBox; | ||
| class QFormLayout; | ||
| class QGroupBox; | ||
| class QHBoxLayout; | ||
| class QLabel; | ||
| class QVBoxLayout; | ||
|
|
||
| class WiimoteEmuMotionControlIMU final : public MappingWidget | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| explicit WiimoteEmuMotionControlIMU(MappingWindow* window); | ||
|
|
||
| InputConfig* GetConfig() override; | ||
|
|
||
| private: | ||
| void LoadSettings() override; | ||
| void SaveSettings() override; | ||
| void CreateMainLayout(); | ||
|
|
||
| // Main | ||
| QVBoxLayout* m_main_layout; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -40,6 +40,9 @@ enum class GroupType | ||
| Triggers, | ||
| Slider, | ||
| Shake, | ||
| IMUAccelerometer, | ||
| IMUGyroscope, | ||
| IMUCursor | ||
| }; | ||
|
|
||
| class ControlGroup | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h" | ||
|
|
||
| #include "Common/Common.h" | ||
| #include "Common/MathUtil.h" | ||
|
|
||
| #include "Core/HW/WiimoteEmu/WiimoteEmu.h" | ||
|
|
||
| #include "InputCommon/ControlReference/ControlReference.h" | ||
| #include "InputCommon/ControllerEmu/Control/Control.h" | ||
| #include "InputCommon/ControllerEmu/Control/Input.h" | ||
| #include "InputCommon/ControllerEmu/ControllerEmu.h" | ||
| #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" | ||
|
|
||
| namespace ControllerEmu | ||
| { | ||
| IMUAccelerometer::IMUAccelerometer(std::string name, std::string ui_name) | ||
| : ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUAccelerometer) | ||
| { | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Left"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Right"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Forward"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Backward"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Up"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Down"))); | ||
| } | ||
|
|
||
| std::optional<IMUAccelerometer::StateData> IMUAccelerometer::GetState() const | ||
| { | ||
| StateData state; | ||
| state.x = (controls[0]->control_ref->State() - controls[1]->control_ref->State()); | ||
| state.y = (controls[3]->control_ref->State() - controls[2]->control_ref->State()); | ||
| state.z = (controls[4]->control_ref->State() - controls[5]->control_ref->State()); | ||
|
|
||
| if (controls[0]->control_ref->BoundCount() != 0) | ||
| return state; | ||
| else | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| } // namespace ControllerEmu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "Common/Matrix.h" | ||
| #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" | ||
| #include "InputCommon/ControllerInterface/Device.h" | ||
|
|
||
| namespace ControllerEmu | ||
| { | ||
| class IMUAccelerometer : public ControlGroup | ||
| { | ||
| public: | ||
| using StateData = Common::Vec3; | ||
|
|
||
| IMUAccelerometer(std::string name, std::string ui_name); | ||
|
|
||
| std::optional<StateData> GetState() const; | ||
| }; | ||
| } // namespace ControllerEmu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "InputCommon/ControllerEmu/ControlGroup/IMUCursor.h" | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "Common/Common.h" | ||
| #include "Common/MathUtil.h" | ||
|
|
||
| #include "InputCommon/ControlReference/ControlReference.h" | ||
| #include "InputCommon/ControllerEmu/Control/Control.h" | ||
| #include "InputCommon/ControllerEmu/Control/Input.h" | ||
| #include "InputCommon/ControllerEmu/ControllerEmu.h" | ||
| #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" | ||
|
|
||
| namespace ControllerEmu | ||
| { | ||
| IMUCursor::IMUCursor(std::string name, std::string ui_name) | ||
| : ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUCursor) | ||
| { | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Recenter"))); | ||
|
|
||
| // Default values are optimized for "Super Mario Galaxy 2". | ||
| // This seems to be acceptable for a good number of games. | ||
|
|
||
| AddSetting(&m_yaw_setting, | ||
| // i18n: Refers to an amount of rotational movement about the "yaw" axis. | ||
| {_trans("Total Yaw"), | ||
| // i18n: The symbol/abbreviation for degrees (unit of angular measure). | ||
| _trans("°"), | ||
| // i18n: Refers to emulated wii remote movements. | ||
| _trans("Total rotation about the yaw axis.")}, | ||
| 15, 0, 360); | ||
| } | ||
|
|
||
| ControlState IMUCursor::GetTotalYaw() const | ||
| { | ||
| return m_yaw_setting.GetValue() * MathUtil::TAU / 360; | ||
| } | ||
|
|
||
| } // namespace ControllerEmu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <chrono> | ||
| #include <string> | ||
|
|
||
| #include "InputCommon/ControllerEmu/StickGate.h" | ||
| #include "InputCommon/ControllerInterface/Device.h" | ||
|
|
||
| namespace ControllerEmu | ||
| { | ||
| class IMUCursor : public ControlGroup | ||
| { | ||
| public: | ||
| IMUCursor(std::string name, std::string ui_name); | ||
|
|
||
| // Yaw movement in radians. | ||
| ControlState GetTotalYaw() const; | ||
|
|
||
| private: | ||
| SettingValue<double> m_yaw_setting; | ||
| }; | ||
| } // namespace ControllerEmu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h" | ||
|
|
||
| #include "Common/Common.h" | ||
| #include "Common/MathUtil.h" | ||
|
|
||
| #include "Core/HW/WiimoteEmu/WiimoteEmu.h" | ||
|
|
||
| #include "InputCommon/ControlReference/ControlReference.h" | ||
| #include "InputCommon/ControllerEmu/Control/Control.h" | ||
| #include "InputCommon/ControllerEmu/Control/Input.h" | ||
| #include "InputCommon/ControllerEmu/ControllerEmu.h" | ||
| #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" | ||
|
|
||
| namespace ControllerEmu | ||
| { | ||
| IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name) | ||
| : ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUGyroscope) | ||
| { | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Pitch Up"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Pitch Down"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Roll Left"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Roll Right"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Yaw Left"))); | ||
| controls.emplace_back(std::make_unique<Input>(Translate, _trans("Yaw Right"))); | ||
| } | ||
|
|
||
| std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const | ||
| { | ||
| StateData state; | ||
| state.x = (controls[1]->control_ref->State() - controls[0]->control_ref->State()); | ||
| state.y = (controls[2]->control_ref->State() - controls[3]->control_ref->State()); | ||
| state.z = (controls[4]->control_ref->State() - controls[5]->control_ref->State()); | ||
|
|
||
| if (controls[0]->control_ref->BoundCount() != 0) | ||
| return state; | ||
| else | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| } // namespace ControllerEmu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "Common/Matrix.h" | ||
| #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" | ||
| #include "InputCommon/ControllerInterface/Device.h" | ||
|
|
||
| namespace ControllerEmu | ||
| { | ||
| class IMUGyroscope : public ControlGroup | ||
| { | ||
| public: | ||
| using StateData = Common::Vec3; | ||
|
|
||
| IMUGyroscope(std::string name, std::string ui_name); | ||
|
|
||
| std::optional<StateData> GetState() const; | ||
| }; | ||
| } // namespace ControllerEmu |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -29,6 +29,7 @@ | ||
| #if defined(USE_PIPES) | ||
| #define CIFACE_USE_PIPES | ||
| #endif | ||
| #define CIFACE_USE_DUALSHOCKUDPCLIENT | ||
|
|
||
| // | ||
| // ControllerInterface | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "Common/Config/Config.h" | ||
|
|
||
| namespace ciface::DualShockUDPClient | ||
| { | ||
| namespace Settings | ||
| { | ||
| extern const Config::ConfigInfo<bool> SERVER_ENABLED; | ||
| extern const Config::ConfigInfo<std::string> SERVER_ADDRESS; | ||
| extern const Config::ConfigInfo<int> SERVER_PORT; | ||
| } // namespace Settings | ||
|
|
||
| void Init(); | ||
| void PopulateDevices(); | ||
| void DeInit(); | ||
| } // namespace ciface::DualShockUDPClient |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,270 @@ | ||
| // Copyright 2019 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include <optional> | ||
|
|
||
| #include <zlib.h> | ||
|
|
||
| #include "Common/CommonTypes.h" | ||
|
|
||
| namespace ciface::DualShockUDPClient::Proto | ||
| { | ||
| // CemuHook DualShockUDP protocol implementation using UdpServer.cs from | ||
| // https://github.com/Ryochan7/DS4Windows as documentation. | ||
| // | ||
| // WARNING: Little endian host assumed | ||
|
|
||
| static constexpr u16 CEMUHOOK_PROTOCOL_VERSION = 1001; | ||
|
|
||
| static constexpr int PORT_COUNT = 4; | ||
|
|
||
| static constexpr char CLIENT[] = "DSUC"; | ||
| static constexpr char SERVER[] = "DSUS"; | ||
|
|
||
| #pragma pack(push, 1) | ||
|
|
||
| enum class DsState : u8 | ||
| { | ||
| Disconnected = 0x00, | ||
| Reserved = 0x01, | ||
| Connected = 0x02 | ||
| }; | ||
|
|
||
| enum class DsConnection : u8 | ||
| { | ||
| None = 0x00, | ||
| Usb = 0x01, | ||
| Bluetooth = 0x02 | ||
| }; | ||
|
|
||
| enum class DsModel : u8 | ||
| { | ||
| None = 0, | ||
| DS3 = 1, | ||
| DS4 = 2, | ||
| Generic = 3 | ||
| }; | ||
|
|
||
| enum class DsBattery : u8 | ||
| { | ||
| None = 0x00, | ||
| Dying = 0x01, | ||
| Low = 0x02, | ||
| Medium = 0x03, | ||
| High = 0x04, | ||
| Full = 0x05, | ||
| Charging = 0xEE, | ||
| Charged = 0xEF | ||
| }; | ||
|
|
||
| enum RegisterFlags : u8 | ||
| { | ||
| AllPads = 0x00, | ||
| PadID = 0x01, | ||
| PadMACAdddress = 0x02 | ||
| }; | ||
|
|
||
| struct MessageHeader | ||
| { | ||
| u8 source[4]; | ||
| u16 protocol_version; | ||
| u16 message_length; // actually message size minus header size | ||
| u32 crc32; | ||
| u32 source_uid; | ||
| }; | ||
|
|
||
| struct Touch | ||
| { | ||
| u8 active; | ||
| u8 id; | ||
| s16 x; | ||
| s16 y; | ||
| }; | ||
|
|
||
| namespace MessageType | ||
| { | ||
| struct VersionRequest | ||
| { | ||
| static constexpr auto FROM = CLIENT; | ||
| static constexpr auto TYPE = 0x100000U; | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| }; | ||
|
|
||
| struct VersionResponse | ||
| { | ||
| static constexpr auto FROM = SERVER; | ||
| static constexpr auto TYPE = 0x100000U; | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| u16 max_protocol_version; | ||
| u8 padding[2]; | ||
| }; | ||
|
|
||
| struct ListPorts | ||
| { | ||
| static constexpr auto FROM = CLIENT; | ||
| static constexpr auto TYPE = 0x100001U; | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| u32 pad_request_count; | ||
| u8 pad_id[4]; | ||
| }; | ||
|
|
||
| struct PortInfo | ||
| { | ||
| static constexpr auto FROM = SERVER; | ||
| static constexpr auto TYPE = 0x100001U; | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| u8 pad_id; | ||
| DsState pad_state; | ||
| DsModel model; | ||
| DsConnection connection_type; | ||
| u8 pad_mac_address[6]; | ||
| DsBattery battery_status; | ||
| u8 padding; | ||
| }; | ||
|
|
||
| struct PadDataRequest | ||
| { | ||
| static constexpr auto FROM = CLIENT; | ||
| static constexpr auto TYPE = 0x100002U; | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| RegisterFlags register_flags; | ||
| u8 pad_id_to_register; | ||
| u8 mac_address_to_register[6]; | ||
| }; | ||
|
|
||
| struct PadDataResponse | ||
| { | ||
| static constexpr auto FROM = SERVER; | ||
| static constexpr auto TYPE = 0x100002U; | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| u8 pad_id; | ||
| DsState pad_state; | ||
| DsModel model; | ||
| DsConnection connection_type; | ||
| u8 pad_mac_address[6]; | ||
| DsBattery battery_status; | ||
| u8 active; | ||
| u32 hid_packet_counter; | ||
| u8 button_states1; | ||
| u8 button_states2; | ||
| u8 button_ps; | ||
| u8 button_touch; | ||
| u8 left_stick_x; | ||
| u8 left_stick_y_inverted; | ||
| u8 right_stick_x; | ||
| u8 right_stick_y_inverted; | ||
| u8 button_dpad_left_analog; | ||
| u8 button_dpad_down_analog; | ||
| u8 button_dpad_right_analog; | ||
| u8 button_dpad_up_analog; | ||
| u8 button_square_analog; | ||
| u8 button_cross_analog; | ||
| u8 button_circle_analog; | ||
| u8 button_triangle_analog; | ||
| u8 button_r1_analog; | ||
| u8 button_l1_analog; | ||
| u8 trigger_r2; | ||
| u8 trigger_l2; | ||
| Touch touch1; | ||
| Touch touch2; | ||
| u64 timestamp_us; | ||
| float accelerometer_x_g; | ||
| float accelerometer_y_g; | ||
| float accelerometer_z_inverted_g; | ||
| float gyro_pitch_deg_s; | ||
| float gyro_yaw_deg_s; | ||
| float gyro_roll_deg_s; | ||
| }; | ||
|
|
||
| struct FromServer | ||
| { | ||
| union | ||
| { | ||
| struct | ||
| { | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| }; | ||
| MessageType::VersionResponse version_response; | ||
| MessageType::PortInfo port_info; | ||
| MessageType::PadDataResponse pad_data_response; | ||
| }; | ||
| }; | ||
|
|
||
| struct FromClient | ||
| { | ||
| union | ||
| { | ||
| struct | ||
| { | ||
| MessageHeader header; | ||
| u32 message_type; | ||
| }; | ||
| MessageType::VersionRequest version_request; | ||
| MessageType::ListPorts list_ports; | ||
| MessageType::PadDataRequest pad_data_request; | ||
| }; | ||
| }; | ||
| } // namespace MessageType | ||
|
|
||
| static inline u32 CRC32(const void* buffer, unsigned length) | ||
| { | ||
| return crc32(crc32(0L, Z_NULL, 0), static_cast<const Bytef*>(buffer), length); | ||
| } | ||
|
|
||
| template <typename MsgType> | ||
| struct Message | ||
| { | ||
| Message() : m_message{} {} | ||
|
|
||
| explicit Message(u32 source_uid) : m_message{} | ||
| { | ||
| memcpy((char*)m_message.header.source, MsgType::FROM, sizeof(m_message.header.source)); | ||
| m_message.header.protocol_version = CEMUHOOK_PROTOCOL_VERSION; | ||
| m_message.header.message_length = sizeof(*this) - sizeof(m_message.header); | ||
| m_message.header.source_uid = source_uid; | ||
| m_message.message_type = MsgType::TYPE; | ||
| } | ||
|
|
||
| void Finish() { m_message.header.crc32 = CRC32(&m_message, sizeof(m_message)); } | ||
|
|
||
| template <class ToMsgType> | ||
| std::optional<ToMsgType> CheckAndCastTo() | ||
| { | ||
| u32 crc32_in_header = m_message.header.crc32; | ||
| // zero out the crc32 in the packet once we got it since that's whats needed for calculation | ||
| m_message.header.crc32 = 0; | ||
| u32 crc32_calculated = CRC32(&m_message, sizeof(ToMsgType)); | ||
| if (crc32_in_header != crc32_calculated) | ||
| { | ||
| NOTICE_LOG(SERIALINTERFACE, | ||
| "DualShockUDPClient Received message with bad CRC in header: got %u, expected %u", | ||
| crc32_in_header, crc32_calculated); | ||
| return std::nullopt; | ||
| } | ||
| if (m_message.header.protocol_version > CEMUHOOK_PROTOCOL_VERSION) | ||
| return std::nullopt; | ||
| if (memcmp(m_message.header.source, ToMsgType::FROM, sizeof(m_message.header.source))) | ||
| return std::nullopt; | ||
| if (m_message.message_type != ToMsgType::TYPE) | ||
| return std::nullopt; | ||
| if (m_message.header.message_length + sizeof(m_message.header) > sizeof(ToMsgType)) | ||
| return std::nullopt; | ||
|
|
||
| ToMsgType tomsg; | ||
| memcpy(&tomsg, &m_message, sizeof(tomsg)); | ||
| return tomsg; | ||
| } | ||
|
|
||
| MsgType m_message; | ||
| }; | ||
|
|
||
| #pragma pack(pop) | ||
| } // namespace ciface::DualShockUDPClient::Proto |