diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index fe7b849782..20ffacfa62 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -279,6 +279,8 @@ add_library(core HW/WiimoteCommon/WiimoteReport.h HW/WiimoteEmu/Camera.cpp HW/WiimoteEmu/Camera.h + HW/WiimoteEmu/DesiredWiimoteState.cpp + HW/WiimoteEmu/DesiredWiimoteState.h HW/WiimoteEmu/Dynamics.cpp HW/WiimoteEmu/Dynamics.h HW/WiimoteEmu/EmuSubroutines.cpp @@ -286,6 +288,7 @@ add_library(core HW/WiimoteEmu/Encryption.h HW/WiimoteEmu/Extension/Classic.cpp HW/WiimoteEmu/Extension/Classic.h + HW/WiimoteEmu/Extension/DesiredExtensionState.h HW/WiimoteEmu/Extension/DrawsomeTablet.cpp HW/WiimoteEmu/Extension/DrawsomeTablet.h HW/WiimoteEmu/Extension/Drums.cpp diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 913c9956e9..8a02534c57 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -498,9 +498,6 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi if (core_parameter.bWii && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED)) { Wiimote::LoadConfig(); - - if (NetPlay::IsNetPlayRunning()) - NetPlay::SetupWiimotes(); } FreeLook::LoadInputConfig(); diff --git a/Source/Core/Core/HW/Wiimote.h b/Source/Core/Core/HW/Wiimote.h index a15671cc93..1acdc3a184 100644 --- a/Source/Core/Core/HW/Wiimote.h +++ b/Source/Core/Core/HW/Wiimote.h @@ -94,8 +94,6 @@ ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(int number, WiimoteEmu::DrawsomeTabletGroup group); ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGroup group); ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group); - -bool NetPlay_GetButtonPress(int wiimote, bool pressed); } // namespace Wiimote namespace WiimoteReal diff --git a/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h b/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h index bfa947a451..0b3b5d37d6 100644 --- a/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h +++ b/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h @@ -7,6 +7,11 @@ #include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteReport.h" +namespace WiimoteEmu +{ +struct DesiredWiimoteState; +} + namespace WiimoteCommon { // Source: HID_010_SPC_PFL/1.0 (official HID specification) @@ -30,8 +35,12 @@ public: virtual void EventLinked() = 0; virtual void EventUnlinked() = 0; + virtual u8 GetWiimoteDeviceIndex() const = 0; + virtual void SetWiimoteDeviceIndex(u8 index) = 0; + // Called every ~200hz after HID channels are established. - virtual void Update() = 0; + virtual void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) = 0; + virtual void Update(const WiimoteEmu::DesiredWiimoteState& target_state) = 0; void SetInterruptCallback(InterruptCallbackType callback) { m_callback = std::move(callback); } @@ -39,8 +48,10 @@ public: // Does not include HID-type header. virtual void InterruptDataOutput(const u8* data, u32 size) = 0; - // Used to connect a disconnected wii remote on button press. - virtual bool IsButtonPressed() = 0; + // Get a snapshot of the current state of the Wiimote's buttons. + // Note that only the button bits of the return value are meaningful, the rest should be ignored. + // This is used to query a disconnected Wiimote whether it wants to reconnect. + virtual ButtonData GetCurrentlyPressedButtons() = 0; protected: void InterruptDataInputCallback(const u8* data, u32 size) diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp index f2a9885fc7..854c9e8577 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp @@ -52,36 +52,14 @@ int CameraLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) return RawWrite(&m_reg_data, addr, count, data_in); } -void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_of_view) +std::array +CameraLogic::GetCameraPoints(const Common::Matrix44& transform, Common::Vec2 field_of_view) { - // IR data is read from offset 0x37 on real hardware. - auto& data = m_reg_data.camera_data; - data.fill(0xff); - - constexpr u8 OBJECT_TRACKING_ENABLE = 0x08; - - // If Address 0x30 is not 0x08 the camera will return 0xFFs. - // The Wii seems to write 0x01 here before changing modes/sensitivities. - if (m_reg_data.enable_object_tracking != OBJECT_TRACKING_ENABLE) - return; - - // If the sensor bar is off the camera will see no LEDs and return 0xFFs. - if (!IOS::g_gpio_out[IOS::GPIO::SENSOR_BAR]) - return; - using Common::Matrix33; using Common::Matrix44; using Common::Vec3; using Common::Vec4; - // FYI: A real wiimote normally only returns 1 point for each LED cluster (2 total). - // Sending all 4 points can actually cause some stuttering issues. - constexpr int NUM_POINTS = 2; - - // Range from 0-15. Small values (2-4) seem to be very typical. - // This is reduced based on distance from sensor bar. - constexpr int MAX_POINT_SIZE = 15; - const std::array leds{ Vec3{-SENSOR_BAR_LED_SEPARATION / 2, 0, 0}, Vec3{SENSOR_BAR_LED_SEPARATION / 2, 0, 0}, @@ -91,12 +69,6 @@ void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_o Matrix44::Perspective(field_of_view.y, field_of_view.x / field_of_view.y, 0.001f, 1000) * Matrix44::FromMatrix33(Matrix33::RotateX(float(MathUtil::TAU / 4))) * transform; - struct CameraPoint - { - IRBasic::IRObject position; - u8 size = 0; - }; - std::array camera_points; std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) { @@ -112,13 +84,32 @@ void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_o const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2); if (x >= 0 && y >= 0 && x < CAMERA_RES_X && y < CAMERA_RES_Y) - return CameraPoint{{u16(x), u16(y)}, u8(point_size)}; + return CameraPoint({u16(x), u16(y)}, u8(point_size)); } - // 0xFFFFs are interpreted as "not visible". - return CameraPoint{{0xffff, 0xffff}, 0xff}; + return CameraPoint(); }); + return camera_points; +} + +void CameraLogic::Update(const std::array& camera_points) +{ + // IR data is read from offset 0x37 on real hardware. + auto& data = m_reg_data.camera_data; + data.fill(0xff); + + constexpr u8 OBJECT_TRACKING_ENABLE = 0x08; + + // If Address 0x30 is not 0x08 the camera will return 0xFFs. + // The Wii seems to write 0x01 here before changing modes/sensitivities. + if (m_reg_data.enable_object_tracking != OBJECT_TRACKING_ENABLE) + return; + + // If the sensor bar is off the camera will see no LEDs and return 0xFFs. + if (!IOS::g_gpio_out[IOS::GPIO::SENSOR_BAR]) + return; + switch (m_reg_data.mode) { case IR_MODE_BASIC: diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.h b/Source/Core/Core/HW/WiimoteEmu/Camera.h index 5c2a186ced..693b07e515 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.h +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.h @@ -16,11 +16,26 @@ class Matrix44; namespace WiimoteEmu { +using IRObject = Common::TVec2; + +struct CameraPoint +{ + IRObject position; + u8 size; + + // 0xFFFFs are interpreted as "not visible". + constexpr CameraPoint() : position({0xffff, 0xffff}), size(0xff) {} + constexpr CameraPoint(IRObject position_, u8 size_) : position(position_), size(size_) {} + constexpr bool operator==(const CameraPoint& other) const + { + return this->position == other.position && this->size == other.size; + } + constexpr bool operator!=(const CameraPoint& other) const { return !(*this == other); } +}; + // Four bytes for two objects. Filled with 0xFF if empty struct IRBasic { - using IRObject = Common::TVec2; - u8 x1; u8 y1; u8 x2hi : 2; @@ -59,8 +74,8 @@ struct IRExtended u8 xhi : 2; u8 yhi : 2; - auto GetPosition() const { return IRBasic::IRObject(xhi << 8 | x, yhi << 8 | y); } - void SetPosition(const IRBasic::IRObject& obj) + auto GetPosition() const { return IRObject(xhi << 8 | x, yhi << 8 | y); } + void SetPosition(const IRObject& obj) { x = obj.x; xhi = obj.x >> 8; @@ -109,9 +124,19 @@ public: IR_MODE_FULL = 5, }; + // FYI: A real wiimote normally only returns 1 point for each LED cluster (2 total). + // Sending all 4 points can actually cause some stuttering issues. + static constexpr int NUM_POINTS = 2; + + // Range from 0-15. Small values (2-4) seem to be very typical. + // This is reduced based on distance from sensor bar. + static constexpr int MAX_POINT_SIZE = 15; + void Reset(); void DoState(PointerWrap& p); - void Update(const Common::Matrix44& transform, Common::Vec2 field_of_view); + static std::array GetCameraPoints(const Common::Matrix44& transform, + Common::Vec2 field_of_view); + void Update(const std::array& camera_points); void SetEnabled(bool is_enabled); static constexpr u8 I2C_ADDR = 0x58; diff --git a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp new file mode 100644 index 0000000000..d99efefdee --- /dev/null +++ b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp @@ -0,0 +1,328 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/BitUtils.h" +#include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" +#include "Core/HW/WiimoteEmu/Extension/Classic.h" +#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h" +#include "Core/HW/WiimoteEmu/Extension/Drums.h" +#include "Core/HW/WiimoteEmu/Extension/Guitar.h" +#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h" +#include "Core/HW/WiimoteEmu/Extension/Shinkansen.h" +#include "Core/HW/WiimoteEmu/Extension/TaTaCon.h" +#include "Core/HW/WiimoteEmu/Extension/Turntable.h" +#include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h" +#include "Core/HW/WiimoteEmu/MotionPlus.h" + +namespace WiimoteEmu +{ +SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state) +{ + const u8 has_buttons = (state.buttons.hex & WiimoteCommon::ButtonData::BUTTON_MASK) != 0 ? 1 : 0; + const u8 has_accel = state.acceleration != DesiredWiimoteState::DEFAULT_ACCELERATION ? 1 : 0; + const u8 has_camera = state.camera_points != DesiredWiimoteState::DEFAULT_CAMERA ? 1 : 0; + const u8 has_motion_plus = state.motion_plus.has_value() ? 1 : 0; + + // Right now we support < 16 extensions so the info which extension is in use fits into 4 bits. + // This allows 'empty' packets to be a single byte, which is very nice for reducing bandwidth. + // If we ever support 16 or more we have to redesign this a bit; ideally use a variable-length + // encoding so that typical extensions (None, Nunchuk, Classic Controller) still fit into the + // initial 4 bits. + static_assert(std::variant_size_v <= (1 << 4)); + const u8 extension = u8(state.extension.data.index()); + + SerializedWiimoteState s; + s.length = 0; + s.data[s.length++] = u8(has_buttons | (has_accel << 1) | (has_camera << 2) | + (has_motion_plus << 3) | (extension << 4)); + + if (has_buttons) + { + const u8 buttons = u8((state.buttons.a) | (state.buttons.b << 1) | (state.buttons.plus << 2) | + (state.buttons.minus << 3) | (state.buttons.one << 4) | + (state.buttons.two << 5) | (state.buttons.home << 6)); + const u8 dpad = u8((state.buttons.up) | (state.buttons.down << 1) | (state.buttons.left << 2) | + (state.buttons.right << 3)); + s.data[s.length++] = buttons; + s.data[s.length++] = dpad; + } + + if (has_accel) + { + const u16 accel_x = state.acceleration.value.x; // 10 bits + const u16 accel_y = state.acceleration.value.y; // 9 bits (ignore lowest bit) + const u16 accel_z = state.acceleration.value.z; // 9 bits (ignore lowest bit) + const u8 accel_x_high = u8(accel_x >> 2); + const u8 accel_y_high = u8(accel_y >> 2); + const u8 accel_z_high = u8(accel_z >> 2); + const u8 accel_low = u8((accel_x & 0b11) | (Common::ExtractBit<1>(accel_y) << 2) | + (Common::ExtractBit<1>(accel_z) << 3)); + + if (has_buttons) + { + // can use the high bits of the dpad field from buttons + s.data[s.length - 1] |= u8(accel_low << 4); + } + else + { + s.data[s.length++] = u8(accel_low << 4); + } + + s.data[s.length++] = accel_x_high; + s.data[s.length++] = accel_y_high; + s.data[s.length++] = accel_z_high; + } + + if (has_camera) + { + for (size_t i = 0; i < 2; ++i) + { + const u16 camera_x = state.camera_points[i].position.x; // 10 bits + const u16 camera_y = state.camera_points[i].position.y; // 10 bits + const u8 camera_size = state.camera_points[i].size; // 4 bits + s.data[s.length++] = u8((camera_x & 0b11) | ((camera_y & 0b11) << 2) | (camera_size << 4)); + s.data[s.length++] = u8(camera_x >> 2); + s.data[s.length++] = u8(camera_y >> 2); + } + } + + if (has_motion_plus) + { + const u16 pitch_slow = state.motion_plus->is_slow.x ? 1 : 0; + const u16 roll_slow = state.motion_plus->is_slow.y ? 1 : 0; + const u16 yaw_slow = state.motion_plus->is_slow.z ? 1 : 0; + const u16 pitch_value = state.motion_plus->gyro.value.x; // 14 bits + const u16 roll_value = state.motion_plus->gyro.value.y; // 14 bits + const u16 yaw_value = state.motion_plus->gyro.value.z; // 14 bits + s.data[s.length++] = u8(pitch_value); + s.data[s.length++] = u8(((pitch_value >> 8) & 0x3f) | (pitch_slow << 7)); + s.data[s.length++] = u8(roll_value); + s.data[s.length++] = u8(((roll_value >> 8) & 0x3f) | (roll_slow << 7)); + s.data[s.length++] = u8(yaw_value); + s.data[s.length++] = u8(((yaw_value >> 8) & 0x3f) | (yaw_slow << 7)); + } + + if (extension) + { + std::visit( + [&s](const auto& arg) { + using T = std::decay_t; + if constexpr (!std::is_same_v) + { + static_assert(sizeof(arg) <= 6); + static_assert(std::is_trivially_copyable_v); + std::memcpy(&s.data[s.length], &arg, sizeof(arg)); + s.length += sizeof(arg); + } + }, + state.extension.data); + } + + return s; +} + +template +static bool DeserializeExtensionState(DesiredWiimoteState* state, + const SerializedWiimoteState& serialized, size_t offset) +{ + if (serialized.length < offset + sizeof(T)) + return false; + auto& e = state->extension.data.emplace(); + static_assert(std::is_trivially_copyable_v); + std::memcpy(&e, &serialized.data[offset], sizeof(T)); + return true; +} + +bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimoteState& serialized) +{ + // clear state + state->buttons.hex = 0; + state->acceleration = DesiredWiimoteState::DEFAULT_ACCELERATION; + state->camera_points = DesiredWiimoteState::DEFAULT_CAMERA; + state->motion_plus = std::nullopt; + state->extension.data = std::monostate(); + + if (serialized.length < 1) + { + // can't be valid + return false; + } + + const auto& d = serialized.data; + const u8 has_buttons = d[0] & 1; + const u8 has_accel = (d[0] >> 1) & 1; + const u8 has_camera = (d[0] >> 2) & 1; + const u8 has_motion_plus = (d[0] >> 3) & 1; + const u8 extension = (d[0] >> 4); + + if (extension >= ExtensionNumber::MAX) + { + // invalid extension + return false; + } + + const size_t expected_size = [&]() { + size_t s = 1; + if (has_buttons && has_accel) + s += 5; + else if (has_buttons) + s += 2; + else if (has_accel) + s += 4; + if (has_camera) + s += 6; + if (has_motion_plus) + s += 6; + switch (extension) + { + case ExtensionNumber::NONE: + break; + case ExtensionNumber::NUNCHUK: + s += sizeof(Nunchuk::DataFormat); + break; + case ExtensionNumber::CLASSIC: + s += sizeof(Classic::DataFormat); + break; + case ExtensionNumber::GUITAR: + s += sizeof(Guitar::DataFormat); + break; + case ExtensionNumber::DRUMS: + s += sizeof(Drums::DesiredState); + break; + case ExtensionNumber::TURNTABLE: + s += sizeof(Turntable::DataFormat); + break; + case ExtensionNumber::UDRAW_TABLET: + s += sizeof(UDrawTablet::DataFormat); + break; + case ExtensionNumber::DRAWSOME_TABLET: + s += sizeof(DrawsomeTablet::DataFormat); + break; + case ExtensionNumber::TATACON: + s += sizeof(TaTaCon::DataFormat); + break; + case ExtensionNumber::SHINKANSEN: + s += sizeof(Shinkansen::DesiredState); + break; + default: + break; + } + return s; + }(); + + if (serialized.length != expected_size) + { + // invalid length + return false; + } + + size_t pos = 1; + + if (has_buttons) + { + state->buttons.a = d[pos] & 1; + state->buttons.b = (d[pos] >> 1) & 1; + state->buttons.plus = (d[pos] >> 2) & 1; + state->buttons.minus = (d[pos] >> 3) & 1; + state->buttons.one = (d[pos] >> 4) & 1; + state->buttons.two = (d[pos] >> 5) & 1; + state->buttons.home = (d[pos] >> 6) & 1; + state->buttons.up = d[pos + 1] & 1; + state->buttons.down = (d[pos + 1] >> 1) & 1; + state->buttons.left = (d[pos + 1] >> 2) & 1; + state->buttons.right = (d[pos + 1] >> 3) & 1; + pos += 2; + } + + if (has_accel) + { + if (has_buttons) + pos -= 1; + const u8 accel_low = d[pos] >> 4; + const u8 accel_x_high = d[pos + 1]; + const u8 accel_y_high = d[pos + 2]; + const u8 accel_z_high = d[pos + 3]; + state->acceleration.value.x = (accel_x_high << 2) | (accel_low & 0b11); + state->acceleration.value.y = + Common::ExpandValue((accel_y_high << 1) | Common::ExtractBit<2>(accel_low), 1); + state->acceleration.value.z = + Common::ExpandValue((accel_z_high << 1) | Common::ExtractBit<3>(accel_low), 1); + pos += 4; + } + + if (has_camera) + { + for (size_t i = 0; i < 2; ++i) + { + const u8 camera_misc = d[pos]; + const u8 camera_x_high = d[pos + 1]; + const u8 camera_y_high = d[pos + 2]; + const u16 camera_x = (camera_x_high << 2) | (camera_misc & 0b11); + const u16 camera_y = (camera_y_high << 2) | ((camera_misc >> 2) & 0b11); + const u8 camera_size = camera_misc >> 4; + if (camera_y < CameraLogic::CAMERA_RES_Y) + { + state->camera_points[i] = CameraPoint({camera_x, camera_y}, camera_size); + } + else + { + // indicates an invalid camera point + state->camera_points[i] = CameraPoint(); + } + pos += 3; + } + } + + if (has_motion_plus) + { + const u16 pitch_value = d[pos] | ((d[pos + 1] & 0x3f) << 8); + const u16 roll_value = d[pos + 2] | ((d[pos + 3] & 0x3f) << 8); + const u16 yaw_value = d[pos + 4] | ((d[pos + 5] & 0x3f) << 8); + const bool pitch_slow = (d[pos + 1] & 0x80) != 0; + const bool roll_slow = (d[pos + 3] & 0x80) != 0; + const bool yaw_slow = (d[pos + 5] & 0x80) != 0; + state->motion_plus = MotionPlus::DataFormat::Data{ + MotionPlus::DataFormat::GyroRawValue{ + MotionPlus::DataFormat::GyroType(pitch_value, roll_value, yaw_value)}, + MotionPlus::DataFormat::SlowType(pitch_slow, roll_slow, yaw_slow)}; + pos += 6; + } + + switch (extension) + { + case ExtensionNumber::NONE: + return true; + case ExtensionNumber::NUNCHUK: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::CLASSIC: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::GUITAR: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::DRUMS: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::TURNTABLE: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::UDRAW_TABLET: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::DRAWSOME_TABLET: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::TATACON: + return DeserializeExtensionState(state, serialized, pos); + case ExtensionNumber::SHINKANSEN: + return DeserializeExtensionState(state, serialized, pos); + default: + break; + } + + return false; +} +} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h new file mode 100644 index 0000000000..3cde05f7a9 --- /dev/null +++ b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h @@ -0,0 +1,42 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Core/HW/WiimoteCommon/WiimoteReport.h" +#include "Core/HW/WiimoteEmu/Camera.h" +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" +#include "Core/HW/WiimoteEmu/MotionPlus.h" +#include "Core/HW/WiimoteEmu/WiimoteEmu.h" + +namespace WiimoteEmu +{ +struct DesiredWiimoteState +{ + // 1g in Z direction, which is the default returned by an unmoving emulated Wiimote. + static constexpr WiimoteCommon::AccelData DEFAULT_ACCELERATION = WiimoteCommon::AccelData( + {Wiimote::ACCEL_ZERO_G << 2, Wiimote::ACCEL_ZERO_G << 2, Wiimote::ACCEL_ONE_G << 2}); + + // No light detected by the IR camera. + static constexpr std::array DEFAULT_CAMERA = {CameraPoint(), CameraPoint()}; + + WiimoteCommon::ButtonData buttons{}; // non-button state in this is ignored + WiimoteCommon::AccelData acceleration = DEFAULT_ACCELERATION; + std::array camera_points = DEFAULT_CAMERA; + std::optional motion_plus = std::nullopt; + DesiredExtensionState extension; +}; + +// For Netplay. +struct SerializedWiimoteState +{ + u8 length; + std::array data; // 12 bytes Wiimote, 6 bytes MotionPlus, 6 bytes Extension +}; + +SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state); +bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimoteState& serialized); +} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index 4d1d84da9d..d4d6575e7f 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -142,7 +142,8 @@ void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code) InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); } -void Wiimote::HandleExtensionSwap() +void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number, + bool desired_motion_plus) { if (WIIMOTE_BALANCE_BOARD == m_index) { @@ -151,15 +152,13 @@ void Wiimote::HandleExtensionSwap() return; } - ExtensionNumber desired_extension_number = - static_cast(m_attachments->GetSelectedAttachment()); - - const bool desired_motion_plus = m_motion_plus_setting.GetValue(); - // FYI: AttachExtension also connects devices to the i2c bus if (m_is_motion_plus_attached && !desired_motion_plus) { + INFO_LOG_FMT(WIIMOTE, "Detaching Motion Plus (Wiimote {} in slot {})", m_index, + m_bt_device_index); + // M+ is attached and it's not wanted, so remove it. m_extension_port.AttachExtension(GetNoneExtension()); m_is_motion_plus_attached = false; @@ -184,6 +183,9 @@ void Wiimote::HandleExtensionSwap() } else { + INFO_LOG_FMT(WIIMOTE, "Attaching Motion Plus (Wiimote {} in slot {})", m_index, + m_bt_device_index); + // No extension attached so attach M+. m_is_motion_plus_attached = true; m_extension_port.AttachExtension(&m_motion_plus); @@ -198,12 +200,18 @@ void Wiimote::HandleExtensionSwap() // A different extension is wanted (either by user or by the M+ logic above) if (GetActiveExtensionNumber() != ExtensionNumber::NONE) { + INFO_LOG_FMT(WIIMOTE, "Detaching Extension (Wiimote {} in slot {})", m_index, + m_bt_device_index); + // First we must detach the current extension. // The next call will change to the new extension if needed. m_active_extension = ExtensionNumber::NONE; } else { + INFO_LOG_FMT(WIIMOTE, "Switching to Extension {} (Wiimote {} in slot {})", + desired_extension_number, m_index, m_bt_device_index); + m_active_extension = desired_extension_number; } diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.cpp index 3b8d60d344..3cdc02e0b3 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.cpp @@ -10,6 +10,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -105,7 +107,7 @@ Classic::Classic() : Extension1stParty("Classic", _trans("Classic Controller")) } } -void Classic::Update() +void Classic::BuildDesiredExtensionState(DesiredExtensionState* target_state) { DataFormat classic_data = {}; @@ -149,7 +151,12 @@ void Classic::Update() classic_data.SetButtons(buttons); - Common::BitCastPtr(&m_reg.controller_data) = classic_data; + target_state->data = classic_data; +} + +void Classic::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void Classic::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.h index 36bc2a7f8b..3ab0a94a38 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Classic.h @@ -178,7 +178,8 @@ public: Classic(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(ClassicGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h b/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h new file mode 100644 index 0000000000..4929c91cd1 --- /dev/null +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h @@ -0,0 +1,76 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/BitUtils.h" + +#include "Core/HW/WiimoteEmu/Extension/Classic.h" +#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h" +#include "Core/HW/WiimoteEmu/Extension/Drums.h" +#include "Core/HW/WiimoteEmu/Extension/Extension.h" +#include "Core/HW/WiimoteEmu/Extension/Guitar.h" +#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h" +#include "Core/HW/WiimoteEmu/Extension/Shinkansen.h" +#include "Core/HW/WiimoteEmu/Extension/TaTaCon.h" +#include "Core/HW/WiimoteEmu/Extension/Turntable.h" +#include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h" +#include "Core/HW/WiimoteEmu/ExtensionPort.h" + +namespace WiimoteEmu +{ +struct DesiredExtensionState +{ + using ExtensionData = + std::variant; + ExtensionData data = std::monostate(); + + static_assert(std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert( + std::is_same_v>); + static_assert(std::variant_size_v == ExtensionNumber::MAX); +}; + +template +void DefaultExtensionUpdate(EncryptedExtension::Register* reg, + const DesiredExtensionState& target_state) +{ + if (std::holds_alternative(target_state.data)) + { + Common::BitCastPtr(®->controller_data) = std::get(target_state.data); + } + else + { + Common::BitCastPtr(®->controller_data) = T{}; + } +} +} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.cpp index a145f37015..9a0d16790e 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.cpp @@ -9,6 +9,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -31,9 +33,9 @@ DrawsomeTablet::DrawsomeTablet() : Extension3rdParty("Drawsome", _trans("Drawsom m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure")); } -void DrawsomeTablet::Update() +void DrawsomeTablet::BuildDesiredExtensionState(DesiredExtensionState* target_state) { - DataFormat tablet_data = {}; + DataFormat& tablet_data = target_state->data.emplace(); // Stylus X/Y (calibrated values): constexpr u16 MIN_X = 0x0000; @@ -77,8 +79,11 @@ void DrawsomeTablet::Update() tablet_data.pressure1 = u8(pressure); tablet_data.pressure2 = u8(pressure >> 8); +} - Common::BitCastPtr(&m_reg.controller_data) = tablet_data; +void DrawsomeTablet::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void DrawsomeTablet::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h b/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h index 0bb41cc3cd..fe4a213d09 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h @@ -27,7 +27,8 @@ class DrawsomeTablet : public Extension3rdParty public: DrawsomeTablet(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(DrawsomeTabletGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp index a3fd38f564..0a37f278d2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp @@ -9,6 +9,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -77,8 +79,43 @@ Drums::Drums() : Extension1stParty("Drums", _trans("Drum Kit")) m_buttons->AddInput(ControllerEmu::DoNotTranslate, "+"); } -void Drums::Update() +void Drums::BuildDesiredExtensionState(DesiredExtensionState* target_state) { + DesiredState& state = target_state->data.emplace(); + + { + const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState(); + + state.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX); + state.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX); + } + + state.buttons = 0; + m_buttons->GetState(&state.buttons, drum_button_bitmasks.data()); + + state.drum_pads = 0; + m_pads->GetState(&state.drum_pads, drum_pad_bitmasks.data()); + + state.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100)); +} + +void Drums::Update(const DesiredExtensionState& target_state) +{ + DesiredState desired_state; + if (std::holds_alternative(target_state.data)) + { + desired_state = std::get(target_state.data); + } + else + { + // Set a sane default + desired_state.stick_x = STICK_CENTER; + desired_state.stick_y = STICK_CENTER; + desired_state.buttons = 0; + desired_state.drum_pads = 0; + desired_state.softness = 7; + } + DataFormat drum_data = {}; // The meaning of these bits are unknown but they are usually set. @@ -94,20 +131,12 @@ void Drums::Update() drum_data.no_velocity_data_2 = 1; drum_data.softness = 7; - // Stick. - { - const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState(); - - drum_data.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX); - drum_data.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX); - } - - // Buttons. - m_buttons->GetState(&drum_data.buttons, drum_button_bitmasks.data()); + drum_data.stick_x = desired_state.stick_x; + drum_data.stick_y = desired_state.stick_y; + drum_data.buttons = desired_state.buttons; // Drum pads. - u8 current_pad_input = 0; - m_pads->GetState(¤t_pad_input, drum_pad_bitmasks.data()); + u8 current_pad_input = desired_state.drum_pads; m_new_pad_hits |= ~m_prev_pad_input & current_pad_input; m_prev_pad_input = current_pad_input; @@ -130,8 +159,7 @@ void Drums::Update() drum_data.no_velocity_data_1 = 0; drum_data.no_velocity_data_2 = 0; - // Set softness from user-configured hit strength setting. - drum_data.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100)); + drum_data.softness = desired_state.softness; // A drum-pad hit causes the relevent bit to be triggered for the next 10 frames. constexpr u8 HIT_FRAME_COUNT = 10; diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h index a74013bf32..ba65e48877 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h @@ -28,6 +28,15 @@ enum class DrumsGroup class Drums : public Extension1stParty { public: + struct DesiredState + { + u8 stick_x; // 6 bits + u8 stick_y; // 6 bits + u8 buttons; // 2 bits + u8 drum_pads; // 6 bits + u8 softness; // 3 bits + }; + struct DataFormat { u8 stick_x : 6; @@ -77,7 +86,8 @@ public: Drums(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(DrumsGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp index aa9d419977..f20beef7e3 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp @@ -9,6 +9,8 @@ #include "Common/CommonTypes.h" #include "Common/Inline.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Common/Logging/Log.h" @@ -43,7 +45,12 @@ bool None::ReadDeviceDetectPin() const return false; } -void None::Update() +void None::BuildDesiredExtensionState(DesiredExtensionState* target_state) +{ + target_state->data.emplace(); +} + +void None::Update(const DesiredExtensionState& target_state) { // Nothing needed. } diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h index b5fb55958d..d052952273 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h @@ -16,6 +16,8 @@ namespace WiimoteEmu { +struct DesiredExtensionState; + class Extension : public ControllerEmu::EmulatedController, public I2CSlave { public: @@ -32,7 +34,8 @@ public: virtual void Reset() = 0; virtual void DoState(PointerWrap& p) = 0; - virtual void Update() = 0; + virtual void BuildDesiredExtensionState(DesiredExtensionState* target_state) = 0; + virtual void Update(const DesiredExtensionState& target_state) = 0; private: const char* const m_config_name; @@ -46,7 +49,8 @@ public: private: bool ReadDeviceDetectPin() const override; - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; void DoState(PointerWrap& p) override; @@ -67,7 +71,6 @@ public: // TODO: TAS handles encryption poorly. EncryptionKey ext_key; -protected: static constexpr int CALIBRATION_CHECKSUM_BYTES = 2; #pragma pack(push, 1) @@ -97,6 +100,7 @@ protected: static_assert(0x100 == sizeof(Register)); +protected: Register m_reg = {}; void Reset() override; diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.cpp index 2c3bdb6bd9..cd86a04a32 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.cpp @@ -11,6 +11,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -93,7 +95,7 @@ Guitar::Guitar() : Extension1stParty(_trans("Guitar")) groups.emplace_back(m_slider_bar = new ControllerEmu::Slider(_trans("Slider Bar"))); } -void Guitar::Update() +void Guitar::BuildDesiredExtensionState(DesiredExtensionState* target_state) { DataFormat guitar_data = {}; @@ -135,7 +137,12 @@ void Guitar::Update() // flip button bits guitar_data.bt ^= 0xFFFF; - Common::BitCastPtr(&m_reg.controller_data) = guitar_data; + target_state->data = guitar_data; +} + +void Guitar::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void Guitar::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.h index 04630c428e..c0d332d537 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.h @@ -50,7 +50,8 @@ public: Guitar(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(GuitarGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp index 5e7f7425a8..73f73aadd0 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp @@ -12,7 +12,9 @@ #include "Common/Common.h" #include "Common/CommonTypes.h" #include "Common/MathUtil.h" + #include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -60,7 +62,7 @@ Nunchuk::Nunchuk() : Extension1stParty(_trans("Nunchuk")) "IMUAccelerometer", _trans("Accelerometer"))); } -void Nunchuk::Update() +void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state) { DataFormat nc_data = {}; @@ -110,7 +112,12 @@ void Nunchuk::Update() const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2); nc_data.SetAccel(acc.value); - Common::BitCastPtr(&m_reg.controller_data) = nc_data; + target_state->data = nc_data; +} + +void Nunchuk::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void Nunchuk::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.h index 704de14d4b..678abd23d2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.h @@ -149,7 +149,8 @@ public: Nunchuk(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; void DoState(PointerWrap& p) override; diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.cpp index 81e4a52070..91bad8e995 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.cpp @@ -8,6 +8,8 @@ #include "Common/Assert.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" @@ -50,9 +52,9 @@ Shinkansen::Shinkansen() : Extension3rdParty("Shinkansen", _trans("Shinkansen Co m_led->AddOutput(ControllerEmu::Translate, _trans("Doors Locked")); } -void Shinkansen::Update() +void Shinkansen::BuildDesiredExtensionState(DesiredExtensionState* target_state) { - DataFormat ext_data = {}; + DesiredState& state = target_state->data.emplace(); u16 digital = 0; const u16 lever_bitmasks[2] = {}; @@ -62,8 +64,8 @@ void Shinkansen::Update() // guesses). const u8 brake_values[] = {0, 53, 79, 105, 132, 159, 187, 217, 250}; const u8 power_values[] = {255, 229, 208, 189, 170, 153, 135, 118, 101, 85, 68, 51, 35, 17}; - ext_data.brake = brake_values[size_t(analog[0] * (sizeof(brake_values) - 1))]; - ext_data.power = power_values[size_t(analog[1] * (sizeof(power_values) - 1))]; + state.brake = brake_values[size_t(analog[0] * (sizeof(brake_values) - 1))]; + state.power = power_values[size_t(analog[1] * (sizeof(power_values) - 1))]; // Note: This currently assumes a little-endian host. const u16 button_bitmasks[] = { @@ -78,8 +80,27 @@ void Shinkansen::Update() 0x0010, // Select 0x0004, // Start }; - m_buttons->GetState(&ext_data.buttons, button_bitmasks); - ext_data.buttons ^= 0xFFFF; + m_buttons->GetState(&state.buttons, button_bitmasks); +} + +void Shinkansen::Update(const DesiredExtensionState& target_state) +{ + DesiredState desired_state; + if (std::holds_alternative(target_state.data)) + { + desired_state = std::get(target_state.data); + } + else + { + desired_state.brake = 0; + desired_state.power = 255; + desired_state.buttons = 0; + } + + DataFormat ext_data = {}; + ext_data.brake = desired_state.brake; + ext_data.power = desired_state.power; + ext_data.buttons = desired_state.buttons ^ 0xFFFF; Common::BitCastPtr(&m_reg.controller_data) = ext_data; const auto lock = GetStateLock(); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.h index d237f484ad..7c3cbb019d 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.h @@ -24,9 +24,17 @@ enum class ShinkansenGroup class Shinkansen : public Extension3rdParty { public: + struct DesiredState + { + u8 brake; + u8 power; + u16 buttons; + }; + Shinkansen(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(ShinkansenGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.cpp index c20d541d7a..04c13dc961 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.cpp @@ -10,6 +10,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -48,7 +50,7 @@ TaTaCon::TaTaCon() : Extension3rdParty("TaTaCon", _trans("Taiko Drum")) m_rim->AddInput(ControllerEmu::Translate, name); } -void TaTaCon::Update() +void TaTaCon::BuildDesiredExtensionState(DesiredExtensionState* target_state) { DataFormat tatacon_data = {}; @@ -58,7 +60,12 @@ void TaTaCon::Update() // Flip button bits. tatacon_data.state ^= 0xff; - Common::BitCastPtr(&m_reg.controller_data) = tatacon_data; + target_state->data = tatacon_data; +} + +void TaTaCon::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void TaTaCon::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.h b/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.h index 7c7b071dac..7ce54770bc 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.h @@ -31,7 +31,8 @@ public: TaTaCon(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(TaTaConGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.cpp index f00b6df080..aaa928686f 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.cpp @@ -10,6 +10,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -78,7 +80,7 @@ Turntable::Turntable() : Extension1stParty("Turntable", _trans("DJ Turntable")) groups.emplace_back(m_crossfade = new ControllerEmu::Slider(_trans("Crossfade"))); } -void Turntable::Update() +void Turntable::BuildDesiredExtensionState(DesiredExtensionState* target_state) { DataFormat tt_data = {}; @@ -133,7 +135,12 @@ void Turntable::Update() tt_data.bt ^= (BUTTON_L_GREEN | BUTTON_L_RED | BUTTON_L_BLUE | BUTTON_R_GREEN | BUTTON_R_RED | BUTTON_R_BLUE | BUTTON_MINUS | BUTTON_PLUS | BUTTON_EUPHORIA); - Common::BitCastPtr(&m_reg.controller_data) = tt_data; + target_state->data = tt_data; +} + +void Turntable::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void Turntable::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.h index 539f5ce10c..451135d812 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.h @@ -56,7 +56,8 @@ public: Turntable(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(TurntableGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.cpp index cf3253b3ee..e78d41f015 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.cpp @@ -9,6 +9,8 @@ #include "Common/BitUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "InputCommon/ControllerEmu/Control/Input.h" @@ -48,7 +50,7 @@ UDrawTablet::UDrawTablet() : Extension3rdParty("uDraw", _trans("uDraw GameTablet m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure")); } -void UDrawTablet::Update() +void UDrawTablet::BuildDesiredExtensionState(DesiredExtensionState* target_state) { DataFormat tablet_data = {}; @@ -105,7 +107,12 @@ void UDrawTablet::Update() // Always 0xff tablet_data.unk = 0xff; - Common::BitCastPtr(&m_reg.controller_data) = tablet_data; + target_state->data = tablet_data; +} + +void UDrawTablet::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); } void UDrawTablet::Reset() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.h b/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.h index e7569ffb23..7052b55ede 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/UDrawTablet.h @@ -27,7 +27,8 @@ class UDrawTablet : public Extension3rdParty public: UDrawTablet(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; ControllerEmu::ControlGroup* GetGroup(UDrawTabletGroup group); diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp index 65fdb475d7..11441a0ae2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp @@ -18,6 +18,7 @@ #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteEmu/Dynamics.h" +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" namespace { @@ -387,7 +388,12 @@ bool MotionPlus::ReadDeviceDetectPin() const } } -void MotionPlus::Update() +void MotionPlus::BuildDesiredExtensionState(DesiredExtensionState* target_state) +{ + // MotionPlus is handled separately, nothing to do here. +} + +void MotionPlus::Update(const DesiredExtensionState& target_state) { if (m_progress_timer) --m_progress_timer; @@ -522,9 +528,56 @@ void MotionPlus::Update() } } +MotionPlus::DataFormat::Data MotionPlus::GetGyroscopeData(const Common::Vec3& angular_velocity) +{ + // Conversion from radians to the calibrated values in degrees. + constexpr float VALUE_SCALE = + (CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)) / float(MathUtil::TAU) * + 360; + + constexpr float SLOW_SCALE = VALUE_SCALE / CALIBRATION_SLOW_SCALE_DEGREES; + constexpr float FAST_SCALE = VALUE_SCALE / CALIBRATION_FAST_SCALE_DEGREES; + + static_assert(ZERO_VALUE == 1 << (BITS_OF_PRECISION - 1), + "SLOW_MAX_RAD_PER_SEC assumes calibrated zero is at center of sensor values."); + + constexpr u16 SENSOR_RANGE = 1 << (BITS_OF_PRECISION - 1); + constexpr float SLOW_MAX_RAD_PER_SEC = SENSOR_RANGE / SLOW_SCALE; + + // Slow (high precision) scaling can be used if it fits in the sensor range. + const float yaw = angular_velocity.z; + const bool yaw_slow = (std::abs(yaw) < SLOW_MAX_RAD_PER_SEC); + const s32 yaw_value = yaw * (yaw_slow ? SLOW_SCALE : FAST_SCALE); + + const float roll = angular_velocity.y; + const bool roll_slow = (std::abs(roll) < SLOW_MAX_RAD_PER_SEC); + const s32 roll_value = roll * (roll_slow ? SLOW_SCALE : FAST_SCALE); + + const float pitch = angular_velocity.x; + const bool pitch_slow = (std::abs(pitch) < SLOW_MAX_RAD_PER_SEC); + const s32 pitch_value = pitch * (pitch_slow ? SLOW_SCALE : FAST_SCALE); + + const u16 clamped_yaw_value = u16(std::clamp(yaw_value + ZERO_VALUE, 0, MAX_VALUE)); + const u16 clamped_roll_value = u16(std::clamp(roll_value + ZERO_VALUE, 0, MAX_VALUE)); + const u16 clamped_pitch_value = u16(std::clamp(pitch_value + ZERO_VALUE, 0, MAX_VALUE)); + + return MotionPlus::DataFormat::Data{ + MotionPlus::DataFormat::GyroRawValue{MotionPlus::DataFormat::GyroType( + clamped_pitch_value, clamped_roll_value, clamped_yaw_value)}, + MotionPlus::DataFormat::SlowType(pitch_slow, roll_slow, yaw_slow)}; +} + +MotionPlus::DataFormat::Data MotionPlus::GetDefaultGyroscopeData() +{ + return MotionPlus::DataFormat::Data{ + MotionPlus::DataFormat::GyroRawValue{ + MotionPlus::DataFormat::GyroType(u16(ZERO_VALUE), u16(ZERO_VALUE), u16(ZERO_VALUE))}, + MotionPlus::DataFormat::SlowType(true, true, true)}; +} + // This is something that is triggered by a read of 0x00 on real hardware. // But we do it here for determinism reasons. -void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity) +void MotionPlus::PrepareInput(const MotionPlus::DataFormat::Data& gyroscope_data) { if (GetActivationStatus() != ActivationStatus::Active) return; @@ -592,41 +645,16 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity) // If the above logic determined this should be M+ data, update it here. if (mplus_data.is_mp_data) { - constexpr int BITS_OF_PRECISION = 14; + const bool pitch_slow = gyroscope_data.is_slow.x; + const bool roll_slow = gyroscope_data.is_slow.y; + const bool yaw_slow = gyroscope_data.is_slow.z; + const u16 pitch_value = gyroscope_data.gyro.value.x; + const u16 roll_value = gyroscope_data.gyro.value.y; + const u16 yaw_value = gyroscope_data.gyro.value.z; - // Conversion from radians to the calibrated values in degrees. - constexpr float VALUE_SCALE = - (CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)) / - float(MathUtil::TAU) * 360; - - constexpr float SLOW_SCALE = VALUE_SCALE / CALIBRATION_SLOW_SCALE_DEGREES; - constexpr float FAST_SCALE = VALUE_SCALE / CALIBRATION_FAST_SCALE_DEGREES; - - constexpr s32 ZERO_VALUE = CALIBRATION_ZERO >> (CALIBRATION_BITS - BITS_OF_PRECISION); - constexpr s32 MAX_VALUE = (1 << BITS_OF_PRECISION) - 1; - - static_assert(ZERO_VALUE == 1 << (BITS_OF_PRECISION - 1), - "SLOW_MAX_RAD_PER_SEC assumes calibrated zero is at center of sensor values."); - - constexpr u16 SENSOR_RANGE = 1 << (BITS_OF_PRECISION - 1); - constexpr float SLOW_MAX_RAD_PER_SEC = SENSOR_RANGE / SLOW_SCALE; - - // Slow (high precision) scaling can be used if it fits in the sensor range. - const float yaw = angular_velocity.z; - mplus_data.yaw_slow = (std::abs(yaw) < SLOW_MAX_RAD_PER_SEC); - s32 yaw_value = yaw * (mplus_data.yaw_slow ? SLOW_SCALE : FAST_SCALE); - - const float roll = angular_velocity.y; - mplus_data.roll_slow = (std::abs(roll) < SLOW_MAX_RAD_PER_SEC); - s32 roll_value = roll * (mplus_data.roll_slow ? SLOW_SCALE : FAST_SCALE); - - const float pitch = angular_velocity.x; - mplus_data.pitch_slow = (std::abs(pitch) < SLOW_MAX_RAD_PER_SEC); - s32 pitch_value = pitch * (mplus_data.pitch_slow ? SLOW_SCALE : FAST_SCALE); - - yaw_value = std::clamp(yaw_value + ZERO_VALUE, 0, MAX_VALUE); - roll_value = std::clamp(roll_value + ZERO_VALUE, 0, MAX_VALUE); - pitch_value = std::clamp(pitch_value + ZERO_VALUE, 0, MAX_VALUE); + mplus_data.yaw_slow = u8(yaw_slow); + mplus_data.roll_slow = u8(roll_slow); + mplus_data.pitch_slow = u8(pitch_slow); // Bits 0-7 mplus_data.yaw1 = u8(yaw_value); diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h index db7cf2b044..28386f9107 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h @@ -118,14 +118,18 @@ public: MotionPlus(); - void Update() override; + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; void Reset() override; void DoState(PointerWrap& p) override; ExtensionPort& GetExtPort(); // Vec3 is interpreted as radians/s about the x,y,z axes following the "right-hand rule". - void PrepareInput(const Common::Vec3& angular_velocity); + static MotionPlus::DataFormat::Data GetGyroscopeData(const Common::Vec3& angular_velocity); + static MotionPlus::DataFormat::Data GetDefaultGyroscopeData(); + + void PrepareInput(const MotionPlus::DataFormat::Data& gyroscope_data); // Pointer to 6 bytes is expected. static void ApplyPassthroughModifications(PassthroughMode, u8* data); @@ -218,6 +222,10 @@ private: static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0; static constexpr u16 CALIBRATION_SLOW_SCALE_DEGREES = 0x10e; + static constexpr int BITS_OF_PRECISION = 14; + static constexpr s32 ZERO_VALUE = CALIBRATION_ZERO >> (CALIBRATION_BITS - BITS_OF_PRECISION); + static constexpr s32 MAX_VALUE = (1 << BITS_OF_PRECISION) - 1; + void Activate(); void Deactivate(); void OnPassthroughModeWrite(); diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index ca071a5e10..4aa11b3e70 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -21,11 +21,12 @@ #include "Core/Core.h" #include "Core/HW/Wiimote.h" #include "Core/Movie.h" -#include "Core/NetPlayClient.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h" +#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" #include "Core/HW/WiimoteEmu/Extension/Classic.h" +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h" #include "Core/HW/WiimoteEmu/Extension/Drums.h" #include "Core/HW/WiimoteEmu/Extension/Guitar.h" @@ -68,6 +69,8 @@ constexpr std::array named_buttons{ void Wiimote::Reset() { + const bool want_determinism = Core::WantsDeterminism(); + SetRumble(false); // Wiimote starts in non-continuous CORE mode: @@ -77,8 +80,12 @@ void Wiimote::Reset() m_speaker_mute = false; // EEPROM + + // TODO: This feels sketchy, this needs to properly handle the case where the load and the write + // happen under different Wii Roots and/or determinism modes. + std::string eeprom_file = (File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/" + GetName() + ".bin"); - if (m_eeprom_dirty) + if (!want_determinism && m_eeprom_dirty) { // Write out existing EEPROM INFO_LOG_FMT(WIIMOTE, "Wrote EEPROM for {}", GetName()); @@ -91,7 +98,7 @@ void Wiimote::Reset() } m_eeprom = {}; - if (File::Exists(eeprom_file)) + if (!want_determinism && File::Exists(eeprom_file)) { // Read existing EEPROM std::ifstream file; @@ -171,18 +178,26 @@ void Wiimote::Reset() m_extension_port.AttachExtension(GetNoneExtension()); m_motion_plus.GetExtPort().AttachExtension(GetNoneExtension()); - // Switch to desired M+ status and extension (if any). - // M+ and EXT are reset on attachment. - HandleExtensionSwap(); + if (!want_determinism) + { + // Switch to desired M+ status and extension (if any). + // M+ and EXT are reset on attachment. + HandleExtensionSwap(static_cast(m_attachments->GetSelectedAttachment()), + m_motion_plus_setting.GetValue()); + } // Reset sub-devices. m_speaker_logic.Reset(); m_camera_logic.Reset(); m_status = {}; - // This will suppress a status report on connect when an extension is already attached. - // TODO: I am not 100% sure if this is proper. - m_status.extension = m_extension_port.IsDeviceConnected(); + + if (!want_determinism) + { + // This will suppress a status report on connect when an extension is already attached. + // TODO: I am not 100% sure if this is proper. + m_status.extension = m_extension_port.IsDeviceConnected(); + } // Dynamics: m_swing_state = {}; @@ -193,7 +208,7 @@ void Wiimote::Reset() m_imu_cursor_state = {}; } -Wiimote::Wiimote(const unsigned int index) : m_index(index) +Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(index) { // Buttons groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons"))); @@ -427,20 +442,13 @@ bool Wiimote::ProcessExtensionPortEvent() return true; } -// Update buttons in status struct from user input. -void Wiimote::UpdateButtonsStatus() +void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state) { - m_status.buttons.hex = 0; - - m_buttons->GetState(&m_status.buttons.hex, button_bitmasks); - m_dpad->GetState(&m_status.buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks); + m_status.buttons.hex = target_state.buttons.hex & ButtonData::BUTTON_MASK; } -// This is called every ::Wiimote::UPDATE_FREQ (200hz) -void Wiimote::Update() +void Wiimote::BuildDesiredWiimoteState(DesiredWiimoteState* target_state) { - const auto lock = GetStateLock(); - // Hotkey / settings modifier // Data is later accessed in IsSideways and IsUpright m_hotkeys->UpdateState(); @@ -448,26 +456,70 @@ void Wiimote::Update() // Update our motion simulations. StepDynamics(); + // Fetch pressed buttons from user input. + target_state->buttons.hex = 0; + m_buttons->GetState(&target_state->buttons.hex, button_bitmasks); + m_dpad->GetState(&target_state->buttons.hex, + IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks); + + // Calculate accelerometer state. + // Calibration values are 8-bit but we want 10-bit precision, so << 2. + target_state->acceleration = + ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2); + + // Calculate IR camera state. + target_state->camera_points = CameraLogic::GetCameraPoints( + GetTotalTransformation(), + Common::Vec2(m_fov_x_setting.GetValue(), m_fov_y_setting.GetValue()) / 360 * + float(MathUtil::TAU)); + + // Calculate MotionPlus state. + if (m_motion_plus_setting.GetValue()) + target_state->motion_plus = MotionPlus::GetGyroscopeData(GetTotalAngularVelocity()); + else + target_state->motion_plus = std::nullopt; + + // Build Extension state. + // This also allows the extension to perform any regular duties it may need. + // (e.g. Nunchuk motion simulation step) + static_cast( + m_attachments->GetAttachmentList()[m_attachments->GetSelectedAttachment()].get()) + ->BuildDesiredExtensionState(&target_state->extension); +} + +u8 Wiimote::GetWiimoteDeviceIndex() const +{ + return m_bt_device_index; +} + +void Wiimote::SetWiimoteDeviceIndex(u8 index) +{ + m_bt_device_index = index; +} + +// This is called every ::Wiimote::UPDATE_FREQ (200hz) +void Wiimote::PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) +{ + const auto lock = GetStateLock(); + BuildDesiredWiimoteState(target_state); +} + +void Wiimote::Update(const WiimoteEmu::DesiredWiimoteState& target_state) +{ // Update buttons in the status struct which is sent in 99% of input reports. - // FYI: Movies only sync button updates in data reports. - if (!Core::WantsDeterminism()) - { - UpdateButtonsStatus(); - } + UpdateButtonsStatus(target_state); // If a new extension is requested in the GUI the change will happen here. - HandleExtensionSwap(); + HandleExtensionSwap(static_cast(target_state.extension.data.index()), + target_state.motion_plus.has_value()); - // Allow extension to perform any regular duties it may need. - // (e.g. Nunchuk motion simulation step) - // Input is prepared here too. - // TODO: Separate input preparation from Update. - GetActiveExtension()->Update(); + // Prepare input data of the extension for reading. + GetActiveExtension()->Update(target_state.extension); if (m_is_motion_plus_attached) { // M+ has some internal state that must processed. - m_motion_plus.Update(); + m_motion_plus.Update(target_state.extension); } // Returns true if a report was sent. @@ -485,10 +537,10 @@ void Wiimote::Update() return; } - SendDataReport(); + SendDataReport(target_state); } -void Wiimote::SendDataReport() +void Wiimote::SendDataReport(const DesiredWiimoteState& target_state) { Movie::SetPolledDevice(); @@ -508,7 +560,8 @@ void Wiimote::SendDataReport() DataReportBuilder rpt_builder(m_reporting_mode); if (Movie::IsPlayingInput() && - Movie::PlayWiimote(m_index, rpt_builder, m_active_extension, GetExtensionEncryptionKey())) + Movie::PlayWiimote(m_bt_device_index, rpt_builder, m_active_extension, + GetExtensionEncryptionKey())) { // Update buttons in status struct from movie: rpt_builder.GetCoreData(&m_status.buttons); @@ -518,22 +571,13 @@ void Wiimote::SendDataReport() // Core buttons: if (rpt_builder.HasCore()) { - if (Core::WantsDeterminism()) - { - // When running non-deterministically we've already updated buttons in Update() - UpdateButtonsStatus(); - } - rpt_builder.SetCoreData(m_status.buttons); } // Acceleration: if (rpt_builder.HasAccel()) { - // Calibration values are 8-bit but we want 10-bit precision, so << 2. - AccelData accel = - ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2); - rpt_builder.SetAccelData(accel); + rpt_builder.SetAccelData(target_state.acceleration); } // IR Camera: @@ -541,9 +585,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(GetTotalTransformation(), - Common::Vec2(m_fov_x_setting.GetValue(), m_fov_y_setting.GetValue()) / - 360 * float(MathUtil::TAU)); + m_camera_logic.Update(target_state.camera_points); // The real wiimote reads camera data from the i2c bus starting at offset 0x37: const u8 camera_data_offset = @@ -571,7 +613,9 @@ void Wiimote::SendDataReport() if (m_is_motion_plus_attached) { // TODO: Make input preparation triggered by bus read. - m_motion_plus.PrepareInput(GetTotalAngularVelocity()); + m_motion_plus.PrepareInput(target_state.motion_plus.has_value() ? + target_state.motion_plus.value() : + MotionPlus::GetDefaultGyroscopeData()); } u8* ext_data = rpt_builder.GetExtDataPtr(); @@ -585,19 +629,12 @@ void Wiimote::SendDataReport() } } - Movie::CallWiiInputManip(rpt_builder, m_index, m_active_extension, GetExtensionEncryptionKey()); + Movie::CallWiiInputManip(rpt_builder, m_bt_device_index, m_active_extension, + GetExtensionEncryptionKey()); } - if (NetPlay::IsNetPlayRunning()) - { - NetPlay_GetWiimoteData(m_index, rpt_builder.GetDataPtr(), rpt_builder.GetDataSize(), - u8(m_reporting_mode)); - - // TODO: clean up how m_status.buttons is updated. - rpt_builder.GetCoreData(&m_status.buttons); - } - - Movie::CheckWiimoteStatus(m_index, rpt_builder, m_active_extension, GetExtensionEncryptionKey()); + Movie::CheckWiimoteStatus(m_bt_device_index, rpt_builder, m_active_extension, + GetExtensionEncryptionKey()); // Send the report: InterruptDataInputCallback(rpt_builder.GetDataPtr(), rpt_builder.GetDataSize()); @@ -609,14 +646,15 @@ void Wiimote::SendDataReport() m_reporting_mode = InputReportID::ReportInterleave1; } -bool Wiimote::IsButtonPressed() +ButtonData Wiimote::GetCurrentlyPressedButtons() { - u16 buttons = 0; const auto lock = GetStateLock(); - m_buttons->GetState(&buttons, button_bitmasks); - m_dpad->GetState(&buttons, dpad_bitmasks); - return buttons != 0; + ButtonData buttons{}; + m_buttons->GetState(&buttons.hex, button_bitmasks); + m_dpad->GetState(&buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks); + + return buttons; } void Wiimote::LoadDefaults(const ControllerInterface& ciface) diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index ae51b04ace..c360f33ab7 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -37,6 +37,9 @@ class Tilt; namespace WiimoteEmu { +struct DesiredWiimoteState; +struct DesiredExtensionState; + enum class WiimoteGroup { Buttons, @@ -126,11 +129,15 @@ public: ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const; ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup group) const; - void Update() override; + u8 GetWiimoteDeviceIndex() const override; + void SetWiimoteDeviceIndex(u8 index) override; + + void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) override; + void Update(const WiimoteEmu::DesiredWiimoteState& target_state) override; void EventLinked() override; void EventUnlinked() override; void InterruptDataOutput(const u8* data, u32 size) override; - bool IsButtonPressed() override; + WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override; void Reset(); @@ -150,7 +157,8 @@ private: void RefreshConfig(); void StepDynamics(); - void UpdateButtonsStatus(); + void UpdateButtonsStatus(const DesiredWiimoteState& target_state); + void BuildDesiredWiimoteState(DesiredWiimoteState* target_state); // Returns simulated accelerometer data in m/s^2. Common::Vec3 GetAcceleration( @@ -187,9 +195,9 @@ private: template void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size); - void HandleExtensionSwap(); + void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus); bool ProcessExtensionPortEvent(); - void SendDataReport(); + void SendDataReport(const DesiredWiimoteState& target_state); bool ProcessReadDataRequest(); void SetRumble(bool on); @@ -202,8 +210,6 @@ private: Extension* GetActiveExtension() const; Extension* GetNoneExtension() const; - bool NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode); - // TODO: Kill this nonsensical function used for TAS: EncryptionKey GetExtensionEncryptionKey() const; @@ -277,9 +283,15 @@ private: ExtensionPort m_extension_port{&m_i2c_bus}; - // Wiimote index, 0-3 + // Wiimote index, 0-3. + // Can also be 4 for Balance Board. + // This is used to look up the user button config. const u8 m_index; + // The Bluetooth 'slot' this device is connected to. + // This is usually the same as m_index, but can differ during Netplay. + u8 m_bt_device_index; + WiimoteCommon::InputReportID m_reporting_mode; bool m_reporting_continuous; diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp index 6b36551774..10fa8f2686 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp @@ -450,7 +450,22 @@ Report& Wiimote::ProcessReadQueue(bool repeat_last_data_report) return m_last_input_report; } -void Wiimote::Update() +u8 Wiimote::GetWiimoteDeviceIndex() const +{ + return m_bt_device_index; +} + +void Wiimote::SetWiimoteDeviceIndex(u8 index) +{ + m_bt_device_index = index; +} + +void Wiimote::PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) +{ + // Nothing to do here on real Wiimotes. +} + +void Wiimote::Update(const WiimoteEmu::DesiredWiimoteState& target_state) { // Wii remotes send input at 200hz once a Wii enables "sniff mode" on the connection. // PC bluetooth stacks do not enable sniff mode causing remotes to send input at only 100hz. @@ -475,7 +490,7 @@ void Wiimote::Update() u32(rpt.size() - REPORT_HID_HEADER_SIZE)); } -bool Wiimote::IsButtonPressed() +ButtonData Wiimote::GetCurrentlyPressedButtons() { Report& rpt = m_last_input_report; if (rpt.size() >= 4) @@ -489,10 +504,10 @@ bool Wiimote::IsButtonPressed() ButtonData buttons = {}; builder->GetCoreData(&buttons); - return buttons.hex != 0; + return buttons; } } - return false; + return ButtonData{}; } void Wiimote::Prepare() diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index 487d28024f..e597d774bc 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -62,10 +62,15 @@ public: bool IsBalanceBoard(); void InterruptDataOutput(const u8* data, const u32 size) override; - void Update() override; + + u8 GetWiimoteDeviceIndex() const override; + void SetWiimoteDeviceIndex(u8 index) override; + + void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) override; + void Update(const WiimoteEmu::DesiredWiimoteState& target_state) override; void EventLinked() override; void EventUnlinked() override; - bool IsButtonPressed() override; + WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override; void EmuStop(); @@ -98,6 +103,8 @@ protected: // This is not enabled on all platforms as connecting a Wiimote can be a pain on some platforms. bool m_really_disconnect = false; + u8 m_bt_device_index = 0; + private: void Read(); bool Write(); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp index 5994c9640a..dea42fd589 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp @@ -19,8 +19,11 @@ #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" #include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" #include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" +#include "Core/NetPlayClient.h" +#include "Core/NetPlayProto.h" #include "Core/SysConf.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" @@ -58,7 +61,9 @@ BluetoothEmuDevice::BluetoothEmuDevice(Kernel& ios, const std::string& device_na DEBUG_LOG_FMT(IOS_WIIMOTE, "Wii Remote {} BT ID {:x},{:x},{:x},{:x},{:x},{:x}", i, tmp_bd[0], tmp_bd[1], tmp_bd[2], tmp_bd[3], tmp_bd[4], tmp_bd[5]); - m_wiimotes.emplace_back(std::make_unique(this, i, tmp_bd)); + const unsigned int hid_source_number = + NetPlay::IsNetPlayRunning() ? NetPlay::NetPlay_GetLocalWiimoteForSlot(i) : i; + m_wiimotes[i] = std::make_unique(this, tmp_bd, hid_source_number); } bt_dinf.num_registered = MAX_BBMOTES; @@ -340,8 +345,45 @@ void BluetoothEmuDevice::Update() { g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth); g_controller_interface.UpdateInput(); - for (auto& wiimote : m_wiimotes) - wiimote->UpdateInput(); + + std::array wiimote_states; + std::array next_call; + + for (size_t i = 0; i < m_wiimotes.size(); ++i) + next_call[i] = m_wiimotes[i]->PrepareInput(&wiimote_states[i]); + + if (NetPlay::IsNetPlayRunning()) + { + std::array serialized; + std::array batch; + size_t batch_count = 0; + for (size_t i = 0; i < 4; ++i) + { + if (next_call[i] == WiimoteDevice::NextUpdateInputCall::None) + continue; + serialized[i] = WiimoteEmu::SerializeDesiredState(wiimote_states[i]); + batch[batch_count].state = &serialized[i]; + batch[batch_count].wiimote = static_cast(i); + ++batch_count; + } + + if (batch_count > 0) + { + NetPlay::NetPlay_GetWiimoteData( + std::span(batch.data(), batch_count)); + + for (size_t i = 0; i < batch_count; ++i) + { + const int wiimote = batch[i].wiimote; + if (!WiimoteEmu::DeserializeDesiredState(&wiimote_states[wiimote], serialized[wiimote])) + PanicAlertFmtT("Received invalid Wii Remote data from Netplay."); + } + } + } + + for (size_t i = 0; i < m_wiimotes.size(); ++i) + m_wiimotes[i]->UpdateInput(next_call[i], wiimote_states[i]); + m_last_ticks = now; } diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h index e3b649d252..038ed2f5e2 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h @@ -60,7 +60,7 @@ public: void DoState(PointerWrap& p) override; private: - std::vector> m_wiimotes; + std::array, MAX_BBMOTES> m_wiimotes; bdaddr_t m_controller_bd{{0x11, 0x02, 0x19, 0x79, 0x00, 0xff}}; diff --git a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp index 13b77eb1ad..5a59315606 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp @@ -21,6 +21,7 @@ #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h" +#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" #include "Core/Host.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/WiimoteHIDAttr.h" @@ -54,24 +55,28 @@ private: constexpr int CONNECTION_MESSAGE_TIME = 3000; -WiimoteDevice::WiimoteDevice(BluetoothEmuDevice* host, int number, bdaddr_t bd) +WiimoteDevice::WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int hid_source_number) : m_host(host), m_bd(bd), - m_name(number == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01") + m_name(GetNumber() == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01") { - INFO_LOG_FMT(IOS_WIIMOTE, "Wiimote: #{} Constructed", number); + INFO_LOG_FMT(IOS_WIIMOTE, "Wiimote: #{} Constructed", GetNumber()); - m_link_key.fill(0xa0 + number); + m_link_key.fill(0xa0 + GetNumber()); m_class = {0x00, 0x04, 0x48}; m_features = {0xBC, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00}; m_lmp_version = 0x2; m_lmp_subversion = 0x229; - const auto hid_source = WiimoteCommon::GetHIDWiimoteSource(GetNumber()); + const auto hid_source = WiimoteCommon::GetHIDWiimoteSource(hid_source_number); - // UGLY: This prevents an OSD message in SetSource -> Activate. if (hid_source) + { + hid_source->SetWiimoteDeviceIndex(GetNumber()); + + // UGLY: This prevents an OSD message in SetSource -> Activate. SetBasebandState(BasebandState::RequestConnection); + } SetSource(hid_source); } @@ -337,25 +342,51 @@ void WiimoteDevice::Update() } } -void WiimoteDevice::UpdateInput() +WiimoteDevice::NextUpdateInputCall +WiimoteDevice::PrepareInput(WiimoteEmu::DesiredWiimoteState* wiimote_state) { if (m_connection_request_counter) --m_connection_request_counter; if (!IsSourceValid()) - return; + return NextUpdateInputCall::None; - // Allow button press to trigger activation after a second of no connection activity. - if (!m_connection_request_counter && m_baseband_state == BasebandState::Inactive) + if (m_baseband_state == BasebandState::Inactive) { - if (Wiimote::NetPlay_GetButtonPress(GetNumber(), m_hid_source->IsButtonPressed())) - Activate(true); + // Allow button press to trigger activation after a second of no connection activity. + if (!m_connection_request_counter) + { + wiimote_state->buttons = m_hid_source->GetCurrentlyPressedButtons(); + return NextUpdateInputCall::Activate; + } + return NextUpdateInputCall::None; } // Verify interrupt channel is connected and configured. const auto* channel = FindChannelWithPSM(L2CAP_PSM_HID_INTR); if (channel && channel->IsComplete()) - m_hid_source->Update(); + { + m_hid_source->PrepareInput(wiimote_state); + return NextUpdateInputCall::Update; + } + return NextUpdateInputCall::None; +} + +void WiimoteDevice::UpdateInput(NextUpdateInputCall next_call, + const WiimoteEmu::DesiredWiimoteState& wiimote_state) +{ + switch (next_call) + { + case NextUpdateInputCall::Activate: + if (wiimote_state.buttons.hex & WiimoteCommon::ButtonData::BUTTON_MASK) + Activate(true); + break; + case NextUpdateInputCall::Update: + m_hid_source->Update(wiimote_state); + break; + default: + break; + } } // This function receives L2CAP commands from the CPU diff --git a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h index a65741d0d7..99ac995f78 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h @@ -13,6 +13,11 @@ class PointerWrap; +namespace WiimoteEmu +{ +struct DesiredWiimoteState; +} + namespace IOS::HLE { class BluetoothEmuDevice; @@ -24,7 +29,7 @@ public: using FeaturesType = std::array; using LinkKeyType = std::array; - WiimoteDevice(BluetoothEmuDevice* host, int number, bdaddr_t bd); + WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int hid_source_number); ~WiimoteDevice(); WiimoteDevice(const WiimoteDevice&) = delete; @@ -38,7 +43,15 @@ public: void Update(); // Called every ~200hz. - void UpdateInput(); + enum class NextUpdateInputCall + { + None, + Activate, + Update + }; + NextUpdateInputCall PrepareInput(WiimoteEmu::DesiredWiimoteState* wiimote_state); + void UpdateInput(NextUpdateInputCall next_call, + const WiimoteEmu::DesiredWiimoteState& wiimote_state); void DoState(PointerWrap& p); diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index b24334cb3e..013d7e4fe5 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -54,6 +56,7 @@ #include "Core/HW/Sram.h" #include "Core/HW/WiiSave.h" #include "Core/HW/WiiSaveStructs.h" +#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/IOS/FS/FileSystem.h" @@ -695,20 +698,29 @@ void NetPlayClient::OnPadHostData(sf::Packet& packet) void NetPlayClient::OnWiimoteData(sf::Packet& packet) { - PadIndex map; - WiimoteInput nw; - u8 size; + while (!packet.endOfPacket()) + { + PadIndex map; + packet >> map; - packet >> map >> nw.report_id >> size; + WiimoteEmu::SerializedWiimoteState pad; + packet >> pad.length; + ASSERT(pad.length <= pad.data.size()); + if (pad.length <= pad.data.size()) + { + for (size_t i = 0; i < pad.length; ++i) + packet >> pad.data[i]; + } + else + { + pad.length = 0; + } - nw.data.resize(size); - for (auto& byte : nw.data) - packet >> byte; - - // Trusting server for good map value (>=0 && <4) - // add to Wiimote buffer - m_wiimote_buffer.at(map).Push(nw); - m_wii_pad_event.Set(); + // Trusting server for good map value (>=0 && <4) + // add to pad buffer + m_wiimote_buffer.at(map).Push(pad); + m_wii_pad_event.Set(); + } } void NetPlayClient::OnPadBuffer(sf::Packet& packet) @@ -886,9 +898,6 @@ void NetPlayClient::OnStartGame(sf::Packet& packet) packet >> m_net_settings.save_data_region; packet >> m_net_settings.sync_codes; - for (int& extension : m_net_settings.wiimote_extension) - packet >> extension; - packet >> m_net_settings.golf_mode; packet >> m_net_settings.use_fma; packet >> m_net_settings.hide_remote_gbas; @@ -1619,15 +1628,14 @@ void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus } // called from ---CPU--- thread -void NetPlayClient::SendWiimoteState(const int in_game_pad, const WiimoteInput& nw) +void NetPlayClient::AddWiimoteStateToPacket(int in_game_pad, + const WiimoteEmu::SerializedWiimoteState& state, + sf::Packet& packet) { - sf::Packet packet; - packet << MessageID::WiimoteData; packet << static_cast(in_game_pad); - packet << static_cast(nw.report_id); - packet << static_cast(nw.data.size()); - packet.append(nw.data.data(), nw.data.size()); - SendAsync(std::move(packet)); + packet << state.length; + for (size_t i = 0; i < state.length; ++i) + packet << state.data[i]; } // called from ---GUI--- thread @@ -2042,79 +2050,42 @@ u64 NetPlayClient::GetInitialRTCValue() const return m_initial_rtc; } -bool NetPlayClient::WaitForWiimoteBuffer(int _number) -{ - while (m_wiimote_buffer[_number].Size() == 0) - { - if (!m_is_running.IsSet()) - { - return false; - } - - // wait for receiving thread to push some data - m_wii_pad_event.Wait(); - } - - return true; -} - // called from ---CPU--- thread -bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size, u8 reporting_mode) +bool NetPlayClient::WiimoteUpdate(const std::span& entries) { - WiimoteInput nw; - nw.report_id = reporting_mode; + for (const WiimoteDataBatchEntry& entry : entries) { - std::lock_guard lkp(m_crit.players); - - // Only send data, if this Wiimote is mapped to this player - if (m_wiimote_map[_number] == m_local_player->pid) + const int local_wiimote = InGameWiimoteToLocalWiimote(entry.wiimote); + DEBUG_LOG_FMT(NETPLAY, + "Entering WiimoteUpdate() with wiimote {}, local_wiimote {}, state [{:02x}]", + entry.wiimote, local_wiimote, + fmt::join(std::span(entry.state->data.data(), entry.state->length), ", ")); + if (local_wiimote < 4) { - nw.data.assign(data, data + size); + sf::Packet packet; + packet << MessageID::WiimoteData; + if (AddLocalWiimoteToBuffer(local_wiimote, *entry.state, packet)) + SendAsync(std::move(packet)); + } - // TODO: add a seperate setting for wiimote buffer? - while (m_wiimote_buffer[_number].Size() <= m_target_buffer_size * 200 / 120) + // Now, we either use the data pushed earlier, or wait for the + // other clients to send it to us + while (m_wiimote_buffer[entry.wiimote].Size() == 0) + { + if (!m_is_running.IsSet()) { - // add to buffer - m_wiimote_buffer[_number].Push(nw); - - SendWiimoteState(_number, nw); - } - } - - } // unlock players - - if (!WaitForWiimoteBuffer(_number)) - return false; - - m_wiimote_buffer[_number].Pop(nw); - - // If the reporting mode has changed, we just need to pop through the buffer, - // until we reach a good input - if (nw.report_id != reporting_mode) - { - u32 tries = 0; - while (nw.report_id != reporting_mode) - { - if (!WaitForWiimoteBuffer(_number)) return false; + } - m_wiimote_buffer[_number].Pop(nw); - - ++tries; - if (tries > m_target_buffer_size * 200 / 120) - break; + m_wii_pad_event.Wait(); } - // If it still mismatches, it surely desynced - if (nw.report_id != reporting_mode) - { - PanicAlertFmtT("Netplay has desynced. There is no way to recover from this."); - return false; - } + m_wiimote_buffer[entry.wiimote].Pop(*entry.state); + + DEBUG_LOG_FMT(NETPLAY, "Exiting WiimoteUpdate() with wiimote {}, state [{:02x}]", entry.wiimote, + fmt::join(std::span(entry.state->data.data(), entry.state->length), ", ")); } - ASSERT(nw.data.size() == size); - std::copy(nw.data.begin(), nw.data.end(), data); return true; } @@ -2171,6 +2142,28 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet) return data_added; } +bool NetPlayClient::AddLocalWiimoteToBuffer(const int local_wiimote, + const WiimoteEmu::SerializedWiimoteState& state, + sf::Packet& packet) +{ + const int ingame_pad = LocalWiimoteToInGameWiimote(local_wiimote); + bool data_added = false; + + // adjust the buffer either up or down + // inserting multiple padstates or dropping states + while (m_wiimote_buffer[ingame_pad].Size() <= m_target_buffer_size) + { + // add to buffer + m_wiimote_buffer[ingame_pad].Push(state); + + // add to packet + AddWiimoteStateToPacket(ingame_pad, state, packet); + data_added = true; + } + + return data_added; +} + void NetPlayClient::SendPadHostPoll(const PadIndex pad_num) { // Here we handle polling for the Host Input Authority and Golf modes. Pad data is "polled" from @@ -2328,32 +2321,43 @@ bool NetPlayClient::IsFirstInGamePad(int ingame_pad) const [](auto mapping) { return mapping > 0; }); } -int NetPlayClient::NumLocalPads() const +static int CountLocalPads(const PadMappingArray& pad_map, const PlayerId& local_player_pid) { - return static_cast(std::count_if(m_pad_map.begin(), m_pad_map.end(), [this](auto mapping) { - return mapping == m_local_player->pid; - })); + return static_cast( + std::count_if(pad_map.begin(), pad_map.end(), [&local_player_pid](const auto& mapping) { + return mapping == local_player_pid; + })); } -int NetPlayClient::InGamePadToLocalPad(int ingame_pad) const +int NetPlayClient::NumLocalPads() const +{ + return CountLocalPads(m_pad_map, m_local_player->pid); +} + +int NetPlayClient::NumLocalWiimotes() const +{ + return CountLocalPads(m_wiimote_map, m_local_player->pid); +} + +static int InGameToLocal(int ingame_pad, const PadMappingArray& pad_map, PlayerId local_player_pid) { // not our pad - if (m_pad_map[ingame_pad] != m_local_player->pid) + if (pad_map[ingame_pad] != local_player_pid) return 4; int local_pad = 0; int pad = 0; - for (; pad < ingame_pad; pad++) + for (; pad < ingame_pad; ++pad) { - if (m_pad_map[pad] == m_local_player->pid) + if (pad_map[pad] == local_player_pid) local_pad++; } return local_pad; } -int NetPlayClient::LocalPadToInGamePad(int local_pad) const +static int LocalToInGame(int local_pad, const PadMappingArray& pad_map, PlayerId local_player_pid) { // Figure out which in-game pad maps to which local pad. // The logic we have here is that the local slots always @@ -2362,7 +2366,7 @@ int NetPlayClient::LocalPadToInGamePad(int local_pad) const int ingame_pad = 0; for (; ingame_pad < 4; ingame_pad++) { - if (m_pad_map[ingame_pad] == m_local_player->pid) + if (pad_map[ingame_pad] == local_player_pid) local_pad_count++; if (local_pad_count == local_pad) @@ -2372,6 +2376,26 @@ int NetPlayClient::LocalPadToInGamePad(int local_pad) const return ingame_pad; } +int NetPlayClient::InGamePadToLocalPad(int ingame_pad) const +{ + return InGameToLocal(ingame_pad, m_pad_map, m_local_player->pid); +} + +int NetPlayClient::LocalPadToInGamePad(int local_pad) const +{ + return LocalToInGame(local_pad, m_pad_map, m_local_player->pid); +} + +int NetPlayClient::InGameWiimoteToLocalWiimote(int ingame_wiimote) const +{ + return InGameToLocal(ingame_wiimote, m_wiimote_map, m_local_player->pid); +} + +int NetPlayClient::LocalWiimoteToInGameWiimote(int local_wiimote) const +{ + return LocalToInGame(local_wiimote, m_wiimote_map, m_local_player->pid); +} + bool NetPlayClient::PlayerHasControllerMapped(const PlayerId pid) const { const auto mapping_matches_player_id = [pid](const PlayerId& mapping) { return mapping == pid; }; @@ -2385,6 +2409,11 @@ bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const return pid == m_local_player->pid; } +const PlayerId& NetPlayClient::GetLocalPlayerId() const +{ + return m_local_player->pid; +} + void NetPlayClient::SendGameStatus() { sf::Packet packet; @@ -2580,23 +2609,6 @@ void SendPowerButtonEvent() netplay_client->SendPowerButtonEvent(); } -void SetupWiimotes() -{ - ASSERT(IsNetPlayRunning()); - const NetSettings& netplay_settings = netplay_client->GetNetSettings(); - const PadMappingArray& wiimote_map = netplay_client->GetWiimoteMapping(); - for (size_t i = 0; i < netplay_settings.wiimote_extension.size(); i++) - { - if (wiimote_map[i] > 0) - { - static_cast( - static_cast(Wiimote::GetConfig()->GetController(int(i))) - ->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments)) - ->SetSelectedAttachment(netplay_settings.wiimote_extension[i]); - } - } -} - std::string GetGBASavePath(int pad_num) { std::lock_guard lk(crit_netplay_client); @@ -2652,6 +2664,14 @@ PadDetails GetPadDetails(int pad_num) return res; } +int NumLocalWiimotes() +{ + std::lock_guard lk(crit_netplay_client); + if (netplay_client) + return netplay_client->NumLocalWiimotes(); + return 0; +} + void NetPlay_Enable(NetPlayClient* const np) { std::lock_guard lk(crit_netplay_client); @@ -2679,37 +2699,51 @@ bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPa return false; } -bool WiimoteEmu::Wiimote::NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode) +bool NetPlay::NetPlay_GetWiimoteData(const std::span& entries) { - std::lock_guard lk(NetPlay::crit_netplay_client); + std::lock_guard lk(crit_netplay_client); - if (NetPlay::netplay_client) - return NetPlay::netplay_client->WiimoteUpdate(wiimote, data, size, reporting_mode); + if (netplay_client) + return netplay_client->WiimoteUpdate(entries); return false; } -// Sync the info whether a button was pressed or not. Used for the reconnect on button press feature -bool Wiimote::NetPlay_GetButtonPress(int wiimote, bool pressed) +unsigned int NetPlay::NetPlay_GetLocalWiimoteForSlot(unsigned int slot) { - std::lock_guard lk(NetPlay::crit_netplay_client); + if (slot >= std::tuple_size_v) + return slot; - // Use the reporting mode 0 for the button pressed event, the real ones start at RT_REPORT_CORE - static const u8 BUTTON_PRESS_REPORTING_MODE = 0; + std::lock_guard lk(crit_netplay_client); - if (NetPlay::netplay_client) + if (!netplay_client) + return slot; + + const auto& mapping = netplay_client->GetWiimoteMapping(); + const auto& local_player_id = netplay_client->GetLocalPlayerId(); + + std::array>> slot_map; + size_t player_count = 0; + for (size_t i = 0; i < mapping.size(); ++i) { - std::array data = {u8(pressed)}; - if (NetPlay::netplay_client->WiimoteUpdate(wiimote, data.data(), data.size(), - BUTTON_PRESS_REPORTING_MODE)) + if (mapping[i] == local_player_id) { - return data[0]; + slot_map[i] = static_cast(player_count); + ++player_count; + } + } + for (size_t i = 0; i < mapping.size(); ++i) + { + if (mapping[i] != local_player_id) + { + slot_map[i] = static_cast(player_count); + ++player_count; } - PanicAlertFmtT("Netplay has desynced in NetPlay_GetButtonPress()"); - return false; } - return pressed; + INFO_LOG_FMT(NETPLAY, "Wiimote slot map: [{}]", fmt::join(slot_map, ", ")); + + return slot_map[slot]; } // called from ---CPU--- thread diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 5e7fe3a3c5..dbd1095204 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,11 @@ namespace UICommon class GameFile; } +namespace WiimoteEmu +{ +struct SerializedWiimoteState; +} + namespace NetPlay { class NetPlayUI @@ -129,7 +135,12 @@ public: std::string GetCurrentGolfer(); // Send and receive pads values - bool WiimoteUpdate(int _number, u8* data, std::size_t size, u8 reporting_mode); + struct WiimoteDataBatchEntry + { + int wiimote; + WiimoteEmu::SerializedWiimoteState* state; + }; + bool WiimoteUpdate(const std::span& entries); bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status); u64 GetInitialRTCValue() const; @@ -140,13 +151,17 @@ public: bool IsFirstInGamePad(int ingame_pad) const; int NumLocalPads() const; + int NumLocalWiimotes() const; int InGamePadToLocalPad(int ingame_pad) const; - int LocalPadToInGamePad(int localPad) const; + int LocalPadToInGamePad(int local_pad) const; + int InGameWiimoteToLocalWiimote(int ingame_wiimote) const; + int LocalWiimoteToInGameWiimote(int local_wiimote) const; bool PlayerHasControllerMapped(PlayerId pid) const; bool LocalPlayerHasControllerMapped() const; bool IsLocalPlayer(PlayerId pid) const; + const PlayerId& GetLocalPlayerId() const; static void SendTimeBase(); bool DoAllPlayersHaveGame(); @@ -182,7 +197,7 @@ protected: Common::SPSCQueue m_async_queue; std::array, 4> m_pad_buffer; - std::array, 4> m_wiimote_buffer; + std::array, 4> m_wiimote_buffer; std::array m_last_pad_status{}; std::array m_first_pad_status_received{}; @@ -242,9 +257,13 @@ private: bool PollLocalPad(int local_pad, sf::Packet& packet); void SendPadHostPoll(PadIndex pad_num); + bool AddLocalWiimoteToBuffer(int local_wiimote, const WiimoteEmu::SerializedWiimoteState& state, + sf::Packet& packet); + void UpdateDevices(); void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet); - void SendWiimoteState(int in_game_pad, const WiimoteInput& nw); + void AddWiimoteStateToPacket(int in_game_pad, const WiimoteEmu::SerializedWiimoteState& np, + sf::Packet& packet); void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL); void Disconnect(); bool Connect(); @@ -253,8 +272,6 @@ private: void DisplayPlayersPing(); u32 GetPlayersMaxPing() const; - bool WaitForWiimoteBuffer(int _number); - void OnData(sf::Packet& packet); void OnPlayerJoin(sf::Packet& packet); void OnPlayerLeave(sf::Packet& packet); @@ -335,4 +352,6 @@ private: void NetPlay_Enable(NetPlayClient* const np); void NetPlay_Disable(); +bool NetPlay_GetWiimoteData(const std::span& entries); +unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot); } // namespace NetPlay diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index f1e109d633..f7137f66d8 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -101,7 +101,6 @@ struct NetSettings bool strict_settings_sync = false; bool sync_codes = false; std::string save_data_region; - std::array wiimote_extension{}; bool golf_mode = false; bool use_fma = false; bool hide_remote_gbas = false; @@ -228,11 +227,6 @@ enum : u8 CHANNEL_COUNT }; -struct WiimoteInput -{ - u8 report_id = 0; - std::vector data; -}; using PlayerId = u8; using FrameNum = u32; using PadIndex = s8; @@ -260,7 +254,7 @@ std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map, bool IsNetPlayRunning(); void SetSIPollBatching(bool state); void SendPowerButtonEvent(); -void SetupWiimotes(); std::string GetGBASavePath(int pad_num); PadDetails GetPadDetails(int pad_num); +int NumLocalWiimotes(); } // namespace NetPlay diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index c8567eb788..3d044e9121 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -52,6 +52,7 @@ #include "Core/HW/Sram.h" #include "Core/HW/WiiSave.h" #include "Core/HW/WiiSaveStructs.h" +#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/IOS/ES/ES.h" @@ -827,27 +828,33 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) if (player.current_game != m_current_game) break; - PadIndex map; - u8 size; - packet >> map >> size; - std::vector data(size); - for (u8& byte : data) - packet >> byte; - - // If the data is not from the correct player, - // then disconnect them. - if (m_wiimote_map.at(map) != player.pid) - { - return 1; - } - - // relay to clients sf::Packet spac; spac << MessageID::WiimoteData; - spac << map; - spac << size; - for (const u8& byte : data) - spac << byte; + + while (!packet.endOfPacket()) + { + PadIndex map; + packet >> map; + + // If the data is not from the correct player, + // then disconnect them. + if (m_wiimote_map.at(map) != player.pid) + { + return 1; + } + + WiimoteEmu::SerializedWiimoteState pad; + packet >> pad.length; + if (pad.length > pad.data.size()) + return 1; + for (size_t i = 0; i < pad.length; ++i) + packet >> pad.data[i]; + + spac << map; + spac << pad.length; + for (size_t i = 0; i < pad.length; ++i) + spac << pad.data[i]; + } SendToClients(spac, player.pid); } @@ -1518,16 +1525,6 @@ bool NetPlayServer::StartGame() spac << region; spac << m_settings.sync_codes; - for (size_t i = 0; i < m_settings.wiimote_extension.size(); i++) - { - const int extension = - static_cast( - static_cast(Wiimote::GetConfig()->GetController(int(i))) - ->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments)) - ->GetSelectedAttachment(); - spac << extension; - } - spac << m_settings.golf_mode; spac << m_settings.use_fma; spac << m_settings.hide_remote_gbas; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index e0c8baf5ab..b492166222 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -315,8 +315,10 @@ + + @@ -931,6 +933,7 @@ + diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp index 2f9fc53183..df7d66262f 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp @@ -18,6 +18,7 @@ #include "Core/Core.h" #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_Device.h" +#include "Core/NetPlayProto.h" #include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" @@ -60,11 +61,13 @@ static SerialInterface::SIDevices FromGCMenuIndex(const int menudevice) GamecubeControllersWidget::GamecubeControllersWidget(QWidget* parent) : QWidget(parent) { CreateLayout(); - LoadSettings(); ConnectWidgets(); connect(&Settings::Instance(), &Settings::ConfigChanged, this, - &GamecubeControllersWidget::LoadSettings); + [this] { LoadSettings(Core::GetState()); }); + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + [this](Core::State state) { LoadSettings(state); }); + LoadSettings(Core::GetState()); } void GamecubeControllersWidget::CreateLayout() @@ -160,8 +163,9 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index) window->show(); } -void GamecubeControllersWidget::LoadSettings() +void GamecubeControllersWidget::LoadSettings(Core::State state) { + const bool running = state != Core::State::Uninitialized; for (size_t i = 0; i < m_gc_groups.size(); i++) { const SerialInterface::SIDevices si_device = @@ -170,6 +174,7 @@ void GamecubeControllersWidget::LoadSettings() if (gc_index) { SignalBlocking(m_gc_controller_boxes[i])->setCurrentIndex(*gc_index); + m_gc_controller_boxes[i]->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true); OnGCTypeChanged(i); } } diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h index edaf405476..f4d1b69f60 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h @@ -13,6 +13,11 @@ class QGridLayout; class QGroupBox; class QPushButton; +namespace Core +{ +enum class State; +} + class GamecubeControllersWidget final : public QWidget { Q_OBJECT @@ -20,7 +25,7 @@ public: explicit GamecubeControllersWidget(QWidget* parent); private: - void LoadSettings(); + void LoadSettings(Core::State state); void SaveSettings(); void OnGCTypeChanged(size_t index); diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp index 6db5bc28ec..21b0fc1b33 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp @@ -27,6 +27,7 @@ #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/IOS/IOS.h" #include "Core/IOS/USB/Bluetooth/BTReal.h" +#include "Core/NetPlayProto.h" #include "Core/WiiUtils.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" @@ -286,6 +287,8 @@ void WiimoteControllersWidget::LoadSettings(Core::State state) const bool running_gc = running && !SConfig::GetInstance().bWii; const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc; const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc; + const bool is_netplay = NetPlay::IsNetPlayRunning(); + const bool running_netplay = running && is_netplay; m_wiimote_sync->setEnabled(enable_passthrough); m_wiimote_reset->setEnabled(enable_passthrough); @@ -293,17 +296,19 @@ void WiimoteControllersWidget::LoadSettings(Core::State state) for (auto* pt_label : m_wiimote_pt_labels) pt_label->setEnabled(enable_passthrough); + const int num_local_wiimotes = is_netplay ? NetPlay::NumLocalWiimotes() : 4; for (size_t i = 0; i < m_wiimote_groups.size(); i++) { m_wiimote_labels[i]->setEnabled(enable_emu_bt); - m_wiimote_boxes[i]->setEnabled(enable_emu_bt); + m_wiimote_boxes[i]->setEnabled(enable_emu_bt && !running_netplay); const bool is_emu_wiimote = m_wiimote_boxes[i]->currentIndex() == 1; - m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote); + m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote && + static_cast(i) < num_local_wiimotes); } - m_wiimote_real_balance_board->setEnabled(enable_emu_bt); - m_wiimote_speaker_data->setEnabled(enable_emu_bt); + m_wiimote_real_balance_board->setEnabled(enable_emu_bt && !running_netplay); + m_wiimote_speaker_data->setEnabled(enable_emu_bt && !running_netplay); const bool ciface_wiimotes = m_wiimote_ciface->isChecked(); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 0e833a7d2c..df7e7371ac 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -360,8 +360,6 @@ void MainWindow::InitCoreCallbacks() connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) { if (state == Core::State::Uninitialized) OnStopComplete(); - if (state != Core::State::Uninitialized && NetPlay::IsNetPlayRunning() && m_controllers_window) - m_controllers_window->reject(); if (state == Core::State::Running && m_fullscreen_requested) { diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index e1737f79d3..f901f3e726 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -131,9 +131,6 @@ void MenuBar::OnEmulationStateChanged(Core::State state) m_recording_play->setEnabled(m_game_selected && !running); m_recording_start->setEnabled((m_game_selected || running) && !Movie::IsPlayingInput()); - // Options - m_controllers_action->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true); - // JIT m_jit_interpreter_core->setEnabled(running); m_jit_block_linking->setEnabled(!running); diff --git a/Source/Core/DolphinQt/ToolBar.cpp b/Source/Core/DolphinQt/ToolBar.cpp index ad79c6f91a..49f384848d 100644 --- a/Source/Core/DolphinQt/ToolBar.cpp +++ b/Source/Core/DolphinQt/ToolBar.cpp @@ -61,7 +61,6 @@ void ToolBar::OnEmulationStateChanged(Core::State state) m_stop_action->setEnabled(running); m_fullscreen_action->setEnabled(running); m_screenshot_action->setEnabled(running); - m_controllers_action->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true); bool playing = running && state != Core::State::Paused; UpdatePausePlayButtonState(playing); @@ -130,7 +129,6 @@ void ToolBar::MakeActions() m_config_action = addAction(tr("Config"), this, &ToolBar::SettingsPressed); m_graphics_action = addAction(tr("Graphics"), this, &ToolBar::GraphicsPressed); m_controllers_action = addAction(tr("Controllers"), this, &ToolBar::ControllersPressed); - m_controllers_action->setEnabled(true); // Ensure every button has about the same width std::vector items; diff --git a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp index 6ab4e599cb..6b92bb9b16 100644 --- a/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Wiimote/WiimoteController.cpp @@ -1256,7 +1256,7 @@ void Device::IRState::ProcessData(const std::array& data // A better implementation might extrapolate points when they fall out of camera view. // But just averaging visible points actually seems to work very well. - using IRObject = WiimoteEmu::IRBasic::IRObject; + using IRObject = WiimoteEmu::IRObject; MathUtil::RunningVariance points;