summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/main.cpp13
-rw-r--r--src/input_common/sdl/sdl_impl.cpp16
-rw-r--r--src/input_common/udp/client.cpp286
-rw-r--r--src/input_common/udp/client.h95
-rw-r--r--src/input_common/udp/protocol.cpp79
-rw-r--r--src/input_common/udp/protocol.h255
-rw-r--r--src/input_common/udp/udp.cpp98
-rw-r--r--src/input_common/udp/udp.h25
9 files changed, 871 insertions, 4 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 5b4e032bd..2520ba321 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -9,6 +9,12 @@ add_library(input_common STATIC
9 motion_emu.h 9 motion_emu.h
10 sdl/sdl.cpp 10 sdl/sdl.cpp
11 sdl/sdl.h 11 sdl/sdl.h
12 udp/client.cpp
13 udp/client.h
14 udp/protocol.cpp
15 udp/protocol.h
16 udp/udp.cpp
17 udp/udp.h
12) 18)
13 19
14if(SDL2_FOUND) 20if(SDL2_FOUND)
@@ -21,4 +27,4 @@ if(SDL2_FOUND)
21endif() 27endif()
22 28
23create_target_directory_groups(input_common) 29create_target_directory_groups(input_common)
24target_link_libraries(input_common PUBLIC core PRIVATE common) 30target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES})
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 8e66c1b15..c98c848cf 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -9,6 +9,7 @@
9#include "input_common/keyboard.h" 9#include "input_common/keyboard.h"
10#include "input_common/main.h" 10#include "input_common/main.h"
11#include "input_common/motion_emu.h" 11#include "input_common/motion_emu.h"
12#include "input_common/udp/udp.h"
12#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
13#include "input_common/sdl/sdl.h" 14#include "input_common/sdl/sdl.h"
14#endif 15#endif
@@ -18,6 +19,7 @@ namespace InputCommon {
18static std::shared_ptr<Keyboard> keyboard; 19static std::shared_ptr<Keyboard> keyboard;
19static std::shared_ptr<MotionEmu> motion_emu; 20static std::shared_ptr<MotionEmu> motion_emu;
20static std::unique_ptr<SDL::State> sdl; 21static std::unique_ptr<SDL::State> sdl;
22static std::unique_ptr<CemuhookUDP::State> udp;
21 23
22void Init() { 24void Init() {
23 keyboard = std::make_shared<Keyboard>(); 25 keyboard = std::make_shared<Keyboard>();
@@ -28,6 +30,8 @@ void Init() {
28 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); 30 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
29 31
30 sdl = SDL::Init(); 32 sdl = SDL::Init();
33
34 udp = CemuhookUDP::Init();
31} 35}
32 36
33void Shutdown() { 37void Shutdown() {
@@ -37,6 +41,7 @@ void Shutdown() {
37 Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); 41 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
38 motion_emu.reset(); 42 motion_emu.reset();
39 sdl.reset(); 43 sdl.reset();
44 udp.reset();
40} 45}
41 46
42Keyboard* GetKeyboard() { 47Keyboard* GetKeyboard() {
@@ -72,11 +77,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
72namespace Polling { 77namespace Polling {
73 78
74std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { 79std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
80 std::vector<std::unique_ptr<DevicePoller>> pollers;
81
75#ifdef HAVE_SDL2 82#ifdef HAVE_SDL2
76 return sdl->GetPollers(type); 83 pollers = sdl->GetPollers(type);
77#else
78 return {};
79#endif 84#endif
85
86 return pollers;
80} 87}
81 88
82} // namespace Polling 89} // namespace Polling
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index d2e9d278f..a2e0c0bd2 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -342,6 +342,22 @@ public:
342 return std::make_tuple<float, float>(0.0f, 0.0f); 342 return std::make_tuple<float, float>(0.0f, 0.0f);
343 } 343 }
344 344
345 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
346 const auto [x, y] = GetStatus();
347 const float directional_deadzone = 0.4f;
348 switch (direction) {
349 case Input::AnalogDirection::RIGHT:
350 return x > directional_deadzone;
351 case Input::AnalogDirection::LEFT:
352 return x < -directional_deadzone;
353 case Input::AnalogDirection::UP:
354 return y > directional_deadzone;
355 case Input::AnalogDirection::DOWN:
356 return y < -directional_deadzone;
357 }
358 return false;
359 }
360
345private: 361private:
346 std::shared_ptr<SDLJoystick> joystick; 362 std::shared_ptr<SDLJoystick> joystick;
347 const int axis_x; 363 const int axis_x;
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
new file mode 100644
index 000000000..2228571a6
--- /dev/null
+++ b/src/input_common/udp/client.cpp
@@ -0,0 +1,286 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <chrono>
8#include <cstring>
9#include <functional>
10#include <thread>
11#include <boost/asio.hpp>
12#include <boost/bind.hpp>
13#include "common/logging/log.h"
14#include "input_common/udp/client.h"
15#include "input_common/udp/protocol.h"
16
17using boost::asio::ip::udp;
18
19namespace InputCommon::CemuhookUDP {
20
21struct SocketCallback {
22 std::function<void(Response::Version)> version;
23 std::function<void(Response::PortInfo)> port_info;
24 std::function<void(Response::PadData)> pad_data;
25};
26
27class Socket {
28public:
29 using clock = std::chrono::system_clock;
30
31 explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
32 SocketCallback callback)
33 : callback(std::move(callback)), timer(io_service),
34 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id),
35 pad_index(pad_index),
36 send_endpoint(udp::endpoint(boost::asio::ip::make_address_v4(host), port)) {}
37
38 void Stop() {
39 io_service.stop();
40 }
41
42 void Loop() {
43 io_service.run();
44 }
45
46 void StartSend(const clock::time_point& from) {
47 timer.expires_at(from + std::chrono::seconds(3));
48 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
49 }
50
51 void StartReceive() {
52 socket.async_receive_from(
53 boost::asio::buffer(receive_buffer), receive_endpoint,
54 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
55 HandleReceive(error, bytes_transferred);
56 });
57 }
58
59private:
60 void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
61 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
62 switch (*type) {
63 case Type::Version: {
64 Response::Version version;
65 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
66 callback.version(std::move(version));
67 break;
68 }
69 case Type::PortInfo: {
70 Response::PortInfo port_info;
71 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
72 sizeof(Response::PortInfo));
73 callback.port_info(std::move(port_info));
74 break;
75 }
76 case Type::PadData: {
77 Response::PadData pad_data;
78 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
79 callback.pad_data(std::move(pad_data));
80 break;
81 }
82 }
83 }
84 StartReceive();
85 }
86
87 void HandleSend(const boost::system::error_code& error) {
88 // Send a request for getting port info for the pad
89 Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
90 const auto port_message = Request::Create(port_info, client_id);
91 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
92 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint);
93
94 // Send a request for getting pad data for the pad
95 Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
96 const auto pad_message = Request::Create(pad_data, client_id);
97 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
98 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint);
99 StartSend(timer.expiry());
100 }
101
102 SocketCallback callback;
103 boost::asio::io_service io_service;
104 boost::asio::basic_waitable_timer<clock> timer;
105 udp::socket socket;
106
107 u32 client_id{};
108 u8 pad_index{};
109
110 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
111 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
112 std::array<u8, PORT_INFO_SIZE> send_buffer1;
113 std::array<u8, PAD_DATA_SIZE> send_buffer2;
114 udp::endpoint send_endpoint;
115
116 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
117 udp::endpoint receive_endpoint;
118};
119
120static void SocketLoop(Socket* socket) {
121 socket->StartReceive();
122 socket->StartSend(Socket::clock::now());
123 socket->Loop();
124}
125
126Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
127 u8 pad_index, u32 client_id)
128 : status(std::move(status)) {
129 StartCommunication(host, port, pad_index, client_id);
130}
131
132Client::~Client() {
133 socket->Stop();
134 thread.join();
135}
136
137void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
138 socket->Stop();
139 thread.join();
140 StartCommunication(host, port, pad_index, client_id);
141}
142
143void Client::OnVersion(Response::Version data) {
144 LOG_TRACE(Input, "Version packet received: {}", data.version);
145}
146
147void Client::OnPortInfo(Response::PortInfo data) {
148 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
149}
150
151void Client::OnPadData(Response::PadData data) {
152 LOG_TRACE(Input, "PadData packet received");
153 if (data.packet_counter <= packet_sequence) {
154 LOG_WARNING(
155 Input,
156 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
157 packet_sequence, data.packet_counter);
158 return;
159 }
160 packet_sequence = data.packet_counter;
161 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
162 // directions correspond to the ones of the Switch
163 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
164 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
165 {
166 std::lock_guard guard(status->update_mutex);
167
168 status->motion_status = {accel, gyro};
169
170 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
171 // between a simple "tap" and a hard press that causes the touch screen to click.
172 const bool is_active = data.touch_1.is_active != 0;
173
174 float x = 0;
175 float y = 0;
176
177 if (is_active && status->touch_calibration) {
178 const u16 min_x = status->touch_calibration->min_x;
179 const u16 max_x = status->touch_calibration->max_x;
180 const u16 min_y = status->touch_calibration->min_y;
181 const u16 max_y = status->touch_calibration->max_y;
182
183 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
184 static_cast<float>(max_x - min_x);
185 y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
186 static_cast<float>(max_y - min_y);
187 }
188
189 status->touch_status = {x, y, is_active};
190 }
191}
192
193void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
194 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
195 [this](Response::PortInfo info) { OnPortInfo(info); },
196 [this](Response::PadData data) { OnPadData(data); }};
197 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
198 socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
199 thread = std::thread{SocketLoop, this->socket.get()};
200}
201
202void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
203 std::function<void()> success_callback,
204 std::function<void()> failure_callback) {
205 std::thread([=] {
206 Common::Event success_event;
207 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
208 [&](Response::PadData data) { success_event.Set(); }};
209 Socket socket{host, port, pad_index, client_id, std::move(callback)};
210 std::thread worker_thread{SocketLoop, &socket};
211 bool result = success_event.WaitFor(std::chrono::seconds(8));
212 socket.Stop();
213 worker_thread.join();
214 if (result) {
215 success_callback();
216 } else {
217 failure_callback();
218 }
219 })
220 .detach();
221}
222
223CalibrationConfigurationJob::CalibrationConfigurationJob(
224 const std::string& host, u16 port, u8 pad_index, u32 client_id,
225 std::function<void(Status)> status_callback,
226 std::function<void(u16, u16, u16, u16)> data_callback) {
227
228 std::thread([=] {
229 constexpr u16 CALIBRATION_THRESHOLD = 100;
230
231 u16 min_x{UINT16_MAX};
232 u16 min_y{UINT16_MAX};
233 u16 max_x{};
234 u16 max_y{};
235
236 Status current_status{Status::Initialized};
237 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
238 [&](Response::PadData data) {
239 if (current_status == Status::Initialized) {
240 // Receiving data means the communication is ready now
241 current_status = Status::Ready;
242 status_callback(current_status);
243 }
244 if (!data.touch_1.is_active) {
245 return;
246 }
247 LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
248 data.touch_1.y);
249 min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
250 min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
251 if (current_status == Status::Ready) {
252 // First touch - min data (min_x/min_y)
253 current_status = Status::Stage1Completed;
254 status_callback(current_status);
255 }
256 if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
257 data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
258 // Set the current position as max value and finishes
259 // configuration
260 max_x = data.touch_1.x;
261 max_y = data.touch_1.y;
262 current_status = Status::Completed;
263 data_callback(min_x, min_y, max_x, max_y);
264 status_callback(current_status);
265
266 complete_event.Set();
267 }
268 }};
269 Socket socket{host, port, pad_index, client_id, std::move(callback)};
270 std::thread worker_thread{SocketLoop, &socket};
271 complete_event.Wait();
272 socket.Stop();
273 worker_thread.join();
274 })
275 .detach();
276}
277
278CalibrationConfigurationJob::~CalibrationConfigurationJob() {
279 Stop();
280}
281
282void CalibrationConfigurationJob::Stop() {
283 complete_event.Set();
284}
285
286} // 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..b8c654755
--- /dev/null
+++ b/src/input_common/udp/client.h
@@ -0,0 +1,95 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <mutex>
10#include <optional>
11#include <string>
12#include <thread>
13#include <tuple>
14#include "common/common_types.h"
15#include "common/thread.h"
16#include "common/vector_math.h"
17
18namespace InputCommon::CemuhookUDP {
19
20constexpr u16 DEFAULT_PORT = 26760;
21constexpr char DEFAULT_ADDR[] = "127.0.0.1";
22
23class Socket;
24
25namespace Response {
26struct PadData;
27struct PortInfo;
28struct Version;
29} // namespace Response
30
31struct DeviceStatus {
32 std::mutex update_mutex;
33 std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
34 std::tuple<float, float, bool> touch_status;
35
36 // calibration data for scaling the device's touch area to 3ds
37 struct CalibrationData {
38 u16 min_x{};
39 u16 min_y{};
40 u16 max_x{};
41 u16 max_y{};
42 };
43 std::optional<CalibrationData> touch_calibration;
44};
45
46class Client {
47public:
48 explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
49 u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
50 ~Client();
51 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
52 u32 client_id = 24872);
53
54private:
55 void OnVersion(Response::Version);
56 void OnPortInfo(Response::PortInfo);
57 void OnPadData(Response::PadData);
58 void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
59
60 std::unique_ptr<Socket> socket;
61 std::shared_ptr<DeviceStatus> status;
62 std::thread thread;
63 u64 packet_sequence = 0;
64};
65
66/// An async job allowing configuration of the touchpad calibration.
67class CalibrationConfigurationJob {
68public:
69 enum class Status {
70 Initialized,
71 Ready,
72 Stage1Completed,
73 Completed,
74 };
75 /**
76 * Constructs and starts the job with the specified parameter.
77 *
78 * @param status_callback Callback for job status updates
79 * @param data_callback Called when calibration data is ready
80 */
81 explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
82 u32 client_id, std::function<void(Status)> status_callback,
83 std::function<void(u16, u16, u16, u16)> data_callback);
84 ~CalibrationConfigurationJob();
85 void Stop();
86
87private:
88 Common::Event complete_event;
89};
90
91void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
92 std::function<void()> success_callback,
93 std::function<void()> failure_callback);
94
95} // 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..a982ac49d
--- /dev/null
+++ b/src/input_common/udp/protocol.cpp
@@ -0,0 +1,79 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstddef>
6#include <cstring>
7#include "common/logging/log.h"
8#include "input_common/udp/protocol.h"
9
10namespace InputCommon::CemuhookUDP {
11
12static constexpr std::size_t GetSizeOfResponseType(Type t) {
13 switch (t) {
14 case Type::Version:
15 return sizeof(Response::Version);
16 case Type::PortInfo:
17 return sizeof(Response::PortInfo);
18 case Type::PadData:
19 return sizeof(Response::PadData);
20 }
21 return 0;
22}
23
24namespace Response {
25
26/**
27 * Returns Type if the packet is valid, else none
28 *
29 * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
30 * copying the buffer)
31 */
32std::optional<Type> Validate(u8* data, std::size_t size) {
33 if (size < sizeof(Header)) {
34 LOG_DEBUG(Input, "Invalid UDP packet received");
35 return std::nullopt;
36 }
37 Header header{};
38 std::memcpy(&header, data, sizeof(Header));
39 if (header.magic != SERVER_MAGIC) {
40 LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
41 return std::nullopt;
42 }
43 if (header.protocol_version != PROTOCOL_VERSION) {
44 LOG_ERROR(Input, "UDP Packet protocol mismatch");
45 return std::nullopt;
46 }
47 if (header.type < Type::Version || header.type > Type::PadData) {
48 LOG_ERROR(Input, "UDP Packet is an unknown type");
49 return std::nullopt;
50 }
51
52 // Packet size must equal sizeof(Header) + sizeof(Data)
53 // and also verify that the packet info mentions the correct size. Since the spec includes the
54 // type of the packet as part of the data, we need to include it in size calculations here
55 // ie: payload_length == sizeof(T) + sizeof(Type)
56 const std::size_t data_len = GetSizeOfResponseType(header.type);
57 if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
58 LOG_ERROR(
59 Input,
60 "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
61 size, header.payload_length, data_len + sizeof(Type));
62 return std::nullopt;
63 }
64
65 const u32 crc32 = header.crc;
66 boost::crc_32_type result;
67 // zero out the crc in the buffer and then run the crc against it
68 std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));
69
70 result.process_bytes(data, data_len + sizeof(Header));
71 if (crc32 != result.checksum()) {
72 LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
73 return std::nullopt;
74 }
75 return header.type;
76}
77} // namespace Response
78
79} // 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..3ba4d1fc8
--- /dev/null
+++ b/src/input_common/udp/protocol.h
@@ -0,0 +1,255 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <optional>
9#include <type_traits>
10#include <boost/crc.hpp>
11#include "common/bit_field.h"
12#include "common/swap.h"
13
14namespace InputCommon::CemuhookUDP {
15
16constexpr std::size_t MAX_PACKET_SIZE = 100;
17constexpr u16 PROTOCOL_VERSION = 1001;
18constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
19constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
20
21enum class Type : u32 {
22 Version = 0x00100000,
23 PortInfo = 0x00100001,
24 PadData = 0x00100002,
25};
26
27struct Header {
28 u32_le magic{};
29 u16_le protocol_version{};
30 u16_le payload_length{};
31 u32_le crc{};
32 u32_le id{};
33 ///> In the protocol, the type of the packet is not part of the header, but its convenient to
34 ///> include in the header so the callee doesn't have to duplicate the type twice when building
35 ///> the data
36 Type type{};
37};
38static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
39static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
40
41using MacAddress = std::array<u8, 6>;
42constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
43
44#pragma pack(push, 1)
45template <typename T>
46struct Message {
47 Header header{};
48 T data;
49};
50#pragma pack(pop)
51
52template <typename T>
53constexpr Type GetMessageType();
54
55namespace Request {
56
57struct Version {};
58/**
59 * Requests the server to send information about what controllers are plugged into the ports
60 * In citra's case, we only have one controller, so for simplicity's sake, we can just send a
61 * request explicitly for the first controller port and leave it at that. In the future it would be
62 * nice to make this configurable
63 */
64constexpr u32 MAX_PORTS = 4;
65struct PortInfo {
66 u32_le pad_count{}; ///> Number of ports to request data for
67 std::array<u8, MAX_PORTS> port;
68};
69static_assert(std::is_trivially_copyable_v<PortInfo>,
70 "UDP Request PortInfo is not trivially copyable");
71
72/**
73 * Request the latest pad information from the server. If the server hasn't received this message
74 * from the client in a reasonable time frame, the server will stop sending updates. The default
75 * timeout seems to be 5 seconds.
76 */
77struct PadData {
78 enum class Flags : u8 {
79 AllPorts,
80 Id,
81 Mac,
82 };
83 /// Determines which method will be used as a look up for the controller
84 Flags flags{};
85 /// Index of the port of the controller to retrieve data about
86 u8 port_id{};
87 /// Mac address of the controller to retrieve data about
88 MacAddress mac;
89};
90static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
91static_assert(std::is_trivially_copyable_v<PadData>,
92 "UDP Request PadData is not trivially copyable");
93
94/**
95 * Creates a message with the proper header data that can be sent to the server.
96 * @param T data Request body to send
97 * @param client_id ID of the udp client (usually not checked on the server)
98 */
99template <typename T>
100Message<T> Create(const T data, const u32 client_id = 0) {
101 boost::crc_32_type crc;
102 Header header{
103 CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
104 };
105 Message<T> message{header, data};
106 crc.process_bytes(&message, sizeof(Message<T>));
107 message.header.crc = crc.checksum();
108 return message;
109}
110} // namespace Request
111
112namespace Response {
113
114struct Version {
115 u16_le version{};
116};
117static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
118static_assert(std::is_trivially_copyable_v<Version>,
119 "UDP Response Version is not trivially copyable");
120
121struct PortInfo {
122 u8 id{};
123 u8 state{};
124 u8 model{};
125 u8 connection_type{};
126 MacAddress mac;
127 u8 battery{};
128 u8 is_pad_active{};
129};
130static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
131static_assert(std::is_trivially_copyable_v<PortInfo>,
132 "UDP Response PortInfo is not trivially copyable");
133
134#pragma pack(push, 1)
135struct PadData {
136 PortInfo info{};
137 u32_le packet_counter{};
138
139 u16_le digital_button{};
140 // The following union isn't trivially copyable but we don't use this input anyway.
141 // union DigitalButton {
142 // u16_le button;
143 // BitField<0, 1, u16> button_1; // Share
144 // BitField<1, 1, u16> button_2; // L3
145 // BitField<2, 1, u16> button_3; // R3
146 // BitField<3, 1, u16> button_4; // Options
147 // BitField<4, 1, u16> button_5; // Up
148 // BitField<5, 1, u16> button_6; // Right
149 // BitField<6, 1, u16> button_7; // Down
150 // BitField<7, 1, u16> button_8; // Left
151 // BitField<8, 1, u16> button_9; // L2
152 // BitField<9, 1, u16> button_10; // R2
153 // BitField<10, 1, u16> button_11; // L1
154 // BitField<11, 1, u16> button_12; // R1
155 // BitField<12, 1, u16> button_13; // Triangle
156 // BitField<13, 1, u16> button_14; // Circle
157 // BitField<14, 1, u16> button_15; // Cross
158 // BitField<15, 1, u16> button_16; // Square
159 // } digital_button;
160
161 u8 home;
162 /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
163 u8 touch_hard_press{};
164 u8 left_stick_x{};
165 u8 left_stick_y{};
166 u8 right_stick_x{};
167 u8 right_stick_y{};
168
169 struct AnalogButton {
170 u8 button_8{};
171 u8 button_7{};
172 u8 button_6{};
173 u8 button_5{};
174 u8 button_12{};
175 u8 button_11{};
176 u8 button_10{};
177 u8 button_9{};
178 u8 button_16{};
179 u8 button_15{};
180 u8 button_14{};
181 u8 button_13{};
182 } analog_button;
183
184 struct TouchPad {
185 u8 is_active{};
186 u8 id{};
187 u16_le x{};
188 u16_le y{};
189 } touch_1, touch_2;
190
191 u64_le motion_timestamp;
192
193 struct Accelerometer {
194 float x{};
195 float y{};
196 float z{};
197 } accel;
198
199 struct Gyroscope {
200 float pitch{};
201 float yaw{};
202 float roll{};
203 } gyro;
204};
205#pragma pack(pop)
206
207static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
208static_assert(std::is_trivially_copyable_v<PadData>,
209 "UDP Response PadData is not trivially copyable");
210
211static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
212 "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
213
214static_assert(sizeof(PadData::AnalogButton) == 12,
215 "UDP Response AnalogButton struct has wrong size ");
216static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
217static_assert(sizeof(PadData::Accelerometer) == 12,
218 "UDP Response Accelerometer struct has wrong size ");
219static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
220
221/**
222 * Create a Response Message from the data
223 * @param data array of bytes sent from the server
224 * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
225 * copy the data into the appropriate struct for that Type
226 */
227std::optional<Type> Validate(u8* data, std::size_t size);
228
229} // namespace Response
230
231template <>
232constexpr Type GetMessageType<Request::Version>() {
233 return Type::Version;
234}
235template <>
236constexpr Type GetMessageType<Request::PortInfo>() {
237 return Type::PortInfo;
238}
239template <>
240constexpr Type GetMessageType<Request::PadData>() {
241 return Type::PadData;
242}
243template <>
244constexpr Type GetMessageType<Response::Version>() {
245 return Type::Version;
246}
247template <>
248constexpr Type GetMessageType<Response::PortInfo>() {
249 return Type::PortInfo;
250}
251template <>
252constexpr Type GetMessageType<Response::PadData>() {
253 return Type::PadData;
254}
255} // 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..ca99cc22f
--- /dev/null
+++ b/src/input_common/udp/udp.cpp
@@ -0,0 +1,98 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <tuple>
7
8#include "common/param_package.h"
9#include "core/frontend/input.h"
10#include "core/settings.h"
11#include "input_common/udp/client.h"
12#include "input_common/udp/udp.h"
13
14namespace InputCommon::CemuhookUDP {
15
16class UDPTouchDevice final : public Input::TouchDevice {
17public:
18 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
19 std::tuple<float, float, bool> GetStatus() const override {
20 std::lock_guard guard(status->update_mutex);
21 return status->touch_status;
22 }
23
24private:
25 std::shared_ptr<DeviceStatus> status;
26};
27
28class UDPMotionDevice final : public Input::MotionDevice {
29public:
30 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
31 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
32 std::lock_guard guard(status->update_mutex);
33 return status->motion_status;
34 }
35
36private:
37 std::shared_ptr<DeviceStatus> status;
38};
39
40class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
41public:
42 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
43
44 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
45 {
46 std::lock_guard guard(status->update_mutex);
47 status->touch_calibration.emplace();
48 // These default values work well for DS4 but probably not other touch inputs
49 status->touch_calibration->min_x = params.Get("min_x", 100);
50 status->touch_calibration->min_y = params.Get("min_y", 50);
51 status->touch_calibration->max_x = params.Get("max_x", 1800);
52 status->touch_calibration->max_y = params.Get("max_y", 850);
53 }
54 return std::make_unique<UDPTouchDevice>(status);
55 }
56
57private:
58 std::shared_ptr<DeviceStatus> status;
59};
60
61class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
62public:
63 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
64
65 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
66 return std::make_unique<UDPMotionDevice>(status);
67 }
68
69private:
70 std::shared_ptr<DeviceStatus> status;
71};
72
73State::State() {
74 auto status = std::make_shared<DeviceStatus>();
75 client =
76 std::make_unique<Client>(status, Settings::values.udp_input_address,
77 Settings::values.udp_input_port, Settings::values.udp_pad_index);
78
79 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
80 std::make_shared<UDPTouchFactory>(status));
81 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
82 std::make_shared<UDPMotionFactory>(status));
83}
84
85State::~State() {
86 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
87 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
88}
89
90void State::ReloadUDPClient() {
91 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
92 Settings::values.udp_pad_index);
93}
94
95std::unique_ptr<State> Init() {
96 return std::make_unique<State>();
97}
98} // 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..4f83f0441
--- /dev/null
+++ b/src/input_common/udp/udp.h
@@ -0,0 +1,25 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8
9namespace InputCommon::CemuhookUDP {
10
11class Client;
12
13class State {
14public:
15 State();
16 ~State();
17 void ReloadUDPClient();
18
19private:
20 std::unique_ptr<Client> client;
21};
22
23std::unique_ptr<State> Init();
24
25} // namespace InputCommon::CemuhookUDP