Merge pull request #8352 from rlnilsen/motion-controller-support-via-cemuhook-protocol

Support for motion controllers like the DualShock 4
This commit is contained in:
Pierre Bourdon 2019-10-28 16:39:10 +01:00 committed by GitHub
commit 1f3d1a9b7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1548 additions and 45 deletions

View File

@ -85,6 +85,7 @@
#define GFX_CONFIG "GFX.ini"
#define DEBUGGER_CONFIG "Debugger.ini"
#define LOGGER_CONFIG "Logger.ini"
#define DUALSHOCKUDPCLIENT_CONFIG "DSUClient.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::DualShockUDPClient, "DualShockUDPClient"}};
const std::string& GetSystemName(System system)
{

View File

@ -30,6 +30,7 @@ enum class System
GFX,
Logger,
Debugger,
DualShockUDPClient,
};
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_DUALSHOCKUDPCLIENTCONFIG_IDX] =
s_user_paths[D_CONFIG_IDX] + DUALSHOCKUDPCLIENT_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_DUALSHOCKUDPCLIENTCONFIG_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::DualShockUDPClient, F_DUALSHOCKUDPCLIENTCONFIG_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::DualShockUDPClient)
return true;
if (config_location.system == Config::System::Logger)
return true;

View File

@ -1661,6 +1661,9 @@
<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" />

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

