diff options
Diffstat (limited to 'src/input_common/udp')
-rw-r--r-- | src/input_common/udp/client.cpp | 526 | ||||
-rw-r--r-- | src/input_common/udp/client.h | 183 | ||||
-rw-r--r-- | src/input_common/udp/protocol.cpp | 78 | ||||
-rw-r--r-- | src/input_common/udp/protocol.h | 259 | ||||
-rw-r--r-- | src/input_common/udp/udp.cpp | 110 | ||||
-rw-r--r-- | src/input_common/udp/udp.h | 57 |
6 files changed, 0 insertions, 1213 deletions
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp deleted file mode 100644 index b9512aa2e..000000000 --- a/src/input_common/udp/client.cpp +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <chrono> -#include <cstring> -#include <functional> -#include <random> -#include <thread> -#include <boost/asio.hpp> -#include "common/logging/log.h" -#include "common/settings.h" -#include "input_common/udp/client.h" -#include "input_common/udp/protocol.h" - -using boost::asio::ip::udp; - -namespace InputCommon::CemuhookUDP { - -struct SocketCallback { - std::function<void(Response::Version)> version; - std::function<void(Response::PortInfo)> port_info; - std::function<void(Response::PadData)> pad_data; -}; - -class Socket { -public: - using clock = std::chrono::system_clock; - - explicit Socket(const std::string& host, u16 port, SocketCallback callback_) - : callback(std::move(callback_)), timer(io_service), - socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) { - boost::system::error_code ec{}; - auto ipv4 = boost::asio::ip::make_address_v4(host, ec); - if (ec.value() != boost::system::errc::success) { - LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host); - ipv4 = boost::asio::ip::address_v4{}; - } - - send_endpoint = {udp::endpoint(ipv4, port)}; - } - - 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: - u32 GenerateRandomClientId() const { - std::random_device device; - return device(); - } - - void HandleReceive(const boost::system::error_code&, 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)); - SanitizeMotion(pad_data); - callback.pad_data(std::move(pad_data)); - break; - } - } - } - StartReceive(); - } - - void HandleSend(const boost::system::error_code&) { - boost::system::error_code _ignored{}; - // Send a request for getting port info for the pad - const Request::PortInfo port_info{4, {0, 1, 2, 3}}; - const auto port_message = Request::Create(port_info, client_id); - std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); - socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); - - // Send a request for getting pad data for the pad - const Request::PadData pad_data{ - Request::PadData::Flags::AllPorts, - 0, - EMPTY_MAC_ADDRESS, - }; - const auto pad_message = Request::Create(pad_data, client_id); - std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); - socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); - StartSend(timer.expiry()); - } - - void SanitizeMotion(Response::PadData& data) { - // Zero out any non number value - if (!std::isnormal(data.gyro.pitch)) { - data.gyro.pitch = 0; - } - if (!std::isnormal(data.gyro.roll)) { - data.gyro.roll = 0; - } - if (!std::isnormal(data.gyro.yaw)) { - data.gyro.yaw = 0; - } - if (!std::isnormal(data.accel.x)) { - data.accel.x = 0; - } - if (!std::isnormal(data.accel.y)) { - data.accel.y = 0; - } - if (!std::isnormal(data.accel.z)) { - data.accel.z = 0; - } - } - - SocketCallback callback; - boost::asio::io_service io_service; - boost::asio::basic_waitable_timer<clock> timer; - udp::socket socket; - - const u32 client_id; - - static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); - static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); - std::array<u8, PORT_INFO_SIZE> send_buffer1; - std::array<u8, PAD_DATA_SIZE> send_buffer2; - udp::endpoint send_endpoint; - - std::array<u8, MAX_PACKET_SIZE> receive_buffer; - udp::endpoint receive_endpoint; -}; - -static void SocketLoop(Socket* socket) { - socket->StartReceive(); - socket->StartSend(Socket::clock::now()); - socket->Loop(); -} - -Client::Client() { - LOG_INFO(Input, "Udp Initialization started"); - finger_id.fill(MAX_TOUCH_FINGERS); - ReloadSockets(); -} - -Client::~Client() { - Reset(); -} - -Client::ClientConnection::ClientConnection() = default; - -Client::ClientConnection::~ClientConnection() = default; - -std::vector<Common::ParamPackage> Client::GetInputDevices() const { - std::vector<Common::ParamPackage> devices; - for (std::size_t pad = 0; pad < pads.size(); pad++) { - if (!DeviceConnected(pad)) { - continue; - } - std::string name = fmt::format("UDP Controller {}", pad); - devices.emplace_back(Common::ParamPackage{ - {"class", "cemuhookudp"}, - {"display", std::move(name)}, - {"port", std::to_string(pad)}, - }); - } - return devices; -} - -bool Client::DeviceConnected(std::size_t pad) const { - // Use last timestamp to detect if the socket has stopped sending data - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast<u64>( - std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count()); - return time_difference < 1000 && pads[pad].connected; -} - -void Client::ReloadSockets() { - Reset(); - - std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers)); - std::string server_token; - std::size_t client = 0; - while (std::getline(servers_ss, server_token, ',')) { - if (client == MAX_UDP_CLIENTS) { - break; - } - std::stringstream server_ss(server_token); - std::string token; - std::getline(server_ss, token, ':'); - std::string udp_input_address = token; - std::getline(server_ss, token, ':'); - char* temp; - const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0)); - if (*temp != '\0') { - LOG_ERROR(Input, "Port number is not valid {}", token); - continue; - } - - const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); - if (client_number != MAX_UDP_CLIENTS) { - LOG_ERROR(Input, "Duplicated UDP servers found"); - continue; - } - StartCommunication(client++, udp_input_address, udp_input_port); - } -} - -std::size_t Client::GetClientNumber(std::string_view host, u16 port) const { - for (std::size_t client = 0; client < clients.size(); client++) { - if (clients[client].active == -1) { - continue; - } - if (clients[client].host == host && clients[client].port == port) { - return client; - } - } - return MAX_UDP_CLIENTS; -} - -void Client::OnVersion([[maybe_unused]] Response::Version data) { - LOG_TRACE(Input, "Version packet received: {}", data.version); -} - -void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) { - LOG_TRACE(Input, "PortInfo packet received: {}", data.model); -} - -void Client::OnPadData(Response::PadData data, std::size_t client) { - const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id; - - if (pad_index >= pads.size()) { - LOG_ERROR(Input, "Invalid pad id {}", data.info.id); - return; - } - - LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter == pads[pad_index].packet_sequence) { - LOG_WARNING( - Input, - "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - pads[pad_index].packet_sequence, data.packet_counter); - pads[pad_index].connected = false; - return; - } - - clients[client].active = 1; - pads[pad_index].connected = true; - pads[pad_index].packet_sequence = data.packet_counter; - - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast<u64>( - std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update) - .count()); - pads[pad_index].last_update = now; - - const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; - pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); - // Gyroscope values are not it the correct scale from better joy. - // Dividing by 312 allows us to make one full turn = 1 turn - // This must be a configurable valued called sensitivity - pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f); - pads[pad_index].motion.UpdateRotation(time_difference); - pads[pad_index].motion.UpdateOrientation(time_difference); - - { - std::lock_guard guard(pads[pad_index].status.update_mutex); - pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion(); - - for (std::size_t id = 0; id < data.touch.size(); ++id) { - UpdateTouchInput(data.touch[id], client, id); - } - - if (configuring) { - const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope(); - const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration(); - UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope); - } - } -} - -void Client::StartCommunication(std::size_t client, const std::string& host, u16 port) { - SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, - [this](Response::PortInfo info) { OnPortInfo(info); }, - [this, client](Response::PadData data) { OnPadData(data, client); }}; - LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - clients[client].host = host; - clients[client].port = port; - clients[client].active = 0; - clients[client].socket = std::make_unique<Socket>(host, port, callback); - clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; - - // Set motion parameters - // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode - // Real HW values are unknown, 0.0001 is an approximate to Standard - for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) { - pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f); - } -} - -void Client::Reset() { - for (auto& client : clients) { - if (client.thread.joinable()) { - client.active = -1; - client.socket->Stop(); - client.thread.join(); - } - } -} - -void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro) { - if (gyro.Length() > 0.2f) { - LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client, - gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]); - } - UDPPadStatus pad{ - .host = clients[client].host, - .port = clients[client].port, - .pad_index = pad_index, - }; - for (std::size_t i = 0; i < 3; ++i) { - if (gyro[i] > 5.0f || gyro[i] < -5.0f) { - pad.motion = static_cast<PadMotion>(i); - pad.motion_value = gyro[i]; - pad_queue.Push(pad); - } - if (acc[i] > 1.75f || acc[i] < -1.75f) { - pad.motion = static_cast<PadMotion>(i + 3); - pad.motion_value = acc[i]; - pad_queue.Push(pad); - } - } -} - -std::optional<std::size_t> Client::GetUnusedFingerID() const { - std::size_t first_free_id = 0; - while (first_free_id < MAX_TOUCH_FINGERS) { - if (!std::get<2>(touch_status[first_free_id])) { - return first_free_id; - } else { - first_free_id++; - } - } - return std::nullopt; -} - -void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) { - // TODO: Use custom calibration per device - const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); - const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100)); - const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50)); - const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800)); - const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850)); - const std::size_t touch_id = client * 2 + id; - if (touch_pad.is_active) { - if (finger_id[touch_id] == MAX_TOUCH_FINGERS) { - const auto first_free_id = GetUnusedFingerID(); - if (!first_free_id) { - // Invalid finger id skip to next input - return; - } - finger_id[touch_id] = *first_free_id; - } - auto& [x, y, pressed] = touch_status[finger_id[touch_id]]; - x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) / - static_cast<float>(max_x - min_x); - y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) / - static_cast<float>(max_y - min_y); - pressed = true; - return; - } - - if (finger_id[touch_id] != MAX_TOUCH_FINGERS) { - touch_status[finger_id[touch_id]] = {}; - finger_id[touch_id] = MAX_TOUCH_FINGERS; - } -} - -void Client::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; -} - -void Client::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -Input::TouchStatus& Client::GetTouchState() { - return touch_status; -} - -const Input::TouchStatus& Client::GetTouchState() const { - return touch_status; -} - -Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const { - return pad_queue; -} - -void TestCommunication(const std::string& host, u16 port, - const std::function<void()>& success_callback, - const std::function<void()>& failure_callback) { - std::thread([=] { - Common::Event success_event; - SocketCallback callback{ - .version = [](Response::Version) {}, - .port_info = [](Response::PortInfo) {}, - .pad_data = [&](Response::PadData) { success_event.Set(); }, - }; - Socket socket{host, port, std::move(callback)}; - std::thread worker_thread{SocketLoop, &socket}; - const bool result = - success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10)); - socket.Stop(); - worker_thread.join(); - if (result) { - success_callback(); - } else { - failure_callback(); - } - }).detach(); -} - -CalibrationConfigurationJob::CalibrationConfigurationJob( - const std::string& host, u16 port, std::function<void(Status)> status_callback, - std::function<void(u16, u16, u16, u16)> data_callback) { - - std::thread([=, this] { - Status current_status{Status::Initialized}; - SocketCallback callback{ - [](Response::Version) {}, [](Response::PortInfo) {}, - [&](Response::PadData data) { - static constexpr u16 CALIBRATION_THRESHOLD = 100; - static constexpr u16 MAX_VALUE = UINT16_MAX; - - if (current_status == Status::Initialized) { - // Receiving data means the communication is ready now - current_status = Status::Ready; - status_callback(current_status); - } - const auto& touchpad_0 = data.touch[0]; - if (touchpad_0.is_active == 0) { - return; - } - LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); - const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x)); - const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y)); - if (current_status == Status::Ready) { - // First touch - min data (min_x/min_y) - current_status = Status::Stage1Completed; - status_callback(current_status); - } - if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && - touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { - // Set the current position as max value and finishes configuration - const u16 max_x = touchpad_0.x; - const u16 max_y = touchpad_0.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, std::move(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 deleted file mode 100644 index 380f9bb76..000000000 --- a/src/input_common/udp/client.h +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <functional> -#include <memory> -#include <mutex> -#include <optional> -#include <string> -#include <thread> -#include <tuple> -#include "common/common_types.h" -#include "common/param_package.h" -#include "common/thread.h" -#include "common/threadsafe_queue.h" -#include "common/vector_math.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" - -namespace InputCommon::CemuhookUDP { - -class Socket; - -namespace Response { -struct PadData; -struct PortInfo; -struct TouchPad; -struct Version; -} // namespace Response - -enum class PadMotion { - GyroX, - GyroY, - GyroZ, - AccX, - AccY, - AccZ, - Undefined, -}; - -enum class PadTouch { - Click, - Undefined, -}; - -struct UDPPadStatus { - std::string host{"127.0.0.1"}; - u16 port{26760}; - std::size_t pad_index{}; - PadMotion motion{PadMotion::Undefined}; - f32 motion_value{0.0f}; -}; - -struct DeviceStatus { - std::mutex update_mutex; - Input::MotionStatus motion_status; - std::tuple<float, float, bool> 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<CalibrationData> touch_calibration; -}; - -class Client { -public: - // Initialize the UDP client capture and read sequence - Client(); - - // Close and release the client - ~Client(); - - // Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - std::vector<Common::ParamPackage> GetInputDevices() const; - - bool DeviceConnected(std::size_t pad) const; - void ReloadSockets(); - - Common::SPSCQueue<UDPPadStatus>& GetPadQueue(); - const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const; - - DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); - const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; - - Input::TouchStatus& GetTouchState(); - const Input::TouchStatus& GetTouchState() const; - -private: - struct PadData { - std::size_t pad_index{}; - bool connected{}; - DeviceStatus status; - u64 packet_sequence{}; - - // Realtime values - // motion is initalized with PID values for drift correction on joycons - InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; - std::chrono::time_point<std::chrono::steady_clock> last_update; - }; - - struct ClientConnection { - ClientConnection(); - ~ClientConnection(); - std::string host{"127.0.0.1"}; - u16 port{26760}; - s8 active{-1}; - std::unique_ptr<Socket> socket; - std::thread thread; - }; - - // For shutting down, clear all data, join all threads, release usb - void Reset(); - - // Translates configuration to client number - std::size_t GetClientNumber(std::string_view host, u16 port) const; - - void OnVersion(Response::Version); - void OnPortInfo(Response::PortInfo); - void OnPadData(Response::PadData, std::size_t client); - void StartCommunication(std::size_t client, const std::string& host, u16 port); - void UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro); - - // Returns an unused finger id, if there is no fingers available std::nullopt will be - // returned - std::optional<std::size_t> GetUnusedFingerID() const; - - // Merges and updates all touch inputs into the touch_status array - void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id); - - bool configuring = false; - - // Allocate clients for 8 udp servers - static constexpr std::size_t MAX_UDP_CLIENTS = 8; - static constexpr std::size_t PADS_PER_CLIENT = 4; - // Each client can have up 2 touch inputs - static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2; - std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{}; - std::array<ClientConnection, MAX_UDP_CLIENTS> clients{}; - Common::SPSCQueue<UDPPadStatus> pad_queue{}; - Input::TouchStatus touch_status{}; - std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{}; -}; - -/// 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, - std::function<void(Status)> status_callback, - std::function<void(u16, u16, u16, u16)> data_callback); - ~CalibrationConfigurationJob(); - void Stop(); - -private: - Common::Event complete_event; -}; - -void TestCommunication(const std::string& host, u16 port, - const std::function<void()>& success_callback, - const std::function<void()>& failure_callback); - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp deleted file mode 100644 index 5e50bd612..000000000 --- a/src/input_common/udp/protocol.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstddef> -#include <cstring> -#include "common/logging/log.h" -#include "input_common/udp/protocol.h" - -namespace InputCommon::CemuhookUDP { - -static constexpr 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<Type> Validate(u8* data, std::size_t size) { - if (size < sizeof(Header)) { - 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 std::nullopt; - } - if (header.protocol_version != PROTOCOL_VERSION) { - LOG_ERROR(Input, "UDP Packet protocol mismatch"); - return std::nullopt; - } - if (header.type < Type::Version || header.type > Type::PadData) { - LOG_ERROR(Input, "UDP Packet is an unknown type"); - return std::nullopt; - } - - // 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 std::nullopt; - } - - 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 std::nullopt; - } - return header.type; -} -} // namespace Response - -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h deleted file mode 100644 index 1bdc9209e..000000000 --- a/src/input_common/udp/protocol.h +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <optional> -#include <type_traits> - -#include <boost/crc.hpp> - -#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<Header>, "UDP Message Header is not trivially copyable"); - -using MacAddress = std::array<u8, 6>; -constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0}; - -#pragma pack(push, 1) -template <typename T> -struct Message { - Header header{}; - T data; -}; -#pragma pack(pop) - -template <typename T> -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<u8, MAX_PORTS> port; -}; -static_assert(std::is_trivially_copyable_v<PortInfo>, - "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<PadData>, - "UDP Request PadData is not trivially copyable"); - -/** - * Creates a message with the proper header data that can be sent to the server. - * @param data Request body to send - * @param client_id ID of the udp client (usually not checked on the server) - */ -template <typename T> -Message<T> 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<T>(), - }; - Message<T> message{header, data}; - crc.process_bytes(&message, sizeof(Message<T>)); - 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<Version>, - "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<PortInfo>, - "UDP Response PortInfo is not trivially copyable"); - -struct TouchPad { - u8 is_active{}; - u8 id{}; - u16_le x{}; - u16_le y{}; -}; -static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); - -#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; - - std::array<TouchPad, 2> touch; - - 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<PadData>, - "UDP Response PadData is not trivially copyable"); - -static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE, - "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>"); - -static_assert(sizeof(PadData::AnalogButton) == 12, - "UDP Response AnalogButton 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 - * @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<Type> Validate(u8* data, std::size_t size); - -} // namespace Response - -template <> -constexpr Type GetMessageType<Request::Version>() { - return Type::Version; -} -template <> -constexpr Type GetMessageType<Request::PortInfo>() { - return Type::PortInfo; -} -template <> -constexpr Type GetMessageType<Request::PadData>() { - return Type::PadData; -} -template <> -constexpr Type GetMessageType<Response::Version>() { - return Type::Version; -} -template <> -constexpr Type GetMessageType<Response::PortInfo>() { - return Type::PortInfo; -} -template <> -constexpr Type GetMessageType<Response::PadData>() { - return Type::PadData; -} -} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp deleted file mode 100644 index 9829da6f0..000000000 --- a/src/input_common/udp/udp.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <mutex> -#include <utility> -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" - -namespace InputCommon { - -class UDPMotion final : public Input::MotionDevice { -public: - explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::MotionStatus GetStatus() const override { - return client->GetPadState(ip, port, pad).motion_status; - } - -private: - const std::string ip; - const u16 port; - const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast<u16>(params.Get("port", 26760)); - const auto pad = static_cast<u16>(params.Get("pad_index", 0)); - - return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get()); -} - -void UDPMotionFactory::BeginConfiguration() { - polling = true; - client->BeginConfiguration(); -} - -void UDPMotionFactory::EndConfiguration() { - polling = false; - client->EndConfiguration(); -} - -Common::ParamPackage UDPMotionFactory::GetNextInput() { - Common::ParamPackage params; - CemuhookUDP::UDPPadStatus pad; - auto& queue = client->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { - continue; - } - params.Set("engine", "cemuhookudp"); - params.Set("ip", pad.host); - params.Set("port", static_cast<u16>(pad.port)); - params.Set("pad_index", static_cast<u16>(pad.pad_index)); - params.Set("motion", static_cast<u16>(pad.motion)); - return params; - } - return params; -} - -class UDPTouch final : public Input::TouchDevice { -public: - explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::TouchStatus GetStatus() const override { - return client->GetTouchState(); - } - -private: - const std::string ip; - [[maybe_unused]] const u16 port; - [[maybe_unused]] const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast<u16>(params.Get("port", 26760)); - const auto pad = static_cast<u16>(params.Get("pad_index", 0)); - - return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h deleted file mode 100644 index ea3fd4175..000000000 --- a/src/input_common/udp/udp.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "core/frontend/input.h" -#include "input_common/udp/client.h" - -namespace InputCommon { - -/// A motion device factory that creates motion devices from udp clients -class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { -public: - explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_); - - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<CemuhookUDP::Client> client; - bool polling = false; -}; - -/// A touch device factory that creates touch devices from udp clients -class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { -public: - explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_); - - std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<CemuhookUDP::Client> client; - bool polling = false; -}; - -} // namespace InputCommon |