From ac3690f2057fb93ce18f156ff5ffd720a6d6f60c Mon Sep 17 00:00:00 2001
From: fearlessTobi <thm.frey@gmail.com>
Date: Sat, 24 Aug 2019 15:57:49 +0200
Subject: [PATCH] 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>
---
 CMakeLists.txt                    |   7 +
 src/common/thread.h               |   9 +
 src/core/settings.h               |   3 +
 src/input_common/CMakeLists.txt   |   8 +-
 src/input_common/main.cpp         |  12 +-
 src/input_common/udp/client.cpp   | 283 ++++++++++++++++++++++++++++++
 src/input_common/udp/client.h     |  96 ++++++++++
 src/input_common/udp/protocol.cpp |  79 +++++++++
 src/input_common/udp/protocol.h   | 249 ++++++++++++++++++++++++++
 src/input_common/udp/udp.cpp      |  96 ++++++++++
 src/input_common/udp/udp.h        |  27 +++
 src/yuzu/configuration/config.cpp |  17 ++
 src/yuzu_cmd/config.cpp           |   5 +
 src/yuzu_cmd/default_ini.h        |  17 ++
 14 files changed, 904 insertions(+), 4 deletions(-)
 create mode 100644 src/input_common/udp/client.cpp
 create mode 100644 src/input_common/udp/client.h
 create mode 100644 src/input_common/udp/protocol.cpp
 create mode 100644 src/input_common/udp/protocol.h
 create mode 100644 src/input_common/udp/udp.cpp
 create mode 100644 src/input_common/udp/udp.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 118572c03c..dc782e2529 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -350,6 +350,13 @@ function(create_target_directory_groups target_name)
     endforeach()
 endfunction()
 
+# Prevent boost from linking against libs when building
+add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
+    -DBOOST_SYSTEM_NO_LIB
+    -DBOOST_DATE_TIME_NO_LIB
+    -DBOOST_REGEX_NO_LIB
+)
+
 enable_testing()
 add_subdirectory(externals)
 add_subdirectory(src)
diff --git a/src/common/thread.h b/src/common/thread.h
index 0cfd98be6c..5584c3bf36 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -28,6 +28,15 @@ public:
         is_set = false;
     }
 
+    template <class Duration>
+    bool WaitFor(const std::chrono::duration<Duration>& time) {
+        std::unique_lock<std::mutex> lk(mutex);
+        if (!condvar.wait_for(lk, time, [this] { return is_set; }))
+            return false;
+        is_set = false;
+        return true;
+    }
+
     template <class Clock, class Duration>
     bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
         std::unique_lock lk{mutex};
