From 43746003678df348acdbee9aa866511ad9dd9bfc Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 19 Mar 2019 19:15:17 -0500 Subject: [PATCH] WiimoteEmu: Implement MotionPlus parameter y0 and other cleanups. --- Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp | 30 +- Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp | 359 +++++++++++------- Source/Core/Core/HW/WiimoteEmu/MotionPlus.h | 62 +-- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp | 32 +- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h | 3 + .../Config/Mapping/MappingIndicator.cpp | 4 +- .../Config/Mapping/WiimoteEmuGeneral.cpp | 2 +- .../ControllerEmu/ControlGroup/Cursor.cpp | 9 +- .../ControllerEmu/ControlGroup/Cursor.h | 2 + 9 files changed, 298 insertions(+), 205 deletions(-) diff --git a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp index 49e8cb2693..c109e2dcf6 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp @@ -131,8 +131,15 @@ WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& void EmulateCursor(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed) { - using Common::Matrix33; - using Common::Matrix44; + const auto cursor = ir_group->GetState(true); + + if (!cursor.IsVisible()) + { + // Move the wiimote a kilometer forward so the sensor bar is always behind it. + *state = {}; + state->position = {0, -1000, 0}; + return; + } // Nintendo recommends a distance of 1-3 meters. constexpr float NEUTRAL_DISTANCE = 2.f; @@ -147,21 +154,30 @@ void EmulateCursor(MotionState* state, ControllerEmu::Cursor* ir_group, float ti const float yaw_scale = ir_group->GetTotalYaw() / 2; const float pitch_scale = ir_group->GetTotalPitch() / 2; - const auto cursor = ir_group->GetState(true); - // TODO: Move state out of ControllerEmu::Cursor // TODO: Use ApproachPositionWithJerk // TODO: Move forward/backward after rotation. const auto new_position = - Common::Vec3{0, NEUTRAL_DISTANCE - MOVE_DISTANCE * float(cursor.z), height}; + Common::Vec3(0, NEUTRAL_DISTANCE - MOVE_DISTANCE * float(cursor.z), -height); + + const auto target_angle = Common::Vec3(pitch_scale * -cursor.y, 0, yaw_scale * -cursor.x); + + // If cursor was hidden, jump to the target position/angle immediately. + if (state->position.y < 0) + { + state->position = new_position; + state->angle = target_angle; + + return; + } + state->acceleration = new_position - state->position; state->position = new_position; // TODO: expose this setting in UI: constexpr auto MAX_ACCEL = float(MathUtil::TAU * 100); - ApproachAngleWithAccel(state, Common::Vec3(pitch_scale * -cursor.y, 0, yaw_scale * -cursor.x), - MAX_ACCEL, time_elapsed); + ApproachAngleWithAccel(state, target_angle, MAX_ACCEL, time_elapsed); } void ApproachAngleWithAccel(RotationalState* state, const Common::Vec3& angle_target, diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp index 0833f68c28..1afe31b2ad 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp @@ -4,6 +4,10 @@ #include "Core/HW/WiimoteEmu/MotionPlus.h" +#include +#include + +#include #include #include "Common/BitUtils.h" @@ -16,6 +20,39 @@ #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteEmu/Dynamics.h" +namespace +{ +// Minimal wrapper mainly to handle init/free +struct MPI : mbedtls_mpi +{ + explicit MPI(const char* base_10_str) : MPI() { mbedtls_mpi_read_string(this, 10, base_10_str); } + + MPI() { mbedtls_mpi_init(this); } + ~MPI() { mbedtls_mpi_free(this); } + + mbedtls_mpi* Data() { return this; }; + + template + bool ReadBinary(const u8 (&in_data)[N]) + { + return 0 == mbedtls_mpi_read_binary(this, std::begin(in_data), ArraySize(in_data)); + } + + template + bool WriteLittleEndianBinary(std::array* out_data) + { + if (mbedtls_mpi_write_binary(this, out_data->data(), out_data->size())) + return false; + + std::reverse(out_data->begin(), out_data->end()); + return true; + } + + MPI(const MPI&) = delete; + MPI& operator=(const MPI&) = delete; +}; +} // namespace + namespace WiimoteEmu { MotionPlus::MotionPlus() : Extension("MotionPlus") @@ -26,7 +63,7 @@ void MotionPlus::Reset() { m_reg_data = {}; - m_activation_progress = {}; + m_progress_timer = {}; // Seeing as we allow disconnection of the M+, we'll say we're not integrated. // (0x00 or 0x01) @@ -69,21 +106,23 @@ void MotionPlus::Reset() static_assert(sizeof(CalibrationData) == 0x20, "Bad size."); - static_assert(CALIBRATION_FAST_SCALE_DEGREES % 6 == 0, "Value aught to be divisible by 6."); - static_assert(CALIBRATION_SLOW_SCALE_DEGREES % 6 == 0, "Value aught to be divisible by 6."); + static_assert(CALIBRATION_FAST_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6."); + static_assert(CALIBRATION_SLOW_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6."); CalibrationData calibration; calibration.fast.degrees_div_6 = CALIBRATION_FAST_SCALE_DEGREES / 6; calibration.slow.degrees_div_6 = CALIBRATION_SLOW_SCALE_DEGREES / 6; // From what I can tell, this value is only used to compare against a previously made copy. - // I've copied the values from my Wiimote+ just in case it's something relevant. + // If the value matches that of the last connected wiimote which passed the "challenge", + // then it seems the "challenge" is not performed a second time. calibration.uid_1 = 0x0b; calibration.uid_2 = 0xe9; // Update checksum (crc32 of all data other than the checksum itself): - const auto crc_result = crc32(crc32(0, reinterpret_cast(&calibration), 0xe), - reinterpret_cast(&calibration) + 0x10, 0xe); + auto crc_result = crc32(0, Z_NULL, 0); + crc_result = crc32(crc_result, reinterpret_cast(&calibration), 0xe); + crc_result = crc32(crc_result, reinterpret_cast(&calibration) + 0x10, 0xe); calibration.crc32_lsb = u16(crc_result); calibration.crc32_msb = u16(crc_result >> 16); @@ -94,25 +133,21 @@ void MotionPlus::Reset() void MotionPlus::DoState(PointerWrap& p) { p.Do(m_reg_data); - p.Do(m_activation_progress); + p.Do(m_progress_timer); } MotionPlus::ActivationStatus MotionPlus::GetActivationStatus() const { - // M+ takes a bit of time to activate. During which it is completely unresponsive. - constexpr int ACTIVATION_MS = 20; - constexpr u8 ACTIVATION_STEPS = ::Wiimote::UPDATE_FREQ * ACTIVATION_MS / 1000; - if ((ACTIVE_DEVICE_ADDR << 1) == m_reg_data.ext_identifier[2]) { - if (m_activation_progress < ACTIVATION_STEPS) + if (ChallengeState::Activating == m_reg_data.challenge_state) return ActivationStatus::Activating; else return ActivationStatus::Active; } else { - if (m_activation_progress != 0) + if (m_progress_timer != 0) return ActivationStatus::Deactivating; else return ActivationStatus::Inactive; @@ -174,6 +209,8 @@ int MotionPlus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) return m_i2c_bus.BusWrite(slave_addr, addr, count, data_in); } + DEBUG_LOG(WIIMOTE, "Inactive M+ write 0x%x : %s", addr, ArrayToString(data_in, count).c_str()); + auto const result = RawWrite(&m_reg_data, addr, count, data_in); if (PASSTHROUGH_MODE_OFFSET == addr) @@ -193,30 +230,47 @@ int MotionPlus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) return 0; } + DEBUG_LOG(WIIMOTE, "Active M+ write 0x%x : %s", addr, ArrayToString(data_in, count).c_str()); + auto const result = RawWrite(&m_reg_data, addr, count, data_in); switch (addr) { - case offsetof(Register, initialized): + case offsetof(Register, init_trigger): // It seems a write of any value here triggers deactivation on a real M+. Deactivate(); // Passthrough the write to the attached extension. - // The M+ deactivation signal is cleverly the same as EXT activation. + // The M+ deactivation signal is cleverly the same as EXT initialization. m_i2c_bus.BusWrite(slave_addr, addr, count, data_in); break; case offsetof(Register, challenge_type): - if (CHALLENGE_X_READY == m_reg_data.challenge_progress) + if (ChallengeState::ParameterXReady == m_reg_data.challenge_state) { + DEBUG_LOG(WIIMOTE, "M+ challenge: 0x%x", m_reg_data.challenge_type); + + // After games read parameter x they write here to request y0 or y1. if (0 == m_reg_data.challenge_type) { - ERROR_LOG(WIIMOTE, "M+ parameter y0 is not yet supported! Deactivating."); - - Deactivate(); + // Preparing y0 on the real M+ is almost instant (30ms maybe). + constexpr int PREPARE_Y0_MS = 30; + m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_Y0_MS / 1000; + } + else + { + // A real M+ takes about 1200ms to prepare y1. + // Games seem to not care that we don't take that long. + constexpr int PREPARE_Y1_MS = 500; + m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_Y1_MS / 1000; } - m_reg_data.challenge_progress = CHALLENGE_PREPARE_Y; + // Games give the M+ a bit of time to compute the value. + // y0 gets about half a second. + // y1 gets at about 9.5 seconds. + // After this the M+ will fail the "challenge". + + m_reg_data.challenge_state = ChallengeState::PreparingY; } break; @@ -267,10 +321,16 @@ void MotionPlus::Activate() DEBUG_LOG(WIIMOTE, "M+ has been activated."); m_reg_data.ext_identifier[2] = ACTIVE_DEVICE_ADDR << 1; - m_reg_data.challenge_progress = CHALLENGE_START; - // We must do this to reset our extension_connected flag: + // We must do this to reset our extension_connected and is_mp_data flags: m_reg_data.controller_data = {}; + + m_reg_data.challenge_state = ChallengeState::Activating; + + // M+ takes a bit of time to activate. During which it is completely unresponsive. + // This also affects the device detect pin which results in wiimote status reports. + constexpr int ACTIVATION_MS = 20; + m_progress_timer = ::Wiimote::UPDATE_FREQ * ACTIVATION_MS / 1000; } void MotionPlus::Deactivate() @@ -278,7 +338,11 @@ void MotionPlus::Deactivate() DEBUG_LOG(WIIMOTE, "M+ has been deactivated."); m_reg_data.ext_identifier[2] = INACTIVE_DEVICE_ADDR << 1; - m_reg_data.challenge_progress = 0x0; + + // M+ takes a bit of time to deactivate. During which it is completely unresponsive. + // This also affects the device detect pin which results in wiimote status reports. + constexpr int DEACTIVATION_MS = 20; + m_progress_timer = ::Wiimote::UPDATE_FREQ * DEACTIVATION_MS / 1000; } bool MotionPlus::ReadDeviceDetectPin() const @@ -306,70 +370,134 @@ bool MotionPlus::IsButtonPressed() const void MotionPlus::Update() { - switch (GetActivationStatus()) + if (m_progress_timer) + --m_progress_timer; + + if (!m_progress_timer && ActivationStatus::Activating == GetActivationStatus()) { - case ActivationStatus::Activating: - ++m_activation_progress; - break; + // M+ is active now that the timer is up. + m_reg_data.challenge_state = ChallengeState::PreparingX; - case ActivationStatus::Deactivating: - --m_activation_progress; - break; + // Games give the M+ about a minute to prepare x before failure. + // A real M+ can take about 1500ms. + // The SDK seems to have a race condition that fails if a non-ready value is not read. + // A necessary delay preventing challenge failure is not inserted if x is immediately ready. + // So we must use at least a small delay. + // Note: This does not delay game start. The challenge takes place in the background. + constexpr int PREPARE_X_MS = 500; + m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_X_MS / 1000; + } - case ActivationStatus::Active: + if (ActivationStatus::Active != GetActivationStatus()) + return; + + u8* const data = m_reg_data.controller_data.data(); + DataFormat mplus_data = Common::BitCastPtr(data); + + const bool is_ext_connected = m_extension_port.IsDeviceConnected(); + + // Check for extension change: + if (is_ext_connected != mplus_data.extension_connected) { - u8* const data = m_reg_data.controller_data.data(); - DataFormat mplus_data = Common::BitCastPtr(data); - - const bool is_ext_connected = m_extension_port.IsDeviceConnected(); - - // Check for extension change: - if (is_ext_connected != mplus_data.extension_connected) + if (is_ext_connected) { - if (is_ext_connected) + DEBUG_LOG(WIIMOTE, "M+ initializing new extension."); + + // The M+ automatically initializes an extension when attached. + + // What we do here does not exactly match a real M+, + // but it's close enough for our emulated extensions which are not very picky. + + // Disable encryption { - DEBUG_LOG(WIIMOTE, "M+ initializing new extension."); - - // The M+ automatically initializes an extension when attached. - - // What we do here does not exactly match a real M+, - // but it's close enough for our emulated extensions which are not very picky. - - // Disable encryption - { - constexpr u8 INIT_OFFSET = offsetof(Register, initialized); - std::array enc_data = {0x55}; - m_i2c_bus.BusWrite(ACTIVE_DEVICE_ADDR, INIT_OFFSET, int(enc_data.size()), - enc_data.data()); - } - - // Read identifier - { - constexpr u8 ID_OFFSET = offsetof(Register, ext_identifier); - std::array id_data = {}; - m_i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, ID_OFFSET, int(id_data.size()), id_data.data()); - m_reg_data.passthrough_ext_id_0 = id_data[0]; - m_reg_data.passthrough_ext_id_4 = id_data[4]; - m_reg_data.passthrough_ext_id_5 = id_data[5]; - } - - // Read calibration data - { - constexpr u8 CAL_OFFSET = offsetof(Register, calibration_data); - m_i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, CAL_OFFSET, - int(m_reg_data.passthrough_ext_calib.size()), - m_reg_data.passthrough_ext_calib.data()); - } + constexpr u8 INIT_OFFSET = offsetof(Register, init_trigger); + std::array enc_data = {0x55}; + m_i2c_bus.BusWrite(ACTIVE_DEVICE_ADDR, INIT_OFFSET, int(enc_data.size()), enc_data.data()); } - // Update flag in register: - mplus_data.extension_connected = is_ext_connected; - Common::BitCastPtr(data) = mplus_data; + // Read identifier + { + constexpr u8 ID_OFFSET = offsetof(Register, ext_identifier); + std::array id_data = {}; + m_i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, ID_OFFSET, int(id_data.size()), id_data.data()); + m_reg_data.passthrough_ext_id_0 = id_data[0]; + m_reg_data.passthrough_ext_id_4 = id_data[4]; + m_reg_data.passthrough_ext_id_5 = id_data[5]; + } + + // Read calibration data + { + constexpr u8 CAL_OFFSET = offsetof(Register, calibration_data); + m_i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, CAL_OFFSET, + int(m_reg_data.passthrough_ext_calib.size()), + m_reg_data.passthrough_ext_calib.data()); + } } + // Update flag in register: + mplus_data.extension_connected = is_ext_connected; + Common::BitCastPtr(data) = mplus_data; + } + + // Only perform any of the following challenge logic if our timer is up. + if (m_progress_timer) + return; + + // This is potentially any value that is less than cert_n and >= 2. + // A real M+ uses random values each run. + constexpr u8 magic[] = "DOLPHIN DOES WHAT NINTENDON'T."; + + constexpr char cert_n[] = + "67614561104116375676885818084175632651294951727285593632649596941616763967271774525888270484" + "88546653264235848263182009106217734439508352645687684489830161"; + + constexpr char sqrt_v[] = + "22331959796794118515742337844101477131884013381589363004659408068948154670914705521646304758" + "02483462872732436570235909421331424649287229820640697259759264"; + + switch (m_reg_data.challenge_state) + { + case ChallengeState::PreparingX: + { + MPI param_x; + param_x.ReadBinary(magic); + + mbedtls_mpi_mul_mpi(¶m_x, ¶m_x, ¶m_x); + mbedtls_mpi_mod_mpi(¶m_x, ¶m_x, MPI(cert_n).Data()); + + // Big-int little endian parameter x. + param_x.WriteLittleEndianBinary(&m_reg_data.challenge_data); + + DEBUG_LOG(WIIMOTE, "M+ parameter x ready."); + m_reg_data.challenge_state = ChallengeState::ParameterXReady; break; } + case ChallengeState::PreparingY: + if (0 == m_reg_data.challenge_type) + { + MPI param_y0; + param_y0.ReadBinary(magic); + + // Big-int little endian parameter y0. + param_y0.WriteLittleEndianBinary(&m_reg_data.challenge_data); + } + else + { + MPI param_y1; + param_y1.ReadBinary(magic); + + mbedtls_mpi_mul_mpi(¶m_y1, ¶m_y1, MPI(sqrt_v).Data()); + mbedtls_mpi_mod_mpi(¶m_y1, ¶m_y1, MPI(cert_n).Data()); + + // Big-int little endian parameter y1. + param_y1.WriteLittleEndianBinary(&m_reg_data.challenge_data); + } + + DEBUG_LOG(WIIMOTE, "M+ parameter y ready."); + m_reg_data.challenge_state = ChallengeState::ParameterYReady; + break; + default: break; } @@ -384,86 +512,31 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity) u8* const data = m_reg_data.controller_data.data(); - // Try to alternate between M+ and EXT data: - // This flag is checked down below where the controller data is prepared. + // FYI: A real M+ seems to always send some garbage/mystery data for the first report, + // followed by a normal M+ data report, and then finally passhrough data (if enabled). + // Things seem to work without doing that so we'll just send normal M+ data right away. DataFormat mplus_data = Common::BitCastPtr(data); - mplus_data.is_mp_data ^= true; // Maintain the current state of this bit rather than reading from the port. // We update this bit elsewhere and performs some tasks on change. const bool is_ext_connected = mplus_data.extension_connected; - switch (m_reg_data.challenge_progress) - { - case CHALLENGE_START: - // Activation starts the challenge_progress. - // Harness this to force non-passthrough data for the first report. - mplus_data.is_mp_data = true; - - // Note: A real M+ seems to always send some garbage/mystery data for the first report. - // Things seem to work without doing that so we'll just send normal data. - - m_reg_data.challenge_progress = CHALLENGE_PREPARE_X; - break; - - case CHALLENGE_PREPARE_X: - // Force another report of M+ data. - // The second data report is regular M+ data, even if a passthrough mode is set. - mplus_data.is_mp_data = true; - - // Big-int little endian parameter x. - m_reg_data.challenge_data = { - 0x99, 0x1a, 0x07, 0x1b, 0x97, 0xf1, 0x11, 0x78, 0x0c, 0x42, 0x2b, 0x68, 0xdf, - 0x44, 0x38, 0x0d, 0x2b, 0x7e, 0xd6, 0x84, 0x84, 0x58, 0x65, 0xc9, 0xf2, 0x95, - 0xd9, 0xaf, 0xb6, 0xc4, 0x87, 0xd5, 0x18, 0xdb, 0x67, 0x3a, 0xc0, 0x71, 0xec, - 0x3e, 0xf4, 0xe6, 0x7e, 0x35, 0xa3, 0x29, 0xf8, 0x1f, 0xc5, 0x7c, 0x3d, 0xb9, - 0x56, 0x22, 0x95, 0x98, 0x8f, 0xfb, 0x66, 0x3e, 0x9a, 0xdd, 0xeb, 0x7e, - }; - - m_reg_data.challenge_progress = CHALLENGE_X_READY; - break; - - case CHALLENGE_PREPARE_Y: - if (0 == m_reg_data.challenge_type) - { - // TODO: Prepare y0. - } - else - { - // Big-int little endian parameter y1. - m_reg_data.challenge_data = { - 0xa5, 0x84, 0x1f, 0xd6, 0xbd, 0xdc, 0x7a, 0x4c, 0xf3, 0xc0, 0x24, 0xe0, 0x92, - 0xef, 0x19, 0x28, 0x65, 0xe0, 0x62, 0x7c, 0x9b, 0x41, 0x6f, 0x12, 0xc3, 0xac, - 0x78, 0xe4, 0xfc, 0x6b, 0x7b, 0x0a, 0xb4, 0x50, 0xd6, 0xf2, 0x45, 0xf7, 0x93, - 0x04, 0xaf, 0xf2, 0xb7, 0x26, 0x94, 0xee, 0xad, 0x92, 0x05, 0x6d, 0xe5, 0xc6, - 0xd6, 0x36, 0xdc, 0xa5, 0x69, 0x0f, 0xc8, 0x99, 0xf2, 0x1c, 0x4e, 0x0d, - }; - } - - // Note. A real M+ takes about 1200ms to reach this state (for y1) - // (y0 is almost instant) - m_reg_data.challenge_progress = CHALLENGE_Y_READY; - break; - - default: - break; - } - - // After the first two data reports it alternates between EXT and M+ data. + // After the first "garbage" report a real M+ alternates between M+ and EXT data. // Failure to read from the extension results in a fallback to M+ data. - - // Real M+ only ever reads 6 bytes from the extension which is triggered by a read at 0x00. - // Data after 6 bytes seems to be zero-filled. - // After reading from the EXT, the real M+ uses that data for the next frame. - // But we are going to use it for the current frame, because we can. - constexpr int EXT_AMT = 6; - // Always read from 0x52 @ 0x00: - constexpr u8 EXT_SLAVE = ExtensionPort::REPORT_I2C_SLAVE; - constexpr u8 EXT_ADDR = ExtensionPort::REPORT_I2C_ADDR; + mplus_data.is_mp_data ^= true; // If the last frame had M+ data try to send some non-M+ data: if (!mplus_data.is_mp_data) { + // Real M+ only ever reads 6 bytes from the extension which is triggered by a read at 0x00. + // Data after 6 bytes seems to be zero-filled. + // After reading from the EXT, the real M+ uses that data for the next frame. + // But we are going to use it for the current frame, because we can. + constexpr int EXT_AMT = 6; + // Always read from 0x52 @ 0x00: + constexpr u8 EXT_SLAVE = ExtensionPort::REPORT_I2C_SLAVE; + constexpr u8 EXT_ADDR = ExtensionPort::REPORT_I2C_ADDR; + switch (GetPassthroughMode()) { case PassthroughMode::Disabled: diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h index df24f2a234..07869957f7 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h @@ -30,6 +30,33 @@ public: void PrepareInput(const Common::Vec3& angular_velocity); private: + enum class ChallengeState : u8 + { + // Note: This is not a value seen on a real M+. + // Used to emulate activation state during which the M+ is not responsive. + Activating = 0x00, + + PreparingX = 0x02, + ParameterXReady = 0x0e, + PreparingY = 0x14, + ParameterYReady = 0x1a, + }; + + enum class PassthroughMode : u8 + { + Disabled = 0x04, + Nunchuk = 0x05, + Classic = 0x07, + }; + + enum class ActivationStatus + { + Inactive, + Activating, + Deactivating, + Active, + }; + #pragma pack(push, 1) struct DataFormat { @@ -71,7 +98,9 @@ private: u8 unknown_0x90[0x60]; // address 0xF0 - u8 initialized; + // Writes initialize the M+ to it's default (non-activated) state. + // Used to deactivate the M+ and activate an attached extension. + u8 init_trigger; // address 0xF1 // Value is either 0 or 1. @@ -90,12 +119,12 @@ private: // address 0xF7 // Games read this value to know when the data at 0x50 is ready. - // Value is 0x02 upon activation. - // Real M+ changes this value from 0x4, 0x8, 0xc, and finally 0xe. + // Value is 0x02 upon activation. (via a write to 0xfe) + // Real M+ changes this value to 0x4, 0x8, 0xc, and finally 0xe. // Games then trigger a 2nd stage via a write to 0xf1. // Real M+ changes this value to 0x14, 0x18, and finally 0x1a. // Note: We don't progress like this. We jump to the final value as soon as possible. - u8 challenge_progress; + ChallengeState challenge_state; // address 0xF8 // Values are taken from the extension on the passthrough port. @@ -116,34 +145,12 @@ private: static constexpr int CALIBRATION_BITS = 16; - // static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1); static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1); // Values are similar to that of a typical real M+. static constexpr u16 CALIBRATION_SCALE_OFFSET = 0x4400; static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0; static constexpr u16 CALIBRATION_SLOW_SCALE_DEGREES = 0x10e; - static constexpr u8 CHALLENGE_START = 0x02; - static constexpr u8 CHALLENGE_PREPARE_X = 0x04; - static constexpr u8 CHALLENGE_PREPARE_Y = 0x18; - static constexpr u8 CHALLENGE_X_READY = 0x0e; - static constexpr u8 CHALLENGE_Y_READY = 0x1a; - - enum class PassthroughMode : u8 - { - Disabled = 0x04, - Nunchuk = 0x05, - Classic = 0x07, - }; - - enum class ActivationStatus - { - Inactive, - Activating, - Deactivating, - Active, - }; - void Activate(); void Deactivate(); void OnPassthroughModeWrite(); @@ -159,7 +166,8 @@ private: Register m_reg_data = {}; - u8 m_activation_progress = {}; + // Used for timing of activation, deactivation, and preparation of challenge values. + u8 m_progress_timer = {}; // The port on the end of the motion plus: I2CBus m_i2c_bus; diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index 3fdf239dda..096247e8e6 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -688,21 +688,10 @@ void Wiimote::StepDynamics() Common::Vec3 Wiimote::GetAcceleration() { - // Includes effects of: - // IR, Tilt, Swing, Orientation, Shake - - auto orientation = Common::Matrix33::Identity(); - - if (IsSideways()) - orientation *= Common::Matrix33::RotateZ(float(MathUtil::TAU / -4)); - - if (IsUpright()) - orientation *= Common::Matrix33::RotateX(float(MathUtil::TAU / 4)); - // TODO: Cursor movement should produce acceleration. Common::Vec3 accel = - orientation * + GetOrientation() * GetTransformation().Transform( m_swing_state.acceleration + Common::Vec3(0, 0, float(GRAVITY_ACCELERATION)), 0); @@ -713,17 +702,8 @@ Common::Vec3 Wiimote::GetAcceleration() Common::Vec3 Wiimote::GetAngularVelocity() { - // TODO: make cursor movement produce angular velocity. - - auto orientation = Common::Matrix33::Identity(); - - // TODO: make a function out of this: - if (IsSideways()) - orientation *= Common::Matrix33::RotateZ(float(MathUtil::TAU / -4)); - if (IsUpright()) - orientation *= Common::Matrix33::RotateX(float(MathUtil::TAU / 4)); - - return orientation * (m_tilt_state.angular_velocity + m_swing_state.angular_velocity); + return GetOrientation() * (m_tilt_state.angular_velocity + m_swing_state.angular_velocity + + m_cursor_state.angular_velocity); } Common::Matrix44 Wiimote::GetTransformation() const @@ -738,4 +718,10 @@ Common::Matrix44 Wiimote::GetTransformation() const Common::Matrix44::Translate(-m_swing_state.position - m_cursor_state.position); } +Common::Matrix33 Wiimote::GetOrientation() const +{ + return Common::Matrix33::RotateZ(float(MathUtil::TAU / -4 * IsSideways())) * + Common::Matrix33::RotateX(float(MathUtil::TAU / 4 * IsUpright())); +} + } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index 4ba86a7217..9db4e9aae2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -148,6 +148,9 @@ private: // Does not include orientation transformations. Common::Matrix44 GetTransformation() const; + // Returns the world rotation from the effects of sideways/upright settings. + Common::Matrix33 GetOrientation() const; + void HIDOutputReport(const void* data, u32 size); void HandleReportRumble(const WiimoteCommon::OutputReportRumble&); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index ca6bab91ba..e69b93c9d1 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -217,7 +217,7 @@ void MappingIndicator::DrawCursor(ControllerEmu::Cursor& cursor) QRectF(-scale, raw_coord.z * scale - INPUT_DOT_RADIUS / 2, scale * 2, INPUT_DOT_RADIUS)); // Adjusted Z (if not hidden): - if (adj_coord.z && adj_coord.x < 10000) + if (adj_coord.IsVisible()) { p.setBrush(GetAdjustedInputColor()); p.drawRect( @@ -250,7 +250,7 @@ void MappingIndicator::DrawCursor(ControllerEmu::Cursor& cursor) p.drawEllipse(QPointF{raw_coord.x, raw_coord.y} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS); // Adjusted cursor position (if not hidden): - if (adj_coord.x < 10000) + if (adj_coord.IsVisible()) { p.setPen(Qt::NoPen); p.setBrush(GetAdjustedInputColor()); diff --git a/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.cpp b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.cpp index 0c3ffe5deb..29ba6f19ef 100644 --- a/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.cpp @@ -55,7 +55,7 @@ void WiimoteEmuGeneral::CreateMainLayout() extension->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - static_cast(extension->layout())->addRow(m_extension_combo); + static_cast(extension->layout())->insertRow(0, m_extension_combo); layout->addWidget(extension, 0, 3); layout->addWidget(CreateGroupBox(tr("Rumble"), Wiimote::GetWiimoteGroup( diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp index 5818f35d87..dd5bc87887 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -153,8 +154,7 @@ Cursor::StateData Cursor::GetState(const bool adjusted) // If auto-hide time is up or hide button is held: if (!m_auto_hide_timer || controls[6]->control_ref->State() > BUTTON_THRESHOLD) { - // TODO: Use NaN or something: - result.x = 10000; + result.x = std::numeric_limits::quiet_NaN(); result.y = 0; } @@ -176,4 +176,9 @@ ControlState Cursor::GetVerticalOffset() const return m_vertical_offset_setting.GetValue() / 100; } +bool Cursor::StateData::IsVisible() const +{ + return !std::isnan(x); +} + } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h index 7def851cc4..4b0d13eb6c 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Cursor.h @@ -20,6 +20,8 @@ public: ControlState x{}; ControlState y{}; ControlState z{}; + + bool IsVisible() const; }; explicit Cursor(const std::string& name);