summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface/DualShockUDPClient')
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp444
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h19
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h270
3 files changed, 733 insertions, 0 deletions
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 <chrono>
+#include <cstring>
+
+#include <SFML/Network/SocketSelector.hpp>
+#include <SFML/Network/UdpSocket.hpp>
+
+#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 T>
+ 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 T>
+ 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<int>
+ {
+ 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<double>
+ {
+ 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<int> 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<bool> SERVER_ENABLED{
+ {Config::System::DualShockUDPClient, "Server", "Enabled"}, false};
+const Config::ConfigInfo<std::string> SERVER_ADDRESS{
+ {Config::System::DualShockUDPClient, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS};
+const Config::ConfigInfo<int> 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<Proto::MessageType::ListPorts> 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<Proto::MessageType::FromServer> 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<std::chrono::milliseconds>(timeout).count();
+ timeout_ms = std::max<decltype(timeout_ms)>(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<Proto::MessageType::PortInfo>())
+ {
+ const bool port_changed = !IsSameController(*port_info, s_port_info[port_info->pad_id]);
+ {
+ std::lock_guard<std::mutex> 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<u32>();
+ 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<std::mutex> 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<Device>(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<u32>()), 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<u8>("Pad W", m_pad_data.button_dpad_left_analog, 255));
+ AddInput(new AnalogInput<u8>("Pad S", m_pad_data.button_dpad_down_analog, 255));
+ AddInput(new AnalogInput<u8>("Pad E", m_pad_data.button_dpad_right_analog, 255));
+ AddInput(new AnalogInput<u8>("Pad N", m_pad_data.button_dpad_up_analog, 255));
+ AddInput(new AnalogInput<u8>("Square", m_pad_data.button_square_analog, 255));
+ AddInput(new AnalogInput<u8>("Cross", m_pad_data.button_cross_analog, 255));
+ AddInput(new AnalogInput<u8>("Circle", m_pad_data.button_circle_analog, 255));
+ AddInput(new AnalogInput<u8>("Triangle", m_pad_data.button_triangle_analog, 255));
+ AddInput(new AnalogInput<u8>("L1", m_pad_data.button_l1_analog, 255));
+ AddInput(new AnalogInput<u8>("R1", m_pad_data.button_r1_analog, 255));
+
+ AddInput(new AnalogInput<u8>("L2", m_pad_data.trigger_l2, 255));
+ AddInput(new AnalogInput<u8>("R2", m_pad_data.trigger_r2, 255));
+
+ AddInput(new Button<u8>("L3", m_pad_data.button_states1, 0x2));
+ AddInput(new Button<u8>("R3", m_pad_data.button_states1, 0x4));
+ AddInput(new Button<u8>("Share", m_pad_data.button_states1, 0x1));
+ AddInput(new Button<u8>("Options", m_pad_data.button_states1, 0x8));
+ AddInput(new Button<u8>("PS", m_pad_data.button_ps, 0x1));
+ AddInput(new Button<u8>("Touch Button", m_pad_data.button_touch, 0x1));
+
+ AddInput(new AnalogInput<u8>("Left X-", m_pad_data.left_stick_x, -128, -128));
+ AddInput(new AnalogInput<u8>("Left X+", m_pad_data.left_stick_x, 127, -128));
+ AddInput(new AnalogInput<u8>("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128));
+ AddInput(new AnalogInput<u8>("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128));
+ AddInput(new AnalogInput<u8>("Right X-", m_pad_data.right_stick_x, -128, -128));
+ AddInput(new AnalogInput<u8>("Right X+", m_pad_data.right_stick_x, 127, -128));
+ AddInput(new AnalogInput<u8>("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128));
+ AddInput(new AnalogInput<u8>("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<Proto::MessageType::PadDataRequest> 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<Proto::MessageType::FromServer> 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<Proto::MessageType::PadDataResponse>())
+ {
+ 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<int> 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<bool> SERVER_ENABLED;
+extern const Config::ConfigInfo<std::string> SERVER_ADDRESS;
+extern const Config::ConfigInfo<int> SERVER_PORT;
+} // namespace Settings
+
+void Init();
+void PopulateDevices();
+void DeInit();
+} // namespace ciface::DualShockUDPClient
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 <optional>
+
+#include <zlib.h>
+
+#include "Common/CommonTypes.h"
+
+namespace ciface::DualShockUDPClient::Proto
+{
+// CemuHook DualShockUDP protocol implementation using UdpServer.cs from
+// https://github.com/Ryochan7/DS4Windows as documentation.
+//
+// WARNING: Little endian host assumed
+
+static constexpr u16 CEMUHOOK_PROTOCOL_VERSION = 1001;
+
+static constexpr int PORT_COUNT = 4;
+
+static constexpr char CLIENT[] = "DSUC";
+static constexpr char SERVER[] = "DSUS";
+
+#pragma pack(push, 1)
+
+enum class DsState : u8
+{
+ Disconnected = 0x00,
+ Reserved = 0x01,
+ Connected = 0x02
+};
+
+enum class DsConnection : u8
+{
+ None = 0x00,
+ Usb = 0x01,
+ Bluetooth = 0x02
+};
+
+enum class DsModel : u8
+{
+ None = 0,
+ DS3 = 1,
+ DS4 = 2,
+ Generic = 3
+};
+
+enum class DsBattery : u8
+{
+ None = 0x00,
+ Dying = 0x01,
+ Low = 0x02,
+ Medium = 0x03,
+ High = 0x04,
+ Full = 0x05,
+ Charging = 0xEE,
+ Charged = 0xEF
+};
+
+enum RegisterFlags : u8
+{
+ AllPads = 0x00,
+ PadID = 0x01,
+ PadMACAdddress = 0x02
+};
+
+struct MessageHeader
+{
+ u8 source[4];
+ u16 protocol_version;
+ u16 message_length; // actually message size minus header size
+ u32 crc32;
+ u32 source_uid;
+};
+
+struct Touch
+{
+ u8 active;
+ u8 id;
+ s16 x;
+ s16 y;
+};
+
+namespace MessageType
+{
+struct VersionRequest
+{
+ static constexpr auto FROM = CLIENT;
+ static constexpr auto TYPE = 0x100000U;
+ MessageHeader header;
+ u32 message_type;
+};
+
+struct VersionResponse
+{
+ static constexpr auto FROM = SERVER;
+ static constexpr auto TYPE = 0x100000U;
+ MessageHeader header;
+ u32 message_type;
+ u16 max_protocol_version;
+ u8 padding[2];
+};
+
+struct ListPorts
+{
+ static constexpr auto FROM = CLIENT;
+ static constexpr auto TYPE = 0x100001U;
+ MessageHeader header;
+ u32 message_type;
+ u32 pad_request_count;
+ u8 pad_id[4];
+};
+
+struct PortInfo
+{
+ static constexpr auto FROM = SERVER;
+ static constexpr auto TYPE = 0x100001U;
+ MessageHeader header;
+ u32 message_type;
+ u8 pad_id;
+ DsState pad_state;
+ DsModel model;
+ DsConnection connection_type;
+ u8 pad_mac_address[6];
+ DsBattery battery_status;
+ u8 padding;
+};
+
+struct PadDataRequest
+{
+ static constexpr auto FROM = CLIENT;
+ static constexpr auto TYPE = 0x100002U;
+ MessageHeader header;
+ u32 message_type;
+ RegisterFlags register_flags;
+ u8 pad_id_to_register;
+ u8 mac_address_to_register[6];
+};
+
+struct PadDataResponse
+{
+ static constexpr auto FROM = SERVER;
+ static constexpr auto TYPE = 0x100002U;
+ MessageHeader header;
+ u32 message_type;
+ u8 pad_id;
+ DsState pad_state;
+ DsModel model;
+ DsConnection connection_type;
+ u8 pad_mac_address[6];
+ DsBattery battery_status;
+ u8 active;
+ u32 hid_packet_counter;
+ u8 button_states1;
+ u8 button_states2;
+ u8 button_ps;
+ u8 button_touch;
+ u8 left_stick_x;
+ u8 left_stick_y_inverted;
+ u8 right_stick_x;
+ u8 right_stick_y_inverted;
+ u8 button_dpad_left_analog;
+ u8 button_dpad_down_analog;
+ u8 button_dpad_right_analog;
+ u8 button_dpad_up_analog;
+ u8 button_square_analog;
+ u8 button_cross_analog;
+ u8 button_circle_analog;
+ u8 button_triangle_analog;
+ u8 button_r1_analog;
+ u8 button_l1_analog;
+ u8 trigger_r2;
+ u8 trigger_l2;
+ Touch touch1;
+ Touch touch2;
+ u64 timestamp_us;
+ float accelerometer_x_g;
+ float accelerometer_y_g;
+ float accelerometer_z_inverted_g;
+ float gyro_pitch_deg_s;
+ float gyro_yaw_deg_s;
+ float gyro_roll_deg_s;
+};
+
+struct FromServer
+{
+ union
+ {
+ struct
+ {
+ MessageHeader header;
+ u32 message_type;
+ };
+ MessageType::VersionResponse version_response;
+ MessageType::PortInfo port_info;
+ MessageType::PadDataResponse pad_data_response;
+ };
+};
+
+struct FromClient
+{
+ union
+ {
+ struct
+ {
+ MessageHeader header;
+ u32 message_type;
+ };
+ MessageType::VersionRequest version_request;
+ MessageType::ListPorts list_ports;
+ MessageType::PadDataRequest pad_data_request;
+ };
+};
+} // namespace MessageType
+
+static inline u32 CRC32(const void* buffer, unsigned length)
+{
+ return crc32(crc32(0L, Z_NULL, 0), static_cast<const Bytef*>(buffer), length);
+}
+
+template <typename MsgType>
+struct Message
+{
+ Message() : m_message{} {}
+
+ explicit Message(u32 source_uid) : m_message{}
+ {
+ memcpy((char*)m_message.header.source, MsgType::FROM, sizeof(m_message.header.source));
+ m_message.header.protocol_version = CEMUHOOK_PROTOCOL_VERSION;
+ m_message.header.message_length = sizeof(*this) - sizeof(m_message.header);
+ m_message.header.source_uid = source_uid;
+ m_message.message_type = MsgType::TYPE;
+ }
+
+ void Finish() { m_message.header.crc32 = CRC32(&m_message, sizeof(m_message)); }
+
+ template <class ToMsgType>
+ std::optional<ToMsgType> CheckAndCastTo()
+ {
+ u32 crc32_in_header = m_message.header.crc32;
+ // zero out the crc32 in the packet once we got it since that's whats needed for calculation
+ m_message.header.crc32 = 0;
+ u32 crc32_calculated = CRC32(&m_message, sizeof(ToMsgType));
+ if (crc32_in_header != crc32_calculated)
+ {
+ NOTICE_LOG(SERIALINTERFACE,
+ "DualShockUDPClient Received message with bad CRC in header: got %u, expected %u",
+ crc32_in_header, crc32_calculated);
+ return std::nullopt;
+ }
+ if (m_message.header.protocol_version > CEMUHOOK_PROTOCOL_VERSION)
+ return std::nullopt;
+ if (memcmp(m_message.header.source, ToMsgType::FROM, sizeof(m_message.header.source)))
+ return std::nullopt;
+ if (m_message.message_type != ToMsgType::TYPE)
+ return std::nullopt;
+ if (m_message.header.message_length + sizeof(m_message.header) > sizeof(ToMsgType))
+ return std::nullopt;
+
+ ToMsgType tomsg;
+ memcpy(&tomsg, &m_message, sizeof(tomsg));
+ return tomsg;
+ }
+
+ MsgType m_message;
+};
+
+#pragma pack(pop)
+} // namespace ciface::DualShockUDPClient::Proto