diff options
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface/DualShockUDPClient')
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 |
