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". --- .../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 +++++++++++++ 8 files changed, 742 insertions(+), 742 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/ControllerInterface') 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 -- cgit v1.2.3