303 lines
8.8 KiB
C++
Executable File
303 lines
8.8 KiB
C++
Executable File
// 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
|