mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									1e724b046b
								
							
						
					
					
						commit
						6bcbda5ab2
					
				
					 16 changed files with 758 additions and 14 deletions
				
			
		
							
								
								
									
										190
									
								
								src/input_common/udp/client.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/input_common/udp/client.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,190 @@ | |||
| // Copyright 2018 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <chrono> | ||||
| #include <cstring> | ||||
| #include <functional> | ||||
| #include <thread> | ||||
| #include <boost/asio.hpp> | ||||
| #include <boost/bind.hpp> | ||||
| #include "common/logging/log.h" | ||||
| #include "common/vector_math.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<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, 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)), 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) { | ||||
|         // TODO: add something to the UI to let people choose what ports to listen on
 | ||||
|         // Send a request for getting port info for pad 1
 | ||||
|         Request::PortInfo port_info{1, {0, 0, 0, 0}}; | ||||
|         auto port_message = Request::Create(port_info, client_id); | ||||
|         std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); | ||||
|         size_t len = socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); | ||||
| 
 | ||||
|         // Send a request for getting pad data for pad 1
 | ||||
|         Request::PadData pad_data{Request::PadData::Flags::Id, 0, EMPTY_MAC_ADDRESS}; | ||||
|         auto pad_message = Request::Create(pad_data, client_id); | ||||
|         std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); | ||||
|         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<clock> timer; | ||||
|     udp::socket socket; | ||||
| 
 | ||||
|     u32 client_id; | ||||
| 
 | ||||
|     static constexpr size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); | ||||
|     static constexpr 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(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, | ||||
|                u32 client_id) | ||||
|     : status(status) { | ||||
|     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<Socket>(host, port, client_id, callback); | ||||
|     thread = std::thread{SocketLoop, this->socket.get()}; | ||||
| } | ||||
| 
 | ||||
| Client::~Client() { | ||||
|     socket->Stop(); | ||||
|     thread.join(); | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
|     Math::Vec3f accel = Math::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); | ||||
|     Math::Vec3f gyro = Math::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); | ||||
|     { | ||||
|         std::lock_guard<std::mutex> 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<u16>(data.touch_1.x), min_x, max_x) - min_x) / | ||||
|                 static_cast<float>(max_x - min_x); | ||||
|             y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) / | ||||
|                 static_cast<float>(max_y - min_y); | ||||
|         } | ||||
| 
 | ||||
|         status->touch_status = {x, y, is_active}; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
							
								
								
									
										61
									
								
								src/input_common/udp/client.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/input_common/udp/client.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| // Copyright 2018 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <tuple> | ||||
| #include <vector> | ||||
| #include <boost/optional.hpp> | ||||
| #include "common/common_types.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<Math::Vec3<float>, Math::Vec3<float>> 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; | ||||
|     }; | ||||
|     boost::optional<CalibrationData> touch_calibration; | ||||
| }; | ||||
| 
 | ||||
| class Client { | ||||
| public: | ||||
|     explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, | ||||
|                     u16 port = DEFAULT_PORT, u32 client_id = 24872); | ||||
|     ~Client(); | ||||
| 
 | ||||
| private: | ||||
|     void OnVersion(Response::Version); | ||||
|     void OnPortInfo(Response::PortInfo); | ||||
|     void OnPadData(Response::PadData); | ||||
| 
 | ||||
|     std::unique_ptr<Socket> socket; | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
|     std::thread thread; | ||||
|     u64 packet_sequence = 0; | ||||
| }; | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
							
								
								
									
										79
									
								
								src/input_common/udp/protocol.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/input_common/udp/protocol.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| // 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 const 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) | ||||
|  */ | ||||
| boost::optional<Type> Validate(u8* data, size_t size) { | ||||
|     if (size < sizeof(Header)) { | ||||
|         LOG_ERROR(Input, "Invalid UDP packet received"); | ||||
|         return boost::none; | ||||
|     } | ||||
|     Header header; | ||||
|     std::memcpy(&header, data, sizeof(Header)); | ||||
|     if (header.magic != SERVER_MAGIC) { | ||||
|         LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); | ||||
|         return boost::none; | ||||
|     } | ||||
|     if (header.protocol_version != PROTOCOL_VERSION) { | ||||
|         LOG_ERROR(Input, "UDP Packet protocol mismatch"); | ||||
|         return boost::none; | ||||
|     } | ||||
|     if (header.type < Type::Version || header.type > Type::PadData) { | ||||
|         LOG_ERROR(Input, "UDP Packet is an unknown type"); | ||||
|         return boost::none; | ||||
|     } | ||||
| 
 | ||||
|     // 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 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 boost::none; | ||||
|     } | ||||
| 
 | ||||
