summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar fearlessTobi2019-08-24 15:57:49 +0200
committerGravatar FearlessTobi2020-01-23 20:55:26 +0100
commitac3690f2057fb93ce18f156ff5ffd720a6d6f60c (patch)
treed0ec80a2537b992146d34f5bf17ba0cc549bd88e
parentMerge pull request #3341 from bunnei/time-posix-myrule (diff)
downloadyuzu-ac3690f2057fb93ce18f156ff5ffd720a6d6f60c.tar.gz
yuzu-ac3690f2057fb93ce18f156ff5ffd720a6d6f60c.tar.xz
yuzu-ac3690f2057fb93ce18f156ff5ffd720a6d6f60c.zip
Input: UDP Client to provide motion and touch controls
An implementation of the cemuhook motion/touch protocol, this adds the ability for users to connect several different devices to citra to send direct motion and touch data to citra. Co-Authored-By: jroweboy <jroweboy@gmail.com>
Diffstat (limited to '')
-rw-r--r--CMakeLists.txt7
-rw-r--r--src/common/thread.h9
-rw-r--r--src/core/settings.h3
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/main.cpp12
-rw-r--r--src/input_common/udp/client.cpp283
-rw-r--r--src/input_common/udp/client.h96
-rw-r--r--src/input_common/udp/protocol.cpp79
-rw-r--r--src/input_common/udp/protocol.h249
-rw-r--r--src/input_common/udp/udp.cpp96
-rw-r--r--src/input_common/udp/udp.h27
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/default_ini.h17
14 files changed, 904 insertions, 4 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 118572c03..dc782e252 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -350,6 +350,13 @@ function(create_target_directory_groups target_name)
350 endforeach() 350 endforeach()
351endfunction() 351endfunction()
352 352
353# Prevent boost from linking against libs when building
354add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
355 -DBOOST_SYSTEM_NO_LIB
356 -DBOOST_DATE_TIME_NO_LIB
357 -DBOOST_REGEX_NO_LIB
358)
359
353enable_testing() 360enable_testing()
354add_subdirectory(externals) 361add_subdirectory(externals)
355add_subdirectory(src) 362add_subdirectory(src)
diff --git a/src/common/thread.h b/src/common/thread.h
index 0cfd98be6..5584c3bf3 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -28,6 +28,15 @@ public:
28 is_set = false; 28 is_set = false;
29 } 29 }
30 30
31 template <class Duration>
32 bool WaitFor(const std::chrono::duration<Duration>& time) {
33 std::unique_lock<std::mutex> lk(mutex);
34 if (!condvar.wait_for(lk, time, [this] { return is_set; }))
35 return false;
36 is_set = false;
37 return true;
38 }
39
31 template <class Clock, class Duration> 40 template <class Clock, class Duration>
32 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { 41 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
33 std::unique_lock lk{mutex}; 42 std::unique_lock lk{mutex};
diff --git a/src/core/settings.h b/src/core/settings.h
index 9c98a9287..421e76f5f 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -401,6 +401,9 @@ struct Values {
401 std::string motion_device; 401 std::string motion_device;
402 TouchscreenInput touchscreen; 402 TouchscreenInput touchscreen;
403 std::atomic_bool is_device_reload_pending{true}; 403 std::atomic_bool is_device_reload_pending{true};
404 std::string udp_input_address;
405 u16 udp_input_port;
406 u8 udp_pad_index;
404 407
405 // Core 408 // Core
406 bool use_multi_core; 409 bool use_multi_core;
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..9e028da89 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() {
@@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
72namespace Polling { 76namespace Polling {
73 77
74std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { 78std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
79 std::vector<std::unique_ptr<DevicePoller>> pollers;
80
75#ifdef HAVE_SDL2 81#ifdef HAVE_SDL2
76 return sdl->GetPollers(type); 82 pollers = sdl->GetPollers(type);
77#else
78 return {};
79#endif 83#endif
84
85 return pollers;
80} 86}
81 87
82} // namespace Polling 88} // namespace Polling
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
new file mode 100644
index 000000000..c31236c7c
--- /dev/null
+++ b/src/input_common/udp/client.cpp
@@ -0,0 +1,283 @@
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::address_v4;
18using boost::asio::ip::udp;
19
20namespace InputCommon::CemuhookUDP {
21
22struct SocketCallback {
23 std::function<void(Response::Version)> version;
24 std::function<void(Response::PortInfo)> port_info;
25 std::function<void(Response::PadData)> pad_data;
26};
27
28class Socket {
29public:
30 using clock = std::chrono::system_clock;
31
32 explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
33 SocketCallback callback)
34 : client_id(client_id), timer(io_service),
35 send_endpoint(udp::endpoint(address_v4::from_string(host), port)),
36 socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index),
37 callback(std::move(callback)) {}
38
39 void Stop() {
40 io_service.stop();
41 }
42
43 void Loop() {
44 io_service.run();
45 }
46
47 void StartSend(const clock::time_point& from) {
48 timer.expires_at(from + std::chrono::seconds(3));
49 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
50 }
51
52 void StartReceive() {
53 socket.async_receive_from(
54 boost::asio::buffer(receive_buffer), receive_endpoint,
55 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
56 HandleReceive(error, bytes_transferred);
57 });
58 }
59
60private:
61 void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
62 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
63 switch (*type) {
64 case Type::Version: {
65 Response::Version version;
66 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
67 callback.version(std::move(version));
68 break;
69 }
70 case Type::PortInfo: {
71 Response::PortInfo port_info;
72 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
73 sizeof(Response::PortInfo));
74 callback.port_info(std::move(port_info));
75 break;
76 }
77 case Type::PadData: {
78 Response::PadData pad_data;
79 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
80 callback.pad_data(std::move(pad_data));
81 break;
82 }
83 }
84 }
85 StartReceive();
86 }
87
88 void HandleSend(const boost::system::error_code& error) {
89 // Send a request for getting port info for the pad
90 Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
91 auto port_message = Request::Create(port_info, client_id);
92 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
93 std::size_t len = socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint);
94
95 // Send a request for getting pad data for the pad
96 Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
97 auto pad_message = Request::Create(pad_data, client_id);
98 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
99 std::size_t len2 = socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint);
100 StartSend(timer.expiry());
101 }
102
103 SocketCallback callback;
104 boost::asio::io_service io_service;
105 boost::asio::basic_waitable_timer<clock> timer;
106 udp::socket socket;
107
108 u32 client_id;
109 u8 pad_index;
110
111 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
112 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
113 std::array<u8, PORT_INFO_SIZE> send_buffer1;
114 std::array<u8, PAD_DATA_SIZE> send_buffer2;
115 udp::endpoint send_endpoint;
116
117 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
118 udp::endpoint receive_endpoint;
119};
120
121static void SocketLoop(Socket* socket) {
122 socket->StartReceive();
123 socket->StartSend(Socket::clock::now());
124 socket->Loop();
125}
126
127Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
128 u8 pad_index, u32 client_id)
129 : status(status) {
130 StartCommunication(host, port, pad_index, client_id);
131}
132
133Client::~Client() {
134 socket->Stop();
135 thread.join();
136}
137
138void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
139 socket->Stop();
140 thread.join();
141 StartCommunication(host, port, pad_index, client_id);
142}
143
144void Client::OnVersion(Response::Version data) {
145 LOG_TRACE(Input, "Version packet received: {}", data.version);
146}
147
148void Client::OnPortInfo(Response::PortInfo data) {
149 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
150}
151
152void Client::OnPadData(Response::PadData data) {
153 LOG_TRACE(Input, "PadData packet received");
154 if (data.packet_counter <= packet_sequence) {
155 LOG_WARNING(
156 Input,
157 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
158 packet_sequence, data.packet_counter);
159 return;
160 }
161 packet_sequence = data.packet_counter;
162 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
163 // directions correspond to the ones of the Switch
164 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
165 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
166 {
167 std::lock_guard guard(status->update_mutex);
168
169 status->motion_status = {accel, gyro};
170
171 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
172 // between a simple "tap" and a hard press that causes the touch screen to click.
173 bool is_active = data.touch_1.is_active != 0;
174
175 float x = 0;
176 float y = 0;
177
178 if (is_active && status->touch_calibration) {
179 u16 min_x = status->touch_calibration->min_x;
180 u16 max_x = status->touch_calibration->max_x;
181 u16 min_y = status->touch_calibration->min_y;
182 u16 max_y = status->touch_calibration->max_y;
183
184 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
185 static_cast<float>(max_x - min_x);
186 y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
187 static_cast<float>(max_y - min_y);
188 }
189
190 status->touch_status = {x, y, is_active};
191 }
192}
193
194void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
195 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
196 [this](Response::PortInfo info) { OnPortInfo(info); },
197 [this](Response::PadData data) { OnPadData(data); }};
198 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
199 socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
200 thread = std::thread{SocketLoop, this->socket.get()};
201}
202
203void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
204 std::function<void()> success_callback,
205 std::function<void()> failure_callback) {
206 std::thread([=] {
207 Common::Event success_event;
208 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
209 [&](Response::PadData data) { success_event.Set(); }};
210 Socket socket{host, port, pad_index, client_id, callback};
211 std::thread worker_thread{SocketLoop, &socket};
212 bool result = success_event.WaitFor(std::chrono::seconds(8));
213 socket.Stop();
214 worker_thread.join();
215 if (result)
216 success_callback();
217 else
218 failure_callback();
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}, min_y{UINT16_MAX};
232 u16 max_x, max_y;
233
234 Status current_status{Status::Initialized};
235 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
236 [&](Response::PadData data) {
237 if (current_status == Status::Initialized) {
238 // Receiving data means the communication is ready now
239 current_status = Status::Ready;
240 status_callback(current_status);
241 }
242 if (!data.touch_1.is_active)
243 return;
244 LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
245 data.touch_1.y);
246 min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
247 min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
248 if (current_status == Status::Ready) {
249 // First touch - min data (min_x/min_y)
250 current_status = Status::Stage1Completed;
251 status_callback(current_status);
252 }
253 if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
254 data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
255 // Set the current position as max value and finishes
256 // configuration
257 max_x = data.touch_1.x;
258 max_y = data.touch_1.y;
259 current_status = Status::Completed;
260 data_callback(min_x, min_y, max_x, max_y);
261 status_callback(current_status);
262
263 complete_event.Set();
264 }
265 }};
266 Socket socket{host, port, pad_index, client_id, callback};
267 std::thread worker_thread{SocketLoop, &socket};
268 complete_event.Wait();
269 socket.Stop();
270 worker_thread.join();
271 })
272 .detach();
273}
274
275CalibrationConfigurationJob::~CalibrationConfigurationJob() {
276 Stop();
277}
278
279void CalibrationConfigurationJob::Stop() {
280 complete_event.Set();
281}
282
283} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
new file mode 100644
index 000000000..5177f46be
--- /dev/null
+++ b/src/input_common/udp/client.h
@@ -0,0 +1,96 @@
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 <vector>
15#include "common/common_types.h"
16#include "common/thread.h"
17#include "common/vector_math.h"
18
19namespace InputCommon::CemuhookUDP {
20
21static constexpr u16 DEFAULT_PORT = 26760;
22static constexpr const char* DEFAULT_ADDR = "127.0.0.1";
23
24class Socket;
25
26namespace Response {
27struct PadData;
28struct PortInfo;
29struct Version;
30} // namespace Response
31
32struct DeviceStatus {
33 std::mutex update_mutex;
34 std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
35 std::tuple<float, float, bool> touch_status;
36
37 // calibration data for scaling the device's touch area to 3ds
38 struct CalibrationData {
39 u16 min_x;
40 u16 min_y;
41 u16 max_x;
42 u16 max_y;
43 };
44 std::optional<CalibrationData> touch_calibration;
45};
46
47class Client {
48public:
49 explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
50 u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
51 ~Client();
52 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
53 u32 client_id = 24872);
54
55private:
56 void OnVersion(Response::Version);
57 void OnPortInfo(Response::PortInfo);
58 void OnPadData(Response::PadData);
59 void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
60
61 std::unique_ptr<Socket> socket;
62 std::shared_ptr<DeviceStatus> status;
63 std::thread thread;
64 u64 packet_sequence = 0;
65};
66
67/// An async job allowing configuration of the touchpad calibration.
68class CalibrationConfigurationJob {
69public:
70 enum class Status {
71 Initialized,
72 Ready,
73 Stage1Completed,
74 Completed,
75 };
76 /**
77 * Constructs and starts the job with the specified parameter.
78 *
79 * @param status_callback Callback for job status updates
80 * @param data_callback Called when calibration data is ready
81 */
82 explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
83 u32 client_id, std::function<void(Status)> status_callback,
84 std::function<void(u16, u16, u16, u16)> data_callback);
85 ~CalibrationConfigurationJob();
86 void Stop();
87
88private:
89 Common::Event complete_event;
90};
91
92void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
93 std::function<void()> success_callback,
94 std::function<void()> failure_callback);
95
96} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp
new file mode 100644
index 000000000..d65069207
--- /dev/null
+++ b/src/input_common/udp/protocol.cpp
@@ -0,0 +1,79 @@
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 const 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 {};
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 {};
42 }
43 if (header.protocol_version != PROTOCOL_VERSION) {
44 LOG_ERROR(Input, "UDP Packet protocol mismatch");
45 return {};
46 }
47 if (header.type < Type::Version || header.type > Type::PadData) {
48 LOG_ERROR(Input, "UDP Packet is an unknown type");
49 return {};
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 {};
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 {};
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..d31bbeb89
--- /dev/null
+++ b/src/input_common/udp/protocol.h
@@ -0,0 +1,249 @@
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 <vector>
11#include <boost/crc.hpp>
12#include "common/bit_field.h"
13#include "common/swap.h"
14
15namespace InputCommon::CemuhookUDP {
16
17constexpr std::size_t MAX_PACKET_SIZE = 100;
18constexpr u16 PROTOCOL_VERSION = 1001;
19constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
20constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
21
22enum class Type : u32 {
23 Version = 0x00100000,
24 PortInfo = 0x00100001,
25 PadData = 0x00100002,
26};
27
28struct Header {
29 u32_le magic;
30 u16_le protocol_version;
31 u16_le payload_length;
32 u32_le crc;
33 u32_le id;
34 ///> In the protocol, the type of the packet is not part of the header, but its convenient to
35 ///> include in the header so the callee doesn't have to duplicate the type twice when building
36 ///> the data
37 Type type;
38};
39static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
40static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
41
42using MacAddress = std::array<u8, 6>;
43constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
44
45#pragma pack(push, 1)
46template <typename T>
47struct Message {
48 Header header;
49 T data;
50};
51#pragma pack(pop)
52
53template <typename T>
54constexpr Type GetMessageType();
55
56namespace Request {
57
58struct Version {};
59/**
60 * Requests the server to send information about what controllers are plugged into the ports
61 * In citra's case, we only have one controller, so for simplicity's sake, we can just send a
62 * request explicitly for the first controller port and leave it at that. In the future it would be
63 * nice to make this configurable
64 */
65constexpr u32 MAX_PORTS = 4;
66struct PortInfo {
67 u32_le pad_count; ///> Number of ports to request data for
68 std::array<u8, MAX_PORTS> port;
69};
70static_assert(std::is_trivially_copyable_v<PortInfo>,
71 "UDP Request PortInfo is not trivially copyable");
72
73/**
74 * Request the latest pad information from the server. If the server hasn't received this message
75 * from the client in a reasonable time frame, the server will stop sending updates. The default
76 * timeout seems to be 5 seconds.
77 */
78struct PadData {
79 enum class Flags : u8 {
80 AllPorts,
81 Id,
82 Mac,
83 };
84 /// Determines which method will be used as a look up for the controller
85 Flags flags;
86 /// Index of the port of the controller to retrieve data about
87 u8 port_id;
88 /// Mac address of the controller to retrieve data about
89 MacAddress mac;
90};
91static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
92static_assert(std::is_trivially_copyable_v<PadData>,
93 "UDP Request PadData is not trivially copyable");
94
95/**
96 * Creates a message with the proper header data that can be sent to the server.
97 * @param T data Request body to send
98 * @param client_id ID of the udp client (usually not checked on the server)
99 */
100template <typename T>
101Message<T> Create(const T data, const u32 client_id = 0) {
102 boost::crc_32_type crc;
103 Header header{
104 CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
105 };
106 Message<T> message{header, data};
107 crc.process_bytes(&message, sizeof(Message<T>));
108 message.header.crc = crc.checksum();
109 return message;
110}
111} // namespace Request
112
113namespace Response {
114
115struct Version {
116 u16_le version;
117};
118static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
119static_assert(std::is_trivially_copyable_v<Version>,
120 "UDP Response Version is not trivially copyable");
121
122struct PortInfo {
123 u8 id;
124 u8 state;
125 u8 model;
126 u8 connection_type;
127 MacAddress mac;
128 u8 battery;
129 u8 is_pad_active;
130};
131static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
132static_assert(std::is_trivially_copyable_v<PortInfo>,
133 "UDP Response PortInfo is not trivially copyable");
134
135#pragma pack(push, 1)
136struct PadData {
137 PortInfo info;
138 u32_le packet_counter;
139
140 u16_le digital_button;
141 // The following union isn't trivially copyable but we don't use this input anyway.
142 // union DigitalButton {
143 // u16_le button;
144 // BitField<0, 1, u16> button_1; // Share
145 // BitField<1, 1, u16> button_2; // L3
146 // BitField<2, 1, u16> button_3; // R3
147 // BitField<3, 1, u16> button_4; // Options
148 // BitField<4, 1, u16> button_5; // Up
149 // BitField<5, 1, u16> button_6; // Right
150 // BitField<6, 1, u16> button_7; // Down
151 // BitField<7, 1, u16> button_8; // Left
152 // BitField<8, 1, u16> button_9; // L2
153 // BitField<9, 1, u16> button_10; // R2
154 // BitField<10, 1, u16> button_11; // L1
155 // BitField<11, 1, u16> button_12; // R1
156 // BitField<12, 1, u16> button_13; // Triangle
157 // BitField<13, 1, u16> button_14; // Circle
158 // BitField<14, 1, u16> button_15; // Cross
159 // BitField<15, 1, u16> button_16; // Square
160 // } digital_button;
161
162 u8 home;
163 /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
164 u8 touch_hard_press;
165 u8 left_stick_x;
166 u8 left_stick_y;
167 u8 right_stick_x;
168 u8 right_stick_y;
169
170 struct AnalogButton {
171 u8 button_8;
172 u8 button_7;
173 u8 button_6;
174 u8 button_5;
175 u8 button_12;
176 u8 button_11;
177 u8 button_10;
178 u8 button_9;
179 u8 button_16;
180 u8 button_15;
181 u8 button_14;
182 u8 button_13;
183 } analog_button;
184
185 struct TouchPad {
186 u8 is_active;
187 u8 id;
188 u16_le x;
189 u16_le y;
190 } touch_1, touch_2;
191
192 u64_le motion_timestamp;
193
194 struct Accelerometer {
195 float x;
196 float y;
197 float z;
198 } accel;
199
200 struct Gyroscope {
201 float pitch;
202 float yaw;
203 float roll;
204 } gyro;
205};
206#pragma pack(pop)
207
208static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
209static_assert(std::is_trivially_copyable_v<PadData>,
210 "UDP Response PadData is not trivially copyable");
211
212static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
213 "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
214
215/**
216 * Create a Response Message from the data
217 * @param data array of bytes sent from the server
218 * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
219 * copy the data into the appropriate struct for that Type
220 */
221std::optional<Type> Validate(u8* data, std::size_t size);
222
223} // namespace Response
224
225template <>
226constexpr Type GetMessageType<Request::Version>() {
227 return Type::Version;
228}
229template <>
230constexpr Type GetMessageType<Request::PortInfo>() {
231 return Type::PortInfo;
232}
233template <>
234constexpr Type GetMessageType<Request::PadData>() {
235 return Type::PadData;
236}
237template <>
238constexpr Type GetMessageType<Response::Version>() {
239 return Type::Version;
240}
241template <>
242constexpr Type GetMessageType<Response::PortInfo>() {
243 return Type::PortInfo;
244}
245template <>
246constexpr Type GetMessageType<Response::PadData>() {
247 return Type::PadData;
248}
249} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
new file mode 100644
index 000000000..a80f38614
--- /dev/null
+++ b/src/input_common/udp/udp.cpp
@@ -0,0 +1,96 @@
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 "common/logging/log.h"
6#include "common/param_package.h"
7#include "core/frontend/input.h"
8#include "core/settings.h"
9#include "input_common/udp/client.h"
10#include "input_common/udp/udp.h"
11
12namespace InputCommon::CemuhookUDP {
13
14class UDPTouchDevice final : public Input::TouchDevice {
15public:
16 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
17 std::tuple<float, float, bool> GetStatus() const {
18 std::lock_guard guard(status->update_mutex);
19 return status->touch_status;
20 }
21
22private:
23 std::shared_ptr<DeviceStatus> status;
24};
25
26class UDPMotionDevice final : public Input::MotionDevice {
27public:
28 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
29 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const {
30 std::lock_guard guard(status->update_mutex);
31 return status->motion_status;
32 }
33
34private:
35 std::shared_ptr<DeviceStatus> status;
36};
37
38class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
39public:
40 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
41
42 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
43 {
44 std::lock_guard guard(status->update_mutex);
45 status->touch_calibration.emplace();
46 // These default values work well for DS4 but probably not other touch inputs
47 status->touch_calibration->min_x = params.Get("min_x", 100);
48 status->touch_calibration->min_y = params.Get("min_y", 50);
49 status->touch_calibration->max_x = params.Get("max_x", 1800);
50 status->touch_calibration->max_y = params.Get("max_y", 850);
51 }
52 return std::make_unique<UDPTouchDevice>(status);
53 }
54
55private:
56 std::shared_ptr<DeviceStatus> status;
57};
58
59class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
60public:
61 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
62
63 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
64 return std::make_unique<UDPMotionDevice>(status);
65 }
66
67private:
68 std::shared_ptr<DeviceStatus> status;
69};
70
71State::State() {
72 auto status = std::make_shared<DeviceStatus>();
73 client =
74 std::make_unique<Client>(status, Settings::values.udp_input_address,
75 Settings::values.udp_input_port, Settings::values.udp_pad_index);
76
77 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
78 std::make_shared<UDPTouchFactory>(status));
79 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
80 std::make_shared<UDPMotionFactory>(status));
81}
82
83State::~State() {
84 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
85 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
86}
87
88void State::ReloadUDPClient() {
89 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
90 Settings::values.udp_pad_index);
91}
92
93std::unique_ptr<State> Init() {
94 return std::make_unique<State>();
95}
96} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
new file mode 100644
index 000000000..ea3de60bb
--- /dev/null
+++ b/src/input_common/udp/udp.h
@@ -0,0 +1,27 @@
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 <memory>
6#include <unordered_map>
7#include "input_common/main.h"
8#include "input_common/udp/client.h"
9
10namespace InputCommon::CemuhookUDP {
11
12class UDPTouchDevice;
13class UDPMotionDevice;
14
15class State {
16public:
17 State();
18 ~State();
19 void ReloadUDPClient();
20
21private:
22 std::unique_ptr<Client> client;
23};
24
25std::unique_ptr<State> Init();
26
27} // namespace InputCommon::CemuhookUDP
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f92a4b3c3..59918847a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -10,6 +10,7 @@
10#include "core/hle/service/acc/profile_manager.h" 10#include "core/hle/service/acc/profile_manager.h"
11#include "core/hle/service/hid/controllers/npad.h" 11#include "core/hle/service/hid/controllers/npad.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/udp/client.h"
13#include "yuzu/configuration/config.h" 14#include "yuzu/configuration/config.h"
14#include "yuzu/uisettings.h" 15#include "yuzu/uisettings.h"
15 16
@@ -429,6 +430,16 @@ void Config::ReadControlValues() {
429 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) 430 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
430 .toString() 431 .toString()
431 .toStdString(); 432 .toStdString();
433 Settings::values.udp_input_address =
434 ReadSetting(QStringLiteral("udp_input_address"),
435 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
436 .toString()
437 .toStdString();
438 Settings::values.udp_input_port = static_cast<u16>(
439 ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
440 .toInt());
441 Settings::values.udp_pad_index =
442 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
432 443
433 qt_config->endGroup(); 444 qt_config->endGroup();
434} 445}
@@ -911,6 +922,12 @@ void Config::SaveControlValues() {
911 QString::fromStdString(Settings::values.motion_device), 922 QString::fromStdString(Settings::values.motion_device),
912 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 923 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
913 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); 924 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
925 WriteSetting(QStringLiteral("udp_input_address"),
926 QString::fromStdString(Settings::values.udp_input_address),
927 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
928 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
929 InputCommon::CemuhookUDP::DEFAULT_PORT);
930 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
914 931
915 qt_config->endGroup(); 932 qt_config->endGroup();
916} 933}
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 1a812cb87..86f65cf46 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -12,6 +12,7 @@
12#include "core/hle/service/acc/profile_manager.h" 12#include "core/hle/service/acc/profile_manager.h"
13#include "core/settings.h" 13#include "core/settings.h"
14#include "input_common/main.h" 14#include "input_common/main.h"
15#include "input_common/udp/client.h"
15#include "yuzu_cmd/config.h" 16#include "yuzu_cmd/config.h"
16#include "yuzu_cmd/default_ini.h" 17#include "yuzu_cmd/default_ini.h"
17 18
@@ -297,6 +298,10 @@ void Config::ReadValues() {
297 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); 298 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
298 Settings::values.touchscreen.diameter_y = 299 Settings::values.touchscreen.diameter_y =
299 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); 300 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
301 Settings::values.udp_input_address = sdl2_config->GetString(
302 "Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
303 Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger(
304 "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));
300 305
301 std::transform(keyboard_keys.begin(), keyboard_keys.end(), 306 std::transform(keyboard_keys.begin(), keyboard_keys.end(),
302 Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); 307 Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 8d18a4a5a..e829f8695 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -69,12 +69,29 @@ rstick=
69# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: 69# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
70# - "update_period": update period in milliseconds (default to 100) 70# - "update_period": update period in milliseconds (default to 100)
71# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) 71# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
72# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
72motion_device= 73motion_device=
73 74
74# for touch input, the following devices are available: 75# for touch input, the following devices are available:
75# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required 76# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
77# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
78# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
76touch_device= 79touch_device=
77 80
81# Most desktop operating systems do not expose a way to poll the motion state of the controllers
82# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
83# from a controller device to the client program. Citra has a client that can connect and read
84# from any cemuhook compatible motion program.
85
86# IPv4 address of the udp input server (Default "127.0.0.1")
87udp_input_address=
88
89# Port of the udp input server. (Default 26760)
90udp_input_port=
91
92# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
93udp_pad_index=
94
78[Core] 95[Core]
79# Whether to use multi-core for CPU emulation 96# Whether to use multi-core for CPU emulation
80# 0 (default): Disabled, 1: Enabled 97# 0 (default): Disabled, 1: Enabled