summaryrefslogtreecommitdiffstats
path: root/src/input_common/helpers
diff options
context:
space:
mode:
authorFernando S <fsahmkow27@gmail.com>2021-11-27 11:52:08 +0100
committerGitHub <noreply@github.com>2021-11-27 11:52:08 +0100
commit564f10527745f870621c08bbb5d16badee0ed861 (patch)
treee8ac8dee60086facf1837393882865f5df18c95e /src/input_common/helpers
parentMerge pull request #7431 from liushuyu/fix-linux-decoding (diff)
parentconfig: Remove vibration configuration (diff)
downloadyuzu-564f10527745f870621c08bbb5d16badee0ed861.tar
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.gz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.bz2
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.lz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.xz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.zst
yuzu-564f10527745f870621c08bbb5d16badee0ed861.zip
Diffstat (limited to 'src/input_common/helpers')
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp304
-rw-r--r--src/input_common/helpers/stick_from_buttons.h30
-rw-r--r--src/input_common/helpers/touch_from_buttons.cpp81
-rw-r--r--src/input_common/helpers/touch_from_buttons.h22
-rw-r--r--src/input_common/helpers/udp_protocol.cpp78
-rw-r--r--src/input_common/helpers/udp_protocol.h290
6 files changed, 805 insertions, 0 deletions
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
new file mode 100644
index 000000000..77fcd655e
--- /dev/null
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -0,0 +1,304 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <cmath>
+#include "common/math_util.h"
+#include "common/settings.h"
+#include "input_common/helpers/stick_from_buttons.h"
+
+namespace InputCommon {
+
+class Stick final : public Common::Input::InputDevice {
+public:
+ using Button = std::unique_ptr<Common::Input::InputDevice>;
+
+ Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
+ float modifier_scale_, float modifier_angle_)
+ : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
+ right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
+ modifier_angle(modifier_angle_) {
+ Common::Input::InputCallback button_up_callback{
+ [this](Common::Input::CallbackStatus callback_) { UpdateUpButtonStatus(callback_); }};
+ Common::Input::InputCallback button_down_callback{
+ [this](Common::Input::CallbackStatus callback_) { UpdateDownButtonStatus(callback_); }};
+ Common::Input::InputCallback button_left_callback{
+ [this](Common::Input::CallbackStatus callback_) { UpdateLeftButtonStatus(callback_); }};
+ Common::Input::InputCallback button_right_callback{
+ [this](Common::Input::CallbackStatus callback_) {
+ UpdateRightButtonStatus(callback_);
+ }};
+ Common::Input::InputCallback button_modifier_callback{
+ [this](Common::Input::CallbackStatus callback_) { UpdateModButtonStatus(callback_); }};
+ up->SetCallback(button_up_callback);
+ down->SetCallback(button_down_callback);
+ left->SetCallback(button_left_callback);
+ right->SetCallback(button_right_callback);
+ modifier->SetCallback(button_modifier_callback);
+ last_x_axis_value = 0.0f;
+ last_y_axis_value = 0.0f;
+ }
+
+ bool IsAngleGreater(float old_angle, float new_angle) const {
+ constexpr float TAU = Common::PI * 2.0f;
+ // Use wider angle to ease the transition.
+ constexpr float aperture = TAU * 0.15f;
+ const float top_limit = new_angle + aperture;
+ return (old_angle > new_angle && old_angle <= top_limit) ||
+ (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
+ }
+
+ bool IsAngleSmaller(float old_angle, float new_angle) const {
+ constexpr float TAU = Common::PI * 2.0f;
+ // Use wider angle to ease the transition.
+ constexpr float aperture = TAU * 0.15f;
+ const float bottom_limit = new_angle - aperture;
+ return (old_angle >= bottom_limit && old_angle < new_angle) ||
+ (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
+ }
+
+ float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
+ constexpr float TAU = Common::PI * 2.0f;
+ float new_angle = angle;
+
+ auto time_difference = static_cast<float>(
+ std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
+ time_difference /= 1000.0f * 1000.0f;
+ if (time_difference > 0.5f) {
+ time_difference = 0.5f;
+ }
+
+ if (IsAngleGreater(new_angle, goal_angle)) {
+ new_angle -= modifier_angle * time_difference;
+ if (new_angle < 0) {
+ new_angle += TAU;
+ }
+ if (!IsAngleGreater(new_angle, goal_angle)) {
+ return goal_angle;
+ }
+ } else if (IsAngleSmaller(new_angle, goal_angle)) {
+ new_angle += modifier_angle * time_difference;
+ if (new_angle >= TAU) {
+ new_angle -= TAU;
+ }
+ if (!IsAngleSmaller(new_angle, goal_angle)) {
+ return goal_angle;
+ }
+ } else {
+ return goal_angle;
+ }
+ return new_angle;
+ }
+
+ void SetGoalAngle(bool r, bool l, bool u, bool d) {
+ // Move to the right
+ if (r && !u && !d) {
+ goal_angle = 0.0f;
+ }
+
+ // Move to the upper right
+ if (r && u && !d) {
+ goal_angle = Common::PI * 0.25f;
+ }
+
+ // Move up
+ if (u && !l && !r) {
+ goal_angle = Common::PI * 0.5f;
+ }
+
+ // Move to the upper left
+ if (l && u && !d) {
+ goal_angle = Common::PI * 0.75f;
+ }
+
+ // Move to the left
+ if (l && !u && !d) {
+ goal_angle = Common::PI;
+ }
+
+ // Move to the bottom left
+ if (l && !u && d) {
+ goal_angle = Common::PI * 1.25f;
+ }
+
+ // Move down
+ if (d && !l && !r) {
+ goal_angle = Common::PI * 1.5f;
+ }
+
+ // Move to the bottom right
+ if (r && !u && d) {
+ goal_angle = Common::PI * 1.75f;
+ }
+ }
+
+ void UpdateUpButtonStatus(Common::Input::CallbackStatus button_callback) {
+ up_status = button_callback.button_status.value;
+ UpdateStatus();
+ }
+
+ void UpdateDownButtonStatus(Common::Input::CallbackStatus button_callback) {
+ down_status = button_callback.button_status.value;
+ UpdateStatus();
+ }
+
+ void UpdateLeftButtonStatus(Common::Input::CallbackStatus button_callback) {
+ left_status = button_callback.button_status.value;
+ UpdateStatus();
+ }
+
+ void UpdateRightButtonStatus(Common::Input::CallbackStatus button_callback) {
+ right_status = button_callback.button_status.value;
+ UpdateStatus();
+ }
+
+ void UpdateModButtonStatus(Common::Input::CallbackStatus button_callback) {
+ modifier_status = button_callback.button_status.value;
+ UpdateStatus();
+ }
+
+ void UpdateStatus() {
+ const float coef = modifier_status ? modifier_scale : 1.0f;
+
+ bool r = right_status;
+ bool l = left_status;
+ bool u = up_status;
+ bool d = down_status;
+
+ // Eliminate contradictory movements
+ if (r && l) {
+ r = false;
+ l = false;
+ }
+ if (u && d) {
+ u = false;
+ d = false;
+ }
+
+ // Move if a key is pressed
+ if (r || l || u || d) {
+ amplitude = coef;
+ } else {
+ amplitude = 0;
+ }
+
+ const auto now = std::chrono::steady_clock::now();
+ const auto time_difference = static_cast<u64>(
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
+
+ if (time_difference < 10) {
+ // Disable analog mode if inputs are too fast
+ SetGoalAngle(r, l, u, d);
+ angle = goal_angle;
+ } else {
+ angle = GetAngle(now);
+ SetGoalAngle(r, l, u, d);
+ }
+
+ last_update = now;
+ Common::Input::CallbackStatus status{
+ .type = Common::Input::InputType::Stick,
+ .stick_status = GetStatus(),
+ };
+ last_x_axis_value = status.stick_status.x.raw_value;
+ last_y_axis_value = status.stick_status.y.raw_value;
+ TriggerOnChange(status);
+ }
+
+ void ForceUpdate() override {
+ up->ForceUpdate();
+ down->ForceUpdate();
+ left->ForceUpdate();
+ right->ForceUpdate();
+ modifier->ForceUpdate();
+ }
+
+ void SoftUpdate() override {
+ Common::Input::CallbackStatus status{
+ .type = Common::Input::InputType::Stick,
+ .stick_status = GetStatus(),
+ };
+ if (last_x_axis_value == status.stick_status.x.raw_value &&
+ last_y_axis_value == status.stick_status.y.raw_value) {
+ return;
+ }
+ last_x_axis_value = status.stick_status.x.raw_value;
+ last_y_axis_value = status.stick_status.y.raw_value;
+ TriggerOnChange(status);
+ }
+
+ Common::Input::StickStatus GetStatus() const {
+ Common::Input::StickStatus status{};
+ status.x.properties = properties;
+ status.y.properties = properties;
+ if (Settings::values.emulate_analog_keyboard) {
+ const auto now = std::chrono::steady_clock::now();
+ float angle_ = GetAngle(now);
+ status.x.raw_value = std::cos(angle_) * amplitude;
+ status.y.raw_value = std::sin(angle_) * amplitude;
+ return status;
+ }
+ constexpr float SQRT_HALF = 0.707106781f;
+ int x = 0, y = 0;
+ if (right_status) {
+ ++x;
+ }
+ if (left_status) {
+ --x;
+ }
+ if (up_status) {
+ ++y;
+ }
+ if (down_status) {
+ --y;
+ }
+ const float coef = modifier_status ? modifier_scale : 1.0f;
+ status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
+ status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
+ return status;
+ }
+
+private:
+ Button up;
+ Button down;
+ Button left;
+ Button right;
+ Button modifier;
+ float modifier_scale;
+ float modifier_angle;
+ float angle{};
+ float goal_angle{};
+ float amplitude{};
+ bool up_status;
+ bool down_status;
+ bool left_status;
+ bool right_status;
+ bool modifier_status;
+ float last_x_axis_value;
+ float last_y_axis_value;
+ const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
+ std::chrono::time_point<std::chrono::steady_clock> last_update;
+};
+
+std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
+ const Common::ParamPackage& params) {
+ const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
+ auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("up", null_engine));
+ auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("down", null_engine));
+ auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("left", null_engine));
+ auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("right", null_engine));
+ auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("modifier", null_engine));
+ auto modifier_scale = params.Get("modifier_scale", 0.5f);
+ auto modifier_angle = params.Get("modifier_angle", 5.5f);
+ return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
+ std::move(right), std::move(modifier), modifier_scale,
+ modifier_angle);
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/helpers/stick_from_buttons.h b/src/input_common/helpers/stick_from_buttons.h
new file mode 100644
index 000000000..437ace4f7
--- /dev/null
+++ b/src/input_common/helpers/stick_from_buttons.h
@@ -0,0 +1,30 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/input.h"
+
+namespace InputCommon {
+
+/**
+ * An analog device factory that takes direction button devices and combines them into a analog
+ * device.
+ */
+class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
+public:
+ /**
+ * Creates an analog device from direction button devices
+ * @param params contains parameters for creating the device:
+ * - "up": a serialized ParamPackage for creating a button device for up direction
+ * - "down": a serialized ParamPackage for creating a button device for down direction
+ * - "left": a serialized ParamPackage for creating a button device for left direction
+ * - "right": a serialized ParamPackage for creating a button device for right direction
+ * - "modifier": a serialized ParamPackage for creating a button device as the modifier
+ * - "modifier_scale": a float for the multiplier the modifier gives to the position
+ */
+ std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp
new file mode 100644
index 000000000..35d60bc90
--- /dev/null
+++ b/src/input_common/helpers/touch_from_buttons.cpp
@@ -0,0 +1,81 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include "common/settings.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "input_common/helpers/touch_from_buttons.h"
+
+namespace InputCommon {
+
+class TouchFromButtonDevice final : public Common::Input::InputDevice {
+public:
+ using Button = std::unique_ptr<Common::Input::InputDevice>;
+ TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_)
+ : button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) {
+ Common::Input::InputCallback button_up_callback{
+ [this](Common::Input::CallbackStatus callback_) { UpdateButtonStatus(callback_); }};
+ last_button_value = false;
+ button->SetCallback(button_up_callback);
+ button->ForceUpdate();
+ }
+
+ void ForceUpdate() override {
+ button->ForceUpdate();
+ }
+
+ Common::Input::TouchStatus GetStatus(bool pressed) const {
+ const Common::Input::ButtonStatus button_status{
+ .value = pressed,
+ };
+ Common::Input::TouchStatus status{
+ .pressed = button_status,
+ .x = {},
+ .y = {},
+ .id = touch_id,
+ };
+ status.x.properties = properties;
+ status.y.properties = properties;
+
+ if (!pressed) {
+ return status;
+ }
+
+ status.x.raw_value = x;
+ status.y.raw_value = y;
+ return status;
+ }
+
+ void UpdateButtonStatus(Common::Input::CallbackStatus button_callback) {
+ const Common::Input::CallbackStatus status{
+ .type = Common::Input::InputType::Touch,
+ .touch_status = GetStatus(button_callback.button_status.value),
+ };
+ if (last_button_value != button_callback.button_status.value) {
+ last_button_value = button_callback.button_status.value;
+ TriggerOnChange(status);
+ }
+ }
+
+private:
+ Button button;
+ bool last_button_value;
+ const int touch_id;
+ const float x;
+ const float y;
+ const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
+};
+
+std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
+ const Common::ParamPackage& params) {
+ const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
+ auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+ params.Get("button", null_engine));
+ const auto touch_id = params.Get("touch_id", 0);
+ const float x = params.Get("x", 0.0f) / 1280.0f;
+ const float y = params.Get("y", 0.0f) / 720.0f;
+ return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y);
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/helpers/touch_from_buttons.h b/src/input_common/helpers/touch_from_buttons.h
new file mode 100644
index 000000000..628f18215
--- /dev/null
+++ b/src/input_common/helpers/touch_from_buttons.h
@@ -0,0 +1,22 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/input.h"
+
+namespace InputCommon {
+
+/**
+ * A touch device factory that takes a list of button devices and combines them into a touch device.
+ */
+class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
+public:
+ /**
+ * Creates a touch device from a list of button devices
+ */
+ std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/helpers/udp_protocol.cpp b/src/input_common/helpers/udp_protocol.cpp
new file mode 100644
index 000000000..cdeab7e11
--- /dev/null
+++ b/src/input_common/helpers/udp_protocol.cpp
@@ -0,0 +1,78 @@
+// 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/helpers/udp_protocol.h"
+
+namespace InputCommon::CemuhookUDP {
+
+static constexpr std::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)
+ */
+std::optional<Type> Validate(u8* data, std::size_t size) {
+ if (size < sizeof(Header)) {
+ return std::nullopt;
+ }
+ Header header{};
+ std::memcpy(&header, data, sizeof(Header));
+ if (header.magic != SERVER_MAGIC) {
+ LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
+ return std::nullopt;
+ }
+ if (header.protocol_version != PROTOCOL_VERSION) {
+ LOG_ERROR(Input, "UDP Packet protocol mismatch");
+ return std::nullopt;
+ }
+ if (header.type < Type::Version || header.type > Type::PadData) {
+ LOG_ERROR(Input, "UDP Packet is an unknown type");
+ return std::nullopt;
+ }
+
+ // 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 std::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 std::nullopt;
+ }
+
+ 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 std::nullopt;
+ }
+ return header.type;
+}
+} // namespace Response
+
+} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/helpers/udp_protocol.h b/src/input_common/helpers/udp_protocol.h
new file mode 100644
index 000000000..bcba12c58
--- /dev/null
+++ b/src/input_common/helpers/udp_protocol.h
@@ -0,0 +1,290 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <optional>
+#include <type_traits>
+
+#include <boost/crc.hpp>
+
+#include "common/bit_field.h"
+#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();
+
+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 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 {
+ /// 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) {
+ 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 {
+
+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