mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 06:09:50 -06:00
Add support for motion controllers via the CemuHook controller input protocol.
This is done by: 1) Implementing said protocol in a new controller input class CemuHookUDPServer. 2) Adding functionality in the WiimoteEmu class for pushing that motion input to the emulated Wiimote and MotionPlus. 3) Suitably modifying the UI for configuring an Emulated Wii Remote.
This commit is contained in:
@ -0,0 +1,432 @@
|
||||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
#include <SFML/Network/SocketSelector.hpp>
|
||||
#include <SFML/Network/UdpSocket.hpp>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Matrix.h"
|
||||
#include "Common/Random.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
namespace ciface::CemuHookUDPServer
|
||||
{
|
||||
class Device : public Core::Device
|
||||
{
|
||||
private:
|
||||
template <class T>
|
||||
class Button : public Core::Device::Input
|
||||
{
|
||||
public:
|
||||
Button(std::string name, const T& buttons, unsigned mask)
|
||||
: m_name(std::move(name)), m_buttons(buttons), m_mask(mask)
|
||||
{
|
||||
}
|
||||
std::string GetName() const override { return m_name; }
|
||||
ControlState GetState() const override { return (m_buttons & m_mask) != 0; }
|
||||
|
||||
private:
|
||||
const std::string m_name;
|
||||
const T& m_buttons;
|
||||
unsigned m_mask;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class AnalogInput : public Core::Device::Input
|
||||
{
|
||||
public:
|
||||
AnalogInput(std::string name, const T& input, ControlState range, ControlState offset = 0)
|
||||
: m_name(std::move(name)), m_input(input), m_range(range), m_offset(offset)
|
||||
{
|
||||
}
|
||||
std::string GetName() const override { return m_name; }
|
||||
ControlState GetState() const override { return (ControlState(m_input) + m_offset) / m_range; }
|
||||
|
||||
private:
|
||||
const std::string m_name;
|
||||
const T& m_input;
|
||||
const ControlState m_range;
|
||||
const ControlState m_offset;
|
||||
};
|
||||
|
||||
class TouchInput : public AnalogInput<int>
|
||||
{
|
||||
public:
|
||||
TouchInput(std::string name, const int& input, ControlState range)
|
||||
: AnalogInput(std::move(name), input, range)
|
||||
{
|
||||
}
|
||||
bool IsDetectable() override { return false; }
|
||||
};
|
||||
|
||||
class AccelerometerInput : public AnalogInput<double>
|
||||
{
|
||||
public:
|
||||
AccelerometerInput(std::string name, const double& input, ControlState range)
|
||||
: AnalogInput(std::move(name), input, range)
|
||||
{
|
||||
}
|
||||
bool IsDetectable() override { return false; }
|
||||
};
|
||||
|
||||
using GyroInput = AccelerometerInput;
|
||||
|
||||
public:
|
||||
void UpdateInput() override;
|
||||
|
||||
Device(Proto::DsModel model, int index);
|
||||
|
||||
std::string GetName() const final override;
|
||||
std::string GetSource() const final override;
|
||||
std::optional<int> GetPreferredId() const final override;
|
||||
|
||||
private:
|
||||
const Proto::DsModel m_model;
|
||||
const int m_index;
|
||||
u32 m_client_uid;
|
||||
sf::UdpSocket m_socket;
|
||||
Common::DVec3 m_accel;
|
||||
Common::DVec3 m_gyro;
|
||||
std::chrono::steady_clock::time_point m_next_reregister;
|
||||
Proto::MessageType::PadDataResponse m_pad_data;
|
||||
Proto::Touch m_prev_touch;
|
||||
bool m_prev_touch_valid;
|
||||
int m_touch_x;
|
||||
int m_touch_y;
|
||||
};
|
||||
|
||||
static constexpr double GRAVITY_ACCELERATION = 9.80665;
|
||||
static constexpr char DEFAULT_SERVER_ADDRESS[] = "127.0.0.1";
|
||||
static constexpr u16 DEFAULT_SERVER_PORT = 26760;
|
||||
static constexpr auto SERVER_REREGISTER_INTERVAL = std::chrono::seconds{1};
|
||||
static constexpr auto SERVER_LISTPORTS_INTERVAL = std::chrono::seconds{1};
|
||||
static constexpr int TOUCH_X_AXIS_MAX = 1000;
|
||||
static constexpr int TOUCH_Y_AXIS_MAX = 500;
|
||||
|
||||
namespace Settings
|
||||
{
|
||||
const Config::ConfigInfo<bool> SERVER_ENABLED{
|
||||
{Config::System::CemuHookUdpServer, "Server", "Enabled"}, false};
|
||||
const Config::ConfigInfo<std::string> SERVER_ADDRESS{
|
||||
{Config::System::CemuHookUdpServer, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS};
|
||||
const Config::ConfigInfo<int> SERVER_PORT{{Config::System::CemuHookUdpServer, "Server", "Port"},
|
||||
DEFAULT_SERVER_PORT};
|
||||
} // namespace Settings
|
||||
|
||||
static bool s_server_enabled;
|
||||
static std::string s_server_address;
|
||||
static u16 s_server_port;
|
||||
static u32 s_client_uid;
|
||||
static std::chrono::steady_clock::time_point s_next_listports;
|
||||
static std::thread s_hotplug_thread;
|
||||
static Common::Flag s_hotplug_thread_running;
|
||||
static std::mutex s_port_info_mutex;
|
||||
static Proto::MessageType::PortInfo s_port_info[Proto::PORT_COUNT];
|
||||
|
||||
static bool IsSameController(const Proto::MessageType::PortInfo& a,
|
||||
const Proto::MessageType::PortInfo& b)
|
||||
{
|
||||
// compare everything but battery_status
|
||||
return a.pad_id == b.pad_id && a.pad_state == b.pad_state && a.model == b.model &&
|
||||
a.connection_type == b.connection_type &&
|
||||
memcmp(a.pad_mac_address, b.pad_mac_address, sizeof a.pad_mac_address) == 0;
|
||||
}
|
||||
|
||||
static sf::Socket::Status ReceiveWithTimeout(sf::UdpSocket& socket, void* data, std::size_t size,
|
||||
std::size_t& received, sf::IpAddress& remoteAddress,
|
||||
unsigned short& remotePort, sf::Time timeout)
|
||||
{
|
||||
sf::SocketSelector selector;
|
||||
selector.add(socket);
|
||||
if (selector.wait(timeout))
|
||||
return socket.receive(data, size, received, remoteAddress, remotePort);
|
||||
else
|
||||
return sf::Socket::NotReady;
|
||||
}
|
||||
|
||||
static void HotplugThreadFunc()
|
||||
{
|
||||
Common::SetCurrentThreadName("CemuHookUDPServer Hotplug Thread");
|
||||
NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread started");
|
||||
|
||||
sf::UdpSocket socket;
|
||||
|
||||
while (s_hotplug_thread_running.IsSet())
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now >= s_next_listports)
|
||||
{
|
||||
s_next_listports = now + SERVER_LISTPORTS_INTERVAL;
|
||||
|
||||
// Request info on the four controller ports
|
||||
Proto::Message<Proto::MessageType::ListPorts> msg(s_client_uid);
|
||||
auto& list_ports = msg.m_message;
|
||||
list_ports.pad_request_count = 4;
|
||||
list_ports.pad_id[0] = 0;
|
||||
list_ports.pad_id[1] = 1;
|
||||
list_ports.pad_id[2] = 2;
|
||||
list_ports.pad_id[3] = 3;
|
||||
msg.Finish();
|
||||
if (socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) !=
|
||||
sf::Socket::Status::Done)
|
||||
ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer HotplugThreadFunc send failed");
|
||||
}
|
||||
|
||||
// Receive controller port info
|
||||
Proto::Message<Proto::MessageType::FromServer> msg;
|
||||
const auto timeout = s_next_listports - std::chrono::steady_clock::now();
|
||||
// ReceiveWithTimeout treats a timeout of zero as infinite timeout, which we don't want
|
||||
auto timeout_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count();
|
||||
timeout_ms = std::max<decltype(timeout_ms)>(timeout_ms, 1);
|
||||
std::size_t received_bytes;
|
||||
sf::IpAddress sender;
|
||||
u16 port;
|
||||
if (ReceiveWithTimeout(socket, &msg, sizeof(msg), received_bytes, sender, port,
|
||||
sf::milliseconds(timeout_ms)) == sf::Socket::Status::Done)
|
||||
{
|
||||
if (auto port_info = msg.CheckAndCastTo<Proto::MessageType::PortInfo>())
|
||||
{
|
||||
const bool port_changed = !IsSameController(*port_info, s_port_info[port_info->pad_id]);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_port_info_mutex);
|
||||
s_port_info[port_info->pad_id] = *port_info;
|
||||
}
|
||||
if (port_changed)
|
||||
PopulateDevices();
|
||||
}
|
||||
}
|
||||
}
|
||||
NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread stopped");
|
||||
}
|
||||
|
||||
static void StartHotplugThread()
|
||||
{
|
||||
// Mark the thread as running.
|
||||
if (!s_hotplug_thread_running.TestAndSet())
|
||||
{
|
||||
// It was already running.
|
||||
return;
|
||||
}
|
||||
|
||||
s_hotplug_thread = std::thread(HotplugThreadFunc);
|
||||
}
|
||||
|
||||
static void StopHotplugThread()
|
||||
{
|
||||
// Tell the hotplug thread to stop.
|
||||
if (!s_hotplug_thread_running.TestAndClear())
|
||||
{
|
||||
// It wasn't running, we're done.
|
||||
return;
|
||||
}
|
||||
|
||||
s_hotplug_thread.join();
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
s_server_enabled = Config::Get(Settings::SERVER_ENABLED);
|
||||
s_server_address = Config::Get(Settings::SERVER_ADDRESS);
|
||||
s_server_port = Config::Get(Settings::SERVER_PORT);
|
||||
|
||||
s_client_uid = Common::Random::GenerateValue<u32>();
|
||||
s_next_listports = std::chrono::steady_clock::time_point::min();
|
||||
for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++)
|
||||
{
|
||||
s_port_info[port_index] = {};
|
||||
s_port_info[port_index].pad_id = port_index;
|
||||
}
|
||||
|
||||
if (s_server_enabled)
|
||||
StartHotplugThread();
|
||||
}
|
||||
|
||||
void PopulateDevices()
|
||||
{
|
||||
NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer PopulateDevices");
|
||||
|
||||
g_controller_interface.RemoveDevice(
|
||||
[](const auto* dev) { return dev->GetSource() == "UDPServer"; });
|
||||
|
||||
std::lock_guard<std::mutex> lock(s_port_info_mutex);
|
||||
for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++)
|
||||
{
|
||||
Proto::MessageType::PortInfo port_info = s_port_info[port_index];
|
||||
if (port_info.pad_state == Proto::DsState::Connected)
|
||||
g_controller_interface.AddDevice(std::make_shared<Device>(port_info.model, port_index));
|
||||
}
|
||||
}
|
||||
|
||||
void DeInit()
|
||||
{
|
||||
StopHotplugThread();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void SaveSettings()
|
||||
{
|
||||
Config::ConfigChangeCallbackGuard config_guard;
|
||||
|
||||
Config::SetBaseOrCurrent(Settings::SERVER_ENABLED, s_server_enabled);
|
||||
Config::SetBaseOrCurrent(Settings::SERVER_ADDRESS, s_server_address);
|
||||
Config::SetBaseOrCurrent(Settings::SERVER_PORT, s_server_port);
|
||||
}
|
||||
|
||||
Device::Device(Proto::DsModel model, int index)
|
||||
: m_model(model), m_index(index),
|
||||
m_client_uid(Common::Random::GenerateValue<u32>()), m_accel{}, m_gyro{},
|
||||
m_next_reregister(std::chrono::steady_clock::time_point::min()), m_pad_data{}, m_prev_touch{},
|
||||
m_prev_touch_valid(false), m_touch_x(0), m_touch_y(0)
|
||||
{
|
||||
m_socket.setBlocking(false);
|
||||
|
||||
AddInput(new AnalogInput<u8>("Pad W", m_pad_data.button_dpad_left_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Pad S", m_pad_data.button_dpad_down_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Pad E", m_pad_data.button_dpad_right_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Pad N", m_pad_data.button_dpad_up_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Square", m_pad_data.button_square_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Cross", m_pad_data.button_cross_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Circle", m_pad_data.button_circle_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("Triangle", m_pad_data.button_triangle_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("L1", m_pad_data.button_l1_analog, 255));
|
||||
AddInput(new AnalogInput<u8>("R1", m_pad_data.button_r1_analog, 255));
|
||||
|
||||
AddInput(new AnalogInput<u8>("L2", m_pad_data.trigger_l2, 255));
|
||||
AddInput(new AnalogInput<u8>("R2", m_pad_data.trigger_r2, 255));
|
||||
|
||||
AddInput(new Button<u8>("L3", m_pad_data.button_states1, 0x2));
|
||||
AddInput(new Button<u8>("R3", m_pad_data.button_states1, 0x4));
|
||||
AddInput(new Button<u8>("Share", m_pad_data.button_states1, 0x1));
|
||||
AddInput(new Button<u8>("Options", m_pad_data.button_states1, 0x8));
|
||||
AddInput(new Button<u8>("PS", m_pad_data.button_ps, 0x1));
|
||||
AddInput(new Button<u8>("Touch Button", m_pad_data.button_touch, 0x1));
|
||||
|
||||
AddInput(new AnalogInput<u8>("Left X-", m_pad_data.left_stick_x, -128, -128));
|
||||
AddInput(new AnalogInput<u8>("Left X+", m_pad_data.left_stick_x, 127, -128));
|
||||
AddInput(new AnalogInput<u8>("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128));
|
||||
AddInput(new AnalogInput<u8>("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128));
|
||||
AddInput(new AnalogInput<u8>("Right X-", m_pad_data.right_stick_x, -128, -128));
|
||||
AddInput(new AnalogInput<u8>("Right X+", m_pad_data.right_stick_x, 127, -128));
|
||||
AddInput(new AnalogInput<u8>("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128));
|
||||
AddInput(new AnalogInput<u8>("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128));
|
||||
|
||||
AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX));
|
||||
AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX));
|
||||
AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX));
|
||||
AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX));
|
||||
|
||||
AddInput(new AccelerometerInput("Accel Left", m_accel.x, 1));
|
||||
AddInput(new AccelerometerInput("Accel Right", m_accel.x, -1));
|
||||
AddInput(new AccelerometerInput("Accel Backward", m_accel.y, 1));
|
||||
AddInput(new AccelerometerInput("Accel Forward", m_accel.y, -1));
|
||||
AddInput(new AccelerometerInput("Accel Up", m_accel.z, 1));
|
||||
AddInput(new AccelerometerInput("Accel Down", m_accel.z, -1));
|
||||
|
||||
AddInput(new GyroInput("Gyro Pitch Up", m_gyro.x, -1));
|
||||
AddInput(new GyroInput("Gyro Pitch Down", m_gyro.x, 1));
|
||||
AddInput(new GyroInput("Gyro Roll Right", m_gyro.y, -1));
|
||||
AddInput(new GyroInput("Gyro Roll Left", m_gyro.y, 1));
|
||||
AddInput(new GyroInput("Gyro Yaw Right", m_gyro.z, -1));
|
||||
AddInput(new GyroInput("Gyro Yaw Left", m_gyro.z, 1));
|
||||
}
|
||||
|
||||
std::string Device::GetName() const
|
||||
{
|
||||
switch (m_model)
|
||||
{
|
||||
case Proto::DsModel::None:
|
||||
return "None";
|
||||
case Proto::DsModel::DS3:
|
||||
return "DualShock 3";
|
||||
case Proto::DsModel::DS4:
|
||||
return "DualShock 4";
|
||||
case Proto::DsModel::Generic:
|
||||
return "Generic Gamepad";
|
||||
default:
|
||||
return "Device";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Device::GetSource() const
|
||||
{
|
||||
return "UDPServer";
|
||||
}
|
||||
|
||||
void Device::UpdateInput()
|
||||
{
|
||||
// Regularly tell the UDP server to feed us controller data
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now >= m_next_reregister)
|
||||
{
|
||||
m_next_reregister = now + SERVER_REREGISTER_INTERVAL;
|
||||
|
||||
Proto::Message<Proto::MessageType::PadDataRequest> msg(m_client_uid);
|
||||
auto& data_req = msg.m_message;
|
||||
data_req.register_flags = Proto::RegisterFlags::PadID;
|
||||
data_req.pad_id_to_register = m_index;
|
||||
msg.Finish();
|
||||
if (m_socket.send(&data_req, sizeof(data_req), s_server_address, s_server_port) !=
|
||||
sf::Socket::Status::Done)
|
||||
ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer UpdateInput send failed");
|
||||
}
|
||||
|
||||
// Receive and handle controller data
|
||||
Proto::Message<Proto::MessageType::FromServer> msg;
|
||||
std::size_t received_bytes;
|
||||
sf::IpAddress sender;
|
||||
u16 port;
|
||||
while (m_socket.receive(&msg, sizeof msg, received_bytes, sender, port) ==
|
||||
sf::Socket::Status::Done)
|
||||
{
|
||||
if (auto pad_data = msg.CheckAndCastTo<Proto::MessageType::PadDataResponse>())
|
||||
{
|
||||
m_pad_data = *pad_data;
|
||||
|
||||
m_accel.x = m_pad_data.accelerometer_x_g;
|
||||
m_accel.z = -m_pad_data.accelerometer_y_g;
|
||||
m_accel.y = -m_pad_data.accelerometer_z_inverted_g;
|
||||
m_gyro.x = -m_pad_data.gyro_pitch_deg_s;
|
||||
m_gyro.z = -m_pad_data.gyro_yaw_deg_s;
|
||||
m_gyro.y = -m_pad_data.gyro_roll_deg_s;
|
||||
|
||||
// Convert Gs to meters per second squared
|
||||
m_accel = m_accel * GRAVITY_ACCELERATION;
|
||||
|
||||
// Convert degrees per second to radians per second
|
||||
m_gyro = m_gyro * (MathUtil::TAU / 360);
|
||||
|
||||
// Update touch pad relative coordinates
|
||||
if (m_pad_data.touch1.id != m_prev_touch.id)
|
||||
m_prev_touch_valid = false;
|
||||
if (m_prev_touch_valid)
|
||||
{
|
||||
m_touch_x += m_pad_data.touch1.x - m_prev_touch.x;
|
||||
m_touch_y += m_pad_data.touch1.y - m_prev_touch.y;
|
||||
m_touch_x = std::clamp(m_touch_x, -TOUCH_X_AXIS_MAX, TOUCH_X_AXIS_MAX);
|
||||
m_touch_y = std::clamp(m_touch_y, -TOUCH_Y_AXIS_MAX, TOUCH_Y_AXIS_MAX);
|
||||
}
|
||||
m_prev_touch = m_pad_data.touch1;
|
||||
m_prev_touch_valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> Device::GetPreferredId() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
} // namespace ciface::CemuHookUDPServer
|
@ -0,0 +1,11 @@
|
||||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
namespace ciface::CemuHookUDPServer
|
||||
{
|
||||
void Init();
|
||||
void PopulateDevices();
|
||||
void DeInit();
|
||||
void SaveSettings();
|
||||
} // namespace ciface::CemuHookUDPServer
|
@ -0,0 +1,270 @@
|
||||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace ciface::CemuHookUDPServer::Proto
|
||||
{
|
||||
// CemuHook UDPServer protocol implementation using UdpServer.cs from
|
||||
// https://github.com/Ryochan7/DS4Windows as documentation.
|
||||
//
|
||||
// WARNING: Little endian host assumed
|
||||
|
||||
static constexpr u16 CEMUHOOK_PROTOCOL_VERSION = 1001;
|
||||
|
||||
static constexpr int PORT_COUNT = 4;
|
||||
|
||||
static constexpr char CLIENT[] = "DSUC";
|
||||
static constexpr char SERVER[] = "DSUS";
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
enum class DsState : u8
|
||||
{
|
||||
Disconnected = 0x00,
|
||||
Reserved = 0x01,
|
||||
Connected = 0x02
|
||||
};
|
||||
|
||||
enum class DsConnection : u8
|
||||
{
|
||||
None = 0x00,
|
||||
Usb = 0x01,
|
||||
Bluetooth = 0x02
|
||||
};
|
||||
|
||||
enum class DsModel : u8
|
||||
{
|
||||
None = 0,
|
||||
DS3 = 1,
|
||||
DS4 = 2,
|
||||
Generic = 3
|
||||
};
|
||||
|
||||
enum class DsBattery : u8
|
||||
{
|
||||
None = 0x00,
|
||||
Dying = 0x01,
|
||||
Low = 0x02,
|
||||
Medium = 0x03,
|
||||
High = 0x04,
|
||||
Full = 0x05,
|
||||
Charging = 0xEE,
|
||||
Charged = 0xEF
|
||||
};
|
||||
|
||||
enum RegisterFlags : u8
|
||||
{
|
||||
AllPads = 0x00,
|
||||
PadID = 0x01,
|
||||
PadMACAdddress = 0x02
|
||||
};
|
||||
|
||||
struct MessageHeader
|
||||
{
|
||||
u8 source[4];
|
||||
u16 protocol_version;
|
||||
u16 message_length; // actually message size minus header size
|
||||
u32 crc32;
|
||||
u32 source_uid;
|
||||
};
|
||||
|
||||
struct Touch
|
||||
{
|
||||
u8 active;
|
||||
u8 id;
|
||||
s16 x;
|
||||
s16 y;
|
||||
};
|
||||
|
||||
namespace MessageType
|
||||
{
|
||||
struct VersionRequest
|
||||
{
|
||||
static constexpr auto FROM = CLIENT;
|
||||
static constexpr auto TYPE = 0x100000U;
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
};
|
||||
|
||||
struct VersionResponse
|
||||
{
|
||||
static constexpr auto FROM = SERVER;
|
||||
static constexpr auto TYPE = 0x100000U;
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
u16 max_protocol_version;
|
||||
u8 padding[2];
|
||||
};
|
||||
|
||||
struct ListPorts
|
||||
{
|
||||
static constexpr auto FROM = CLIENT;
|
||||
static constexpr auto TYPE = 0x100001U;
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
u32 pad_request_count;
|
||||
u8 pad_id[4];
|
||||
};
|
||||
|
||||
struct PortInfo
|
||||
{
|
||||
static constexpr auto FROM = SERVER;
|
||||
static constexpr auto TYPE = 0x100001U;
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
u8 pad_id;
|
||||
DsState pad_state;
|
||||
DsModel model;
|
||||
DsConnection connection_type;
|
||||
u8 pad_mac_address[6];
|
||||
DsBattery battery_status;
|
||||
u8 padding;
|
||||
};
|
||||
|
||||
struct PadDataRequest
|
||||
{
|
||||
static constexpr auto FROM = CLIENT;
|
||||
static constexpr auto TYPE = 0x100002U;
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
RegisterFlags register_flags;
|
||||
u8 pad_id_to_register;
|
||||
u8 mac_address_to_register[6];
|
||||
};
|
||||
|
||||
struct PadDataResponse
|
||||
{
|
||||
static constexpr auto FROM = SERVER;
|
||||
static constexpr auto TYPE = 0x100002U;
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
u8 pad_id;
|
||||
DsState pad_state;
|
||||
DsModel model;
|
||||
DsConnection connection_type;
|
||||
u8 pad_mac_address[6];
|
||||
DsBattery battery_status;
|
||||
u8 active;
|
||||
u32 hid_packet_counter;
|
||||
u8 button_states1;
|
||||
u8 button_states2;
|
||||
u8 button_ps;
|
||||
u8 button_touch;
|
||||
u8 left_stick_x;
|
||||
u8 left_stick_y_inverted;
|
||||
u8 right_stick_x;
|
||||
u8 right_stick_y_inverted;
|
||||
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;
|
||||
Touch touch1;
|
||||
Touch touch2;
|
||||
u64 timestamp_us;
|
||||
float accelerometer_x_g;
|
||||
float accelerometer_y_g;
|
||||
float accelerometer_z_inverted_g;
|
||||
float gyro_pitch_deg_s;
|
||||
float gyro_yaw_deg_s;
|
||||
float gyro_roll_deg_s;
|
||||
};
|
||||
|
||||
struct FromServer
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
};
|
||||
MessageType::VersionResponse version_response;
|
||||
MessageType::PortInfo port_info;
|
||||
MessageType::PadDataResponse pad_data_response;
|
||||
};
|
||||
};
|
||||
|
||||
struct FromClient
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
MessageHeader header;
|
||||
u32 message_type;
|
||||
};
|
||||
MessageType::VersionRequest version_request;
|
||||
MessageType::ListPorts list_ports;
|
||||
MessageType::PadDataRequest pad_data_request;
|
||||
};
|
||||
};
|
||||
} // namespace MessageType
|
||||
|
||||
static inline u32 CRC32(const void* buffer, unsigned length)
|
||||
{
|
||||
return crc32(crc32(0L, Z_NULL, 0), static_cast<const Bytef*>(buffer), length);
|
||||
}
|
||||
|
||||
template <typename MsgType>
|
||||
struct Message
|
||||
{
|
||||
Message() : m_message{} {}
|
||||
|
||||
explicit Message(u32 source_uid) : m_message{}
|
||||
{
|
||||
memcpy((char*)m_message.header.source, MsgType::FROM, sizeof(m_message.header.source));
|
||||
m_message.header.protocol_version = CEMUHOOK_PROTOCOL_VERSION;
|
||||
m_message.header.message_length = sizeof(*this) - sizeof(m_message.header);
|
||||
m_message.header.source_uid = source_uid;
|
||||
m_message.message_type = MsgType::TYPE;
|
||||
}
|
||||
|
||||
void Finish() { m_message.header.crc32 = CRC32(&m_message, sizeof(m_message)); }
|
||||
|
||||
template <class ToMsgType>
|
||||
std::optional<ToMsgType> CheckAndCastTo()
|
||||
{
|
||||
u32 crc32_in_header = m_message.header.crc32;
|
||||
// zero out the crc32 in the packet once we got it since that's whats needed for calculation
|
||||
m_message.header.crc32 = 0;
|
||||
u32 crc32_calculated = CRC32(&m_message, sizeof(ToMsgType));
|
||||
if (crc32_in_header != crc32_calculated)
|
||||
{
|
||||
NOTICE_LOG(SERIALINTERFACE,
|
||||
"CemuHookUDPServer Received message with bad CRC in header: got %u, expected %u",
|
||||
crc32_in_header, crc32_calculated);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (m_message.header.protocol_version > CEMUHOOK_PROTOCOL_VERSION)
|
||||
return std::nullopt;
|
||||
if (memcmp(m_message.header.source, ToMsgType::FROM, sizeof(m_message.header.source)))
|
||||
return std::nullopt;
|
||||
if (m_message.message_type != ToMsgType::TYPE)
|
||||
return std::nullopt;
|
||||
if (m_message.header.message_length + sizeof(m_message.header) > sizeof(ToMsgType))
|
||||
return std::nullopt;
|
||||
|
||||
ToMsgType tomsg;
|
||||
memcpy(&tomsg, &m_message, sizeof(tomsg));
|
||||
return tomsg;
|
||||
}
|
||||
|
||||
MsgType m_message;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
} // namespace ciface::CemuHookUDPServer::Proto
|
@ -30,6 +30,9 @@
|
||||
#ifdef CIFACE_USE_PIPES
|
||||
#include "InputCommon/ControllerInterface/Pipes/Pipes.h"
|
||||
#endif
|
||||
#ifdef CIFACE_USE_CEMUHOOKUDPSERVER
|
||||
#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h"
|
||||
#endif
|
||||
|
||||
ControllerInterface g_controller_interface;
|
||||
|
||||
@ -67,6 +70,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
|
||||
#endif
|
||||
#ifdef CIFACE_USE_PIPES
|
||||
// nothing needed
|
||||
#endif
|
||||
#ifdef CIFACE_USE_CEMUHOOKUDPSERVER
|
||||
ciface::CemuHookUDPServer::Init();
|
||||
#endif
|
||||
|
||||
RefreshDevices();
|
||||
@ -122,6 +128,9 @@ void ControllerInterface::RefreshDevices()
|
||||
#ifdef CIFACE_USE_PIPES
|
||||
ciface::Pipes::PopulateDevices();
|
||||
#endif
|
||||
#ifdef CIFACE_USE_CEMUHOOKUDPSERVER
|
||||
ciface::CemuHookUDPServer::PopulateDevices();
|
||||
#endif
|
||||
|
||||
m_is_populating_devices = false;
|
||||
InvokeDevicesChangedCallbacks();
|
||||
@ -172,6 +181,9 @@ void ControllerInterface::Shutdown()
|
||||
#ifdef CIFACE_USE_EVDEV
|
||||
ciface::evdev::Shutdown();
|
||||
#endif
|
||||
#ifdef CIFACE_USE_CEMUHOOKUDPSERVER
|
||||
ciface::CemuHookUDPServer::DeInit();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device)
|
||||
|
@ -29,6 +29,7 @@
|
||||
#if defined(USE_PIPES)
|
||||
#define CIFACE_USE_PIPES
|
||||
#endif
|
||||
#define CIFACE_USE_CEMUHOOKUDPSERVER
|
||||
|
||||
//
|
||||
// ControllerInterface
|
||||
|
Reference in New Issue
Block a user