|     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 boost::none; | ||||
|     } | ||||
|     return header.type; | ||||
| } | ||||
| } // namespace Response
 | ||||
| 
 | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
							
								
								
									
										249
									
								
								src/input_common/udp/protocol.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								src/input_common/udp/protocol.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <array> | ||||
| #include <type_traits> | ||||
| #include <vector> | ||||
| #include <boost/crc.hpp> | ||||
| #include <boost/optional.hpp> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/swap.h" | ||||
| 
 | ||||
| namespace InputCommon::CemuhookUDP { | ||||
| 
 | ||||
| constexpr 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 T 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"); | ||||
| 
 | ||||
| #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_le> button_1;   // Share
 | ||||
|     //     BitField<1, 1, u16_le> button_2;   // L3
 | ||||
|     //     BitField<2, 1, u16_le> button_3;   // R3
 | ||||
|     //     BitField<3, 1, u16_le> button_4;   // Options
 | ||||
|     //     BitField<4, 1, u16_le> button_5;   // Up
 | ||||
|     //     BitField<5, 1, u16_le> button_6;   // Right
 | ||||
|     //     BitField<6, 1, u16_le> button_7;   // Down
 | ||||
|     //     BitField<7, 1, u16_le> button_8;   // Left
 | ||||
|     //     BitField<8, 1, u16_le> button_9;   // L2
 | ||||
|     //     BitField<9, 1, u16_le> button_10;  // R2
 | ||||
|     //     BitField<10, 1, u16_le> button_11; // L1
 | ||||
|     //     BitField<11, 1, u16_le> button_12; // R1
 | ||||
|     //     BitField<12, 1, u16_le> button_13; // Triangle
 | ||||
|     //     BitField<13, 1, u16_le> button_14; // Circle
 | ||||
|     //     BitField<14, 1, u16_le> button_15; // Cross
 | ||||
|     //     BitField<15, 1, u16_le> 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<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>"); | ||||
| 
 | ||||
| /**
 | ||||
|  * 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 | ||||
|  */ | ||||
| boost::optional<Type> Validate(u8* data, 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
 | ||||
							
								
								
									
										90
									
								
								src/input_common/udp/udp.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/input_common/udp/udp.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,90 @@ | |||
| // 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<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
|     std::tuple<float, float, bool> GetStatus() const { | ||||
|         std::lock_guard<std::mutex> guard(status->update_mutex); | ||||
|         return status->touch_status; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| class UDPMotionDevice final : public Input::MotionDevice { | ||||
| public: | ||||
|     explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
|     std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const { | ||||
|         std::lock_guard<std::mutex> guard(status->update_mutex); | ||||
|         return status->motion_status; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { | ||||
| public: | ||||
|     explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
| 
 | ||||
|     std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { | ||||
|         { | ||||
|             std::lock_guard<std::mutex> guard(status->update_mutex); | ||||
|             status->touch_calibration.reset({}); | ||||
|             // 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<UDPTouchDevice>(status); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||||
| public: | ||||
|     explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||
| 
 | ||||
|     std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { | ||||
|         return std::make_unique<UDPMotionDevice>(status); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<DeviceStatus> status; | ||||
| }; | ||||
| 
 | ||||
| State::State() { | ||||
|     auto status = std::make_shared<DeviceStatus>(); | ||||
|     client = std::make_unique<Client>(status, Settings::values.udp_input_address, | ||||
|                                       Settings::values.udp_input_port); | ||||
| 
 | ||||
|     Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", | ||||
|                                                std::make_shared<UDPTouchFactory>(status)); | ||||
|     Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", | ||||
|                                                 std::make_shared<UDPMotionFactory>(status)); | ||||
| } | ||||
| 
 | ||||
| State::~State() { | ||||
|     Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); | ||||
|     Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<State> Init() { | ||||
|     return std::make_unique<State>(); | ||||
| } | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
							
								
								
									
										26
									
								
								src/input_common/udp/udp.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/input_common/udp/udp.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| // Copyright 2018 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <unordered_map> | ||||
| #include "input_common/main.h" | ||||
| #include "input_common/udp/client.h" | ||||
| 
 | ||||
| namespace InputCommon::CemuhookUDP { | ||||
| 
 | ||||
| class UDPTouchDevice; | ||||
| class UDPMotionDevice; | ||||
| 
 | ||||
| class State { | ||||
| public: | ||||
|     State(); | ||||
|     ~State(); | ||||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<Client> client; | ||||
| }; | ||||
| 
 | ||||
| std::unique_ptr<State> Init(); | ||||
| 
 | ||||
| } // namespace InputCommon::CemuhookUDP
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue