diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index bec3fc0772..3ca7f35ab0 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 CEMUHOOKUDPSERVER_CONFIG "UDPServer.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..265da19cff 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_to_name = { - {System::Main, "Dolphin"}, {System::GCPad, "GCPad"}, {System::WiiPad, "Wiimote"}, - {System::GCKeyboard, "GCKeyboard"}, {System::GFX, "Graphics"}, {System::Logger, "Logger"}, - {System::Debugger, "Debugger"}, {System::SYSCONF, "SYSCONF"}}; + {System::Main, "Dolphin"}, + {System::GCPad, "GCPad"}, + {System::WiiPad, "Wiimote"}, + {System::GCKeyboard, "GCKeyboard"}, + {System::GFX, "Graphics"}, + {System::Logger, "Logger"}, + {System::Debugger, "Debugger"}, + {System::SYSCONF, "SYSCONF"}, + {System::CemuHookUdpServer, "CemuHookUdpServer"}}; const std::string& GetSystemName(System system) { diff --git a/Source/Core/Common/Config/Enums.h b/Source/Core/Common/Config/Enums.h index 1363c78913..ff5f543925 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, + CemuHookUdpServer, }; constexpr std::array SEARCH_ORDER{{ diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 3c85563b56..965247cba4 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_CEMUHOOKUDPSERVERCONFIG_IDX] = + s_user_paths[D_CONFIG_IDX] + CEMUHOOKUDPSERVER_CONFIG; s_user_paths[F_MAINLOG_IDX] = s_user_paths[D_LOGS_IDX] + MAIN_LOG; s_user_paths[F_MEM1DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM1_DUMP; s_user_paths[F_MEM2DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM2_DUMP; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 3cb65e093a..7a956e1200 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_CEMUHOOKUDPSERVERCONFIG_IDX, NUM_PATH_INDICES }; diff --git a/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp index 4422a096a5..813e898c20 100644 --- a/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp +++ b/Source/Core/Core/ConfigLoaders/BaseConfigLoader.cpp @@ -89,6 +89,7 @@ const std::map system_to_ini = { {Config::System::GFX, F_GFXCONFIG_IDX}, {Config::System::Logger, F_LOGGERCONFIG_IDX}, {Config::System::Debugger, F_DEBUGGERCONFIG_IDX}, + {Config::System::CemuHookUdpServer, F_CEMUHOOKUDPSERVERCONFIG_IDX}, }; // INI layer configuration loader diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 15711a65b8..7c3ac70b70 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::CemuHookUdpServer) + 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 @@ - + \ 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 @@ HW %28Flipper/Hollywood%29 + + HW %28Flipper/Hollywood%29\Wiimote\Emu + - + \ 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* 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* 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 m_sideways_setting; ControllerEmu::SettingValue m_upright_setting; @@ -284,5 +299,6 @@ private: RotationalState m_tilt_state; MotionState m_cursor_state; PositionalState m_shake_state; + std::optional m_imu_cursor_state; }; } // namespace WiimoteEmu diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 52b13c981d..b84119fad0 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -140,6 +140,8 @@ add_executable(dolphin-emu Config/Mapping/WiimoteEmuGeneral.h Config/Mapping/WiimoteEmuMotionControl.cpp Config/Mapping/WiimoteEmuMotionControl.h + Config/Mapping/WiimoteEmuMotionControlIMU.cpp + Config/Mapping/WiimoteEmuMotionControlIMU.h Config/NewPatchDialog.cpp Config/NewPatchDialog.h Config/PatchesWidget.cpp 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..f15408d64a --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.cpp @@ -0,0 +1,72 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h" + +#include +#include +#include +#include +#include +#include + +#include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteEmu/WiimoteEmu.h" + +#include "InputCommon/InputConfig.h" + +WiimoteEmuMotionControlIMU::WiimoteEmuMotionControlIMU(MappingWindow* window) + : MappingWidget(window) +{ + CreateMainLayout(); +} + +QGroupBox* WiimoteEmuMotionControlIMU::AddWarning(QGroupBox* groupbox) +{ + QFormLayout* layout = static_cast(groupbox->layout()); + QLabel* label; + + label = new QLabel(QLatin1String("")); + layout->addRow(label); + + label = new QLabel(tr("WARNING")); + label->setStyleSheet(QLatin1String("QLabel { color : red; }")); + layout->addRow(label); + + label = new QLabel( + tr("These controls are not intended for mapping regular buttons, triggers or axes.")); + label->setWordWrap(true); + layout->addRow(label); + + return groupbox; +} + +void WiimoteEmuMotionControlIMU::CreateMainLayout() +{ + m_main_layout = new QHBoxLayout(); + + m_main_layout->addWidget( + CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUPoint))); + m_main_layout->addWidget(AddWarning(CreateGroupBox( + Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUAccelerometer)))); + m_main_layout->addWidget(AddWarning( + CreateGroupBox(Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IMUGyroscope)))); + + setLayout(m_main_layout); +} + +void WiimoteEmuMotionControlIMU::LoadSettings() +{ + Wiimote::LoadConfig(); +} + +void WiimoteEmuMotionControlIMU::SaveSettings() +{ + Wiimote::GetConfig()->SaveConfig(); +} + +InputConfig* WiimoteEmuMotionControlIMU::GetConfig() +{ + return Wiimote::GetConfig(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h new file mode 100644 index 0000000000..0ebe152146 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuMotionControlIMU.h @@ -0,0 +1,32 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "DolphinQt/Config/Mapping/MappingWidget.h" + +class QCheckBox; +class QFormLayout; +class QGroupBox; +class QHBoxLayout; +class QLabel; +class QVBoxLayout; + +class WiimoteEmuMotionControlIMU final : public MappingWidget +{ + Q_OBJECT +public: + explicit WiimoteEmuMotionControlIMU(MappingWindow* window); + + InputConfig* GetConfig() override; + +private: + void LoadSettings() override; + void SaveSettings() override; + void CreateMainLayout(); + static QGroupBox* AddWarning(QGroupBox* groupbox); + + // Main + QHBoxLayout* m_main_layout; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 2deb34cffc..0d0aee2c7c 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -89,6 +89,7 @@ + @@ -283,6 +284,7 @@ + @@ -332,6 +334,7 @@ + diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index b47503a9ae..38f2e03d2f 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/CemuHookUDPServer/CemuHookUDPServer.cpp + ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h + ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.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(Translate, _trans("Left"))); + controls.emplace_back(std::make_unique(Translate, _trans("Right"))); + controls.emplace_back(std::make_unique(Translate, _trans("Forward"))); + controls.emplace_back(std::make_unique(Translate, _trans("Backward"))); + controls.emplace_back(std::make_unique(Translate, _trans("Up"))); + controls.emplace_back(std::make_unique(Translate, _trans("Down"))); +} + +std::optional 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 + +#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 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 + +#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(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 +#include + +#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 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(Translate, _trans("Pitch Up"))); + controls.emplace_back(std::make_unique(Translate, _trans("Pitch Down"))); + controls.emplace_back(std::make_unique(Translate, _trans("Roll Left"))); + controls.emplace_back(std::make_unique(Translate, _trans("Roll Right"))); + controls.emplace_back(std::make_unique(Translate, _trans("Yaw Left"))); + controls.emplace_back(std::make_unique(Translate, _trans("Yaw Right"))); +} + +std::optional 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 + +#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 GetState() const; +}; +} // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp new file mode 100644 index 0000000000..a480320b33 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.cpp @@ -0,0 +1,432 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h" + +#include +#include + +#include +#include + +#include "Common/Config/Config.h" +#include "Common/Flag.h" +#include "Common/Logging/Log.h" +#include "Common/MathUtil.h" +#include "Common/Matrix.h" +#include "Common/Random.h" +#include "Common/Thread.h" +#include "Core/CoreTiming.h" +#include "Core/HW/SystemTimers.h" +#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +namespace ciface::CemuHookUDPServer +{ +class Device : public Core::Device +{ +private: + template + class 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 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 + { + 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 + { + 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 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 SERVER_ENABLED{ + {Config::System::CemuHookUdpServer, "Server", "Enabled"}, false}; +const Config::ConfigInfo SERVER_ADDRESS{ + {Config::System::CemuHookUdpServer, "Server", "IPAddress"}, DEFAULT_SERVER_ADDRESS}; +const Config::ConfigInfo SERVER_PORT{{Config::System::CemuHookUdpServer, "Server", "Port"}, + DEFAULT_SERVER_PORT}; +} // namespace Settings + +static bool s_server_enabled; +static std::string s_server_address; +static u16 s_server_port; +static u32 s_client_uid; +static std::chrono::steady_clock::time_point s_next_listports; +static std::thread s_hotplug_thread; +static Common::Flag s_hotplug_thread_running; +static std::mutex s_port_info_mutex; +static Proto::MessageType::PortInfo s_port_info[Proto::PORT_COUNT]; + +static bool IsSameController(const Proto::MessageType::PortInfo& a, + const Proto::MessageType::PortInfo& b) +{ + // compare everything but battery_status + return a.pad_id == b.pad_id && a.pad_state == b.pad_state && a.model == b.model && + a.connection_type == b.connection_type && + memcmp(a.pad_mac_address, b.pad_mac_address, sizeof a.pad_mac_address) == 0; +} + +static sf::Socket::Status ReceiveWithTimeout(sf::UdpSocket& socket, void* data, std::size_t size, + std::size_t& received, sf::IpAddress& remoteAddress, + unsigned short& remotePort, sf::Time timeout) +{ + sf::SocketSelector selector; + selector.add(socket); + if (selector.wait(timeout)) + return socket.receive(data, size, received, remoteAddress, remotePort); + else + return sf::Socket::NotReady; +} + +static void HotplugThreadFunc() +{ + Common::SetCurrentThreadName("CemuHookUDPServer Hotplug Thread"); + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread started"); + + sf::UdpSocket socket; + + while (s_hotplug_thread_running.IsSet()) + { + const auto now = std::chrono::steady_clock::now(); + if (now >= s_next_listports) + { + s_next_listports = now + SERVER_LISTPORTS_INTERVAL; + + // Request info on the four controller ports + Proto::Message msg(s_client_uid); + auto& list_ports = msg.m_message; + list_ports.pad_request_count = 4; + list_ports.pad_id[0] = 0; + list_ports.pad_id[1] = 1; + list_ports.pad_id[2] = 2; + list_ports.pad_id[3] = 3; + msg.Finish(); + if (socket.send(&list_ports, sizeof list_ports, s_server_address, s_server_port) != + sf::Socket::Status::Done) + ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer HotplugThreadFunc send failed"); + } + + // Receive controller port info + Proto::Message 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(timeout).count(); + timeout_ms = std::max(timeout_ms, 1); + std::size_t received_bytes; + sf::IpAddress sender; + u16 port; + if (ReceiveWithTimeout(socket, &msg, sizeof(msg), received_bytes, sender, port, + sf::milliseconds(timeout_ms)) == sf::Socket::Status::Done) + { + if (auto port_info = msg.CheckAndCastTo()) + { + const bool port_changed = !IsSameController(*port_info, s_port_info[port_info->pad_id]); + { + std::lock_guard lock(s_port_info_mutex); + s_port_info[port_info->pad_id] = *port_info; + } + if (port_changed) + PopulateDevices(); + } + } + } + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer hotplug thread stopped"); +} + +static void StartHotplugThread() +{ + // Mark the thread as running. + if (!s_hotplug_thread_running.TestAndSet()) + { + // It was already running. + return; + } + + s_hotplug_thread = std::thread(HotplugThreadFunc); +} + +static void StopHotplugThread() +{ + // Tell the hotplug thread to stop. + if (!s_hotplug_thread_running.TestAndClear()) + { + // It wasn't running, we're done. + return; + } + + s_hotplug_thread.join(); +} + +void Init() +{ + s_server_enabled = Config::Get(Settings::SERVER_ENABLED); + s_server_address = Config::Get(Settings::SERVER_ADDRESS); + s_server_port = Config::Get(Settings::SERVER_PORT); + + s_client_uid = Common::Random::GenerateValue(); + s_next_listports = std::chrono::steady_clock::time_point::min(); + for (int port_index = 0; port_index < Proto::PORT_COUNT; port_index++) + { + s_port_info[port_index] = {}; + s_port_info[port_index].pad_id = port_index; + } + + if (s_server_enabled) + StartHotplugThread(); +} + +void PopulateDevices() +{ + NOTICE_LOG(SERIALINTERFACE, "CemuHookUDPServer PopulateDevices"); + + g_controller_interface.RemoveDevice( + [](const auto* dev) { return dev->GetSource() == "UDPServer"; }); + + std::lock_guard 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(port_info.model, port_index)); + } +} + +void DeInit() +{ + StopHotplugThread(); + SaveSettings(); +} + +void SaveSettings() +{ + Config::ConfigChangeCallbackGuard config_guard; + + Config::SetBaseOrCurrent(Settings::SERVER_ENABLED, s_server_enabled); + Config::SetBaseOrCurrent(Settings::SERVER_ADDRESS, s_server_address); + Config::SetBaseOrCurrent(Settings::SERVER_PORT, s_server_port); +} + +Device::Device(Proto::DsModel model, int index) + : m_model(model), m_index(index), + m_client_uid(Common::Random::GenerateValue()), 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("Pad W", m_pad_data.button_dpad_left_analog, 255)); + AddInput(new AnalogInput("Pad S", m_pad_data.button_dpad_down_analog, 255)); + AddInput(new AnalogInput("Pad E", m_pad_data.button_dpad_right_analog, 255)); + AddInput(new AnalogInput("Pad N", m_pad_data.button_dpad_up_analog, 255)); + AddInput(new AnalogInput("Square", m_pad_data.button_square_analog, 255)); + AddInput(new AnalogInput("Cross", m_pad_data.button_cross_analog, 255)); + AddInput(new AnalogInput("Circle", m_pad_data.button_circle_analog, 255)); + AddInput(new AnalogInput("Triangle", m_pad_data.button_triangle_analog, 255)); + AddInput(new AnalogInput("L1", m_pad_data.button_l1_analog, 255)); + AddInput(new AnalogInput("R1", m_pad_data.button_r1_analog, 255)); + + AddInput(new AnalogInput("L2", m_pad_data.trigger_l2, 255)); + AddInput(new AnalogInput("R2", m_pad_data.trigger_r2, 255)); + + AddInput(new Button("L3", m_pad_data.button_states1, 0x2)); + AddInput(new Button("R3", m_pad_data.button_states1, 0x4)); + AddInput(new Button("Share", m_pad_data.button_states1, 0x1)); + AddInput(new Button("Options", m_pad_data.button_states1, 0x8)); + AddInput(new Button("PS", m_pad_data.button_ps, 0x1)); + AddInput(new Button("Touch Button", m_pad_data.button_touch, 0x1)); + + AddInput(new AnalogInput("Left X-", m_pad_data.left_stick_x, -128, -128)); + AddInput(new AnalogInput("Left X+", m_pad_data.left_stick_x, 127, -128)); + AddInput(new AnalogInput("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128)); + AddInput(new AnalogInput("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128)); + AddInput(new AnalogInput("Right X-", m_pad_data.right_stick_x, -128, -128)); + AddInput(new AnalogInput("Right X+", m_pad_data.right_stick_x, 127, -128)); + AddInput(new AnalogInput("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128)); + AddInput(new AnalogInput("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128)); + + AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX)); + AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX)); + AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX)); + AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX)); + + AddInput(new AccelerometerInput("Accel Left", m_accel.x, 1)); + AddInput(new AccelerometerInput("Accel Right", m_accel.x, -1)); + AddInput(new AccelerometerInput("Accel Backward", m_accel.y, 1)); + AddInput(new AccelerometerInput("Accel Forward", m_accel.y, -1)); + AddInput(new AccelerometerInput("Accel Up", m_accel.z, 1)); + AddInput(new AccelerometerInput("Accel Down", m_accel.z, -1)); + + AddInput(new GyroInput("Gyro Pitch Up", m_gyro.x, -1)); + AddInput(new GyroInput("Gyro Pitch Down", m_gyro.x, 1)); + AddInput(new GyroInput("Gyro Roll Right", m_gyro.y, -1)); + AddInput(new GyroInput("Gyro Roll Left", m_gyro.y, 1)); + AddInput(new GyroInput("Gyro Yaw Right", m_gyro.z, -1)); + AddInput(new GyroInput("Gyro Yaw Left", m_gyro.z, 1)); +} + +std::string Device::GetName() const +{ + switch (m_model) + { + case Proto::DsModel::None: + return "None"; + case Proto::DsModel::DS3: + return "DualShock 3"; + case Proto::DsModel::DS4: + return "DualShock 4"; + case Proto::DsModel::Generic: + return "Generic Gamepad"; + default: + return "Device"; + } +} + +std::string Device::GetSource() const +{ + return "UDPServer"; +} + +void Device::UpdateInput() +{ + // Regularly tell the UDP server to feed us controller data + const auto now = std::chrono::steady_clock::now(); + if (now >= m_next_reregister) + { + m_next_reregister = now + SERVER_REREGISTER_INTERVAL; + + Proto::Message msg(m_client_uid); + auto& data_req = msg.m_message; + data_req.register_flags = Proto::RegisterFlags::PadID; + data_req.pad_id_to_register = m_index; + msg.Finish(); + if (m_socket.send(&data_req, sizeof(data_req), s_server_address, s_server_port) != + sf::Socket::Status::Done) + ERROR_LOG(SERIALINTERFACE, "CemuHookUDPServer UpdateInput send failed"); + } + + // Receive and handle controller data + Proto::Message 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()) + { + 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 Device::GetPreferredId() const +{ + return m_index; +} + +} // namespace ciface::CemuHookUDPServer diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h new file mode 100644 index 0000000000..98b2506d2e --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h @@ -0,0 +1,11 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +namespace ciface::CemuHookUDPServer +{ +void Init(); +void PopulateDevices(); +void DeInit(); +void SaveSettings(); +} // namespace ciface::CemuHookUDPServer diff --git a/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h new file mode 100644 index 0000000000..60c8059292 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServerProto.h @@ -0,0 +1,270 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include + +#include "Common/CommonTypes.h" + +namespace ciface::CemuHookUDPServer::Proto +{ +// CemuHook UDPServer protocol implementation using UdpServer.cs from +// https://github.com/Ryochan7/DS4Windows as documentation. +// +// WARNING: Little endian host assumed + +static constexpr u16 CEMUHOOK_PROTOCOL_VERSION = 1001; + +static constexpr int PORT_COUNT = 4; + +static constexpr char CLIENT[] = "DSUC"; +static constexpr char SERVER[] = "DSUS"; + +#pragma pack(push, 1) + +enum class DsState : u8 +{ + Disconnected = 0x00, + Reserved = 0x01, + Connected = 0x02 +}; + +enum class DsConnection : u8 +{ + None = 0x00, + Usb = 0x01, + Bluetooth = 0x02 +}; + +enum class DsModel : u8 +{ + None = 0, + DS3 = 1, + DS4 = 2, + Generic = 3 +}; + +enum class DsBattery : u8 +{ + None = 0x00, + Dying = 0x01, + Low = 0x02, + Medium = 0x03, + High = 0x04, + Full = 0x05, + Charging = 0xEE, + Charged = 0xEF +}; + +enum RegisterFlags : u8 +{ + AllPads = 0x00, + PadID = 0x01, + PadMACAdddress = 0x02 +}; + +struct MessageHeader +{ + u8 source[4]; + u16 protocol_version; + u16 message_length; // actually message size minus header size + u32 crc32; + u32 source_uid; +}; + +struct Touch +{ + u8 active; + u8 id; + s16 x; + s16 y; +}; + +namespace MessageType +{ +struct VersionRequest +{ + static constexpr auto FROM = CLIENT; + static constexpr auto TYPE = 0x100000U; + MessageHeader header; + u32 message_type; +}; + +struct VersionResponse +{ + static constexpr auto FROM = SERVER; + static constexpr auto TYPE = 0x100000U; + MessageHeader header; + u32 message_type; + u16 max_protocol_version; + u8 padding[2]; +}; + +struct ListPorts +{ + static constexpr auto FROM = CLIENT; + static constexpr auto TYPE = 0x100001U; + MessageHeader header; + u32 message_type; + u32 pad_request_count; + u8 pad_id[4]; +}; + +struct PortInfo +{ + static constexpr auto FROM = SERVER; + static constexpr auto TYPE = 0x100001U; + MessageHeader header; + u32 message_type; + u8 pad_id; + DsState pad_state; + DsModel model; + DsConnection connection_type; + u8 pad_mac_address[6]; + DsBattery battery_status; + u8 padding; +}; + +struct PadDataRequest +{ + static constexpr auto FROM = CLIENT; + static constexpr auto TYPE = 0x100002U; + MessageHeader header; + u32 message_type; + RegisterFlags register_flags; + u8 pad_id_to_register; + u8 mac_address_to_register[6]; +}; + +struct PadDataResponse +{ + static constexpr auto FROM = SERVER; + static constexpr auto TYPE = 0x100002U; + MessageHeader header; + u32 message_type; + u8 pad_id; + DsState pad_state; + DsModel model; + DsConnection connection_type; + u8 pad_mac_address[6]; + DsBattery battery_status; + u8 active; + u32 hid_packet_counter; + u8 button_states1; + u8 button_states2; + u8 button_ps; + u8 button_touch; + u8 left_stick_x; + u8 left_stick_y_inverted; + u8 right_stick_x; + u8 right_stick_y_inverted; + u8 button_dpad_left_analog; + u8 button_dpad_down_analog; + u8 button_dpad_right_analog; + u8 button_dpad_up_analog; + u8 button_square_analog; + u8 button_cross_analog; + u8 button_circle_analog; + u8 button_triangle_analog; + u8 button_r1_analog; + u8 button_l1_analog; + u8 trigger_r2; + u8 trigger_l2; + Touch touch1; + Touch touch2; + u64 timestamp_us; + float accelerometer_x_g; + float accelerometer_y_g; + float accelerometer_z_inverted_g; + float gyro_pitch_deg_s; + float gyro_yaw_deg_s; + float gyro_roll_deg_s; +}; + +struct FromServer +{ + union + { + struct + { + MessageHeader header; + u32 message_type; + }; + MessageType::VersionResponse version_response; + MessageType::PortInfo port_info; + MessageType::PadDataResponse pad_data_response; + }; +}; + +struct FromClient +{ + union + { + struct + { + MessageHeader header; + u32 message_type; + }; + MessageType::VersionRequest version_request; + MessageType::ListPorts list_ports; + MessageType::PadDataRequest pad_data_request; + }; +}; +} // namespace MessageType + +static inline u32 CRC32(const void* buffer, unsigned length) +{ + return crc32(crc32(0L, Z_NULL, 0), static_cast(buffer), length); +} + +template +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 + std::optional CheckAndCastTo() + { + u32 crc32_in_header = m_message.header.crc32; + // zero out the crc32 in the packet once we got it since that's whats needed for calculation + m_message.header.crc32 = 0; + u32 crc32_calculated = CRC32(&m_message, sizeof(ToMsgType)); + if (crc32_in_header != crc32_calculated) + { + NOTICE_LOG(SERIALINTERFACE, + "CemuHookUDPServer Received message with bad CRC in header: got %u, expected %u", + crc32_in_header, crc32_calculated); + return std::nullopt; + } + if (m_message.header.protocol_version > CEMUHOOK_PROTOCOL_VERSION) + return std::nullopt; + if (memcmp(m_message.header.source, ToMsgType::FROM, sizeof(m_message.header.source))) + return std::nullopt; + if (m_message.message_type != ToMsgType::TYPE) + return std::nullopt; + if (m_message.header.message_length + sizeof(m_message.header) > sizeof(ToMsgType)) + return std::nullopt; + + ToMsgType tomsg; + memcpy(&tomsg, &m_message, sizeof(tomsg)); + return tomsg; + } + + MsgType m_message; +}; + +#pragma pack(pop) +} // namespace ciface::CemuHookUDPServer::Proto diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index bb5ccb3f0e..5f20fd475b 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_CEMUHOOKUDPSERVER +#include "InputCommon/ControllerInterface/CemuHookUDPServer/CemuHookUDPServer.h" +#endif ControllerInterface g_controller_interface; @@ -67,6 +70,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) #endif #ifdef CIFACE_USE_PIPES // nothing needed +#endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER + ciface::CemuHookUDPServer::Init(); #endif RefreshDevices(); @@ -122,6 +128,9 @@ void ControllerInterface::RefreshDevices() #ifdef CIFACE_USE_PIPES ciface::Pipes::PopulateDevices(); #endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER + ciface::CemuHookUDPServer::PopulateDevices(); +#endif m_is_populating_devices = false; InvokeDevicesChangedCallbacks(); @@ -172,6 +181,9 @@ void ControllerInterface::Shutdown() #ifdef CIFACE_USE_EVDEV ciface::evdev::Shutdown(); #endif +#ifdef CIFACE_USE_CEMUHOOKUDPSERVER + ciface::CemuHookUDPServer::DeInit(); +#endif } void ControllerInterface::AddDevice(std::shared_ptr device) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 2ad53fc79b..f1a8e0c19e 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_CEMUHOOKUDPSERVER // // ControllerInterface diff --git a/Source/Core/InputCommon/InputCommon.vcxproj b/Source/Core/InputCommon/InputCommon.vcxproj index a22ad21394..e3d252cb5f 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj +++ b/Source/Core/InputCommon/InputCommon.vcxproj @@ -36,6 +36,9 @@ + + + @@ -53,6 +56,7 @@ + @@ -76,6 +80,9 @@ + + + @@ -93,6 +100,8 @@ + + @@ -122,4 +131,4 @@ - + \ No newline at end of file diff --git a/Source/Core/InputCommon/InputCommon.vcxproj.filters b/Source/Core/InputCommon/InputCommon.vcxproj.filters index c1f26d6141..ca29eec811 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj.filters +++ b/Source/Core/InputCommon/InputCommon.vcxproj.filters @@ -28,6 +28,9 @@ {07bad1aa-7e03-4f5c-ade2-a44857c5cbc3} + + {4f71c21c-85eb-4e76-ad64-40fd97bc3d6d} + @@ -120,6 +123,18 @@ ControllerEmu\ControlGroup + + ControllerEmu\ControlGroup + + + ControllerInterface\CemuHookUDPServer + + + ControllerEmu\ControlGroup + + + ControllerEmu\ControlGroup + @@ -216,8 +231,23 @@ ControllerEmu\ControlGroup + + ControllerEmu\ControlGroup + + + ControllerInterface\CemuHookUDPServer + + + ControllerEmu\ControlGroup + + + ControllerEmu\ControlGroup + + + ControllerInterface\CemuHookUDPServer + - + \ No newline at end of file