From 4cb3baba5cf360a02dff9d6af1a839a570c40999 Mon Sep 17 00:00:00 2001 From: rlnilsen <47765059+rlnilsen@users.noreply.github.com> Date: Fri, 6 Sep 2019 17:09:30 +0200 Subject: Add support for motion controllers via the CemuHook controller input protocol. This is done by: 1) Implementing said protocol in a new controller input class CemuHookUDPServer. 2) Adding functionality in the WiimoteEmu class for pushing that motion input to the emulated Wiimote and MotionPlus. 3) Suitably modifying the UI for configuring an Emulated Wii Remote. --- Source/Core/InputCommon/CMakeLists.txt | 9 + .../ControllerEmu/ControlGroup/ControlGroup.h | 3 + .../ControlGroup/IMUAccelerometer.cpp | 44 +++ .../ControllerEmu/ControlGroup/IMUAccelerometer.h | 24 ++ .../ControllerEmu/ControlGroup/IMUCursor.cpp | 43 ++ .../ControllerEmu/ControlGroup/IMUCursor.h | 26 ++ .../ControllerEmu/ControlGroup/IMUGyroscope.cpp | 44 +++ .../ControllerEmu/ControlGroup/IMUGyroscope.h | 24 ++ .../CemuHookUDPServer/CemuHookUDPServer.cpp | 432 +++++++++++++++++++++ .../CemuHookUDPServer/CemuHookUDPServer.h | 11 + .../CemuHookUDPServer/CemuHookUDPServerProto.h | 270 +++++++++++++ .../ControllerInterface/ControllerInterface.cpp | 12 + .../ControllerInterface/ControllerInterface.h | 1 + Source/Core/InputCommon/InputCommon.vcxproj | 11 +- .../Core/InputCommon/InputCommon.vcxproj.filters | 32 +- 15 files changed, 984 insertions(+), 2 deletions(-) create mode 100644 Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp create mode 100644 Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h create mode 100644 Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp create mode 100644 Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h create mode 100644 Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp create mode 100644 Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h create mode 100644 Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp create mode 100644 Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h create mode 100644 Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h (limited to 'Source/Core/InputCommon') diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index b47503a9ae..38f2e03d2f 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -25,6 +25,12 @@ add_library(inputcommon ControllerEmu/ControlGroup/Cursor.h ControllerEmu/ControlGroup/Force.cpp ControllerEmu/ControlGroup/Force.h + ControllerEmu/ControlGroup/IMUAccelerometer.cpp + ControllerEmu/ControlGroup/IMUAccelerometer.h + ControllerEmu/ControlGroup/IMUCursor.cpp + ControllerEmu/ControlGroup/IMUCursor.h + ControllerEmu/ControlGroup/IMUGyroscope.cpp + ControllerEmu/ControlGroup/IMUGyroscope.h ControllerEmu/ControlGroup/MixedTriggers.cpp ControllerEmu/ControlGroup/MixedTriggers.h ControllerEmu/ControlGroup/ModifySettingsButton.cpp @@ -37,6 +43,9 @@ add_library(inputcommon ControllerEmu/ControlGroup/Triggers.h ControllerEmu/Setting/NumericSetting.cpp ControllerEmu/Setting/NumericSetting.h + ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp + ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h + ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h ControllerInterface/ControllerInterface.cpp ControllerInterface/ControllerInterface.h ControllerInterface/Device.cpp diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h index fff118d280..310df90e30 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h @@ -40,6 +40,9 @@ enum class GroupType Triggers, Slider, Shake, + IMUAccelerometer, + IMUGyroscope, + IMUCursor }; class ControlGroup diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp new file mode 100644 index 0000000000..e994ceba5d --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp @@ -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(Translate, _trans("Left"))); + controls.emplace_back(std::make_unique(Translate, _trans("Right"))); + controls.emplace_back(std::make_unique(Translate, _trans("Forward"))); + controls.emplace_back(std::make_unique(Translate, _trans("Backward"))); + controls.emplace_back(std::make_unique(Translate, _trans("Up"))); + controls.emplace_back(std::make_unique(Translate, _trans("Down"))); +} + +std::optional 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 diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h new file mode 100644 index 0000000000..7ac41ede7a --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h @@ -0,0 +1,24 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#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 GetState() const; +}; +} // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp new file mode 100644 index 0000000000..7133a07e59 --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp @@ -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 + +#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(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 diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h new file mode 100644 index 0000000000..578762fb85 --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h @@ -0,0 +1,26 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#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 m_yaw_setting; +}; +} // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp new file mode 100644 index 0000000000..7bae049fff --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp @@ -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(Translate, _trans("Pitch Up"))); + controls.emplace_back(std::make_unique(Translate, _trans("Pitch Down"))); + controls.emplace_back(std::make_unique(Translate, _trans("Roll Left"))); + controls.emplace_back(std::make_unique(Translate, _trans("Roll Right"))); + controls.emplace_back(std::make_unique(Translate, _trans("Yaw Left"))); + controls.emplace_back(std::make_unique(Translate, _trans("Yaw Right"))); +} + +std::optional 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 diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h new file mode 100644 index 0000000000..dac30143dc --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h @@ -0,0 +1,24 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#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 GetState() const; +}; +} // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp new file mode 100644 index 0000000000..a480320b33 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp @@ -0,0 +1,432 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h" + +#include +#include + +#include +#include + +#include "Common/Config/Config.h" +#include "Common/Flag.h" +#include "Common/Logging/Log.h" +#include "Common/MathUtil.h" +#include "Common/Matrix.h" +#include "Common/Random.h" +#include "Common/Thread.h" +#include "Core/CoreTiming.h" +#include "Core/HW/SystemTimers.h" +#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +namespace ciface::CemuHookUDPServer +{ +class Device : public Core::Device +{ +private: + template + class Button : public Core::Device::Input + { + public: + Button(std::string name, const T& buttons, unsigned mask) + : m_name(std::move(name)), m_buttons(buttons), m_mask(mask) + { + } + std::string GetName() const override { return m_name; } + ControlState GetState() const override { return (m_buttons & m_mask) != 0; } + + private: + const std::string m_name; + const T& m_buttons; + unsigned m_mask; + }; + + template + class AnalogInput : public Core::Device::Input + { + public: + AnalogInput(std::string name, const T& input, ControlState range, ControlState offset = 0) + : m_name(std::move(name)), m_input(input), m_range(range), m_offset(offset) + { + } + std::string GetName() const override { return m_name; } + ControlState GetState() const override { return (ControlState(m_input) + m_offset) / m_range; } + + private: + const std::string m_name; + const T& m_input; + const ControlState m_range; + const ControlState m_offset; + }; + + class TouchInput : public AnalogInput + { + public: + TouchInput(std::string name, const int& input, ControlState range) + : AnalogInput(std::move(name), input, range) + { + } + bool IsDetectable() override { return false; } + }; + + class AccelerometerInput : public AnalogInput + { + public: + AccelerometerInput(std::string name, const double& input, ControlState range) + : AnalogInput(std::move(name), input, range) + { + } + bool IsDetectable() override { return false; } + }; + + using GyroInput = AccelerometerInput; + +public: + void UpdateInput() override; + + Device(Proto::DsModel model, int index); + + std::string GetName() const final override; + std::string GetSource() const final override; + std::optional GetPreferredId() const final override; + +private: + const Proto::DsModel m_model; + const int m_index; + u32 m_client_uid; + sf::UdpSocket m_socket; + Common::DVec3 m_accel; + Common::DVec3 m_gyro; + std::chrono::steady_clock::time_point m_next_reregister; + Proto::MessageType::PadDataResponse m_pad_data; + Proto::Touch m_prev_touch; + bool m_prev_touch_valid; + int m_touch_x; + int m_touch_y; +}; + +static constexpr double GRAVITY_ACCELERATION = 9.80665; +static constexpr char DEFAULT_SERVER_ADDRESS[] = "127.0.0.1"; +static constexpr u16 DEFAULT_SERVER_PORT = 26760; +static constexpr auto SERVER_REREGISTER_INTERVAL = std::chrono::seconds{1}; +static constexpr auto SERVER_LISTPORTS_INTERVAL = std::chrono::seconds{1}; +static constexpr int TOUCH_X_AXIS_MAX = 1000; +static constexpr int TOUCH_Y_AXIS_MAX = 500; + +namespace Settings +{ +const Config::ConfigInfo SERVER_ENABLED{ + {Config::System::CemuHookUdpServer, "Server", "Enabled"}, false}; +const Config::ConfigInfo SERVER_ADDRESS{ + {Config::System::CemuHookUdpServer, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS}; +const Config::ConfigInfo SERVER_PORT{{Config::System::CemuHookUdpServer, "Server", "Port"}, + DEFAULT_SERVER_PORT}; +} // namespace Settings + +static bool s_server_enabled; +static std::string s_server_address; +static u16 s_server_port; +static u32 s_client_uid; +static std::chrono::steady_clock::time_point s_next_listports; +static std::thread s_hotplug_thread; +static Common::Flag s_hotplug_thread_running; +static std::mutex s_port_info_mutex; +static Proto::MessageType::PortInfo s_port_info[Proto::PORT_COUNT]; + +static bool IsSameController(const Proto::MessageType::PortInfo& a, + const Proto::MessageType::PortInfo& b) +{ + // compare everything but battery_status + return a.pad_id == b.pad_id && a.pad_state == b.pad_state && a.model == b.model && + a.connection_type == b.connection_type && + memcmp(a.pad_mac_address, b.pad_mac_address, sizeof a.pad_mac_address) == 0; +} + +static sf::Socket::Status ReceiveWithTimeout(sf::UdpSocket& socket, void* data, std::size_t size, + std::size_t& received, sf::IpAddress& remoteAddress, + unsigned short& remotePort, sf::Time timeout) +{ + sf::SocketSelector selector; + selector.add(socket); + if (selector.wait(timeout)) + return socket.receive(data, size, received, remoteAddress, remotePort); + else + return sf::Socket::NotReady; +} + +static void HotplugThreadFunc() +{ + Common::SetCurrentThreadName("CemuHookUDPServer Hotplug Thread"); + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread started"); + + sf::UdpSocket socket; + + while (s_hotplug_thread_running.IsSet()) + { + const auto now = std::chrono::steady_clock::now(); + if (now >= s_next_listports) + { + s_next_listports = now + SERVER_LISTPORTS_INTERVAL; + + // Request info on the four controller ports + Proto::Message msg(s_client_uid); + auto& list_ports = msg.m_message; + list_ports.pad_request_count = 4; + list_ports.pad_id[0] = 0; + list_ports.pad_id[1] = 1; + list_ports.pad_id[2] = 2; + list_ports.pad_id[3] = 3; + msg.Finish(); + if (socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) != + sf::Socket::Status::Done) + ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer HotplugThreadFunc send failed"); + } + + // Receive controller port info + Proto::Message msg; + const auto timeout = s_next_listports - std::chrono::steady_clock::now(); + // ReceiveWithTimeout treats a timeout of zero as infinite timeout, which we don't want + auto timeout_ms = std::chrono::duration_cast(timeout).count(); + timeout_ms = std::max(timeout_ms, 1); + std::size_t received_bytes; + sf::IpAddress sender; + u16 port; + if (ReceiveWithTimeout(socket, &msg, sizeof(msg), received_bytes, sender, port, + sf::milliseconds(timeout_ms)) == sf::Socket::Status::Done) + { + if (auto port_info = msg.CheckAndCastTo()) + { + const bool port_changed = !IsSameController(*port_info, s_port_info[port_info->pad_id]); + { + std::lock_guard lock(s_port_info_mutex); + s_port_info[port_info->pad_id] = *port_info; + } + if (port_changed) + PopulateDevices(); + } + } + } + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread stopped"); +} + +static void StartHotplugThread() +{ + // Mark the thread as running. + if (!s_hotplug_thread_running.TestAndSet()) + { + // It was already running. + return; + } + + s_hotplug_thread = std::thread(HotplugThreadFunc); +} + +static void StopHotplugThread() +{ + // Tell the hotplug thread to stop. + if (!s_hotplug_thread_running.TestAndClear()) + { + // It wasn't running, we're done. + return; + } + + s_hotplug_thread.join(); +} + +void Init() +{ + s_server_enabled = Config::Get(Settings::SERVER_ENABLED); + s_server_address = Config::Get(Settings::SERVER_ADDRESS); + s_server_port = Config::Get(Settings::SERVER_PORT); + + s_client_uid = Common::Random::GenerateValue(); + s_next_listports = std::chrono::steady_clock::time_point::min(); + for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) + { + s_port_info[port_index] = {}; + s_port_info[port_index].pad_id = port_index; + } + + if (s_server_enabled) + StartHotplugThread(); +} + +void PopulateDevices() +{ + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer PopulateDevices"); + + g_controller_interface.RemoveDevice( + [](const auto* dev) { return dev->GetSource() == "UDPServer"; }); + + std::lock_guard lock(s_port_info_mutex); + for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) + { + Proto::MessageType::PortInfo port_info = s_port_info[port_index]; + if (port_info.pad_state == Proto::DsState::Connected) + g_controller_interface.AddDevice(std::make_shared(port_info.model, port_index)); + } +} + +void DeInit() +{ + StopHotplugThread(); + SaveSettings(); +} + +void SaveSettings() +{ + Config::ConfigChangeCallbackGuard config_guard; + + Config::SetBaseOrCurrent(Settings::SERVER_ENABLED, s_server_enabled); + Config::SetBaseOrCurrent(Settings::SERVER_ADDRESS, s_server_address); + Config::SetBaseOrCurrent(Settings::SERVER_PORT, s_server_port); +} + +Device::Device(Proto::DsModel model, int index) + : m_model(model), m_index(index), + m_client_uid(Common::Random::GenerateValue()), m_accel{}, m_gyro{}, + m_next_reregister(std::chrono::steady_clock::time_point::min()), m_pad_data{}, m_prev_touch{}, + m_prev_touch_valid(false), m_touch_x(0), m_touch_y(0) +{ + m_socket.setBlocking(false); + + AddInput(new AnalogInput("Pad W", m_pad_data.button_dpad_left_analog, 255)); + AddInput(new AnalogInput("Pad S", m_pad_data.button_dpad_down_analog, 255)); + AddInput(new AnalogInput("Pad E", m_pad_data.button_dpad_right_analog, 255)); + AddInput(new AnalogInput("Pad N", m_pad_data.button_dpad_up_analog, 255)); + AddInput(new AnalogInput("Square", m_pad_data.button_square_analog, 255)); + AddInput(new AnalogInput("Cross", m_pad_data.button_cross_analog, 255)); + AddInput(new AnalogInput("Circle", m_pad_data.button_circle_analog, 255)); + AddInput(new AnalogInput("Triangle", m_pad_data.button_triangle_analog, 255)); + AddInput(new AnalogInput("L1", m_pad_data.button_l1_analog, 255)); + AddInput(new AnalogInput("R1", m_pad_data.button_r1_analog, 255)); + + AddInput(new AnalogInput("L2", m_pad_data.trigger_l2, 255)); + AddInput(new AnalogInput("R2", m_pad_data.trigger_r2, 255)); + + AddInput(new Button("L3", m_pad_data.button_states1, 0x2)); + AddInput(new Button("R3", m_pad_data.button_states1, 0x4)); + AddInput(new Button("Share", m_pad_data.button_states1, 0x1)); + AddInput(new Button("Options", m_pad_data.button_states1, 0x8)); + AddInput(new Button("PS", m_pad_data.button_ps, 0x1)); + AddInput(new Button("Touch Button", m_pad_data.button_touch, 0x1)); + + AddInput(new AnalogInput("Left X-", m_pad_data.left_stick_x, -128, -128)); + AddInput(new AnalogInput("Left X+", m_pad_data.left_stick_x, 127, -128)); + AddInput(new AnalogInput("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128)); + AddInput(new AnalogInput("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128)); + AddInput(new AnalogInput("Right X-", m_pad_data.right_stick_x, -128, -128)); + AddInput(new AnalogInput("Right X+", m_pad_data.right_stick_x, 127, -128)); + AddInput(new AnalogInput("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128)); + AddInput(new AnalogInput("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128)); + + AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX)); + AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX)); + AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX)); + AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX)); + + AddInput(new AccelerometerInput("Accel Left", m_accel.x, 1)); + AddInput(new AccelerometerInput("Accel Right", m_accel.x, -1)); + AddInput(new AccelerometerInput("Accel Backward", m_accel.y, 1)); + AddInput(new AccelerometerInput("Accel Forward", m_accel.y, -1)); + AddInput(new AccelerometerInput("Accel Up", m_accel.z, 1)); + AddInput(new AccelerometerInput("Accel Down", m_accel.z, -1)); + + AddInput(new GyroInput("Gyro Pitch Up", m_gyro.x, -1)); + AddInput(new GyroInput("Gyro Pitch Down", m_gyro.x, 1)); + AddInput(new GyroInput("Gyro Roll Right", m_gyro.y, -1)); + AddInput(new GyroInput("Gyro Roll Left", m_gyro.y, 1)); + AddInput(new GyroInput("Gyro Yaw Right", m_gyro.z, -1)); + AddInput(new GyroInput("Gyro Yaw Left", m_gyro.z, 1)); +} + +std::string Device::GetName() const +{ + switch (m_model) + { + case Proto::DsModel::None: + return "None"; + case Proto::DsModel::DS3: + return "DualShock 3"; + case Proto::DsModel::DS4: + return "DualShock 4"; + case Proto::DsModel::Generic: + return "Generic Gamepad"; + default: + return "Device"; + } +} + +std::string Device::GetSource() const +{ + return "UDPServer"; +} + +void Device::UpdateInput() +{ + // Regularly tell the UDP server to feed us controller data + const auto now = std::chrono::steady_clock::now(); + if (now >= m_next_reregister) + { + m_next_reregister = now + SERVER_REREGISTER_INTERVAL; + + Proto::Message msg(m_client_uid); + auto& data_req = msg.m_message; + data_req.register_flags = Proto::RegisterFlags::PadID; + data_req.pad_id_to_register = m_index; + msg.Finish(); + if (m_socket.send(&data_req, sizeof(data_req), s_server_address, s_server_port) != + sf::Socket::Status::Done) + ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer UpdateInput send failed"); + } + + // Receive and handle controller data + Proto::Message msg; + std::size_t received_bytes; + sf::IpAddress sender; + u16 port; + while (m_socket.receive(&msg, sizeof msg, received_bytes, sender, port) == + sf::Socket::Status::Done) + { + if (auto pad_data = msg.CheckAndCastTo()) + { + m_pad_data = *pad_data; + + m_accel.x = m_pad_data.accelerometer_x_g; + m_accel.z = -m_pad_data.accelerometer_y_g; + m_accel.y = -m_pad_data.accelerometer_z_inverted_g; + m_gyro.x = -m_pad_data.gyro_pitch_deg_s; + m_gyro.z = -m_pad_data.gyro_yaw_deg_s; + m_gyro.y = -m_pad_data.gyro_roll_deg_s; + + // Convert Gs to meters per second squared + m_accel = m_accel * GRAVITY_ACCELERATION; + + // Convert degrees per second to radians per second + m_gyro = m_gyro * (MathUtil::TAU / 360); + + // Update touch pad relative coordinates + if (m_pad_data.touch1.id != m_prev_touch.id) + m_prev_touch_valid = false; + if (m_prev_touch_valid) + { + m_touch_x += m_pad_data.touch1.x - m_prev_touch.x; + m_touch_y += m_pad_data.touch1.y - m_prev_touch.y; + m_touch_x = std::clamp(m_touch_x, -TOUCH_X_AXIS_MAX, TOUCH_X_AXIS_MAX); + m_touch_y = std::clamp(m_touch_y, -TOUCH_Y_AXIS_MAX, TOUCH_Y_AXIS_MAX); + } + m_prev_touch = m_pad_data.touch1; + m_prev_touch_valid = true; + } + } +} + +std::optional Device::GetPreferredId() const +{ + return m_index; +} + +} // namespace ciface::CemuHookUDPServer diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h new file mode 100644 index 0000000000..98b2506d2e --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h @@ -0,0 +1,11 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +namespace ciface::CemuHookUDPServer +{ +void Init(); +void PopulateDevices(); +void DeInit(); +void SaveSettings(); +} // namespace ciface::CemuHookUDPServer diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h new file mode 100644 index 0000000000..60c8059292 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h @@ -0,0 +1,270 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include + +#include "Common/CommonTypes.h" + +namespace ciface::CemuHookUDPServer::Proto +{ +// CemuHook UDPServer 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(buffer), length); +} + +template +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 + std::optional 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, + "CemuHookUDPServer 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::CemuHookUDPServer::Proto diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index bb5ccb3f0e..5f20fd475b 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -30,6 +30,9 @@ #ifdef CIFACE_USE_PIPES #include "InputCommon/ControllerInterface/Pipes/Pipes.h" #endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER +#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h" +#endif ControllerInterface g_controller_interface; @@ -67,6 +70,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) #endif #ifdef CIFACE_USE_PIPES // nothing needed +#endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER + ciface::CemuHookUDPServer::Init(); #endif RefreshDevices(); @@ -122,6 +128,9 @@ void ControllerInterface::RefreshDevices() #ifdef CIFACE_USE_PIPES ciface::Pipes::PopulateDevices(); #endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER + ciface::CemuHookUDPServer::PopulateDevices(); +#endif m_is_populating_devices = false; InvokeDevicesChangedCallbacks(); @@ -172,6 +181,9 @@ void ControllerInterface::Shutdown() #ifdef CIFACE_USE_EVDEV ciface::evdev::Shutdown(); #endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER + ciface::CemuHookUDPServer::DeInit(); +#endif } void ControllerInterface::AddDevice(std::shared_ptr device) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 2ad53fc79b..f1a8e0c19e 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -29,6 +29,7 @@ #if defined(USE_PIPES) #define CIFACE_USE_PIPES #endif +#define CIFACE_USE_CEMUHOOKUDPSERVER // // ControllerInterface diff --git a/Source/Core/InputCommon/InputCommon.vcxproj b/Source/Core/InputCommon/InputCommon.vcxproj index a22ad21394..e3d252cb5f 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj +++ b/Source/Core/InputCommon/InputCommon.vcxproj @@ -36,6 +36,9 @@ + + + @@ -53,6 +56,7 @@ + @@ -76,6 +80,9 @@ + + + @@ -93,6 +100,8 @@ + + @@ -122,4 +131,4 @@ - + \ No newline at end of file diff --git a/Source/Core/InputCommon/InputCommon.vcxproj.filters b/Source/Core/InputCommon/InputCommon.vcxproj.filters index c1f26d6141..ca29eec811 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj.filters +++ b/Source/Core/InputCommon/InputCommon.vcxproj.filters @@ -28,6 +28,9 @@ {07bad1aa-7e03-4f5c-ade2-a44857c5cbc3} + + {4f71c21c-85eb-4e76-ad64-40fd97bc3d6d} + @@ -120,6 +123,18 @@ ControllerEmu\ControlGroup + + ControllerEmu\ControlGroup + + + ControllerInterface\CemuHookUDPServer + + + ControllerEmu\ControlGroup + + + ControllerEmu\ControlGroup + @@ -216,8 +231,23 @@ ControllerEmu\ControlGroup + + ControllerEmu\ControlGroup + + + ControllerInterface\CemuHookUDPServer + + + ControllerEmu\ControlGroup + + + ControllerEmu\ControlGroup + + + ControllerInterface\CemuHookUDPServer + - + \ No newline at end of file -- cgit v1.2.3 From 5ff79499a593f651978188f1711728cf67d44a59 Mon Sep 17 00:00:00 2001 From: rlnilsen <47765059+rlnilsen@users.noreply.github.com> Date: Wed, 23 Oct 2019 01:49:48 +0200 Subject: UDPServer: Add configuration UI. Accessed through button "Alternate Input Sources" in the "Controller Settings" dialog. --- .../CemuHookUDPServer/CemuHookUDPServer.cpp | 48 ++++++++++++++-------- .../CemuHookUDPServer/CemuHookUDPServer.h | 10 ++++- 2 files changed, 39 insertions(+), 19 deletions(-) (limited to 'Source/Core/InputCommon') diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp index a480320b33..36cd148041 100644 --- a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp @@ -135,6 +135,7 @@ static std::thread s_hotplug_thread; static Common::Flag s_hotplug_thread_running; static std::mutex s_port_info_mutex; static Proto::MessageType::PortInfo s_port_info[Proto::PORT_COUNT]; +static sf::UdpSocket s_socket; static bool IsSameController(const Proto::MessageType::PortInfo& a, const Proto::MessageType::PortInfo& b) @@ -162,8 +163,6 @@ static void HotplugThreadFunc() Common::SetCurrentThreadName("CemuHookUDPServer Hotplug Thread"); NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread started"); - sf::UdpSocket socket; - while (s_hotplug_thread_running.IsSet()) { const auto now = std::chrono::steady_clock::now(); @@ -180,7 +179,7 @@ static void HotplugThreadFunc() list_ports.pad_id[2] = 2; list_ports.pad_id[3] = 3; msg.Finish(); - if (socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) != + if (s_socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) != sf::Socket::Status::Done) ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer HotplugThreadFunc send failed"); } @@ -194,7 +193,7 @@ static void HotplugThreadFunc() std::size_t received_bytes; sf::IpAddress sender; u16 port; - if (ReceiveWithTimeout(socket, &msg, sizeof(msg), received_bytes, sender, port, + if (ReceiveWithTimeout(s_socket, &msg, sizeof(msg), received_bytes, sender, port, sf::milliseconds(timeout_ms)) == sf::Socket::Status::Done) { if (auto port_info = msg.CheckAndCastTo()) @@ -233,14 +232,15 @@ static void StopHotplugThread() return; } + s_socket.unbind(); // interrupt blocking socket s_hotplug_thread.join(); } -void Init() +static void Restart() { - s_server_enabled = Config::Get(Settings::SERVER_ENABLED); - s_server_address = Config::Get(Settings::SERVER_ADDRESS); - s_server_port = Config::Get(Settings::SERVER_PORT); + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer Restart"); + + StopHotplugThread(); s_client_uid = Common::Random::GenerateValue(); s_next_listports = std::chrono::steady_clock::time_point::min(); @@ -250,10 +250,32 @@ void Init() s_port_info[port_index].pad_id = port_index; } + PopulateDevices(); // remove devices + if (s_server_enabled) StartHotplugThread(); } +static void ConfigChanged() +{ + bool server_enabled = Config::Get(Settings::SERVER_ENABLED); + std::string server_address = Config::Get(Settings::SERVER_ADDRESS); + u16 server_port = Config::Get(Settings::SERVER_PORT); + if (server_enabled != s_server_enabled || server_address != s_server_address || + server_port != s_server_port) + { + s_server_enabled = server_enabled; + s_server_address = server_address; + s_server_port = server_port; + Restart(); + } +} + +void Init() +{ + Config::AddConfigChangedCallback(ConfigChanged); +} + void PopulateDevices() { NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer PopulateDevices"); @@ -273,16 +295,6 @@ void PopulateDevices() void DeInit() { StopHotplugThread(); - SaveSettings(); -} - -void SaveSettings() -{ - Config::ConfigChangeCallbackGuard config_guard; - - Config::SetBaseOrCurrent(Settings::SERVER_ENABLED, s_server_enabled); - Config::SetBaseOrCurrent(Settings::SERVER_ADDRESS, s_server_address); - Config::SetBaseOrCurrent(Settings::SERVER_PORT, s_server_port); } Device::Device(Proto::DsModel model, int index) diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h index 98b2506d2e..f7eaa34407 100644 --- a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h @@ -2,10 +2,18 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include "Common/Config/Config.h" + namespace ciface::CemuHookUDPServer { +namespace Settings +{ +extern const Config::ConfigInfo SERVER_ENABLED; +extern const Config::ConfigInfo SERVER_ADDRESS; +extern const Config::ConfigInfo SERVER_PORT; +} // namespace Settings + void Init(); void PopulateDevices(); void DeInit(); -void SaveSettings(); } // namespace ciface::CemuHookUDPServer -- cgit v1.2.3 From da1f153b47a3f3f4eb9b976670bfddf68108bae9 Mon Sep 17 00:00:00 2001 From: rlnilsen <47765059+rlnilsen@users.noreply.github.com> Date: Sat, 26 Oct 2019 18:05:16 +0200 Subject: Rename all instances of "CemuhookUDPServer"/"UDPServer" to "DualShockUDPClient"/"DSUClient". --- Source/Core/InputCommon/CMakeLists.txt | 6 +- .../CemuHookUDPServer/CemuHookUDPServer.cpp | 444 --------------------- .../CemuHookUDPServer/CemuHookUDPServer.h | 19 - .../CemuHookUDPServer/CemuHookUDPServerProto.h | 270 ------------- .../ControllerInterface/ControllerInterface.cpp | 16 +- .../ControllerInterface/ControllerInterface.h | 2 +- .../DualShockUDPClient/DualShockUDPClient.cpp | 444 +++++++++++++++++++++ .../DualShockUDPClient/DualShockUDPClient.h | 19 + .../DualShockUDPClient/DualShockUDPProto.h | 270 +++++++++++++ Source/Core/InputCommon/InputCommon.vcxproj | 6 +- .../Core/InputCommon/InputCommon.vcxproj.filters | 24 +- 11 files changed, 759 insertions(+), 761 deletions(-) delete mode 100644 Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp delete mode 100644 Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h delete mode 100644 Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h create mode 100644 Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp create mode 100644 Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h create mode 100644 Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h (limited to 'Source/Core/InputCommon') diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 38f2e03d2f..b1420155f6 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -43,9 +43,9 @@ add_library(inputcommon ControllerEmu/ControlGroup/Triggers.h ControllerEmu/Setting/NumericSetting.cpp ControllerEmu/Setting/NumericSetting.h - ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp - ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h - ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h + ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp + ControllerInterface/DualShockUDPClient/DualShockUDPClient.h + ControllerInterface/DualShockUDPClient/DualShockUDPProto.h ControllerInterface/ControllerInterface.cpp ControllerInterface/ControllerInterface.h ControllerInterface/Device.cpp diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp deleted file mode 100644 index 36cd148041..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h" - -#include -#include - -#include -#include - -#include "Common/Config/Config.h" -#include "Common/Flag.h" -#include "Common/Logging/Log.h" -#include "Common/MathUtil.h" -#include "Common/Matrix.h" -#include "Common/Random.h" -#include "Common/Thread.h" -#include "Core/CoreTiming.h" -#include "Core/HW/SystemTimers.h" -#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h" -#include "InputCommon/ControllerInterface/ControllerInterface.h" - -namespace ciface::CemuHookUDPServer -{ -class Device : public Core::Device -{ -private: - template - class Button : public Core::Device::Input - { - public: - Button(std::string name, const T& buttons, unsigned mask) - : m_name(std::move(name)), m_buttons(buttons), m_mask(mask) - { - } - std::string GetName() const override { return m_name; } - ControlState GetState() const override { return (m_buttons & m_mask) != 0; } - - private: - const std::string m_name; - const T& m_buttons; - unsigned m_mask; - }; - - template - class AnalogInput : public Core::Device::Input - { - public: - AnalogInput(std::string name, const T& input, ControlState range, ControlState offset = 0) - : m_name(std::move(name)), m_input(input), m_range(range), m_offset(offset) - { - } - std::string GetName() const override { return m_name; } - ControlState GetState() const override { return (ControlState(m_input) + m_offset) / m_range; } - - private: - const std::string m_name; - const T& m_input; - const ControlState m_range; - const ControlState m_offset; - }; - - class TouchInput : public AnalogInput - { - public: - TouchInput(std::string name, const int& input, ControlState range) - : AnalogInput(std::move(name), input, range) - { - } - bool IsDetectable() override { return false; } - }; - - class AccelerometerInput : public AnalogInput - { - public: - AccelerometerInput(std::string name, const double& input, ControlState range) - : AnalogInput(std::move(name), input, range) - { - } - bool IsDetectable() override { return false; } - }; - - using GyroInput = AccelerometerInput; - -public: - void UpdateInput() override; - - Device(Proto::DsModel model, int index); - - std::string GetName() const final override; - std::string GetSource() const final override; - std::optional GetPreferredId() const final override; - -private: - const Proto::DsModel m_model; - const int m_index; - u32 m_client_uid; - sf::UdpSocket m_socket; - Common::DVec3 m_accel; - Common::DVec3 m_gyro; - std::chrono::steady_clock::time_point m_next_reregister; - Proto::MessageType::PadDataResponse m_pad_data; - Proto::Touch m_prev_touch; - bool m_prev_touch_valid; - int m_touch_x; - int m_touch_y; -}; - -static constexpr double GRAVITY_ACCELERATION = 9.80665; -static constexpr char DEFAULT_SERVER_ADDRESS[] = "127.0.0.1"; -static constexpr u16 DEFAULT_SERVER_PORT = 26760; -static constexpr auto SERVER_REREGISTER_INTERVAL = std::chrono::seconds{1}; -static constexpr auto SERVER_LISTPORTS_INTERVAL = std::chrono::seconds{1}; -static constexpr int TOUCH_X_AXIS_MAX = 1000; -static constexpr int TOUCH_Y_AXIS_MAX = 500; - -namespace Settings -{ -const Config::ConfigInfo SERVER_ENABLED{ - {Config::System::CemuHookUdpServer, "Server", "Enabled"}, false}; -const Config::ConfigInfo SERVER_ADDRESS{ - {Config::System::CemuHookUdpServer, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS}; -const Config::ConfigInfo SERVER_PORT{{Config::System::CemuHookUdpServer, "Server", "Port"}, - DEFAULT_SERVER_PORT}; -} // namespace Settings - -static bool s_server_enabled; -static std::string s_server_address; -static u16 s_server_port; -static u32 s_client_uid; -static std::chrono::steady_clock::time_point s_next_listports; -static std::thread s_hotplug_thread; -static Common::Flag s_hotplug_thread_running; -static std::mutex s_port_info_mutex; -static Proto::MessageType::PortInfo s_port_info[Proto::PORT_COUNT]; -static sf::UdpSocket s_socket; - -static bool IsSameController(const Proto::MessageType::PortInfo& a, - const Proto::MessageType::PortInfo& b) -{ - // compare everything but battery_status - return a.pad_id == b.pad_id && a.pad_state == b.pad_state && a.model == b.model && - a.connection_type == b.connection_type && - memcmp(a.pad_mac_address, b.pad_mac_address, sizeof a.pad_mac_address) == 0; -} - -static sf::Socket::Status ReceiveWithTimeout(sf::UdpSocket& socket, void* data, std::size_t size, - std::size_t& received, sf::IpAddress& remoteAddress, - unsigned short& remotePort, sf::Time timeout) -{ - sf::SocketSelector selector; - selector.add(socket); - if (selector.wait(timeout)) - return socket.receive(data, size, received, remoteAddress, remotePort); - else - return sf::Socket::NotReady; -} - -static void HotplugThreadFunc() -{ - Common::SetCurrentThreadName("CemuHookUDPServer Hotplug Thread"); - NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread started"); - - while (s_hotplug_thread_running.IsSet()) - { - const auto now = std::chrono::steady_clock::now(); - if (now >= s_next_listports) - { - s_next_listports = now + SERVER_LISTPORTS_INTERVAL; - - // Request info on the four controller ports - Proto::Message msg(s_client_uid); - auto& list_ports = msg.m_message; - list_ports.pad_request_count = 4; - list_ports.pad_id[0] = 0; - list_ports.pad_id[1] = 1; - list_ports.pad_id[2] = 2; - list_ports.pad_id[3] = 3; - msg.Finish(); - if (s_socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) != - sf::Socket::Status::Done) - ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer HotplugThreadFunc send failed"); - } - - // Receive controller port info - Proto::Message msg; - const auto timeout = s_next_listports - std::chrono::steady_clock::now(); - // ReceiveWithTimeout treats a timeout of zero as infinite timeout, which we don't want - auto timeout_ms = std::chrono::duration_cast(timeout).count(); - timeout_ms = std::max(timeout_ms, 1); - std::size_t received_bytes; - sf::IpAddress sender; - u16 port; - if (ReceiveWithTimeout(s_socket, &msg, sizeof(msg), received_bytes, sender, port, - sf::milliseconds(timeout_ms)) == sf::Socket::Status::Done) - { - if (auto port_info = msg.CheckAndCastTo()) - { - const bool port_changed = !IsSameController(*port_info, s_port_info[port_info->pad_id]); - { - std::lock_guard lock(s_port_info_mutex); - s_port_info[port_info->pad_id] = *port_info; - } - if (port_changed) - PopulateDevices(); - } - } - } - NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread stopped"); -} - -static void StartHotplugThread() -{ - // Mark the thread as running. - if (!s_hotplug_thread_running.TestAndSet()) - { - // It was already running. - return; - } - - s_hotplug_thread = std::thread(HotplugThreadFunc); -} - -static void StopHotplugThread() -{ - // Tell the hotplug thread to stop. - if (!s_hotplug_thread_running.TestAndClear()) - { - // It wasn't running, we're done. - return; - } - - s_socket.unbind(); // interrupt blocking socket - s_hotplug_thread.join(); -} - -static void Restart() -{ - NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer Restart"); - - StopHotplugThread(); - - s_client_uid = Common::Random::GenerateValue(); - s_next_listports = std::chrono::steady_clock::time_point::min(); - for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) - { - s_port_info[port_index] = {}; - s_port_info[port_index].pad_id = port_index; - } - - PopulateDevices(); // remove devices - - if (s_server_enabled) - StartHotplugThread(); -} - -static void ConfigChanged() -{ - bool server_enabled = Config::Get(Settings::SERVER_ENABLED); - std::string server_address = Config::Get(Settings::SERVER_ADDRESS); - u16 server_port = Config::Get(Settings::SERVER_PORT); - if (server_enabled != s_server_enabled || server_address != s_server_address || - server_port != s_server_port) - { - s_server_enabled = server_enabled; - s_server_address = server_address; - s_server_port = server_port; - Restart(); - } -} - -void Init() -{ - Config::AddConfigChangedCallback(ConfigChanged); -} - -void PopulateDevices() -{ - NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer PopulateDevices"); - - g_controller_interface.RemoveDevice( - [](const auto* dev) { return dev->GetSource() == "UDPServer"; }); - - std::lock_guard lock(s_port_info_mutex); - for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) - { - Proto::MessageType::PortInfo port_info = s_port_info[port_index]; - if (port_info.pad_state == Proto::DsState::Connected) - g_controller_interface.AddDevice(std::make_shared(port_info.model, port_index)); - } -} - -void DeInit() -{ - StopHotplugThread(); -} - -Device::Device(Proto::DsModel model, int index) - : m_model(model), m_index(index), - m_client_uid(Common::Random::GenerateValue()), m_accel{}, m_gyro{}, - m_next_reregister(std::chrono::steady_clock::time_point::min()), m_pad_data{}, m_prev_touch{}, - m_prev_touch_valid(false), m_touch_x(0), m_touch_y(0) -{ - m_socket.setBlocking(false); - - AddInput(new AnalogInput("Pad W", m_pad_data.button_dpad_left_analog, 255)); - AddInput(new AnalogInput("Pad S", m_pad_data.button_dpad_down_analog, 255)); - AddInput(new AnalogInput("Pad E", m_pad_data.button_dpad_right_analog, 255)); - AddInput(new AnalogInput("Pad N", m_pad_data.button_dpad_up_analog, 255)); - AddInput(new AnalogInput("Square", m_pad_data.button_square_analog, 255)); - AddInput(new AnalogInput("Cross", m_pad_data.button_cross_analog, 255)); - AddInput(new AnalogInput("Circle", m_pad_data.button_circle_analog, 255)); - AddInput(new AnalogInput("Triangle", m_pad_data.button_triangle_analog, 255)); - AddInput(new AnalogInput("L1", m_pad_data.button_l1_analog, 255)); - AddInput(new AnalogInput("R1", m_pad_data.button_r1_analog, 255)); - - AddInput(new AnalogInput("L2", m_pad_data.trigger_l2, 255)); - AddInput(new AnalogInput("R2", m_pad_data.trigger_r2, 255)); - - AddInput(new Button("L3", m_pad_data.button_states1, 0x2)); - AddInput(new Button("R3", m_pad_data.button_states1, 0x4)); - AddInput(new Button("Share", m_pad_data.button_states1, 0x1)); - AddInput(new Button("Options", m_pad_data.button_states1, 0x8)); - AddInput(new Button("PS", m_pad_data.button_ps, 0x1)); - AddInput(new Button("Touch Button", m_pad_data.button_touch, 0x1)); - - AddInput(new AnalogInput("Left X-", m_pad_data.left_stick_x, -128, -128)); - AddInput(new AnalogInput("Left X+", m_pad_data.left_stick_x, 127, -128)); - AddInput(new AnalogInput("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128)); - AddInput(new AnalogInput("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128)); - AddInput(new AnalogInput("Right X-", m_pad_data.right_stick_x, -128, -128)); - AddInput(new AnalogInput("Right X+", m_pad_data.right_stick_x, 127, -128)); - AddInput(new AnalogInput("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128)); - AddInput(new AnalogInput("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128)); - - AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX)); - AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX)); - AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX)); - AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX)); - - AddInput(new AccelerometerInput("Accel Left", m_accel.x, 1)); - AddInput(new AccelerometerInput("Accel Right", m_accel.x, -1)); - AddInput(new AccelerometerInput("Accel Backward", m_accel.y, 1)); - AddInput(new AccelerometerInput("Accel Forward", m_accel.y, -1)); - AddInput(new AccelerometerInput("Accel Up", m_accel.z, 1)); - AddInput(new AccelerometerInput("Accel Down", m_accel.z, -1)); - - AddInput(new GyroInput("Gyro Pitch Up", m_gyro.x, -1)); - AddInput(new GyroInput("Gyro Pitch Down", m_gyro.x, 1)); - AddInput(new GyroInput("Gyro Roll Right", m_gyro.y, -1)); - AddInput(new GyroInput("Gyro Roll Left", m_gyro.y, 1)); - AddInput(new GyroInput("Gyro Yaw Right", m_gyro.z, -1)); - AddInput(new GyroInput("Gyro Yaw Left", m_gyro.z, 1)); -} - -std::string Device::GetName() const -{ - switch (m_model) - { - case Proto::DsModel::None: - return "None"; - case Proto::DsModel::DS3: - return "DualShock 3"; - case Proto::DsModel::DS4: - return "DualShock 4"; - case Proto::DsModel::Generic: - return "Generic Gamepad"; - default: - return "Device"; - } -} - -std::string Device::GetSource() const -{ - return "UDPServer"; -} - -void Device::UpdateInput() -{ - // Regularly tell the UDP server to feed us controller data - const auto now = std::chrono::steady_clock::now(); - if (now >= m_next_reregister) - { - m_next_reregister = now + SERVER_REREGISTER_INTERVAL; - - Proto::Message msg(m_client_uid); - auto& data_req = msg.m_message; - data_req.register_flags = Proto::RegisterFlags::PadID; - data_req.pad_id_to_register = m_index; - msg.Finish(); - if (m_socket.send(&data_req, sizeof(data_req), s_server_address, s_server_port) != - sf::Socket::Status::Done) - ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer UpdateInput send failed"); - } - - // Receive and handle controller data - Proto::Message msg; - std::size_t received_bytes; - sf::IpAddress sender; - u16 port; - while (m_socket.receive(&msg, sizeof msg, received_bytes, sender, port) == - sf::Socket::Status::Done) - { - if (auto pad_data = msg.CheckAndCastTo()) - { - m_pad_data = *pad_data; - - m_accel.x = m_pad_data.accelerometer_x_g; - m_accel.z = -m_pad_data.accelerometer_y_g; - m_accel.y = -m_pad_data.accelerometer_z_inverted_g; - m_gyro.x = -m_pad_data.gyro_pitch_deg_s; - m_gyro.z = -m_pad_data.gyro_yaw_deg_s; - m_gyro.y = -m_pad_data.gyro_roll_deg_s; - - // Convert Gs to meters per second squared - m_accel = m_accel * GRAVITY_ACCELERATION; - - // Convert degrees per second to radians per second - m_gyro = m_gyro * (MathUtil::TAU / 360); - - // Update touch pad relative coordinates - if (m_pad_data.touch1.id != m_prev_touch.id) - m_prev_touch_valid = false; - if (m_prev_touch_valid) - { - m_touch_x += m_pad_data.touch1.x - m_prev_touch.x; - m_touch_y += m_pad_data.touch1.y - m_prev_touch.y; - m_touch_x = std::clamp(m_touch_x, -TOUCH_X_AXIS_MAX, TOUCH_X_AXIS_MAX); - m_touch_y = std::clamp(m_touch_y, -TOUCH_Y_AXIS_MAX, TOUCH_Y_AXIS_MAX); - } - m_prev_touch = m_pad_data.touch1; - m_prev_touch_valid = true; - } - } -} - -std::optional Device::GetPreferredId() const -{ - return m_index; -} - -} // namespace ciface::CemuHookUDPServer diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h deleted file mode 100644 index f7eaa34407..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "Common/Config/Config.h" - -namespace ciface::CemuHookUDPServer -{ -namespace Settings -{ -extern const Config::ConfigInfo SERVER_ENABLED; -extern const Config::ConfigInfo SERVER_ADDRESS; -extern const Config::ConfigInfo SERVER_PORT; -} // namespace Settings - -void Init(); -void PopulateDevices(); -void DeInit(); -} // namespace ciface::CemuHookUDPServer diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h deleted file mode 100644 index 60c8059292..0000000000 --- a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include - -#include - -#include "Common/CommonTypes.h" - -namespace ciface::CemuHookUDPServer::Proto -{ -// CemuHook UDPServer 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(buffer), length); -} - -template -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 - std::optional 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, - "CemuHookUDPServer 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::CemuHookUDPServer::Proto diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 5f20fd475b..35dfbde0be 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -30,8 +30,8 @@ #ifdef CIFACE_USE_PIPES #include "InputCommon/ControllerInterface/Pipes/Pipes.h" #endif -#ifdef CIFACE_USE_CEMUHOOKUDPSERVER -#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h" +#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT +#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h" #endif ControllerInterface g_controller_interface; @@ -71,8 +71,8 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) #ifdef CIFACE_USE_PIPES // nothing needed #endif -#ifdef CIFACE_USE_CEMUHOOKUDPSERVER - ciface::CemuHookUDPServer::Init(); +#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT + ciface::DualShockUDPClient::Init(); #endif RefreshDevices(); @@ -128,8 +128,8 @@ void ControllerInterface::RefreshDevices() #ifdef CIFACE_USE_PIPES ciface::Pipes::PopulateDevices(); #endif -#ifdef CIFACE_USE_CEMUHOOKUDPSERVER - ciface::CemuHookUDPServer::PopulateDevices(); +#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT + ciface::DualShockUDPClient::PopulateDevices(); #endif m_is_populating_devices = false; @@ -181,8 +181,8 @@ void ControllerInterface::Shutdown() #ifdef CIFACE_USE_EVDEV ciface::evdev::Shutdown(); #endif -#ifdef CIFACE_USE_CEMUHOOKUDPSERVER - ciface::CemuHookUDPServer::DeInit(); +#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT + ciface::DualShockUDPClient::DeInit(); #endif } diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index f1a8e0c19e..359bd8f0c3 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -29,7 +29,7 @@ #if defined(USE_PIPES) #define CIFACE_USE_PIPES #endif -#define CIFACE_USE_CEMUHOOKUDPSERVER +#define CIFACE_USE_DUALSHOCKUDPCLIENT // // ControllerInterface diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp new file mode 100644 index 0000000000..60086d517c --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -0,0 +1,444 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h" + +#include +#include + +#include +#include + +#include "Common/Config/Config.h" +#include "Common/Flag.h" +#include "Common/Logging/Log.h" +#include "Common/MathUtil.h" +#include "Common/Matrix.h" +#include "Common/Random.h" +#include "Common/Thread.h" +#include "Core/CoreTiming.h" +#include "Core/HW/SystemTimers.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h" + +namespace ciface::DualShockUDPClient +{ +class Device : public Core::Device +{ +private: + template + class Button : public Core::Device::Input + { + public: + Button(std::string name, const T& buttons, unsigned mask) + : m_name(std::move(name)), m_buttons(buttons), m_mask(mask) + { + } + std::string GetName() const override { return m_name; } + ControlState GetState() const override { return (m_buttons & m_mask) != 0; } + + private: + const std::string m_name; + const T& m_buttons; + unsigned m_mask; + }; + + template + class AnalogInput : public Core::Device::Input + { + public: + AnalogInput(std::string name, const T& input, ControlState range, ControlState offset = 0) + : m_name(std::move(name)), m_input(input), m_range(range), m_offset(offset) + { + } + std::string GetName() const override { return m_name; } + ControlState GetState() const override { return (ControlState(m_input) + m_offset) / m_range; } + + private: + const std::string m_name; + const T& m_input; + const ControlState m_range; + const ControlState m_offset; + }; + + class TouchInput : public AnalogInput + { + public: + TouchInput(std::string name, const int& input, ControlState range) + : AnalogInput(std::move(name), input, range) + { + } + bool IsDetectable() override { return false; } + }; + + class AccelerometerInput : public AnalogInput + { + public: + AccelerometerInput(std::string name, const double& input, ControlState range) + : AnalogInput(std::move(name), input, range) + { + } + bool IsDetectable() override { return false; } + }; + + using GyroInput = AccelerometerInput; + +public: + void UpdateInput() override; + + Device(Proto::DsModel model, int index); + + std::string GetName() const final override; + std::string GetSource() const final override; + std::optional GetPreferredId() const final override; + +private: + const Proto::DsModel m_model; + const int m_index; + u32 m_client_uid; + sf::UdpSocket m_socket; + Common::DVec3 m_accel; + Common::DVec3 m_gyro; + std::chrono::steady_clock::time_point m_next_reregister; + Proto::MessageType::PadDataResponse m_pad_data; + Proto::Touch m_prev_touch; + bool m_prev_touch_valid; + int m_touch_x; + int m_touch_y; +}; + +static constexpr double GRAVITY_ACCELERATION = 9.80665; +static constexpr char DEFAULT_SERVER_ADDRESS[] = "127.0.0.1"; +static constexpr u16 DEFAULT_SERVER_PORT = 26760; +static constexpr auto SERVER_REREGISTER_INTERVAL = std::chrono::seconds{1}; +static constexpr auto SERVER_LISTPORTS_INTERVAL = std::chrono::seconds{1}; +static constexpr int TOUCH_X_AXIS_MAX = 1000; +static constexpr int TOUCH_Y_AXIS_MAX = 500; + +namespace Settings +{ +const Config::ConfigInfo SERVER_ENABLED{ + {Config::System::DualShockUDPClient, "Server", "Enabled"}, false}; +const Config::ConfigInfo SERVER_ADDRESS{ + {Config::System::DualShockUDPClient, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS}; +const Config::ConfigInfo SERVER_PORT{{Config::System::DualShockUDPClient, "Server", "Port"}, + DEFAULT_SERVER_PORT}; +} // namespace Settings + +static bool s_server_enabled; +static std::string s_server_address; +static u16 s_server_port; +static u32 s_client_uid; +static std::chrono::steady_clock::time_point s_next_listports; +static std::thread s_hotplug_thread; +static Common::Flag s_hotplug_thread_running; +static std::mutex s_port_info_mutex; +static Proto::MessageType::PortInfo s_port_info[Proto::PORT_COUNT]; +static sf::UdpSocket s_socket; + +static bool IsSameController(const Proto::MessageType::PortInfo& a, + const Proto::MessageType::PortInfo& b) +{ + // compare everything but battery_status + return a.pad_id == b.pad_id && a.pad_state == b.pad_state && a.model == b.model && + a.connection_type == b.connection_type && + memcmp(a.pad_mac_address, b.pad_mac_address, sizeof a.pad_mac_address) == 0; +} + +static sf::Socket::Status ReceiveWithTimeout(sf::UdpSocket& socket, void* data, std::size_t size, + std::size_t& received, sf::IpAddress& remoteAddress, + unsigned short& remotePort, sf::Time timeout) +{ + sf::SocketSelector selector; + selector.add(socket); + if (selector.wait(timeout)) + return socket.receive(data, size, received, remoteAddress, remotePort); + else + return sf::Socket::NotReady; +} + +static void HotplugThreadFunc() +{ + Common::SetCurrentThreadName("DualShockUDPClient Hotplug Thread"); + NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient hotplug thread started"); + + while (s_hotplug_thread_running.IsSet()) + { + const auto now = std::chrono::steady_clock::now(); + if (now >= s_next_listports) + { + s_next_listports = now + SERVER_LISTPORTS_INTERVAL; + + // Request info on the four controller ports + Proto::Message msg(s_client_uid); + auto& list_ports = msg.m_message; + list_ports.pad_request_count = 4; + list_ports.pad_id[0] = 0; + list_ports.pad_id[1] = 1; + list_ports.pad_id[2] = 2; + list_ports.pad_id[3] = 3; + msg.Finish(); + if (s_socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) != + sf::Socket::Status::Done) + ERROR_LOG(SERIALINTERFACE, "DualShockUDPClient HotplugThreadFunc send failed"); + } + + // Receive controller port info + Proto::Message msg; + const auto timeout = s_next_listports - std::chrono::steady_clock::now(); + // ReceiveWithTimeout treats a timeout of zero as infinite timeout, which we don't want + auto timeout_ms = std::chrono::duration_cast(timeout).count(); + timeout_ms = std::max(timeout_ms, 1); + std::size_t received_bytes; + sf::IpAddress sender; + u16 port; + if (ReceiveWithTimeout(s_socket, &msg, sizeof(msg), received_bytes, sender, port, + sf::milliseconds(timeout_ms)) == sf::Socket::Status::Done) + { + if (auto port_info = msg.CheckAndCastTo()) + { + const bool port_changed = !IsSameController(*port_info, s_port_info[port_info->pad_id]); + { + std::lock_guard lock(s_port_info_mutex); + s_port_info[port_info->pad_id] = *port_info; + } + if (port_changed) + PopulateDevices(); + } + } + } + NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient hotplug thread stopped"); +} + +static void StartHotplugThread() +{ + // Mark the thread as running. + if (!s_hotplug_thread_running.TestAndSet()) + { + // It was already running. + return; + } + + s_hotplug_thread = std::thread(HotplugThreadFunc); +} + +static void StopHotplugThread() +{ + // Tell the hotplug thread to stop. + if (!s_hotplug_thread_running.TestAndClear()) + { + // It wasn't running, we're done. + return; + } + + s_socket.unbind(); // interrupt blocking socket + s_hotplug_thread.join(); +} + +static void Restart() +{ + NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient Restart"); + + StopHotplugThread(); + + s_client_uid = Common::Random::GenerateValue(); + s_next_listports = std::chrono::steady_clock::time_point::min(); + for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) + { + s_port_info[port_index] = {}; + s_port_info[port_index].pad_id = port_index; + } + + PopulateDevices(); // remove devices + + if (s_server_enabled) + StartHotplugThread(); +} + +static void ConfigChanged() +{ + bool server_enabled = Config::Get(Settings::SERVER_ENABLED); + std::string server_address = Config::Get(Settings::SERVER_ADDRESS); + u16 server_port = Config::Get(Settings::SERVER_PORT); + if (server_enabled != s_server_enabled || server_address != s_server_address || + server_port != s_server_port) + { + s_server_enabled = server_enabled; + s_server_address = server_address; + s_server_port = server_port; + Restart(); + } +} + +void Init() +{ + Config::AddConfigChangedCallback(ConfigChanged); +} + +void PopulateDevices() +{ + NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient PopulateDevices"); + + g_controller_interface.RemoveDevice( + [](const auto* dev) { return dev->GetSource() == "DSUClient"; }); + + std::lock_guard lock(s_port_info_mutex); + for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) + { + Proto::MessageType::PortInfo port_info = s_port_info[port_index]; + if (port_info.pad_state == Proto::DsState::Connected) + g_controller_interface.AddDevice(std::make_shared(port_info.model, port_index)); + } +} + +void DeInit() +{ + StopHotplugThread(); +} + +Device::Device(Proto::DsModel model, int index) + : m_model(model), m_index(index), + m_client_uid(Common::Random::GenerateValue()), m_accel{}, m_gyro{}, + m_next_reregister(std::chrono::steady_clock::time_point::min()), m_pad_data{}, m_prev_touch{}, + m_prev_touch_valid(false), m_touch_x(0), m_touch_y(0) +{ + m_socket.setBlocking(false); + + AddInput(new AnalogInput("Pad W", m_pad_data.button_dpad_left_analog, 255)); + AddInput(new AnalogInput("Pad S", m_pad_data.button_dpad_down_analog, 255)); + AddInput(new AnalogInput("Pad E", m_pad_data.button_dpad_right_analog, 255)); + AddInput(new AnalogInput("Pad N", m_pad_data.button_dpad_up_analog, 255)); + AddInput(new AnalogInput("Square", m_pad_data.button_square_analog, 255)); + AddInput(new AnalogInput("Cross", m_pad_data.button_cross_analog, 255)); + AddInput(new AnalogInput("Circle", m_pad_data.button_circle_analog, 255)); + AddInput(new AnalogInput("Triangle", m_pad_data.button_triangle_analog, 255)); + AddInput(new AnalogInput("L1", m_pad_data.button_l1_analog, 255)); + AddInput(new AnalogInput("R1", m_pad_data.button_r1_analog, 255)); + + AddInput(new AnalogInput("L2", m_pad_data.trigger_l2, 255)); + AddInput(new AnalogInput("R2", m_pad_data.trigger_r2, 255)); + + AddInput(new Button("L3", m_pad_data.button_states1, 0x2)); + AddInput(new Button("R3", m_pad_data.button_states1, 0x4)); + AddInput(new Button("Share", m_pad_data.button_states1, 0x1)); + AddInput(new Button("Options", m_pad_data.button_states1, 0x8)); + AddInput(new Button("PS", m_pad_data.button_ps, 0x1)); + AddInput(new Button("Touch Button", m_pad_data.button_touch, 0x1)); + + AddInput(new AnalogInput("Left X-", m_pad_data.left_stick_x, -128, -128)); + AddInput(new AnalogInput("Left X+", m_pad_data.left_stick_x, 127, -128)); + AddInput(new AnalogInput("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128)); + AddInput(new AnalogInput("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128)); + AddInput(new AnalogInput("Right X-", m_pad_data.right_stick_x, -128, -128)); + AddInput(new AnalogInput("Right X+", m_pad_data.right_stick_x, 127, -128)); + AddInput(new AnalogInput("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128)); + AddInput(new AnalogInput("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128)); + + AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX)); + AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX)); + AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX)); + AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX)); + + AddInput(new AccelerometerInput("Accel Left", m_accel.x, 1)); + AddInput(new AccelerometerInput("Accel Right", m_accel.x, -1)); + AddInput(new AccelerometerInput("Accel Backward", m_accel.y, 1)); + AddInput(new AccelerometerInput("Accel Forward", m_accel.y, -1)); + AddInput(new AccelerometerInput("Accel Up", m_accel.z, 1)); + AddInput(new AccelerometerInput("Accel Down", m_accel.z, -1)); + + AddInput(new GyroInput("Gyro Pitch Up", m_gyro.x, -1)); + AddInput(new GyroInput("Gyro Pitch Down", m_gyro.x, 1)); + AddInput(new GyroInput("Gyro Roll Right", m_gyro.y, -1)); + AddInput(new GyroInput("Gyro Roll Left", m_gyro.y, 1)); + AddInput(new GyroInput("Gyro Yaw Right", m_gyro.z, -1)); + AddInput(new GyroInput("Gyro Yaw Left", m_gyro.z, 1)); +} + +std::string Device::GetName() const +{ + switch (m_model) + { + case Proto::DsModel::None: + return "None"; + case Proto::DsModel::DS3: + return "DualShock 3"; + case Proto::DsModel::DS4: + return "DualShock 4"; + case Proto::DsModel::Generic: + return "Generic Gamepad"; + default: + return "Device"; + } +} + +std::string Device::GetSource() const +{ + return "DSUClient"; +} + +void Device::UpdateInput() +{ + // Regularly tell the UDP server to feed us controller data + const auto now = std::chrono::steady_clock::now(); + if (now >= m_next_reregister) + { + m_next_reregister = now + SERVER_REREGISTER_INTERVAL; + + Proto::Message msg(m_client_uid); + auto& data_req = msg.m_message; + data_req.register_flags = Proto::RegisterFlags::PadID; + data_req.pad_id_to_register = m_index; + msg.Finish(); + if (m_socket.send(&data_req, sizeof(data_req), s_server_address, s_server_port) != + sf::Socket::Status::Done) + ERROR_LOG(SERIALINTERFACE, "DualShockUDPClient UpdateInput send failed"); + } + + // Receive and handle controller data + Proto::Message msg; + std::size_t received_bytes; + sf::IpAddress sender; + u16 port; + while (m_socket.receive(&msg, sizeof msg, received_bytes, sender, port) == + sf::Socket::Status::Done) + { + if (auto pad_data = msg.CheckAndCastTo()) + { + m_pad_data = *pad_data; + + m_accel.x = m_pad_data.accelerometer_x_g; + m_accel.z = -m_pad_data.accelerometer_y_g; + m_accel.y = -m_pad_data.accelerometer_z_inverted_g; + m_gyro.x = -m_pad_data.gyro_pitch_deg_s; + m_gyro.z = -m_pad_data.gyro_yaw_deg_s; + m_gyro.y = -m_pad_data.gyro_roll_deg_s; + + // Convert Gs to meters per second squared + m_accel = m_accel * GRAVITY_ACCELERATION; + + // Convert degrees per second to radians per second + m_gyro = m_gyro * (MathUtil::TAU / 360); + + // Update touch pad relative coordinates + if (m_pad_data.touch1.id != m_prev_touch.id) + m_prev_touch_valid = false; + if (m_prev_touch_valid) + { + m_touch_x += m_pad_data.touch1.x - m_prev_touch.x; + m_touch_y += m_pad_data.touch1.y - m_prev_touch.y; + m_touch_x = std::clamp(m_touch_x, -TOUCH_X_AXIS_MAX, TOUCH_X_AXIS_MAX); + m_touch_y = std::clamp(m_touch_y, -TOUCH_Y_AXIS_MAX, TOUCH_Y_AXIS_MAX); + } + m_prev_touch = m_pad_data.touch1; + m_prev_touch_valid = true; + } + } +} + +std::optional Device::GetPreferredId() const +{ + return m_index; +} + +} // namespace ciface::DualShockUDPClient diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h new file mode 100644 index 0000000000..b4bb1210c6 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h @@ -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 SERVER_ENABLED; +extern const Config::ConfigInfo SERVER_ADDRESS; +extern const Config::ConfigInfo SERVER_PORT; +} // namespace Settings + +void Init(); +void PopulateDevices(); +void DeInit(); +} // namespace ciface::DualShockUDPClient diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h new file mode 100644 index 0000000000..45980bcd5d --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h @@ -0,0 +1,270 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include + +#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(buffer), length); +} + +template +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 + std::optional 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 diff --git a/Source/Core/InputCommon/InputCommon.vcxproj b/Source/Core/InputCommon/InputCommon.vcxproj index e3d252cb5f..262ba2f52e 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj +++ b/Source/Core/InputCommon/InputCommon.vcxproj @@ -56,13 +56,13 @@ - + @@ -100,8 +100,6 @@ - - @@ -109,6 +107,8 @@ + + diff --git a/Source/Core/InputCommon/InputCommon.vcxproj.filters b/Source/Core/InputCommon/InputCommon.vcxproj.filters index ca29eec811..0c8d79326f 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj.filters +++ b/Source/Core/InputCommon/InputCommon.vcxproj.filters @@ -28,8 +28,8 @@ {07bad1aa-7e03-4f5c-ade2-a44857c5cbc3} - - {4f71c21c-85eb-4e76-ad64-40fd97bc3d6d} + + {ff02580e-3a62-4de4-a135-3a6c2c339a90} @@ -126,15 +126,15 @@ ControllerEmu\ControlGroup - - ControllerInterface\CemuHookUDPServer - ControllerEmu\ControlGroup ControllerEmu\ControlGroup + + ControllerInterface\DualShockUDPClient + @@ -224,9 +224,6 @@ ControllerInterface - - ControllerInterface - ControllerEmu\ControlGroup @@ -234,17 +231,18 @@ ControllerEmu\ControlGroup - - ControllerInterface\CemuHookUDPServer - ControllerEmu\ControlGroup ControllerEmu\ControlGroup - - ControllerInterface\CemuHookUDPServer + + + ControllerInterface\DualShockUDPClient + + + ControllerInterface\DualShockUDPClient -- cgit v1.2.3