From ac3690f2057fb93ce18f156ff5ffd720a6d6f60c Mon Sep 17 00:00:00 2001 From: fearlessTobi Date: Sat, 24 Aug 2019 15:57:49 +0200 Subject: Input: UDP Client to provide motion and touch controls An implementation of the cemuhook motion/touch protocol, this adds the ability for users to connect several different devices to citra to send direct motion and touch data to citra. Co-Authored-By: jroweboy --- src/input_common/udp/client.cpp | 283 ++++++++++++++++++++++++++++++++++++++ src/input_common/udp/client.h | 96 +++++++++++++ src/input_common/udp/protocol.cpp | 79 +++++++++++ src/input_common/udp/protocol.h | 249 +++++++++++++++++++++++++++++++++ src/input_common/udp/udp.cpp | 96 +++++++++++++ src/input_common/udp/udp.h | 27 ++++ 6 files changed, 830 insertions(+) create mode 100644 src/input_common/udp/client.cpp create mode 100644 src/input_common/udp/client.h create mode 100644 src/input_common/udp/protocol.cpp create mode 100644 src/input_common/udp/protocol.h create mode 100644 src/input_common/udp/udp.cpp create mode 100644 src/input_common/udp/udp.h (limited to 'src/input_common/udp') diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp new file mode 100644 index 000000000..c31236c7c --- /dev/null +++ b/src/input_common/udp/client.cpp @@ -0,0 +1,283 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/logging/log.h" +#include "input_common/udp/client.h" +#include "input_common/udp/protocol.h" + +using boost::asio::ip::address_v4; +using boost::asio::ip::udp; + +namespace InputCommon::CemuhookUDP { + +struct SocketCallback { + std::function version; + std::function port_info; + std::function pad_data; +}; + +class Socket { +public: + using clock = std::chrono::system_clock; + + explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, + SocketCallback callback) + : client_id(client_id), timer(io_service), + send_endpoint(udp::endpoint(address_v4::from_string(host), port)), + socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index), + callback(std::move(callback)) {} + + void Stop() { + io_service.stop(); + } + + void Loop() { + io_service.run(); + } + + void StartSend(const clock::time_point& from) { + timer.expires_at(from + std::chrono::seconds(3)); + timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); + } + + void StartReceive() { + socket.async_receive_from( + boost::asio::buffer(receive_buffer), receive_endpoint, + [this](const boost::system::error_code& error, std::size_t bytes_transferred) { + HandleReceive(error, bytes_transferred); + }); + } + +private: + void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { + if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { + switch (*type) { + case Type::Version: { + Response::Version version; + std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); + callback.version(std::move(version)); + break; + } + case Type::PortInfo: { + Response::PortInfo port_info; + std::memcpy(&port_info, &receive_buffer[sizeof(Header)], + sizeof(Response::PortInfo)); + callback.port_info(std::move(port_info)); + break; + } + case Type::PadData: { + Response::PadData pad_data; + std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); + callback.pad_data(std::move(pad_data)); + break; + } + } + } + StartReceive(); + } + + void HandleSend(const boost::system::error_code& error) { + // Send a request for getting port info for the pad + Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; + auto port_message = Request::Create(port_info, client_id); + std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); + std::size_t len = socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); + + // Send a request for getting pad data for the pad + Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; + auto pad_message = Request::Create(pad_data, client_id); + std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); + std::size_t len2 = socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint); + StartSend(timer.expiry()); + } + + SocketCallback callback; + boost::asio::io_service io_service; + boost::asio::basic_waitable_timer timer; + udp::socket socket; + + u32 client_id; + u8 pad_index; + + static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); + static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); + std::array send_buffer1; + std::array send_buffer2; + udp::endpoint send_endpoint; + + std::array receive_buffer; + udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { + socket->StartReceive(); + socket->StartSend(Socket::clock::now()); + socket->Loop(); +} + +Client::Client(std::shared_ptr status, const std::string& host, u16 port, + u8 pad_index, u32 client_id) + : status(status) { + StartCommunication(host, port, pad_index, client_id); +} + +Client::~Client() { + socket->Stop(); + thread.join(); +} + +void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { + socket->Stop(); + thread.join(); + StartCommunication(host, port, pad_index, client_id); +} + +void Client::OnVersion(Response::Version data) { + LOG_TRACE(Input, "Version packet received: {}", data.version); +} + +void Client::OnPortInfo(Response::PortInfo data) { + LOG_TRACE(Input, "PortInfo packet received: {}", data.model); +} + +void Client::OnPadData(Response::PadData data) { + LOG_TRACE(Input, "PadData packet received"); + if (data.packet_counter <= packet_sequence) { + LOG_WARNING( + Input, + "PadData packet dropped because its stale info. Current count: {} Packet count: {}", + packet_sequence, data.packet_counter); + return; + } + packet_sequence = data.packet_counter; + // TODO: Check how the Switch handles motions and how the CemuhookUDP motion + // directions correspond to the ones of the Switch + Common::Vec3f accel = Common::MakeVec(data.accel.x, data.accel.y, data.accel.z); + Common::Vec3f gyro = Common::MakeVec(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); + { + std::lock_guard guard(status->update_mutex); + + status->motion_status = {accel, gyro}; + + // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates + // between a simple "tap" and a hard press that causes the touch screen to click. + bool is_active = data.touch_1.is_active != 0; + + float x = 0; + float y = 0; + + if (is_active && status->touch_calibration) { + u16 min_x = status->touch_calibration->min_x; + u16 max_x = status->touch_calibration->max_x; + u16 min_y = status->touch_calibration->min_y; + u16 max_y = status->touch_calibration->max_y; + + x = (std::clamp(static_cast(data.touch_1.x), min_x, max_x) - min_x) / + static_cast(max_x - min_x); + y = (std::clamp(static_cast(data.touch_1.y), min_y, max_y) - min_y) / + static_cast(max_y - min_y); + } + + status->touch_status = {x, y, is_active}; + } +} + +void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { + SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this](Response::PadData data) { OnPadData(data); }}; + LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); + socket = std::make_unique(host, port, pad_index, client_id, callback); + thread = std::thread{SocketLoop, this->socket.get()}; +} + +void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, + std::function success_callback, + std::function failure_callback) { + std::thread([=] { + Common::Event success_event; + SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, + [&](Response::PadData data) { success_event.Set(); }}; + Socket socket{host, port, pad_index, client_id, callback}; + std::thread worker_thread{SocketLoop, &socket}; + bool result = success_event.WaitFor(std::chrono::seconds(8)); + socket.Stop(); + worker_thread.join(); + if (result) + success_callback(); + else + failure_callback(); + }) + .detach(); +} + +CalibrationConfigurationJob::CalibrationConfigurationJob( + const std::string& host, u16 port, u8 pad_index, u32 client_id, + std::function status_callback, + std::function data_callback) { + + std::thread([=] { + constexpr u16 CALIBRATION_THRESHOLD = 100; + + u16 min_x{UINT16_MAX}, min_y{UINT16_MAX}; + u16 max_x, max_y; + + Status current_status{Status::Initialized}; + SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, + [&](Response::PadData data) { + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + if (!data.touch_1.is_active) + return; + LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, + data.touch_1.y); + min_x = std::min(min_x, static_cast(data.touch_1.x)); + min_y = std::min(min_y, static_cast(data.touch_1.y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD && + data.touch_1.y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes + // configuration + max_x = data.touch_1.x; + max_y = data.touch_1.y; + current_status = Status::Completed; + data_callback(min_x, min_y, max_x, max_y); + status_callback(current_status); + + complete_event.Set(); + } + }}; + Socket socket{host, port, pad_index, client_id, callback}; + std::thread worker_thread{SocketLoop, &socket}; + complete_event.Wait(); + socket.Stop(); + worker_thread.join(); + }) + .detach(); +} + +CalibrationConfigurationJob::~CalibrationConfigurationJob() { + Stop(); +} + +void CalibrationConfigurationJob::Stop() { + complete_event.Set(); +} + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h new file mode 100644 index 000000000..5177f46be --- /dev/null +++ b/src/input_common/udp/client.h @@ -0,0 +1,96 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/thread.h" +#include "common/vector_math.h" + +namespace InputCommon::CemuhookUDP { + +static constexpr u16 DEFAULT_PORT = 26760; +static constexpr const char* DEFAULT_ADDR = "127.0.0.1"; + +class Socket; + +namespace Response { +struct PadData; +struct PortInfo; +struct Version; +} // namespace Response + +struct DeviceStatus { + std::mutex update_mutex; + std::tuple, Common::Vec3> motion_status; + std::tuple touch_status; + + // calibration data for scaling the device's touch area to 3ds + struct CalibrationData { + u16 min_x; + u16 min_y; + u16 max_x; + u16 max_y; + }; + std::optional touch_calibration; +}; + +class Client { +public: + explicit Client(std::shared_ptr status, const std::string& host = DEFAULT_ADDR, + u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); + ~Client(); + void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, + u32 client_id = 24872); + +private: + void OnVersion(Response::Version); + void OnPortInfo(Response::PortInfo); + void OnPadData(Response::PadData); + void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); + + std::unique_ptr socket; + std::shared_ptr status; + std::thread thread; + u64 packet_sequence = 0; +}; + +/// An async job allowing configuration of the touchpad calibration. +class CalibrationConfigurationJob { +public: + enum class Status { + Initialized, + Ready, + Stage1Completed, + Completed, + }; + /** + * Constructs and starts the job with the specified parameter. + * + * @param status_callback Callback for job status updates + * @param data_callback Called when calibration data is ready + */ + explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, + u32 client_id, std::function status_callback, + std::function data_callback); + ~CalibrationConfigurationJob(); + void Stop(); + +private: + Common::Event complete_event; +}; + +void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, + std::function success_callback, + std::function failure_callback); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp new file mode 100644 index 000000000..d65069207 --- /dev/null +++ b/src/input_common/udp/protocol.cpp @@ -0,0 +1,79 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/logging/log.h" +#include "input_common/udp/protocol.h" + +namespace InputCommon::CemuhookUDP { + +static const std::size_t GetSizeOfResponseType(Type t) { + switch (t) { + case Type::Version: + return sizeof(Response::Version); + case Type::PortInfo: + return sizeof(Response::PortInfo); + case Type::PadData: + return sizeof(Response::PadData); + } + return 0; +} + +namespace Response { + +/** + * Returns Type if the packet is valid, else none + * + * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without + * copying the buffer) + */ +std::optional Validate(u8* data, std::size_t size) { + if (size < sizeof(Header)) { + LOG_DEBUG(Input, "Invalid UDP packet received"); + return {}; + } + Header header; + std::memcpy(&header, data, sizeof(Header)); + if (header.magic != SERVER_MAGIC) { + LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); + return {}; + } + if (header.protocol_version != PROTOCOL_VERSION) { + LOG_ERROR(Input, "UDP Packet protocol mismatch"); + return {}; + } + if (header.type < Type::Version || header.type > Type::PadData) { + LOG_ERROR(Input, "UDP Packet is an unknown type"); + return {}; + } + + // Packet size must equal sizeof(Header) + sizeof(Data) + // and also verify that the packet info mentions the correct size. Since the spec includes the + // type of the packet as part of the data, we need to include it in size calculations here + // ie: payload_length == sizeof(T) + sizeof(Type) + const std::size_t data_len = GetSizeOfResponseType(header.type); + if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) { + LOG_ERROR( + Input, + "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}", + size, header.payload_length, data_len + sizeof(Type)); + return {}; + } + + const u32 crc32 = header.crc; + boost::crc_32_type result; + // zero out the crc in the buffer and then run the crc against it + std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le)); + + result.process_bytes(data, data_len + sizeof(Header)); + if (crc32 != result.checksum()) { + LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc)); + return {}; + } + return header.type; +} +} // namespace Response + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h new file mode 100644 index 000000000..d31bbeb89 --- /dev/null +++ b/src/input_common/udp/protocol.h @@ -0,0 +1,249 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "common/bit_field.h" +#include "common/swap.h" + +namespace InputCommon::CemuhookUDP { + +constexpr std::size_t MAX_PACKET_SIZE = 100; +constexpr u16 PROTOCOL_VERSION = 1001; +constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE) +constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE) + +enum class Type : u32 { + Version = 0x00100000, + PortInfo = 0x00100001, + PadData = 0x00100002, +}; + +struct Header { + u32_le magic; + u16_le protocol_version; + u16_le payload_length; + u32_le crc; + u32_le id; + ///> In the protocol, the type of the packet is not part of the header, but its convenient to + ///> include in the header so the callee doesn't have to duplicate the type twice when building + ///> the data + Type type; +}; +static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size"); +static_assert(std::is_trivially_copyable_v
, "UDP Message Header is not trivially copyable"); + +using MacAddress = std::array; +constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0}; + +#pragma pack(push, 1) +template +struct Message { + Header header; + T data; +}; +#pragma pack(pop) + +template +constexpr Type GetMessageType(); + +namespace Request { + +struct Version {}; +/** + * Requests the server to send information about what controllers are plugged into the ports + * In citra's case, we only have one controller, so for simplicity's sake, we can just send a + * request explicitly for the first controller port and leave it at that. In the future it would be + * nice to make this configurable + */ +constexpr u32 MAX_PORTS = 4; +struct PortInfo { + u32_le pad_count; ///> Number of ports to request data for + std::array port; +}; +static_assert(std::is_trivially_copyable_v, + "UDP Request PortInfo is not trivially copyable"); + +/** + * Request the latest pad information from the server. If the server hasn't received this message + * from the client in a reasonable time frame, the server will stop sending updates. The default + * timeout seems to be 5 seconds. + */ +struct PadData { + enum class Flags : u8 { + AllPorts, + Id, + Mac, + }; + /// Determines which method will be used as a look up for the controller + Flags flags; + /// Index of the port of the controller to retrieve data about + u8 port_id; + /// Mac address of the controller to retrieve data about + MacAddress mac; +}; +static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size"); +static_assert(std::is_trivially_copyable_v, + "UDP Request PadData is not trivially copyable"); + +/** + * Creates a message with the proper header data that can be sent to the server. + * @param T data Request body to send + * @param client_id ID of the udp client (usually not checked on the server) + */ +template +Message Create(const T data, const u32 client_id = 0) { + boost::crc_32_type crc; + Header header{ + CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType(), + }; + Message message{header, data}; + crc.process_bytes(&message, sizeof(Message)); + message.header.crc = crc.checksum(); + return message; +} +} // namespace Request + +namespace Response { + +struct Version { + u16_le version; +}; +static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size"); +static_assert(std::is_trivially_copyable_v, + "UDP Response Version is not trivially copyable"); + +struct PortInfo { + u8 id; + u8 state; + u8 model; + u8 connection_type; + MacAddress mac; + u8 battery; + u8 is_pad_active; +}; +static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); +static_assert(std::is_trivially_copyable_v, + "UDP Response PortInfo is not trivially copyable"); + +#pragma pack(push, 1) +struct PadData { + PortInfo info; + u32_le packet_counter; + + u16_le digital_button; + // The following union isn't trivially copyable but we don't use this input anyway. + // union DigitalButton { + // u16_le button; + // BitField<0, 1, u16> button_1; // Share + // BitField<1, 1, u16> button_2; // L3 + // BitField<2, 1, u16> button_3; // R3 + // BitField<3, 1, u16> button_4; // Options + // BitField<4, 1, u16> button_5; // Up + // BitField<5, 1, u16> button_6; // Right + // BitField<6, 1, u16> button_7; // Down + // BitField<7, 1, u16> button_8; // Left + // BitField<8, 1, u16> button_9; // L2 + // BitField<9, 1, u16> button_10; // R2 + // BitField<10, 1, u16> button_11; // L1 + // BitField<11, 1, u16> button_12; // R1 + // BitField<12, 1, u16> button_13; // Triangle + // BitField<13, 1, u16> button_14; // Circle + // BitField<14, 1, u16> button_15; // Cross + // BitField<15, 1, u16> button_16; // Square + // } digital_button; + + u8 home; + /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens + u8 touch_hard_press; + u8 left_stick_x; + u8 left_stick_y; + u8 right_stick_x; + u8 right_stick_y; + + struct AnalogButton { + u8 button_8; + u8 button_7; + u8 button_6; + u8 button_5; + u8 button_12; + u8 button_11; + u8 button_10; + u8 button_9; + u8 button_16; + u8 button_15; + u8 button_14; + u8 button_13; + } analog_button; + + struct TouchPad { + u8 is_active; + u8 id; + u16_le x; + u16_le y; + } touch_1, touch_2; + + u64_le motion_timestamp; + + struct Accelerometer { + float x; + float y; + float z; + } accel; + + struct Gyroscope { + float pitch; + float yaw; + float roll; + } gyro; +}; +#pragma pack(pop) + +static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size "); +static_assert(std::is_trivially_copyable_v, + "UDP Response PadData is not trivially copyable"); + +static_assert(sizeof(Message) == MAX_PACKET_SIZE, + "UDP MAX_PACKET_SIZE is no longer larger than Message"); + +/** + * Create a Response Message from the data + * @param data array of bytes sent from the server + * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely + * copy the data into the appropriate struct for that Type + */ +std::optional Validate(u8* data, std::size_t size); + +} // namespace Response + +template <> +constexpr Type GetMessageType() { + return Type::Version; +} +template <> +constexpr Type GetMessageType() { + return Type::PortInfo; +} +template <> +constexpr Type GetMessageType() { + return Type::PadData; +} +template <> +constexpr Type GetMessageType() { + return Type::Version; +} +template <> +constexpr Type GetMessageType() { + return Type::PortInfo; +} +template <> +constexpr Type GetMessageType() { + return Type::PadData; +} +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp new file mode 100644 index 000000000..a80f38614 --- /dev/null +++ b/src/input_common/udp/udp.cpp @@ -0,0 +1,96 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "core/frontend/input.h" +#include "core/settings.h" +#include "input_common/udp/client.h" +#include "input_common/udp/udp.h" + +namespace InputCommon::CemuhookUDP { + +class UDPTouchDevice final : public Input::TouchDevice { +public: + explicit UDPTouchDevice(std::shared_ptr status_) : status(std::move(status_)) {} + std::tuple GetStatus() const { + std::lock_guard guard(status->update_mutex); + return status->touch_status; + } + +private: + std::shared_ptr status; +}; + +class UDPMotionDevice final : public Input::MotionDevice { +public: + explicit UDPMotionDevice(std::shared_ptr status_) : status(std::move(status_)) {} + std::tuple, Common::Vec3> GetStatus() const { + std::lock_guard guard(status->update_mutex); + return status->motion_status; + } + +private: + std::shared_ptr status; +}; + +class UDPTouchFactory final : public Input::Factory { +public: + explicit UDPTouchFactory(std::shared_ptr status_) : status(std::move(status_)) {} + + std::unique_ptr Create(const Common::ParamPackage& params) override { + { + std::lock_guard guard(status->update_mutex); + status->touch_calibration.emplace(); + // These default values work well for DS4 but probably not other touch inputs + status->touch_calibration->min_x = params.Get("min_x", 100); + status->touch_calibration->min_y = params.Get("min_y", 50); + status->touch_calibration->max_x = params.Get("max_x", 1800); + status->touch_calibration->max_y = params.Get("max_y", 850); + } + return std::make_unique(status); + } + +private: + std::shared_ptr status; +}; + +class UDPMotionFactory final : public Input::Factory { +public: + explicit UDPMotionFactory(std::shared_ptr status_) : status(std::move(status_)) {} + + std::unique_ptr Create(const Common::ParamPackage& params) override { + return std::make_unique(status); + } + +private: + std::shared_ptr status; +}; + +State::State() { + auto status = std::make_shared(); + client = + std::make_unique(status, Settings::values.udp_input_address, + Settings::values.udp_input_port, Settings::values.udp_pad_index); + + Input::RegisterFactory("cemuhookudp", + std::make_shared(status)); + Input::RegisterFactory("cemuhookudp", + std::make_shared(status)); +} + +State::~State() { + Input::UnregisterFactory("cemuhookudp"); + Input::UnregisterFactory("cemuhookudp"); +} + +void State::ReloadUDPClient() { + client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, + Settings::values.udp_pad_index); +} + +std::unique_ptr Init() { + return std::make_unique(); +} +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h new file mode 100644 index 000000000..ea3de60bb --- /dev/null +++ b/src/input_common/udp/udp.h @@ -0,0 +1,27 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "input_common/main.h" +#include "input_common/udp/client.h" + +namespace InputCommon::CemuhookUDP { + +class UDPTouchDevice; +class UDPMotionDevice; + +class State { +public: + State(); + ~State(); + void ReloadUDPClient(); + +private: + std::unique_ptr client; +}; + +std::unique_ptr Init(); + +} // namespace InputCommon::CemuhookUDP -- cgit v1.2.3 From 0fe11746fcb37de2465cdbbe74be6ad4a59228e5 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sun, 3 Nov 2019 07:04:28 +0100 Subject: Address review comments --- src/input_common/udp/client.cpp | 24 ++++----- src/input_common/udp/client.h | 8 +-- src/input_common/udp/protocol.cpp | 4 +- src/input_common/udp/protocol.h | 101 ++++++++++++++++++++------------------ 4 files changed, 72 insertions(+), 65 deletions(-) (limited to 'src/input_common/udp') diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index c31236c7c..3c51f72a0 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -88,15 +88,15 @@ private: void HandleSend(const boost::system::error_code& error) { // Send a request for getting port info for the pad Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; - auto port_message = Request::Create(port_info, client_id); + const auto port_message = Request::Create(port_info, client_id); std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); - std::size_t len = socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); + socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); // Send a request for getting pad data for the pad Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; - auto pad_message = Request::Create(pad_data, client_id); + const auto pad_message = Request::Create(pad_data, client_id); std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); - std::size_t len2 = socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint); + socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint); StartSend(timer.expiry()); } @@ -105,8 +105,8 @@ private: boost::asio::basic_waitable_timer timer; udp::socket socket; - u32 client_id; - u8 pad_index; + u32 client_id{}; + u8 pad_index{}; static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); @@ -170,16 +170,16 @@ void Client::OnPadData(Response::PadData data) { // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates // between a simple "tap" and a hard press that causes the touch screen to click. - bool is_active = data.touch_1.is_active != 0; + const bool is_active = data.touch_1.is_active != 0; float x = 0; float y = 0; if (is_active && status->touch_calibration) { - u16 min_x = status->touch_calibration->min_x; - u16 max_x = status->touch_calibration->max_x; - u16 min_y = status->touch_calibration->min_y; - u16 max_y = status->touch_calibration->max_y; + const u16 min_x = status->touch_calibration->min_x; + const u16 max_x = status->touch_calibration->max_x; + const u16 min_y = status->touch_calibration->min_y; + const u16 max_y = status->touch_calibration->max_y; x = (std::clamp(static_cast(data.touch_1.x), min_x, max_x) - min_x) / static_cast(max_x - min_x); @@ -229,7 +229,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( constexpr u16 CALIBRATION_THRESHOLD = 100; u16 min_x{UINT16_MAX}, min_y{UINT16_MAX}; - u16 max_x, max_y; + u16 max_x{}, max_y{}; Status current_status{Status::Initialized}; SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index 5177f46be..b06a3f85a 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -36,10 +36,10 @@ struct DeviceStatus { // calibration data for scaling the device's touch area to 3ds struct CalibrationData { - u16 min_x; - u16 min_y; - u16 max_x; - u16 max_y; + u16 min_x{}; + u16 min_y{}; + u16 max_x{}; + u16 max_y{}; }; std::optional touch_calibration; }; diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp index d65069207..16da706d5 100644 --- a/src/input_common/udp/protocol.cpp +++ b/src/input_common/udp/protocol.cpp @@ -9,7 +9,7 @@ namespace InputCommon::CemuhookUDP { -static const std::size_t GetSizeOfResponseType(Type t) { +static constexpr std::size_t GetSizeOfResponseType(Type t) { switch (t) { case Type::Version: return sizeof(Response::Version); @@ -34,7 +34,7 @@ std::optional Validate(u8* data, std::size_t size) { LOG_DEBUG(Input, "Invalid UDP packet received"); return {}; } - Header header; + Header header{}; std::memcpy(&header, data, sizeof(Header)); if (header.magic != SERVER_MAGIC) { LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h index d31bbeb89..1b521860a 100644 --- a/src/input_common/udp/protocol.h +++ b/src/input_common/udp/protocol.h @@ -26,15 +26,15 @@ enum class Type : u32 { }; struct Header { - u32_le magic; - u16_le protocol_version; - u16_le payload_length; - u32_le crc; - u32_le id; + u32_le magic{}; + u16_le protocol_version{}; + u16_le payload_length{}; + u32_le crc{}; + u32_le id{}; ///> In the protocol, the type of the packet is not part of the header, but its convenient to ///> include in the header so the callee doesn't have to duplicate the type twice when building ///> the data - Type type; + Type type{}; }; static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size"); static_assert(std::is_trivially_copyable_v
, "UDP Message Header is not trivially copyable"); @@ -45,7 +45,7 @@ constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0}; #pragma pack(push, 1) template struct Message { - Header header; + Header header{}; T data; }; #pragma pack(pop) @@ -64,7 +64,7 @@ struct Version {}; */ constexpr u32 MAX_PORTS = 4; struct PortInfo { - u32_le pad_count; ///> Number of ports to request data for + u32_le pad_count{}; ///> Number of ports to request data for std::array port; }; static_assert(std::is_trivially_copyable_v, @@ -82,9 +82,9 @@ struct PadData { Mac, }; /// Determines which method will be used as a look up for the controller - Flags flags; + Flags flags{}; /// Index of the port of the controller to retrieve data about - u8 port_id; + u8 port_id{}; /// Mac address of the controller to retrieve data about MacAddress mac; }; @@ -113,20 +113,20 @@ Message Create(const T data, const u32 client_id = 0) { namespace Response { struct Version { - u16_le version; + u16_le version{}; }; static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size"); static_assert(std::is_trivially_copyable_v, "UDP Response Version is not trivially copyable"); struct PortInfo { - u8 id; - u8 state; - u8 model; - u8 connection_type; + u8 id{}; + u8 state{}; + u8 model{}; + u8 connection_type{}; MacAddress mac; - u8 battery; - u8 is_pad_active; + u8 battery{}; + u8 is_pad_active{}; }; static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); static_assert(std::is_trivially_copyable_v, @@ -134,10 +134,10 @@ static_assert(std::is_trivially_copyable_v, #pragma pack(push, 1) struct PadData { - PortInfo info; - u32_le packet_counter; + PortInfo info{}; + u32_le packet_counter{}; - u16_le digital_button; + u16_le digital_button{}; // The following union isn't trivially copyable but we don't use this input anyway. // union DigitalButton { // u16_le button; @@ -161,46 +161,46 @@ struct PadData { u8 home; /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens - u8 touch_hard_press; - u8 left_stick_x; - u8 left_stick_y; - u8 right_stick_x; - u8 right_stick_y; + u8 touch_hard_press{}; + u8 left_stick_x{}; + u8 left_stick_y{}; + u8 right_stick_x{}; + u8 right_stick_y{}; struct AnalogButton { - u8 button_8; - u8 button_7; - u8 button_6; - u8 button_5; - u8 button_12; - u8 button_11; - u8 button_10; - u8 button_9; - u8 button_16; - u8 button_15; - u8 button_14; - u8 button_13; + u8 button_8{}; + u8 button_7{}; + u8 button_6{}; + u8 button_5{}; + u8 button_12{}; + u8 button_11{}; + u8 button_10{}; + u8 button_9{}; + u8 button_16{}; + u8 button_15{}; + u8 button_14{}; + u8 button_13{}; } analog_button; struct TouchPad { - u8 is_active; - u8 id; - u16_le x; - u16_le y; + u8 is_active{}; + u8 id{}; + u16_le x{}; + u16_le y{}; } touch_1, touch_2; u64_le motion_timestamp; struct Accelerometer { - float x; - float y; - float z; + float x{}; + float y{}; + float z{}; } accel; struct Gyroscope { - float pitch; - float yaw; - float roll; + float pitch{}; + float yaw{}; + float roll{}; } gyro; }; #pragma pack(pop) @@ -212,6 +212,13 @@ static_assert(std::is_trivially_copyable_v, static_assert(sizeof(Message) == MAX_PACKET_SIZE, "UDP MAX_PACKET_SIZE is no longer larger than Message"); +static_assert(sizeof(PadData::AnalogButton) == 12, + "UDP Response AnalogButton struct has wrong size "); +static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); +static_assert(sizeof(PadData::Accelerometer) == 12, + "UDP Response Accelerometer struct has wrong size "); +static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size "); + /** * Create a Response Message from the data * @param data array of bytes sent from the server -- cgit v1.2.3 From bbd85a495a3576a5ec99cd69b54e983653b38ea4 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sun, 3 Nov 2019 08:07:04 +0100 Subject: Address second part of review comments --- src/input_common/udp/client.cpp | 14 +++++++++----- src/input_common/udp/client.h | 4 ++-- src/input_common/udp/protocol.cpp | 12 ++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) (limited to 'src/input_common/udp') diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 3c51f72a0..5f5a9989c 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -212,10 +212,11 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie bool result = success_event.WaitFor(std::chrono::seconds(8)); socket.Stop(); worker_thread.join(); - if (result) + if (result) { success_callback(); - else + } else { failure_callback(); + } }) .detach(); } @@ -228,8 +229,10 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( std::thread([=] { constexpr u16 CALIBRATION_THRESHOLD = 100; - u16 min_x{UINT16_MAX}, min_y{UINT16_MAX}; - u16 max_x{}, max_y{}; + u16 min_x{UINT16_MAX}; + u16 min_y{UINT16_MAX}; + u16 max_x{}; + u16 max_y{}; Status current_status{Status::Initialized}; SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, @@ -239,8 +242,9 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( current_status = Status::Ready; status_callback(current_status); } - if (!data.touch_1.is_active) + if (!data.touch_1.is_active) { return; + } LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, data.touch_1.y); min_x = std::min(min_x, static_cast(data.touch_1.x)); diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index b06a3f85a..0b21f4da6 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -18,8 +18,8 @@ namespace InputCommon::CemuhookUDP { -static constexpr u16 DEFAULT_PORT = 26760; -static constexpr const char* DEFAULT_ADDR = "127.0.0.1"; +constexpr u16 DEFAULT_PORT = 26760; +constexpr char DEFAULT_ADDR[] = "127.0.0.1"; class Socket; diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp index 16da706d5..a982ac49d 100644 --- a/src/input_common/udp/protocol.cpp +++ b/src/input_common/udp/protocol.cpp @@ -32,21 +32,21 @@ namespace Response { std::optional Validate(u8* data, std::size_t size) { if (size < sizeof(Header)) { LOG_DEBUG(Input, "Invalid UDP packet received"); - return {}; + return std::nullopt; } Header header{}; std::memcpy(&header, data, sizeof(Header)); if (header.magic != SERVER_MAGIC) { LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); - return {}; + return std::nullopt; } if (header.protocol_version != PROTOCOL_VERSION) { LOG_ERROR(Input, "UDP Packet protocol mismatch"); - return {}; + return std::nullopt; } if (header.type < Type::Version || header.type > Type::PadData) { LOG_ERROR(Input, "UDP Packet is an unknown type"); - return {}; + return std::nullopt; } // Packet size must equal sizeof(Header) + sizeof(Data) @@ -59,7 +59,7 @@ std::optional Validate(u8* data, std::size_t size) { Input, "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}", size, header.payload_length, data_len + sizeof(Type)); - return {}; + return std::nullopt; } const u32 crc32 = header.crc; @@ -70,7 +70,7 @@ std::optional Validate(u8* data, std::size_t size) { result.process_bytes(data, data_len + sizeof(Header)); if (crc32 != result.checksum()) { LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc)); - return {}; + return std::nullopt; } return header.type; } -- cgit v1.2.3