diff --git a/src/core/settings.h b/src/core/settings.h
index 9c98a92873..421e76f5f7 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -401,6 +401,9 @@ struct Values {
     std::string motion_device;
     TouchscreenInput touchscreen;
     std::atomic_bool is_device_reload_pending{true};
+    std::string udp_input_address;
+    u16 udp_input_port;
+    u8 udp_pad_index;
 
     // Core
     bool use_multi_core;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 5b4e032bd9..2520ba321c 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -9,6 +9,12 @@ add_library(input_common STATIC
     motion_emu.h
     sdl/sdl.cpp
     sdl/sdl.h
+    udp/client.cpp
+    udp/client.h
+    udp/protocol.cpp
+    udp/protocol.h
+    udp/udp.cpp
+    udp/udp.h
 )
 
 if(SDL2_FOUND)
@@ -21,4 +27,4 @@ if(SDL2_FOUND)
 endif()
 
 create_target_directory_groups(input_common)
-target_link_libraries(input_common PUBLIC core PRIVATE common)
+target_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 8e66c1b150..9e028da890 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -9,6 +9,7 @@
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
 #include "input_common/motion_emu.h"
+#include "input_common/udp/udp.h"
 #ifdef HAVE_SDL2
 #include "input_common/sdl/sdl.h"
 #endif
@@ -18,6 +19,7 @@ namespace InputCommon {
 static std::shared_ptr<Keyboard> keyboard;
 static std::shared_ptr<MotionEmu> motion_emu;
 static std::unique_ptr<SDL::State> sdl;
+static std::unique_ptr<CemuhookUDP::State> udp;
 
 void Init() {
     keyboard = std::make_shared<Keyboard>();
@@ -28,6 +30,8 @@ void Init() {
     Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
 
     sdl = SDL::Init();
+
+    udp = CemuhookUDP::Init();
 }
 
 void Shutdown() {
@@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
 namespace Polling {
 
 std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
+    std::vector<std::unique_ptr<DevicePoller>> pollers;
+
 #ifdef HAVE_SDL2
-    return sdl->GetPollers(type);
-#else
-    return {};
+    pollers = sdl->GetPollers(type);
 #endif
+
+    return pollers;
 }
 
 } // namespace Polling
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
new file mode 100644
index 0000000000..c31236c7c4
--- /dev/null
+++ b/src/input_common/udp/client.cpp
@@ -0,0 +1,283 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cstring>
+#include <functional>
+#include <thread>
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+#include "common/logging/log.h"
+#include "input_common/udp/client.h"
+#include "input_common/udp/protocol.h"
+
+using boost::asio::ip::address_v4;
+using boost::asio::ip::udp;
+
+namespace InputCommon::CemuhookUDP {
+
+struct SocketCallback {
+    std::function<void(Response::Version)> version;
+    std::function<void(Response::PortInfo)> port_info;
+    std::function<void(Response::PadData)> pad_data;
+};
+
+class Socket {
+public:
+    using clock = std::chrono::system_clock;
+
+    explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
+                    SocketCallback callback)
+        : client_id(client_id), timer(io_service),
+          send_endpoint(udp::endpoint(address_v4::from_string(host), port)),
+          socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index),
+          callback(std::move(callback)) {}
+
+    void Stop() {
+        io_service.stop();
+    }
+
+    void Loop() {
+        io_service.run();
+    }
+
+    void StartSend(const clock::time_point& from) {
+        timer.expires_at(from + std::chrono::seconds(3));
+        timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
+    }
+
+    void StartReceive() {
+        socket.async_receive_from(
+            boost::asio::buffer(receive_buffer), receive_endpoint,
+            [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
+                HandleReceive(error, bytes_transferred);
+            });
+    }
+
+private:
+    void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
+        if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
+            switch (*type) {
+            case Type::Version: {
+                Response::Version version;
+                std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
+                callback.version(std::move(version));
+                break;
+            }
+            case Type::PortInfo: {
+                Response::PortInfo port_info;
+                std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
+                            sizeof(Response::PortInfo));
+                callback.port_info(std::move(port_info));
+                break;
+            }
+            case Type::PadData: {
+                Response::PadData pad_data;
+                std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
+                callback.pad_data(std::move(pad_data));
+                break;
+            }
+            }
+        }
+        StartReceive();
+    }
+
+    void HandleSend(const boost::system::error_code& error) {
+        // Send a request for getting port info for the pad
+        Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
+        auto port_message = Request::Create(port_info, client_id);
+        std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
+        std::size_t len = socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint);
+
+        // Send a request for getting pad data for the pad
+        Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
+        auto pad_message = Request::Create(pad_data, client_id);
+        std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
+        std::size_t len2 = socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint);
+        StartSend(timer.expiry());
+    }
+
+    SocketCallback callback;
+    boost::asio::io_service io_service;
+    boost::asio::basic_waitable_timer<clock> timer;
+    udp::socket socket;
+
+    u32 client_id;
+    u8 pad_index;
+
+    static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
+    static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
+    std::array<u8, PORT_INFO_SIZE> send_buffer1;
+    std::array<u8, PAD_DATA_SIZE> send_buffer2;
+    udp::endpoint send_endpoint;
+
+    std::array<u8, MAX_PACKET_SIZE> receive_buffer;
+    udp::endpoint receive_endpoint;
+};
+
+static void SocketLoop(Socket* socket) {
+    socket->StartReceive();
+    socket->StartSend(Socket::clock::now());
+    socket->Loop();
+}
+
+Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
+               u8 pad_index, u32 client_id)
+    : status(status) {
+    StartCommunication(host, port, pad_index, client_id);
+}
+
+Client::~Client() {
+    socket->Stop();
+    thread.join();
+}
+
+void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
+    socket->Stop();
+    thread.join();
+    StartCommunication(host, port, pad_index, client_id);
+}
+
+void Client::OnVersion(Response::Version data) {
+    LOG_TRACE(Input, "Version packet received: {}", data.version);
+}
+
+void Client::OnPortInfo(Response::PortInfo data) {
+    LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
+}
+
+void Client::OnPadData(Response::PadData data) {
+    LOG_TRACE(Input, "PadData packet received");
+    if (data.packet_counter <= packet_sequence) {
+        LOG_WARNING(
+            Input,
+            "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
+            packet_sequence, data.packet_counter);
+        return;
+    }
+    packet_sequence = data.packet_counter;
+    // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
+    // directions correspond to the ones of the Switch
+    Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
+    Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
+    {
+        std::lock_guard guard(status->update_mutex);
+
+        status->motion_status = {accel, gyro};
+
+        // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
+        // between a simple "tap" and a hard press that causes the touch screen to click.
+        bool is_active = data.touch_1.is_active != 0;
+
+        float x = 0;
+        float y = 0;
+
+        if (is_active && status->touch_calibration) {
+            u16 min_x = status->touch_calibration->min_x;
+            u16 max_x = status->touch_calibration->max_x;
+            u16 min_y = status->touch_calibration->min_y;
+            u16 max_y = status->touch_calibration->max_y;
+
+            x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
+                static_cast<float>(max_x - min_x);
+            y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
+                static_cast<float>(max_y - min_y);
+        }
+
+        status->touch_status = {x, y, is_active};
+    }
+}
+
+void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
+    SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
+                            [this](Response::PortInfo info) { OnPortInfo(info); },
+                            [this](Response::PadData data) { OnPadData(data); }};
+    LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
+    socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
+    thread = std::thread{SocketLoop, this->socket.get()};
+}
+
+void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
+                       std::function<void()> success_callback,
+                       std::function<void()> failure_callback) {
+    std::thread([=] {
+        Common::Event success_event;
+        SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
+                                [&](Response::PadData data) { success_event.Set(); }};
+        Socket socket{host, port, pad_index, client_id, callback};
+        std::thread worker_thread{SocketLoop, &socket};
+        bool result = success_event.WaitFor(std::chrono::seconds(8));
+        socket.Stop();
+        worker_thread.join();
+        if (result)
+            success_callback();
+        else
+            failure_callback();
+    })
+        .detach();
+}
+
+CalibrationConfigurationJob::CalibrationConfigurationJob(
+    const std::string& host, u16 port, u8 pad_index, u32 client_id,
+    std::function<void(Status)> status_callback,
+    std::function<void(u16, u16, u16, u16)> data_callback) {
+
+    std::thread([=] {
+        constexpr u16 CALIBRATION_THRESHOLD = 100;
+
+        u16 min_x{UINT16_MAX}, min_y{UINT16_MAX};
+        u16 max_x, max_y;
+
+        Status current_status{Status::Initialized};
+        SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
+                                [&](Response::PadData data) {
+                                    if (current_status == Status::Initialized) {
+                                        // Receiving data means the communication is ready now
+                                        current_status = Status::Ready;
+                                        status_callback(current_status);
+                                    }
+                                    if (!data.touch_1.is_active)
+                                        return;
+                                    LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
+                                              data.touch_1.y);
+                                    min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
+                                    min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
+                                    if (current_status == Status::Ready) {
+                                        // First touch - min data (min_x/min_y)
+                                        current_status = Status::Stage1Completed;
+                                        status_callback(current_status);
+                                    }
+                                    if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
+                                        data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
+                                        // Set the current position as max value and finishes
+                                        // configuration
+                                        max_x = data.touch_1.x;
+                                        max_y = data.touch_1.y;
+                                        current_status = Status::Completed;
+                                        data_callback(min_x, min_y, max_x, max_y);
+                                        status_callback(current_status);
+
+                                        complete_event.Set();
+                                    }
+                                }};
+        Socket socket{host, port, pad_index, client_id, callback};
+        std::thread worker_thread{SocketLoop, &socket};
+        complete_event.Wait();
+        socket.Stop();
+        worker_thread.join();
+    })
+        .detach();
+}
+
+CalibrationConfigurationJob::~CalibrationConfigurationJob() {
+    Stop();
+}
+
+void CalibrationConfigurationJob::Stop() {
+    complete_event.Set();
+}
+
+} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
new file mode 100644
index 0000000000..5177f46be1
--- /dev/null
+++ b/src/input_common/udp/client.h
@@ -0,0 +1,96 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <thread>
+#include <tuple>
+#include <vector>
+#include "common/common_types.h"
+#include "common/thread.h"
+#include "common/vector_math.h"
+
+namespace InputCommon::CemuhookUDP {
+
+static constexpr u16 DEFAULT_PORT = 26760;
+static constexpr const char* DEFAULT_ADDR = "127.0.0.1";
+
+class Socket;
+
+namespace Response {
+struct PadData;
+struct PortInfo;
+struct Version;
+} // namespace Response
+
+struct DeviceStatus {
+    std::mutex update_mutex;
+    std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
+    std::tuple<float, float, bool> touch_status;
+
+    // calibration data for scaling the device's touch area to 3ds
+    struct CalibrationData {
+        u16 min_x;
+        u16 min_y;
+        u16 max_x;
+        u16 max_y;
+    };
+    std::optional<CalibrationData> touch_calibration;
+};
+
+class Client {
+public:
+    explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
+                    u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
+    ~Client();
+    void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
+                      u32 client_id = 24872);
+
+private:
+    void OnVersion(Response::Version);
+    void OnPortInfo(Response::PortInfo);
+    void OnPadData(Response::PadData);
+    void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
+
+    std::unique_ptr<Socket> socket;
+    std::shared_ptr<DeviceStatus> status;
+    std::thread thread;
+    u64 packet_sequence = 0;
+};
+
+/// An async job allowing configuration of the touchpad calibration.
+class CalibrationConfigurationJob {
+public:
+    enum class Status {
+        Initialized,
+        Ready,
+        Stage1Completed,
+        Completed,
+    };
+    /**
+     * Constructs and starts the job with the specified parameter.
+     *
+     * @param status_callback Callback for job status updates
+     * @param data_callback Called when calibration data is ready
+     */
+    explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
+                                         u32 client_id, std::function<void(Status)> status_callback,
+                                         std::function<void(u16, u16, u16, u16)> data_callback);
+    ~CalibrationConfigurationJob();
+    void Stop();
+
+private:
+    Common::Event complete_event;
+};
+
+void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
+                       std::function<void()> success_callback,
+                       std::function<void()> failure_callback);
+
+} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp
new file mode 100644
index 0000000000..d650692075
--- /dev/null
+++ b/src/input_common/udp/protocol.cpp
@@ -0,0 +1,79 @@
+// 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/udp/protocol.h"
+
+namespace InputCommon::CemuhookUDP {
+
+static const 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)) {
+        LOG_DEBUG(Input, "Invalid UDP packet received");
+        return {};
+    }
+    Header header;
+    std::memcpy(&header, data, sizeof(Header));
+    if (header.magic != SERVER_MAGIC) {
+        LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
+        return {};
+    }
+    if (header.protocol_version != PROTOCOL_VERSION) {
+        LOG_ERROR(Input, "UDP Packet protocol mismatch");
+        return {};
+    }
+    if (header.type < Type::Version || header.type > Type::PadData) {
+        LOG_ERROR(Input, "UDP Packet is an unknown type");
+        return {};
+    }
+
+    // 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 {};
+    }
+
+    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 {};
+    }
+    return header.type;
+}
+} // namespace Response
+
+} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h
new file mode 100644
index 0000000000..d31bbeb899
--- /dev/null
+++ b/src/input_common/udp/protocol.h
@@ -0,0 +1,249 @@
+// 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 <vector>
+#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 {
+
+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 {
+    enum class Flags : u8 {
+        AllPorts,
+        Id,
+        Mac,
+    };
+    /// Determines which method will be used as a look up for the controller
+    Flags 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 T 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 {
+
+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;
+    u8 state;
+    u8 model;
+    u8 connection_type;
+    MacAddress mac;
+    u8 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");
+
+#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_8;
+        u8 button_7;
+        u8 button_6;
+        u8 button_5;
+        u8 button_12;
+        u8 button_11;
+        u8 button_10;
+        u8 button_9;
+        u8 button_16;
+        u8 button_15;
+        u8 button_14;
+        u8 button_13;
+    } analog_button;
+
+    struct TouchPad {
+        u8 is_active;
+        u8 id;
+        u16_le x;
+        u16_le y;
+    } touch_1, touch_2;
+
+    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>");
+
+/**
+ * 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
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
new file mode 100644
index 0000000000..a80f386149
--- /dev/null
+++ b/src/input_common/udp/udp.cpp
@@ -0,0 +1,96 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "common/param_package.h"
+#include "core/frontend/input.h"
+#include "core/settings.h"
+#include "input_common/udp/client.h"
+#include "input_common/udp/udp.h"
+
+namespace InputCommon::CemuhookUDP {
+
+class UDPTouchDevice final : public Input::TouchDevice {
+public:
+    explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+    std::tuple<float, float, bool> GetStatus() const {
+        std::lock_guard guard(status->update_mutex);
+        return status->touch_status;
+    }
+
+private:
+    std::shared_ptr<DeviceStatus> status;
+};
+
+class UDPMotionDevice final : public Input::MotionDevice {
+public:
+    explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+    std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const {
+        std::lock_guard guard(status->update_mutex);
+        return status->motion_status;
+    }
+
+private:
+    std::shared_ptr<DeviceStatus> status;
+};
+
+class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+    explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+
+    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
+        {
+            std::lock_guard guard(status->update_mutex);
+            status->touch_calibration.emplace();
+            // These default values work well for DS4 but probably not other touch inputs
+            status->touch_calibration->min_x = params.Get("min_x", 100);
+            status->touch_calibration->min_y = params.Get("min_y", 50);
+            status->touch_calibration->max_x = params.Get("max_x", 1800);
+            status->touch_calibration->max_y = params.Get("max_y", 850);
+        }
+        return std::make_unique<UDPTouchDevice>(status);
+    }
+
+private:
+    std::shared_ptr<DeviceStatus> status;
+};
+
+class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
+public:
+    explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+
+    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
+        return std::make_unique<UDPMotionDevice>(status);
+    }
+
+private:
+    std::shared_ptr<DeviceStatus> status;
+};
+
+State::State() {
+    auto status = std::make_shared<DeviceStatus>();
+    client =
+        std::make_unique<Client>(status, Settings::values.udp_input_address,
+                                 Settings::values.udp_input_port, Settings::values.udp_pad_index);
+
+    Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
+                                               std::make_shared<UDPTouchFactory>(status));
+    Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
+                                                std::make_shared<UDPMotionFactory>(status));
+}
+
+State::~State() {
+    Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
+    Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+}
+
+void State::ReloadUDPClient() {
+    client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
+                         Settings::values.udp_pad_index);
+}
+
+std::unique_ptr<State> Init() {
+    return std::make_unique<State>();
+}
+} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
new file mode 100644
index 0000000000..ea3de60bb4
--- /dev/null
+++ b/src/input_common/udp/udp.h
@@ -0,0 +1,27 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <unordered_map>
+#include "input_common/main.h"
+#include "input_common/udp/client.h"
+
+namespace InputCommon::CemuhookUDP {
+
+class UDPTouchDevice;
+class UDPMotionDevice;
+
+class State {
+public:
+    State();
+    ~State();
+    void ReloadUDPClient();
+
+private:
+    std::unique_ptr<Client> client;
+};
+
+std::unique_ptr<State> Init();
+
+} // namespace InputCommon::CemuhookUDP
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f92a4b3c35..59918847a5 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -10,6 +10,7 @@
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/hid/controllers/npad.h"
 #include "input_common/main.h"
+#include "input_common/udp/client.h"
 #include "yuzu/configuration/config.h"
 #include "yuzu/uisettings.h"
 
@@ -429,6 +430,16 @@ void Config::ReadControlValues() {
                     QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
             .toString()
             .toStdString();
+    Settings::values.udp_input_address =
+        ReadSetting(QStringLiteral("udp_input_address"),
+                    QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
+            .toString()
+            .toStdString();
+    Settings::values.udp_input_port = static_cast<u16>(
+        ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
+            .toInt());
+    Settings::values.udp_pad_index =
+        static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
 
     qt_config->endGroup();
 }
@@ -911,6 +922,12 @@ void Config::SaveControlValues() {
                  QString::fromStdString(Settings::values.motion_device),
                  QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
     WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
+    WriteSetting(QStringLiteral("udp_input_address"),
+                 QString::fromStdString(Settings::values.udp_input_address),
+                 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
+    WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
+                 InputCommon::CemuhookUDP::DEFAULT_PORT);
+    WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
 
     qt_config->endGroup();
 }
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 1a812cb878..86f65cf46f 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -12,6 +12,7 @@
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/settings.h"
 #include "input_common/main.h"
+#include "input_common/udp/client.h"
 #include "yuzu_cmd/config.h"
 #include "yuzu_cmd/default_ini.h"
 
@@ -297,6 +298,10 @@ void Config::ReadValues() {
         sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
     Settings::values.touchscreen.diameter_y =
         sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
+    Settings::values.udp_input_address = sdl2_config->GetString(
+        "Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
+    Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger(
+        "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));
 
     std::transform(keyboard_keys.begin(), keyboard_keys.end(),
                    Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 8d18a4a5a9..e829f8695f 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -69,12 +69,29 @@ rstick=
 #  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
 #      - "update_period": update period in milliseconds (default to 100)
 #      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
+#  - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
 motion_device=
 
 # for touch input, the following devices are available:
 #  - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
+#  - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
+#      - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
 touch_device=
 
+# Most desktop operating systems do not expose a way to poll the motion state of the controllers
+# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
+# from a controller device to the client program. Citra has a client that can connect and read
+# from any cemuhook compatible motion program.
+
+# IPv4 address of the udp input server (Default "127.0.0.1")
+udp_input_address=
+
+# Port of the udp input server. (Default 26760)
+udp_input_port=
+
+# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
+udp_pad_index=
+
 [Core]
 # Whether to use multi-core for CPU emulation
 # 0 (default): Disabled, 1: Enabled