@ -51,6 +51,10 @@ add_executable(dolphin-emu
Config/CheatCodeEditor.h
Config/CheatWarningWidget.cpp
Config/CheatWarningWidget.h
Config/ControllerInterface/DualShockUDPClientWidget.cpp
Config/ControllerInterface/DualShockUDPClientWidget.h
Config/ControllerInterface/ControllerInterfaceWindow.cpp
Config/ControllerInterface/ControllerInterfaceWindow.h
Config/ControllersWindow.cpp
Config/ControllersWindow.h
Config/FilesystemWidget.cpp
@ -140,6 +144,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

@ -0,0 +1,47 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h"
#include <QDialogButtonBox>
#include <QLabel>
#include <QTabWidget>
#include <QVBoxLayout>
#if defined(CIFACE_USE_DUALSHOCKUDPCLIENT)
#include "DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h"
#endif
ControllerInterfaceWindow::ControllerInterfaceWindow(QWidget* parent) : QDialog(parent)
{
CreateMainLayout();
setWindowTitle(tr("Alternate Input Sources"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
void ControllerInterfaceWindow::CreateMainLayout()
{
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
m_tab_widget = new QTabWidget();
#if defined(CIFACE_USE_DUALSHOCKUDPCLIENT)
m_dsuclient_widget = new DualShockUDPClientWidget();
m_tab_widget->addTab(m_dsuclient_widget, tr("DSU Client")); // TODO: use GetWrappedWidget()?
#endif
auto* main_layout = new QVBoxLayout();
if (m_tab_widget->count() > 0)
{
main_layout->addWidget(m_tab_widget);
}
else
{
main_layout->addWidget(new QLabel(tr("Nothing to configure")), 0,
Qt::AlignVCenter | Qt::AlignHCenter);
}
main_layout->addWidget(m_button_box);
setLayout(main_layout);
}

View File

@ -0,0 +1,32 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#if defined(CIFACE_USE_DUALSHOCKUDPCLIENT)
class DualShockUDPClientWidget;
#endif
class QTabWidget;
class QDialogButtonBox;
class ControllerInterfaceWindow final : public QDialog
{
Q_OBJECT
public:
explicit ControllerInterfaceWindow(QWidget* parent);
private:
void CreateMainLayout();
QTabWidget* m_tab_widget;
QDialogButtonBox* m_button_box;
#if defined(CIFACE_USE_DUALSHOCKUDPCLIENT)
DualShockUDPClientWidget* m_dsuclient_widget;
#endif
};

View File

@ -0,0 +1,75 @@
// Copyright 2019 Dolphin Emulator Project5~5~5~
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h"
#include <QCheckBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include "Common/Config/Config.h"
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h"
DualShockUDPClientWidget::DualShockUDPClientWidget()
{
CreateWidgets();
ConnectWidgets();
}
void DualShockUDPClientWidget::CreateWidgets()
{
auto* main_layout = new QGridLayout;
m_server_enabled = new QCheckBox(tr("Enable"));
m_server_enabled->setChecked(Config::Get(ciface::DualShockUDPClient::Settings::SERVER_ENABLED));
m_server_address = new QLineEdit(
QString::fromStdString(Config::Get(ciface::DualShockUDPClient::Settings::SERVER_ADDRESS)));
m_server_port = new QSpinBox();
m_server_port->setMaximum(65535);
m_server_port->setValue(Config::Get(ciface::DualShockUDPClient::Settings::SERVER_PORT));
auto* description =
new QLabel(tr("DSU protocol enables the use of input and motion data from compatible "
"sources, like PlayStation, Nintendo Switch and Steam controllers.<br><br>"
"For setup instructions, "
"<a href=\"https://wiki.dolphin-emu.org/index.php?title=DSU_Client\">"
"refer to this page</a>."));
description->setTextFormat(Qt::RichText);
description->setWordWrap(true);
description->setTextInteractionFlags(Qt::TextBrowserInteraction);
description->setOpenExternalLinks(true);
main_layout->addWidget(m_server_enabled, 1, 1);
main_layout->addWidget(new QLabel(tr("Server IP Address")), 2, 1);
main_layout->addWidget(m_server_address, 2, 2);
main_layout->addWidget(new QLabel(tr("Server Port")), 3, 1);
main_layout->addWidget(m_server_port, 3, 2);
main_layout->addWidget(description, 4, 1, 1, 2);
setLayout(main_layout);
}
void DualShockUDPClientWidget::ConnectWidgets()
{
connect(m_server_enabled, &QCheckBox::toggled, this, [this] {
Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVER_ENABLED,
m_server_enabled->isChecked());
});
connect(m_server_address, &QLineEdit::editingFinished, this, [this] {
Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVER_ADDRESS,
m_server_address->text().toStdString());
});
connect(m_server_port, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
[this] {
Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVER_PORT,
static_cast<u16>(m_server_port->value()));
});
}

View File

@ -0,0 +1,26 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QWidget>
class QCheckBox;
class QLineEdit;
class QSpinBox;
class DualShockUDPClientWidget final : public QWidget
{
Q_OBJECT
public:
explicit DualShockUDPClientWidget();
private:
void CreateWidgets();
void ConnectWidgets();
QCheckBox* m_server_enabled;
QLineEdit* m_server_address;
QSpinBox* m_server_port;
};

View File

@ -31,6 +31,7 @@
#include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Bluetooth/BTReal.h"
#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h"
#include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
@ -67,7 +68,7 @@ ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
CreateGamecubeLayout();
CreateWiimoteLayout();
CreateAdvancedLayout();
CreateCommonLayout();
CreateMainLayout();
LoadSettings();
ConnectWidgets();
@ -199,15 +200,18 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1);
}
void ControllersWindow::CreateAdvancedLayout()
void ControllersWindow::CreateCommonLayout()
{
m_advanced_box = new QGroupBox(tr("Advanced"));
m_advanced_layout = new QHBoxLayout();
m_advanced_bg_input = new QCheckBox(tr("Background Input"));
// i18n: This is "common" as in "shared", not the opposite of "uncommon"
m_common_box = new QGroupBox(tr("Common"));
m_common_layout = new QVBoxLayout();
m_common_bg_input = new QCheckBox(tr("Background Input"));
m_common_configure_controller_interface = new QPushButton(tr("Alternate Input Sources"));
m_advanced_layout->addWidget(m_advanced_bg_input);
m_common_layout->addWidget(m_common_bg_input);
m_common_layout->addWidget(m_common_configure_controller_interface);
m_advanced_box->setLayout(m_advanced_layout);
m_common_box->setLayout(m_common_layout);
}
void ControllersWindow::CreateMainLayout()
@ -217,7 +221,8 @@ void ControllersWindow::CreateMainLayout()
layout->addWidget(m_gc_box);
layout->addWidget(m_wiimote_box);
layout->addWidget(m_advanced_box);
layout->addWidget(m_common_box);
layout->addStretch();
layout->addWidget(m_button_box);
WrapInScrollArea(this, layout);
@ -232,7 +237,9 @@ void ControllersWindow::ConnectWidgets()
connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);
connect(m_advanced_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
connect(m_common_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
connect(m_common_configure_controller_interface, &QPushButton::clicked, this,
&ControllersWindow::OnControllerInterfaceConfigure);
connect(m_wiimote_continuous_scanning, &QCheckBox::toggled, this,
&ControllersWindow::SaveSettings);
connect(m_wiimote_real_balance_board, &QCheckBox::toggled, this,
@ -462,6 +469,14 @@ void ControllersWindow::OnWiimoteConfigure()
window->show();
}
void ControllersWindow::OnControllerInterfaceConfigure()
{
ControllerInterfaceWindow* window = new ControllerInterfaceWindow(this);
window->setAttribute(Qt::WA_DeleteOnClose, true);
window->setWindowModality(Qt::WindowModality::WindowModal);
window->show();
}
void ControllersWindow::LoadSettings()
{
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
@ -480,7 +495,7 @@ void ControllersWindow::LoadSettings()
m_wiimote_speaker_data->setChecked(SConfig::GetInstance().m_WiimoteEnableSpeaker);
m_wiimote_continuous_scanning->setChecked(SConfig::GetInstance().m_WiimoteContinuousScanning);
m_advanced_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
m_common_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
if (SConfig::GetInstance().m_bt_passthrough_enabled)
m_wiimote_passthrough->setChecked(true);
@ -495,7 +510,7 @@ void ControllersWindow::SaveSettings()
SConfig::GetInstance().m_WiimoteEnableSpeaker = m_wiimote_speaker_data->isChecked();
SConfig::GetInstance().m_WiimoteContinuousScanning = m_wiimote_continuous_scanning->isChecked();
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
SConfig::GetInstance().m_BackgroundInput = m_advanced_bg_input->isChecked();
SConfig::GetInstance().m_BackgroundInput = m_common_bg_input->isChecked();
WiimoteReal::ChangeWiimoteSource(WIIMOTE_BALANCE_BOARD,
m_wiimote_real_balance_board->isChecked() ? WIIMOTE_SRC_REAL :

View File

@ -37,10 +37,11 @@ private:
void OnWiimoteRefreshPressed();
void OnGCPadConfigure();
void OnWiimoteConfigure();
void OnControllerInterfaceConfigure();
void CreateGamecubeLayout();
void CreateWiimoteLayout();
void CreateAdvancedLayout();
void CreateCommonLayout();
void CreateMainLayout();
void ConnectWidgets();
void LoadSettings();
@ -73,8 +74,9 @@ private:
QCheckBox* m_wiimote_speaker_data;
QPushButton* m_wiimote_refresh;
// Advanced
QGroupBox* m_advanced_box;
QHBoxLayout* m_advanced_layout;
QCheckBox* m_advanced_bg_input;
// Common
QGroupBox* m_common_box;
QVBoxLayout* m_common_layout;
QCheckBox* m_common_bg_input;
QPushButton* m_common_configure_controller_interface;
};

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,75 @@
// 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 <QPushButton>
#include <QString>
#include <QVBoxLayout>
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h"
#include "InputCommon/InputConfig.h"
WiimoteEmuMotionControlIMU::WiimoteEmuMotionControlIMU(MappingWindow* window)
: MappingWidget(window)
{
CreateMainLayout();
}
void WiimoteEmuMotionControlIMU::CreateMainLayout()
{
auto* warning_layout = new QHBoxLayout();
auto* warning_label =
new QLabel(tr("WARNING: The controls under Accelerometer and Gyroscope are designed to "
"interface directly with motion sensor hardware. They are not intended for "
"mapping traditional buttons, triggers or axes. You might need to configure "
"alternate input sources before using these controls."));
warning_label->setWordWrap(true);
auto* warning_input_sources_button = new QPushButton(tr("Alternate Input Sources"));
warning_layout->addWidget(warning_label, 1);
warning_layout->addWidget(warning_input_sources_button, 0, Qt::AlignRight);
connect(warning_input_sources_button, &QPushButton::clicked, this, [this] {
ControllerInterfaceWindow* window = new ControllerInterfaceWindow(this);
window->setAttribute(Qt::WA_DeleteOnClose, true);
window->setWindowModality(Qt::WindowModality::WindowModal);
window->show();
});
auto* groups_layout = new QHBoxLayout();
groups_layout->addWidget(
CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUPoint)));
groups_layout->addWidget(CreateGroupBox(
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUAccelerometer)));
groups_layout->addWidget(
CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUGyroscope)));
m_main_layout = new QVBoxLayout();
m_main_layout->addLayout(warning_layout);
m_main_layout->addLayout(groups_layout);
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,31 @@
// 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();
// Main
QVBoxLayout* m_main_layout;
};

