summaryrefslogtreecommitdiffstats
path: root/src/input_common/helpers/udp_protocol.h
blob: d9643ffe049862f1271d6568d1bc2ceca52e18db (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <optional>
#include <type_traits>

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used
#endif

#include <boost/crc.hpp>

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#include "common/swap.h"

namespace InputCommon::CemuhookUDP {

constexpr std::size_t MAX_PACKET_SIZE = 100;
constexpr u16 PROTOCOL_VERSION = 1001;
constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)

enum class Type : u32 {
    Version = 0x00100000,
    PortInfo = 0x00100001,
    PadData = 0x00100002,
};

struct Header {
    u32_le magic{};
    u16_le protocol_version{};
    u16_le payload_length{};
    u32_le crc{};
    u32_le id{};
    ///> In the protocol, the type of the packet is not part of the header, but its convenient to
    ///> include in the header so the callee doesn't have to duplicate the type twice when building
    ///> the data
    Type type{};
};
static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");

using MacAddress = std::array<u8, 6>;
constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};

#pragma pack(push, 1)
template <typename T>
struct Message {
    Header header{};
    T data;
};
#pragma pack(pop)

template <typename T>
constexpr Type GetMessageType();

template <typename T>
Message<T> CreateMessage(const u32 magic, const T data, const u32 sender_id) {
    boost::crc_32_type crc;
    Header header{
        magic, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, sender_id, GetMessageType<T>(),
    };
    Message<T> message{header, data};
    crc.process_bytes(&message, sizeof(Message<T>));
    message.header.crc = crc.checksum();
    return message;
}

namespace Request {

enum RegisterFlags : u8 {
    AllPads,
    PadID,
    PadMACAdddress,
};

struct Version {};
/**
 * Requests the server to send information about what controllers are plugged into the ports
 * In yuzu'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 {
    /// Determines which method will be used as a look up for the controller
    RegisterFlags flags{};
    /// Index of the port of the controller to retrieve data about
    u8 port_id{};
    /// Mac address of the controller to retrieve data about
    MacAddress mac;
};
static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
static_assert(std::is_trivially_copyable_v<PadData>,
              "UDP Request PadData is not trivially copyable");

/**
 * Creates a message with the proper header data that can be sent to the server.
 * @param data Request body to send
 * @param client_id ID of the udp client (usually not checked on the server)
 */
template <typename T>
Message<T> Create(const T data, const u32 client_id = 0) {
    return CreateMessage(CLIENT_MAGIC, data, client_id);
}
} // namespace Request

namespace Response {

enum class ConnectionType : u8 {
    None,
    Usb,
    Bluetooth,
};

enum class State : u8 {
    Disconnected,
    Reserved,
    Connected,
};

enum class Model : u8 {
    None,
    PartialGyro,
    FullGyro,
    Generic,
};

enum class Battery : u8 {
    None = 0x00,
    Dying = 0x01,
    Low = 0x02,
    Medium = 0x03,
    High = 0x04,
    Full = 0x05,
    Charging = 0xEE,
    Charged = 0xEF,
};

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{};
    State state{};
    Model model{};
    ConnectionType connection_type{};
    MacAddress mac;
    Battery battery{};
    u8 is_pad_active{};
};
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
static_assert(std::is_trivially_copyable_v<PortInfo>,
              "UDP Response PortInfo is not trivially copyable");

struct TouchPad {
    u8 is_active{};
    u8 id{};
    u16_le x{};
    u16_le y{};
};
static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");

#pragma pack(push, 1)
struct PadData {
    PortInfo info{};
    u32_le packet_counter{};

    u16_le digital_button{};
    // The following union isn't trivially copyable but we don't use this input anyway.
    // union DigitalButton {
    //     u16_le button;
    //     BitField<0, 1, u16> button_1;   // Share
    //     BitField<1, 1, u16> button_2;   // L3
    //     BitField<2, 1, u16> button_3;   // R3
    //     BitField<3, 1, u16> button_4;   // Options
    //     BitField<4, 1, u16> button_5;   // Up
    //     BitField<5, 1, u16> button_6;   // Right
    //     BitField<6, 1, u16> button_7;   // Down
    //     BitField<7, 1, u16> button_8;   // Left
    //     BitField<8, 1, u16> button_9;   // L2
    //     BitField<9, 1, u16> button_10;  // R2
    //     BitField<10, 1, u16> button_11; // L1
    //     BitField<11, 1, u16> button_12; // R1
    //     BitField<12, 1, u16> button_13; // Triangle
    //     BitField<13, 1, u16> button_14; // Circle
    //     BitField<14, 1, u16> button_15; // Cross
    //     BitField<15, 1, u16> button_16; // Square
    // } digital_button;

    u8 home;
    /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
    u8 touch_hard_press{};
    u8 left_stick_x{};
    u8 left_stick_y{};
    u8 right_stick_x{};
    u8 right_stick_y{};

    struct AnalogButton {
        u8 button_dpad_left_analog{};
        u8 button_dpad_down_analog{};
        u8 button_dpad_right_analog{};
        u8 button_dpad_up_analog{};
        u8 button_square_analog{};
        u8 button_cross_analog{};
        u8 button_circle_analog{};
        u8 button_triangle_analog{};
        u8 button_r1_analog{};
        u8 button_l1_analog{};
        u8 trigger_r2{};
        u8 trigger_l2{};
    } analog_button;

    std::array<TouchPad, 2> touch;

    u64_le motion_timestamp;

    struct Accelerometer {
        float x{};
        float y{};
        float z{};
    } accel;

    struct Gyroscope {
        float pitch{};
        float yaw{};
        float roll{};
    } gyro;
};
#pragma pack(pop)

static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
static_assert(std::is_trivially_copyable_v<PadData>,
              "UDP Response PadData is not trivially copyable");

static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
              "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");

static_assert(sizeof(PadData::AnalogButton) == 12,
              "UDP Response AnalogButton struct has wrong size ");
static_assert(sizeof(PadData::Accelerometer) == 12,
              "UDP Response Accelerometer struct has wrong size ");
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");

/**
 * Create a Response Message from the data
 * @param data array of bytes sent from the server
 * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
 * copy the data into the appropriate struct for that Type
 */
std::optional<Type> Validate(u8* data, std::size_t size);

} // namespace Response

template <>
constexpr Type GetMessageType<Request::Version>() {
    return Type::Version;
}
template <>
constexpr Type GetMessageType<Request::PortInfo>() {
    return Type::PortInfo;
}
template <>
constexpr Type GetMessageType<Request::PadData>() {
    return Type::PadData;
}
template <>
constexpr Type GetMessageType<Response::Version>() {
    return Type::Version;
}
template <>
constexpr Type GetMessageType<Response::PortInfo>() {
    return Type::PortInfo;
}
template <>
constexpr Type GetMessageType<Response::PadData>() {
    return Type::PadData;
}
} // namespace InputCommon::CemuhookUDP