summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorPierre Bourdon <delroth@gmail.com>2019-10-28 16:39:10 +0100
committerGitHub <noreply@github.com>2019-10-28 16:39:10 +0100
commit1f3d1a9b7fa2d4729ddadfc0fc923c7730a00a49 (patch)
tree59c9aac33c104d3dc5b4039773b876cd6c389b4f /Source/Core
parenta825e7e09f8a39772b10c83db5322dc88e0f591f (diff)
parentda1f153b47a3f3f4eb9b976670bfddf68108bae9 (diff)
Merge pull request #8352 from rlnilsen/motion-controller-support-via-cemuhook-protocol
Support for motion controllers like the DualShock 4
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Common/CommonPaths.h1
-rw-r--r--Source/Core/Common/Config/Config.cpp12
-rw-r--r--Source/Core/Common/Config/Enums.h1
-rw-r--r--Source/Core/Common/FileUtil.cpp2
-rw-r--r--Source/Core/Common/FileUtil.h1
-rw-r--r--Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp1
-rw-r--r--Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp3
-rw-r--r--Source/Core/Core/Core.vcxproj2
-rw-r--r--Source/Core/Core/Core.vcxproj.filters5
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp87
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/Dynamics.h6
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp80
-rw-r--r--Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h26
-rw-r--r--Source/Core/DolphinQt/CMakeLists.txt6
-rw-r--r--Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.cpp47
-rw-r--r--Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h32
-rw-r--r--Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.cpp75
-rw-r--r--Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h26
-rw-r--r--Source/Core/DolphinQt/Config/ControllersWindow.cpp37
-rw-r--r--Source/Core/DolphinQt/Config/ControllersWindow.h12
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp4
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.cpp75
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h31
-rw-r--r--Source/Core/DolphinQt/DolphinQt.vcxproj11
-rw-r--r--Source/Core/InputCommon/CMakeLists.txt9
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h3
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp44
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h24
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp43
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h26
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp44
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h24
-rw-r--r--Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp12
-rw-r--r--Source/Core/InputCommon/ControllerInterface/ControllerInterface.h1
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp444
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h19
-rw-r--r--Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h270
-rw-r--r--Source/Core/InputCommon/InputCommon.vcxproj11
-rw-r--r--Source/Core/InputCommon/InputCommon.vcxproj.filters36
39 files changed, 1548 insertions, 45 deletions
diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h
index bec3fc0772..46a201d823 100644
--- a/Source/Core/Common/CommonPaths.h
+++ b/Source/Core/Common/CommonPaths.h
@@ -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"
diff --git a/Source/Core/Common/Config/Config.cpp b/Source/Core/Common/Config/Config.cpp
index ba7f5b9007..b65b1d6fad 100644
--- a/Source/Core/Common/Config/Config.cpp
+++ b/Source/Core/Common/Config/Config.cpp
@@ -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)
{
diff --git a/Source/Core/Common/Config/Enums.h b/Source/Core/Common/Config/Enums.h
index 1363c78913..831726ae1e 100644
--- a/Source/Core/Common/Config/Enums.h
+++ b/Source/Core/Common/Config/Enums.h
@@ -30,6 +30,7 @@ enum class System
GFX,
Logger,
Debugger,
+ DualShockUDPClient,
};
constexpr std::array<LayerType, 7> SEARCH_ORDER{{
diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp
index d925c24cce..b1885916dc 100644
--- a/Source/Core/Common/FileUtil.cpp
+++ b/Source/Core/Common/FileUtil.cpp
@@ -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;
diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h
index 3cb65e093a..ccabf7fdfe 100644
--- a/Source/Core/Common/FileUtil.h
+++ b/Source/Core/Common/FileUtil.h
@@ -70,6 +70,7 @@ enum
F_MEMORYWATCHERLOCATIONS_IDX,
F_MEMORYWATCHERSOCKET_IDX,
F_WIISDCARD_IDX,
+ F_DUALSHOCKUDPCLIENTCONFIG_IDX,
NUM_PATH_INDICES
};
diff --git a/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp
index 4422a096a5..e8ac86b423 100644
--- a/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp
+++ b/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp
@@ -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
diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
index 15711a65b8..9483aed414 100644
--- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
+++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
@@ -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;
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index a08761f0d9..20e23993b0 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -629,4 +629,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project>
+</Project> \ No newline at end of file
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index 3064754798..06bc1fa6b2 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -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> \ No newline at end of file
diff --git a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp
index bc766ce5b1..00820bf55a 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp
@@ -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)
{
diff --git a/Source/Core/Core/HW/WiimoteEmu/Dynamics.h b/Source/Core/Core/HW/WiimoteEmu/Dynamics.h
index c7483b6e91..6d610c394b 100644
--- a/Source/Core/Core/HW/WiimoteEmu/Dynamics.h
+++ b/Source/Core/Core/HW/WiimoteEmu/Dynamics.h
@@ -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,
diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp
index c1f9663c7e..8c735e5e56 100644
--- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp
+++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp
@@ -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
diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h
index 4bb613e526..0f627e619e 100644
--- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h
+++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h
@@ -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
diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt
index 52b13c981d..8c6936d10d 100644
--- a/Source/Core/DolphinQt/CMakeLists.txt
+++ b/Source/Core/DolphinQt/CMakeLists.txt
@@ -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
diff --git a/Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.cpp b/Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.cpp
new file mode 100644
index 0000000000..991e84c8f9
--- /dev/null
+++ b/Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.cpp
@@ -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);
+}
diff --git a/Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h b/Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h
new file mode 100644
index 0000000000..792cc97c25
--- /dev/null
+++ b/Source/Core/DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h
@@ -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
+};
diff --git a/Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.cpp b/Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.cpp
new file mode 100644
index 0000000000..965f2f6879
--- /dev/null
+++ b/Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.cpp
@@ -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()));
+ });
+}
diff --git a/Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h b/Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h
new file mode 100644
index 0000000000..22e5a88011
--- /dev/null
+++ b/Source/Core/DolphinQt/Config/ControllerInterface/DualShockUDPClientWidget.h
@@ -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;
+};
diff --git a/Source/Core/DolphinQt/Config/ControllersWindow.cpp b/Source/Core/DolphinQt/Config/ControllersWindow.cpp
index 304ae7364c..1df7b1d9a7 100644
--- a/Source/Core/DolphinQt/Config/ControllersWindow.cpp
+++ b/Source/Core/DolphinQt/Config/ControllersWindow.cpp
@@ -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 :
diff --git a/Source/Core/DolphinQt/Config/ControllersWindow.h b/Source/Core/DolphinQt/Config/ControllersWindow.h
index ae03b79491..368ef36cdb 100644
--- a/Source/Core/DolphinQt/Config/ControllersWindow.h
+++ b/Source/Core/DolphinQt/Config/ControllersWindow.h
@@ -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;
};
diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp
index 7c7e99b684..cdddc07fe2 100644
--- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp
+++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp
@@ -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;
}
diff --git a/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.cpp b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.cpp
new file mode 100644
index 0000000000..89920c703d
--- /dev/null
+++ b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.cpp
@@ -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();
+}
diff --git a/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h
new file mode 100644
index 0000000000..b1aab7d1d4
--- /dev/null
+++ b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h
@@ -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;
+};
diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index 2deb34cffc..13922e2894 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -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" />
diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt
index b47503a9ae..b1420155f6 100644
--- a/Source/Core/InputCommon/CMakeLists.txt
+++ b/Source/Core/InputCommon/CMakeLists.txt
@@ -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
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h
index fff118d280..310df90e30 100644
--- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ControlGroup.h
@@ -40,6 +40,9 @@ enum class GroupType
Triggers,
Slider,
Shake,
+ IMUAccelerometer,
+ IMUGyroscope,
+ IMUCursor
};
class ControlGroup
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp
new file mode 100644
index 0000000000..e994ceba5d
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.cpp
@@ -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
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h
new file mode 100644
index 0000000000..7ac41ede7a
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUAccelerometer.h
@@ -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
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp
new file mode 100644
index 0000000000..7133a07e59
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.cpp
@@ -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
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h
new file mode 100644
index 0000000000..578762fb85
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUCursor.h
@@ -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
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp
new file mode 100644
index 0000000000..7bae049fff
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp
@@ -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
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h
new file mode 100644
index 0000000000..dac30143dc
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h
@@ -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
diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp
index bb5ccb3f0e..35dfbde0be 100644
--- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp
+++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp
@@ -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;
@@ -68,6 +71,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
#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)
diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h
index 2ad53fc79b..359bd8f0c3 100644
--- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h
+++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h
@@ -29,6 +29,7 @@
#if defined(USE_PIPES)
#define CIFACE_USE_PIPES
#endif
+#define CIFACE_USE_DUALSHOCKUDPCLIENT
//
// ControllerInterface
diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp
new file mode 100644
index 0000000000..60086d517c
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp
@@ -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
diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h
new file mode 100644
index 0000000000..b4bb1210c6
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h
@@ -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
diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h
new file mode 100644
index 0000000000..45980bcd5d
--- /dev/null
+++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h
@@ -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
diff --git a/Source/Core/InputCommon/InputCommon.vcxproj b/Source/Core/InputCommon/InputCommon.vcxproj
index a22ad21394..262ba2f52e 100644
--- a/Source/Core/InputCommon/InputCommon.vcxproj
+++ b/Source/Core/InputCommon/InputCommon.vcxproj
@@ -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" />
@@ -122,4 +131,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project>
+</Project> \ No newline at end of file
diff --git a/Source/Core/InputCommon/InputCommon.vcxproj.filters b/Source/Core/InputCommon/InputCommon.vcxproj.filters
index c1f26d6141..0c8d79326f 100644
--- a/Source/Core/InputCommon/InputCommon.vcxproj.filters
+++ b/Source/Core/InputCommon/InputCommon.vcxproj.filters
@@ -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,15 +224,28 @@
<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" />
</ItemGroup>
-</Project>
+</Project> \ No newline at end of file