View File

@ -44,7 +44,7 @@
<AdditionalDependencies>avrt.lib;iphlpapi.lib;winmm.lib;setupapi.lib;opengl32.lib;glu32.lib;rpcrt4.lib;comctl32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;Shlwapi.lib;discord-rpc.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<ClCompile>
<AdditionalIncludeDirectories>$(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Debugger;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;$(ProjectDir)TAS;$(ProjectDir)FIFO;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Debugger;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)Config\ControllerInterface;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;$(ProjectDir)TAS;$(ProjectDir)FIFO;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Manifest>
<AdditionalManifestFiles>DolphinQt.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>
@ -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" />
@ -105,6 +106,8 @@
<QtMoc Include="Config\Graphics\HacksWidget.h" />
<QtMoc Include="Config\Graphics\PostProcessingConfigWindow.h" />
<QtMoc Include="Config\Graphics\SoftwareRendererWidget.h" />
<QtMoc Include="Config\ControllerInterface\DualShockUDPClientWidget.h" />
<QtMoc Include="Config\ControllerInterface\ControllerInterfaceWindow.h" />
<QtMoc Include="Config\InfoWidget.h" />
<QtMoc Include="Config\PatchesWidget.h" />
<QtMoc Include="Config\PropertiesDialog.h" />
@ -186,6 +189,8 @@
<ClCompile Include="$(QtMocOutPrefix)ChunkedProgressDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeViewWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)DualShockUDPClientWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ControllerInterfaceWindow.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ControllersWindow.cpp" />
<ClCompile Include="$(QtMocOutPrefix)DiscordHandler.cpp" />
<ClCompile Include="$(QtMocOutPrefix)DiscordJoinRequestDialog.cpp" />
@ -283,12 +288,15 @@
<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" />
<ClCompile Include="Config\CheatCodeEditor.cpp" />
<ClCompile Include="Config\ARCodeWidget.cpp" />
<ClCompile Include="Config\CheatWarningWidget.cpp" />
<ClCompile Include="Config\ControllerInterface\DualShockUDPClientWidget.cpp" />
<ClCompile Include="Config\ControllerInterface\ControllerInterfaceWindow.cpp" />
<ClCompile Include="Config\ControllersWindow.cpp" />
<ClCompile Include="Config\FilesystemWidget.cpp" />
<ClCompile Include="Config\GameConfigEdit.cpp" />
@ -332,6 +340,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/DualShockUDPClient/DualShockUDPClient.cpp
ControllerInterface/DualShockUDPClient/DualShockUDPClient.h
ControllerInterface/DualShockUDPClient/DualShockUDPProto.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

