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:
rlnilsen 2019-09-06 17:09:30 +02:00
parent f54faedd76
commit 4cb3baba5c
33 changed files with 1301 additions and 25 deletions

View file

@ -85,6 +85,7 @@
#define GFX_CONFIG "GFX.ini"
#define DEBUGGER_CONFIG "Debugger.ini"
#define LOGGER_CONFIG "Logger.ini"
#define CEMUHOOKUDPSERVER_CONFIG "UDPServer.ini"
// Files in the directory returned by GetUserPath(D_LOGS_IDX)
#define MAIN_LOG "dolphin.log"

View file

@ -133,9 +133,15 @@ void ClearCurrentRunLayer()
}
static const std::map<System, std::string> system_to_name = {
{System::Main, "Dolphin"}, {System::GCPad, "GCPad"}, {System::WiiPad, "Wiimote"},
{System::GCKeyboard, "GCKeyboard"}, {System::GFX, "Graphics"}, {System::Logger, "Logger"},
{System::Debugger, "Debugger"}, {System::SYSCONF, "SYSCONF"}};
{System::Main, "Dolphin"},
{System::GCPad, "GCPad"},
{System::WiiPad, "Wiimote"},
{System::GCKeyboard, "GCKeyboard"},
{System::GFX, "Graphics"},
{System::Logger, "Logger"},
{System::Debugger, "Debugger"},
{System::SYSCONF, "SYSCONF"},
{System::CemuHookUdpServer, "CemuHookUdpServer"}};
const std::string& GetSystemName(System system)
{

View file

@ -30,6 +30,7 @@ enum class System
GFX,
Logger,
Debugger,
CemuHookUdpServer,
};
constexpr std::array<LayerType, 7> SEARCH_ORDER{{

View file

@ -794,6 +794,8 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[F_GFXCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GFX_CONFIG;
s_user_paths[F_DEBUGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DEBUGGER_CONFIG;
s_user_paths[F_LOGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + LOGGER_CONFIG;
s_user_paths[F_CEMUHOOKUDPSERVERCONFIG_IDX] =
s_user_paths[D_CONFIG_IDX] + CEMUHOOKUDPSERVER_CONFIG;
s_user_paths[F_MAINLOG_IDX] = s_user_paths[D_LOGS_IDX] + MAIN_LOG;
s_user_paths[F_MEM1DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM1_DUMP;
s_user_paths[F_MEM2DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM2_DUMP;

View file

@ -70,6 +70,7 @@ enum
F_MEMORYWATCHERLOCATIONS_IDX,
F_MEMORYWATCHERSOCKET_IDX,
F_WIISDCARD_IDX,
F_CEMUHOOKUDPSERVERCONFIG_IDX,
NUM_PATH_INDICES
};

View file

@ -89,6 +89,7 @@ const std::map<Config::System, int> system_to_ini = {
{Config::System::GFX, F_GFXCONFIG_IDX},
{Config::System::Logger, F_LOGGERCONFIG_IDX},
{Config::System::Debugger, F_DEBUGGERCONFIG_IDX},
{Config::System::CemuHookUdpServer, F_CEMUHOOKUDPSERVERCONFIG_IDX},
};
// INI layer configuration loader

View file

@ -16,6 +16,9 @@ namespace ConfigLoaders
{
bool IsSettingSaveable(const Config::ConfigLocation& config_location)
{
if (config_location.system == Config::System::CemuHookUdpServer)
return true;
if (config_location.system == Config::System::Logger)
return true;

View file

@ -629,4 +629,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -1661,8 +1661,11 @@
<ClInclude Include="HW\AddressSpace.h">
<Filter>HW %28Flipper/Hollywood%29</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Constants.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View file

@ -13,6 +13,9 @@
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUCursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h"
#include "InputCommon/ControllerEmu/ControlGroup/Tilt.h"
namespace
@ -268,6 +271,90 @@ void ApproachAngleWithAccel(RotationalState* state, const Common::Vec3& angle_ta
}
}
static Common::Vec3 NormalizeAngle(Common::Vec3 angle)
{
// TODO: There must be a more elegant way to do this
angle.x = fmod(angle.x, float(MathUtil::TAU));
angle.y = fmod(angle.y, float(MathUtil::TAU));
angle.z = fmod(angle.z, float(MathUtil::TAU));
angle.x += angle.x < 0 ? float(MathUtil::TAU) : 0;
angle.y += angle.y < 0 ? float(MathUtil::TAU) : 0;
angle.z += angle.z < 0 ? float(MathUtil::TAU) : 0;
return angle;
}
static Common::Vec3 ComplementaryFilter(const Common::Vec3& angle,
const Common::Vec3& accelerometer,
const Common::Vec3& gyroscope, float time_elapsed)
{
Common::Vec3 gyroangle = angle + gyroscope * time_elapsed;
gyroangle = NormalizeAngle(gyroangle);
// Calculate accelerometer tilt angles
Common::Vec3 accangle = gyroangle;
if ((accelerometer.x != 0 && accelerometer.y != 0) || accelerometer.z != 0)
{
float accpitch = -atan2(accelerometer.y, -accelerometer.z) + float(MathUtil::PI);
float accroll = atan2(accelerometer.x, -accelerometer.z) + float(MathUtil::PI);
accangle = {accpitch, accroll, gyroangle.z};
}
// Massage accelerometer and gyroscope angle values so that averaging them works when they are on
// opposite sides of TAU / zero (which both represent the same angle)
// TODO: There must be a more elegant way to do this
constexpr float DEG360 = float(MathUtil::TAU);
constexpr float DEG270 = DEG360 * 0.75f;
constexpr float DEG90 = DEG360 * 0.25f;
if (accangle.x < DEG90 && gyroangle.x > DEG270)
accangle.x += DEG360;
else if (gyroangle.x < DEG90 && accangle.x > DEG270)
gyroangle.x += DEG360;
if (accangle.y < DEG90 && gyroangle.y > DEG270)
accangle.y += DEG360;
else if (gyroangle.y < DEG90 && accangle.y > DEG270)
gyroangle.y += DEG360;
// Combine accelerometer and gyroscope angles
return NormalizeAngle((gyroangle * 0.98f) + (accangle * 0.02f));
}
void EmulateIMUCursor(std::optional<RotationalState>* state, ControllerEmu::IMUCursor* imu_ir_group,
ControllerEmu::IMUAccelerometer* imu_accelerometer_group,
ControllerEmu::IMUGyroscope* imu_gyroscope_group, float time_elapsed)
{
// Avoid having to double dereference
auto& st = *state;
auto accel = imu_accelerometer_group->GetState();
auto ang_vel = imu_gyroscope_group->GetState();
// The IMU Cursor requires both an accelerometer and a gyroscope to function correctly.
if (!(accel.has_value() && ang_vel.has_value()))
{
st = std::nullopt;
return;
}
if (!st.has_value())
st = RotationalState{};
st->angle = ComplementaryFilter(st->angle, accel.value(), ang_vel.value(), time_elapsed);
// Reset camera yaw angle
constexpr ControlState BUTTON_THRESHOLD = 0.5;
if (imu_ir_group->controls[0]->control_ref->State() > BUTTON_THRESHOLD)
st->angle.z = 0;
// Limit camera yaw angle
float totalyaw = float(imu_ir_group->GetTotalYaw());
float yawmax = totalyaw / 2;
float yawmin = float(MathUtil::TAU) - totalyaw / 2;
if (st->angle.z > yawmax && st->angle.z <= float(MathUtil::PI))
st->angle.z = yawmax;
if (st->angle.z < yawmin && st->angle.z > float(MathUtil::PI))
st->angle.z = yawmin;
}
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& position_target,
const Common::Vec3& max_jerk, float time_elapsed)
{

View file

@ -11,6 +11,9 @@
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUCursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h"
#include "InputCommon/ControllerEmu/ControlGroup/Tilt.h"
namespace WiimoteEmu
@ -53,6 +56,9 @@ void EmulateShake(PositionalState* state, ControllerEmu::Shake* shake_group, flo
void EmulateTilt(RotationalState* state, ControllerEmu::Tilt* tilt_group, float time_elapsed);
void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float time_elapsed);
void EmulateCursor(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed);
void EmulateIMUCursor(std::optional<RotationalState>* state, ControllerEmu::IMUCursor* imu_ir_group,
ControllerEmu::IMUAccelerometer* imu_accelerometer_group,
ControllerEmu::IMUGyroscope* imu_gyroscope_group, float time_elapsed);
// Convert m/s/s acceleration data to the format used by Wiimote/Nunchuk (10-bit unsigned integers).
WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,

View file

@ -41,6 +41,9 @@
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUCursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h"
#include "InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h"
#include "InputCommon/ControllerEmu/ControlGroup/Tilt.h"
@ -150,6 +153,7 @@ void Wiimote::Reset()
m_tilt_state = {};
m_cursor_state = {};
m_shake_state = {};
m_imu_cursor_state = {};
}
Wiimote::Wiimote(const unsigned int index) : m_index(index)
@ -169,6 +173,11 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index)
groups.emplace_back(m_swing = new ControllerEmu::Force(_trans("Swing")));
groups.emplace_back(m_tilt = new ControllerEmu::Tilt(_trans("Tilt")));
groups.emplace_back(m_shake = new ControllerEmu::Shake(_trans("Shake")));
groups.emplace_back(m_imu_accelerometer = new ControllerEmu::IMUAccelerometer(
"IMUAccelerometer", _trans("Accelerometer")));
groups.emplace_back(m_imu_gyroscope =
new ControllerEmu::IMUGyroscope("IMUGyroscope", _trans("Gyroscope")));
groups.emplace_back(m_imu_ir = new ControllerEmu::IMUCursor("IMUIR", _trans("Point")));
// Extension
groups.emplace_back(m_attachments = new ControllerEmu::Attachments(_trans("Extension")));
@ -263,6 +272,12 @@ ControllerEmu::ControlGroup* Wiimote::GetWiimoteGroup(WiimoteGroup group)
return m_options;
case WiimoteGroup::Hotkeys:
return m_hotkeys;
case WiimoteGroup::IMUAccelerometer:
return m_imu_accelerometer;
case WiimoteGroup::IMUGyroscope:
return m_imu_gyroscope;
case WiimoteGroup::IMUPoint:
return m_imu_ir;
default:
assert(false);
return nullptr;
@ -447,7 +462,7 @@ void Wiimote::SendDataReport()
{
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
DataReportBuilder::AccelData accel =
ConvertAccelData(GetAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
rpt_builder.SetAccelData(accel);
}
@ -456,7 +471,7 @@ void Wiimote::SendDataReport()
{
// Note: Camera logic currently contains no changing state so we can just update it here.
// If that changes this should be moved to Wiimote::Update();
m_camera_logic.Update(GetTransformation());
m_camera_logic.Update(GetTotalTransformation());
// The real wiimote reads camera data from the i2c bus starting at offset 0x37:
const u8 camera_data_offset =
@ -477,7 +492,7 @@ void Wiimote::SendDataReport()
if (m_is_motion_plus_attached)
{
// TODO: Make input preparation triggered by bus read.
m_motion_plus.PrepareInput(GetAngularVelocity());
m_motion_plus.PrepareInput(GetTotalAngularVelocity());
}
u8* ext_data = rpt_builder.GetExtDataPtr();
@ -670,6 +685,20 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface)
m_dpad->SetControlExpression(3, "Right"); // Right
#endif
// Motion Source
m_imu_accelerometer->SetControlExpression(0, "Accel Left");
m_imu_accelerometer->SetControlExpression(1, "Accel Right");
m_imu_accelerometer->SetControlExpression(2, "Accel Forward");
m_imu_accelerometer->SetControlExpression(3, "Accel Backward");
m_imu_accelerometer->SetControlExpression(4, "Accel Up");
m_imu_accelerometer->SetControlExpression(5, "Accel Down");
m_imu_gyroscope->SetControlExpression(0, "Gyro Pitch Up");
m_imu_gyroscope->SetControlExpression(1, "Gyro Pitch Down");
m_imu_gyroscope->SetControlExpression(2, "Gyro Roll Left");
m_imu_gyroscope->SetControlExpression(3, "Gyro Roll Right");
m_imu_gyroscope->SetControlExpression(4, "Gyro Yaw Left");
m_imu_gyroscope->SetControlExpression(5, "Gyro Yaw Right");
// Enable Nunchuk:
constexpr ExtensionNumber DEFAULT_EXT = ExtensionNumber::NUNCHUK;
m_attachments->SetSelectedAttachment(DEFAULT_EXT);
@ -720,14 +749,14 @@ void Wiimote::StepDynamics()
EmulateTilt(&m_tilt_state, m_tilt, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateCursor(&m_cursor_state, m_ir, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateShake(&m_shake_state, m_shake, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateIMUCursor(&m_imu_cursor_state, m_imu_ir, m_imu_accelerometer, m_imu_gyroscope,
1.f / ::Wiimote::UPDATE_FREQ);
}
Common::Vec3 Wiimote::GetAcceleration()
Common::Vec3 Wiimote::GetAcceleration(Common::Vec3 extra_acceleration)
{
Common::Vec3 accel =
GetOrientation() *
GetTransformation().Transform(
m_swing_state.acceleration + Common::Vec3(0, 0, float(GRAVITY_ACCELERATION)), 0);
Common::Vec3 accel = GetOrientation() * GetTransformation().Transform(
m_swing_state.acceleration + extra_acceleration, 0);
// Our shake effects have never been affected by orientation. Should they be?
accel += m_shake_state.acceleration;
@ -735,13 +764,13 @@ Common::Vec3 Wiimote::GetAcceleration()
return accel;
}
Common::Vec3 Wiimote::GetAngularVelocity()
Common::Vec3 Wiimote::GetAngularVelocity(Common::Vec3 extra_angular_velocity)
{
return GetOrientation() * (m_tilt_state.angular_velocity + m_swing_state.angular_velocity +
m_cursor_state.angular_velocity);
m_cursor_state.angular_velocity + extra_angular_velocity);
}
Common::Matrix44 Wiimote::GetTransformation() const
Common::Matrix44 Wiimote::GetTransformation(Common::Vec3 extra_rotation) const
{
// Includes positional and rotational effects of:
// Cursor, Swing, Tilt, Shake
@ -749,7 +778,7 @@ Common::Matrix44 Wiimote::GetTransformation() const
// TODO: Think about and clean up matrix order + make nunchuk match.
return Common::Matrix44::Translate(-m_shake_state.position) *
Common::Matrix44::FromMatrix33(GetRotationalMatrix(
-m_tilt_state.angle - m_swing_state.angle - m_cursor_state.angle)) *
-m_tilt_state.angle - m_swing_state.angle - m_cursor_state.angle - extra_rotation)) *
Common::Matrix44::Translate(-m_swing_state.position - m_cursor_state.position);
}
@ -759,4 +788,31 @@ Common::Matrix33 Wiimote::GetOrientation() const
Common::Matrix33::RotateX(float(MathUtil::TAU / 4 * IsUpright()));
}
Common::Vec3 Wiimote::GetTotalAcceleration()
{
auto accel = m_imu_accelerometer->GetState();
if (accel.has_value())
return GetAcceleration(accel.value());
else
return GetAcceleration();
}
Common::Vec3 Wiimote::GetTotalAngularVelocity()
{
auto ang_vel = m_imu_gyroscope->GetState();
if (ang_vel.has_value())
return GetAngularVelocity(ang_vel.value());
else
return GetAngularVelocity();
}
Common::Matrix44 Wiimote::GetTotalTransformation() const
{
auto state = m_imu_cursor_state;
if (state.has_value())
return GetTransformation(state->angle);
else
return GetTransformation();
}
} // namespace WiimoteEmu

View file

@ -28,6 +28,9 @@ class ControlGroup;
class Cursor;
class Extension;
class Force;
class IMUAccelerometer;
class IMUGyroscope;
class IMUCursor;
class ModifySettingsButton;
class Output;
class Tilt;
@ -45,9 +48,11 @@ enum class WiimoteGroup
Swing,
Rumble,
Attachments,
Options,
Hotkeys
Hotkeys,
IMUAccelerometer,
IMUGyroscope,
IMUPoint,
};
enum class NunchukGroup;
@ -140,22 +145,29 @@ private:
// This is the region exposed over bluetooth:
static constexpr int EEPROM_FREE_SIZE = 0x1700;
static constexpr double BUTTON_THRESHOLD = 0.5;
void UpdateButtonsStatus();
// Returns simulated accelerometer data in m/s^2.
Common::Vec3 GetAcceleration();
Common::Vec3 GetAcceleration(
Common::Vec3 extra_acceleration = Common::Vec3(0, 0, float(GRAVITY_ACCELERATION)));
// Returns simulated gyroscope data in radians/s.
Common::Vec3 GetAngularVelocity();
Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity = {});
// Returns the transformation of the world around the wiimote.
// Used for simulating camera data and for rotating acceleration data.
// Does not include orientation transformations.
Common::Matrix44 GetTransformation() const;
Common::Matrix44 GetTransformation(Common::Vec3 extra_rotation = {}) const;
// Returns the world rotation from the effects of sideways/upright settings.
Common::Matrix33 GetOrientation() const;
Common::Vec3 GetTotalAcceleration();
Common::Vec3 GetTotalAngularVelocity();
Common::Matrix44 GetTotalTransformation() const;
void HIDOutputReport(const void* data, u32 size);
void HandleReportRumble(const WiimoteCommon::OutputReportRumble&);
@ -246,6 +258,9 @@ private:
ControllerEmu::Attachments* m_attachments;
ControllerEmu::ControlGroup* m_options;
ControllerEmu::ModifySettingsButton* m_hotkeys;
ControllerEmu::IMUAccelerometer* m_imu_accelerometer;
ControllerEmu::IMUGyroscope* m_imu_gyroscope;
ControllerEmu::IMUCursor* m_imu_ir;
ControllerEmu::SettingValue<bool> m_sideways_setting;
ControllerEmu::SettingValue<bool> m_upright_setting;
@ -284,5 +299,6 @@ private:
RotationalState m_tilt_state;
MotionState m_cursor_state;
PositionalState m_shake_state;
std::optional<RotationalState> m_imu_cursor_state;
};
} // namespace WiimoteEmu

View file

@ -140,6 +140,8 @@ add_executable(dolphin-emu
Config/Mapping/WiimoteEmuGeneral.h
Config/Mapping/WiimoteEmuMotionControl.cpp
Config/Mapping/WiimoteEmuMotionControl.h
Config/Mapping/WiimoteEmuMotionControlIMU.cpp
Config/Mapping/WiimoteEmuMotionControlIMU.h
Config/NewPatchDialog.cpp
Config/NewPatchDialog.h
Config/PatchesWidget.cpp

View file

@ -35,6 +35,7 @@
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuGeneral.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuMotionControl.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include "DolphinQt/Settings.h"
@ -348,7 +349,8 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
widget = new WiimoteEmuGeneral(this, extension);
setWindowTitle(tr("Wii Remote %1").arg(GetPort() + 1));
AddWidget(tr("General and Options"), widget);
AddWidget(tr("Motion Controls"), new WiimoteEmuMotionControl(this));
AddWidget(tr("Motion Simulation"), new WiimoteEmuMotionControl(this));
AddWidget(tr("Motion Input"), new WiimoteEmuMotionControlIMU(this));
AddWidget(tr("Extension"), extension);
break;
}

View file

@ -0,0 +1,72 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h"
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QString>
#include <QVBoxLayout>
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/InputConfig.h"
WiimoteEmuMotionControlIMU::WiimoteEmuMotionControlIMU(MappingWindow* window)
: MappingWidget(window)
{
CreateMainLayout();
}
QGroupBox* WiimoteEmuMotionControlIMU::AddWarning(QGroupBox* groupbox)
{
QFormLayout* layout = static_cast<QFormLayout*>(groupbox->layout());
QLabel* label;
label = new QLabel(QLatin1String(""));
layout->addRow(label);
label = new QLabel(tr("WARNING"));
label->setStyleSheet(QLatin1String("QLabel { color : red; }"));
layout->addRow(label);
label = new QLabel(
tr("These controls are not intended for mapping regular buttons, triggers or axes."));
label->setWordWrap(true);
layout->addRow(label);
return groupbox;
}
void WiimoteEmuMotionControlIMU::CreateMainLayout()
{
m_main_layout = new QHBoxLayout();
m_main_layout->addWidget(
CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUPoint)));
m_main_layout->addWidget(AddWarning(CreateGroupBox(
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUAccelerometer))));
m_main_layout->addWidget(AddWarning(
CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUGyroscope))));
setLayout(m_main_layout);
}
void WiimoteEmuMotionControlIMU::LoadSettings()
{
Wiimote::LoadConfig();
}
void WiimoteEmuMotionControlIMU::SaveSettings()
{
Wiimote::GetConfig()->SaveConfig();
}
InputConfig* WiimoteEmuMotionControlIMU::GetConfig()
{
return Wiimote::GetConfig();
}

View file

@ -0,0 +1,32 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include "DolphinQt/Config/Mapping/MappingWidget.h"
class QCheckBox;
class QFormLayout;
class QGroupBox;
class QHBoxLayout;
class QLabel;
class QVBoxLayout;
class WiimoteEmuMotionControlIMU final : public MappingWidget
{
Q_OBJECT
public:
explicit WiimoteEmuMotionControlIMU(MappingWindow* window);
InputConfig* GetConfig() override;
private:
void LoadSettings() override;
void SaveSettings() override;
void CreateMainLayout();
static QGroupBox* AddWarning(QGroupBox* groupbox);
// Main
QHBoxLayout* m_main_layout;
};

View file

@ -89,6 +89,7 @@
<QtMoc Include="Config\Mapping\WiimoteEmuExtension.h" />
<QtMoc Include="Config\Mapping\WiimoteEmuGeneral.h" />
<QtMoc Include="Config\Mapping\WiimoteEmuMotionControl.h" />
<QtMoc Include="Config\Mapping\WiimoteEmuMotionControlIMU.h" />
<QtMoc Include="Config\LogConfigWidget.h" />
<QtMoc Include="Config\LogWidget.h" />
<QtMoc Include="Config\NewPatchDialog.h" />
@ -283,6 +284,7 @@
<ClCompile Include="$(QtMocOutPrefix)WiimoteEmuExtension.cpp" />
<ClCompile Include="$(QtMocOutPrefix)WiimoteEmuGeneral.cpp" />
<ClCompile Include="$(QtMocOutPrefix)WiimoteEmuMotionControl.cpp" />
<ClCompile Include="$(QtMocOutPrefix)WiimoteEmuMotionControlIMU.cpp" />
<ClCompile Include="$(QtMocOutPrefix)WindowActivationEventFilter.cpp" />
<ClCompile Include="AboutDialog.cpp" />
<ClCompile Include="CheatsManager.cpp" />
@ -332,6 +334,7 @@
<ClCompile Include="Config\Mapping\WiimoteEmuExtension.cpp" />
<ClCompile Include="Config\Mapping\WiimoteEmuGeneral.cpp" />
<ClCompile Include="Config\Mapping\WiimoteEmuMotionControl.cpp" />
<ClCompile Include="Config\Mapping\WiimoteEmuMotionControlIMU.cpp" />
<ClCompile Include="Config\LogConfigWidget.cpp" />
<ClCompile Include="Config\LogWidget.cpp" />
<ClCompile Include="Config\NewPatchDialog.cpp" />

View file

@ -25,6 +25,12 @@ add_library(inputcommon
ControllerEmu/ControlGroup/Cursor.h
ControllerEmu/ControlGroup/Force.cpp
ControllerEmu/ControlGroup/Force.h
ControllerEmu/ControlGroup/IMUAccelerometer.cpp
ControllerEmu/ControlGroup/IMUAccelerometer.h
ControllerEmu/ControlGroup/IMUCursor.cpp
ControllerEmu/ControlGroup/IMUCursor.h
ControllerEmu/ControlGroup/IMUGyroscope.cpp
ControllerEmu/ControlGroup/IMUGyroscope.h
ControllerEmu/ControlGroup/MixedTriggers.cpp
ControllerEmu/ControlGroup/MixedTriggers.h
ControllerEmu/ControlGroup/ModifySettingsButton.cpp
@ -37,6 +43,9 @@ add_library(inputcommon
ControllerEmu/ControlGroup/Triggers.h
ControllerEmu/Setting/NumericSetting.cpp
ControllerEmu/Setting/NumericSetting.h
ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp
ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h
ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h
ControllerInterface/ControllerInterface.cpp
ControllerInterface/ControllerInterface.h
ControllerInterface/Device.cpp

View file

@ -40,6 +40,9 @@ enum class GroupType
Triggers,
Slider,
Shake,
IMUAccelerometer,
IMUGyroscope,
IMUCursor
};
class ControlGroup

View file

@ -0,0 +1,44 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h"
#include "Common/Common.h"
#include "Common/MathUtil.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
namespace ControllerEmu
{
IMUAccelerometer::IMUAccelerometer(std::string name, std::string ui_name)
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUAccelerometer)
{
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Left")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Right")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Forward")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Backward")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Up")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Down")));
}
std::optional<IMUAccelerometer::StateData> IMUAccelerometer::GetState() const
{
StateData state;
state.x = (controls[0]->control_ref->State() - controls[1]->control_ref->State());
state.y = (controls[3]->control_ref->State() - controls[2]->control_ref->State());
state.z = (controls[4]->control_ref->State() - controls[5]->control_ref->State());
if (controls[0]->control_ref->BoundCount() != 0)
return state;
else
return std::nullopt;
}
} // namespace ControllerEmu

View file

@ -0,0 +1,24 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <string>
#include "Common/Matrix.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerInterface/Device.h"
namespace ControllerEmu
{
class IMUAccelerometer : public ControlGroup
{
public:
using StateData = Common::Vec3;
IMUAccelerometer(std::string name, std::string ui_name);
std::optional<StateData> GetState() const;
};
} // namespace ControllerEmu

View file

@ -0,0 +1,43 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerEmu/ControlGroup/IMUCursor.h"
#include <string>
#include "Common/Common.h"
#include "Common/MathUtil.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
namespace ControllerEmu
{
IMUCursor::IMUCursor(std::string name, std::string ui_name)
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUCursor)
{
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Recenter")));
// Default values are optimized for "Super Mario Galaxy 2".
// This seems to be acceptable for a good number of games.
AddSetting(&m_yaw_setting,
// i18n: Refers to an amount of rotational movement about the "yaw" axis.
{_trans("Total Yaw"),
// i18n: The symbol/abbreviation for degrees (unit of angular measure).
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the yaw axis.")},
15, 0, 360);
}
ControlState IMUCursor::GetTotalYaw() const
{
return m_yaw_setting.GetValue() * MathUtil::TAU / 360;
}
} // namespace ControllerEmu

View file

@ -0,0 +1,26 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <string>
#include "InputCommon/ControllerEmu/StickGate.h"
#include "InputCommon/ControllerInterface/Device.h"
namespace ControllerEmu
{
class IMUCursor : public ControlGroup
{
public:
IMUCursor(std::string name, std::string ui_name);
// Yaw movement in radians.
ControlState GetTotalYaw() const;
private:
SettingValue<double> m_yaw_setting;
};
} // namespace ControllerEmu

View file

@ -0,0 +1,44 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h"
#include "Common/Common.h"
#include "Common/MathUtil.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
namespace ControllerEmu
{
IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUGyroscope)
{
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Pitch Up")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Pitch Down")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Roll Left")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Roll Right")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Yaw Left")));
controls.emplace_back(std::make_unique<Input>(Translate, _trans("Yaw Right")));
}
std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
{
StateData state;
state.x = (controls[1]->control_ref->State() - controls[0]->control_ref->State());
state.y = (controls[2]->control_ref->State() - controls[3]->control_ref->State());
state.z = (controls[4]->control_ref->State() - controls[5]->control_ref->State());
if (controls[0]->control_ref->BoundCount() != 0)
return state;
else
return std::nullopt;
}
} // namespace ControllerEmu

View file

@ -0,0 +1,24 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <string>
#include "Common/Matrix.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerInterface/Device.h"
namespace ControllerEmu
{
class IMUGyroscope : public ControlGroup
{
public:
using StateData = Common::Vec3;
IMUGyroscope(std::string name, std::string ui_name);
std::optional<StateData> GetState() const;
};
} // namespace ControllerEmu

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -29,6 +29,7 @@
#if defined(USE_PIPES)
#define CIFACE_USE_PIPES
#endif
#define CIFACE_USE_CEMUHOOKUDPSERVER
//
// ControllerInterface

View file

@ -36,6 +36,9 @@
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemGroup>
<ClCompile Include="ControllerEmu\ControlGroup\IMUAccelerometer.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\IMUCursor.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\IMUGyroscope.cpp" />
<ClCompile Include="ControllerEmu\ControllerEmu.cpp" />
<ClCompile Include="ControllerEmu\StickGate.cpp" />
<ClCompile Include="ControllerEmu\Control\Control.cpp" />
@ -53,6 +56,7 @@
<ClCompile Include="ControllerEmu\ControlGroup\Tilt.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\Triggers.cpp" />
<ClCompile Include="ControllerEmu\Setting\NumericSetting.cpp" />
<ClCompile Include="ControllerInterface\CemuHookUDPServer\CemuHookUDPServer.cpp" />
<ClCompile Include="ControllerInterface\ControllerInterface.cpp" />
<ClCompile Include="ControllerInterface\Device.cpp" />
<ClCompile Include="ControllerInterface\DInput\DInput.cpp" />
@ -76,6 +80,9 @@
<ClCompile Include="InputProfile.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ControllerEmu\ControlGroup\IMUAccelerometer.h" />
<ClInclude Include="ControllerEmu\ControlGroup\IMUCursor.h" />
<ClInclude Include="ControllerEmu\ControlGroup\IMUGyroscope.h" />
<ClInclude Include="ControllerEmu\ControllerEmu.h" />
<ClInclude Include="ControllerEmu\StickGate.h" />
<ClInclude Include="ControllerEmu\Control\Control.h" />
@ -93,6 +100,8 @@
<ClInclude Include="ControllerEmu\ControlGroup\Tilt.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Triggers.h" />
<ClInclude Include="ControllerEmu\Setting\NumericSetting.h" />
<ClInclude Include="ControllerInterface\CemuHookUDPServer\CemuHookUDPServer.h" />
<ClInclude Include="ControllerInterface\CemuHookUDPServer\CemuHookUDPServerProto.h" />
<ClInclude Include="ControllerInterface\ControllerInterface.h" />
<ClInclude Include="ControllerInterface\Device.h" />
<ClInclude Include="ControllerInterface\DInput\DInput.h" />
@ -122,4 +131,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -28,6 +28,9 @@
<Filter Include="ControllerInterface\XInput">
<UniqueIdentifier>{07bad1aa-7e03-4f5c-ade2-a44857c5cbc3}</UniqueIdentifier>
</Filter>
<Filter Include="ControllerInterface\CemuHookUDPServer">
<UniqueIdentifier>{4f71c21c-85eb-4e76-ad64-40fd97bc3d6d}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="GCAdapter.cpp" />
@ -120,6 +123,18 @@
<ClCompile Include="ControllerEmu\ControlGroup\Attachments.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerEmu\ControlGroup\IMUCursor.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerInterface\CemuHookUDPServer\CemuHookUDPServer.cpp">
<Filter>ControllerInterface\CemuHookUDPServer</Filter>
</ClCompile>
<ClCompile Include="ControllerEmu\ControlGroup\IMUAccelerometer.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerEmu\ControlGroup\IMUGyroscope.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GCAdapter.h" />
@ -216,8 +231,23 @@
<ClInclude Include="ControllerEmu\ControlGroup\Attachments.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerEmu\ControlGroup\IMUCursor.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerInterface\CemuHookUDPServer\CemuHookUDPServer.h">
<Filter>ControllerInterface\CemuHookUDPServer</Filter>
</ClInclude>
<ClInclude Include="ControllerEmu\ControlGroup\IMUAccelerometer.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerEmu\ControlGroup\IMUGyroscope.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerInterface\CemuHookUDPServer\CemuHookUDPServerProto.h">
<Filter>ControllerInterface\CemuHookUDPServer</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>