@ -30,6 +30,9 @@
#ifdef CIFACE_USE_PIPES
#include "InputCommon/ControllerInterface/Pipes/Pipes.h"
#endif
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.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_DUALSHOCKUDPCLIENT
ciface::DualShockUDPClient::Init();
#endif
RefreshDevices();
@ -122,6 +128,9 @@ void ControllerInterface::RefreshDevices()
#ifdef CIFACE_USE_PIPES
ciface::Pipes::PopulateDevices();
#endif
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
ciface::DualShockUDPClient::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_DUALSHOCKUDPCLIENT
ciface::DualShockUDPClient::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_DUALSHOCKUDPCLIENT
//
// ControllerInterface

View File

@ -0,0 +1,444 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.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/ControllerInterface.h"
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h"
namespace ciface::DualShockUDPClient
{
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::DualShockUDPClient, "Server", "Enabled"}, false};
const Config::ConfigInfo<std::string> SERVER_ADDRESS{
{Config::System::DualShockUDPClient, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS};
const Config::ConfigInfo<int> SERVER_PORT{{Config::System::DualShockUDPClient, "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 sf::UdpSocket s_socket;
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("DualShockUDPClient Hotplug Thread");
NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient hotplug thread started");
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 (s_socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) !=
sf::Socket::Status::Done)
ERROR_LOG(SERIALINTERFACE, "DualShockUDPClient 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(s_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, "DualShockUDPClient 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_socket.unbind(); // interrupt blocking socket
s_hotplug_thread.join();
}
static void Restart()
{
NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient Restart");
StopHotplugThread();
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;
}
PopulateDevices(); // remove devices
if (s_server_enabled)
StartHotplugThread();
}
static void ConfigChanged()
{
bool server_enabled = Config::Get(Settings::SERVER_ENABLED);
std::string server_address = Config::Get(Settings::SERVER_ADDRESS);
u16 server_port = Config::Get(Settings::SERVER_PORT);
if (server_enabled != s_server_enabled || server_address != s_server_address ||
server_port != s_server_port)
{
s_server_enabled = server_enabled;
s_server_address = server_address;
s_server_port = server_port;
Restart();
}
}
void Init()
{
Config::AddConfigChangedCallback(ConfigChanged);
}
void PopulateDevices()
{
NOTICE_LOG(SERIALINTERFACE, "DualShockUDPClient PopulateDevices");
g_controller_interface.RemoveDevice(
[](const auto* dev) { return dev->GetSource() == "DSUClient"; });
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();
}
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 "DSUClient";
}
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, "DualShockUDPClient 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::DualShockUDPClient

View File

@ -0,0 +1,19 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Common/Config/Config.h"
namespace ciface::DualShockUDPClient
{
namespace Settings
{
extern const Config::ConfigInfo<bool> SERVER_ENABLED;
extern const Config::ConfigInfo<std::string> SERVER_ADDRESS;
extern const Config::ConfigInfo<int> SERVER_PORT;
} // namespace Settings
void Init();
void PopulateDevices();
void DeInit();
} // namespace ciface::DualShockUDPClient

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::DualShockUDPClient::Proto
{
// CemuHook DualShockUDP 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,
"DualShockUDPClient 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::DualShockUDPClient::Proto

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" />
@ -59,6 +62,7 @@
<ClCompile Include="ControllerInterface\DInput\DInputJoystick.cpp" />
<ClCompile Include="ControllerInterface\DInput\DInputKeyboardMouse.cpp" />
<ClCompile Include="ControllerInterface\DInput\XInputFilter.cpp" />
<ClCompile Include="ControllerInterface\DualShockUDPClient\DualShockUDPClient.cpp" />
<ClCompile Include="ControlReference\ControlReference.cpp" />
<ClCompile Include="ControlReference\ExpressionParser.cpp" />
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.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" />
@ -100,6 +107,8 @@
<ClInclude Include="ControllerInterface\DInput\DInputJoystick.h" />
<ClInclude Include="ControllerInterface\DInput\DInputKeyboardMouse.h" />
<ClInclude Include="ControllerInterface\DInput\XInputFilter.h" />
<ClInclude Include="ControllerInterface\DualShockUDPClient\DualShockUDPClient.h" />
<ClInclude Include="ControllerInterface\DualShockUDPClient\DualShockUDPProto.h" />
<ClInclude Include="ControlReference\ControlReference.h" />
<ClInclude Include="ControlReference\FunctionExpression.h" />
<ClInclude Include="ControlReference\ExpressionParser.h" />

View File

@ -28,6 +28,9 @@
<Filter Include="ControllerInterface\XInput">
<UniqueIdentifier>{07bad1aa-7e03-4f5c-ade2-a44857c5cbc3}</UniqueIdentifier>
</Filter>
<Filter Include="ControllerInterface\DualShockUDPClient">
<UniqueIdentifier>{ff02580e-3a62-4de4-a135-3a6c2c339a90}</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="ControllerEmu\ControlGroup\IMUAccelerometer.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerEmu\ControlGroup\IMUGyroscope.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerInterface\DualShockUDPClient\DualShockUDPClient.cpp">
<Filter>ControllerInterface\DualShockUDPClient</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GCAdapter.h" />
@ -209,13 +224,26 @@
<ClInclude Include="ControlReference\ControlReference.h">
<Filter>ControllerInterface</Filter>
</ClInclude>
<ClCompile Include="ControlReference\FunctionExpression.h">
<Filter>ControllerInterface</Filter>
</ClCompile>
<ClInclude Include="InputProfile.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Attachments.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerEmu\ControlGroup\IMUCursor.h">
<Filter>ControllerEmu\ControlGroup</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="ControlReference\FunctionExpression.h" />
<ClInclude Include="ControllerInterface\DualShockUDPClient\DualShockUDPClient.h">
<Filter>ControllerInterface\DualShockUDPClient</Filter>
</ClInclude>
<ClInclude Include="ControllerInterface\DualShockUDPClient\DualShockUDPProto.h">
<Filter>ControllerInterface\DualShockUDPClient</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />