Merge pull request #11070 from AdmiralCurtiss/netplay-wiimotes

Netplay: Redesign Wiimote data exchange.
This commit is contained in:
JMC47 2022-10-02 18:00:10 -04:00 committed by GitHub
commit 23806f8d60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1282 additions and 422 deletions

View File

@ -279,6 +279,8 @@ add_library(core
HW/WiimoteCommon/WiimoteReport.h HW/WiimoteCommon/WiimoteReport.h
HW/WiimoteEmu/Camera.cpp HW/WiimoteEmu/Camera.cpp
HW/WiimoteEmu/Camera.h HW/WiimoteEmu/Camera.h
HW/WiimoteEmu/DesiredWiimoteState.cpp
HW/WiimoteEmu/DesiredWiimoteState.h
HW/WiimoteEmu/Dynamics.cpp HW/WiimoteEmu/Dynamics.cpp
HW/WiimoteEmu/Dynamics.h HW/WiimoteEmu/Dynamics.h
HW/WiimoteEmu/EmuSubroutines.cpp HW/WiimoteEmu/EmuSubroutines.cpp
@ -286,6 +288,7 @@ add_library(core
HW/WiimoteEmu/Encryption.h HW/WiimoteEmu/Encryption.h
HW/WiimoteEmu/Extension/Classic.cpp HW/WiimoteEmu/Extension/Classic.cpp
HW/WiimoteEmu/Extension/Classic.h HW/WiimoteEmu/Extension/Classic.h
HW/WiimoteEmu/Extension/DesiredExtensionState.h
HW/WiimoteEmu/Extension/DrawsomeTablet.cpp HW/WiimoteEmu/Extension/DrawsomeTablet.cpp
HW/WiimoteEmu/Extension/DrawsomeTablet.h HW/WiimoteEmu/Extension/DrawsomeTablet.h
HW/WiimoteEmu/Extension/Drums.cpp HW/WiimoteEmu/Extension/Drums.cpp

View File

@ -498,9 +498,6 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
if (core_parameter.bWii && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED)) if (core_parameter.bWii && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED))
{ {
Wiimote::LoadConfig(); Wiimote::LoadConfig();
if (NetPlay::IsNetPlayRunning())
NetPlay::SetupWiimotes();
} }
FreeLook::LoadInputConfig(); FreeLook::LoadInputConfig();

View File

@ -94,8 +94,6 @@ ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(int number,
WiimoteEmu::DrawsomeTabletGroup group); WiimoteEmu::DrawsomeTabletGroup group);
ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGroup group); ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGroup group);
ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group); ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group);
bool NetPlay_GetButtonPress(int wiimote, bool pressed);
} // namespace Wiimote } // namespace Wiimote
namespace WiimoteReal namespace WiimoteReal

View File

@ -7,6 +7,11 @@
#include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h" #include "Core/HW/WiimoteCommon/WiimoteReport.h"
namespace WiimoteEmu
{
struct DesiredWiimoteState;
}
namespace WiimoteCommon namespace WiimoteCommon
{ {
// Source: HID_010_SPC_PFL/1.0 (official HID specification) // Source: HID_010_SPC_PFL/1.0 (official HID specification)
@ -30,8 +35,12 @@ public:
virtual void EventLinked() = 0; virtual void EventLinked() = 0;
virtual void EventUnlinked() = 0; virtual void EventUnlinked() = 0;
virtual u8 GetWiimoteDeviceIndex() const = 0;
virtual void SetWiimoteDeviceIndex(u8 index) = 0;
// Called every ~200hz after HID channels are established. // 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); } void SetInterruptCallback(InterruptCallbackType callback) { m_callback = std::move(callback); }
@ -39,8 +48,10 @@ public:
// Does not include HID-type header. // Does not include HID-type header.
virtual void InterruptDataOutput(const u8* data, u32 size) = 0; virtual void InterruptDataOutput(const u8* data, u32 size) = 0;
// Used to connect a disconnected wii remote on button press. // Get a snapshot of the current state of the Wiimote's buttons.
virtual bool IsButtonPressed() = 0; // 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: protected:
void InterruptDataInputCallback(const u8* data, u32 size) void InterruptDataInputCallback(const u8* data, u32 size)

View File

@ -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); return RawWrite(&m_reg_data, addr, count, data_in);
} }
void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_of_view) std::array<CameraPoint, CameraLogic::NUM_POINTS>
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::Matrix33;
using Common::Matrix44; using Common::Matrix44;
using Common::Vec3; using Common::Vec3;
using Common::Vec4; 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<Vec3, NUM_POINTS> leds{ const std::array<Vec3, NUM_POINTS> leds{
Vec3{-SENSOR_BAR_LED_SEPARATION / 2, 0, 0}, Vec3{-SENSOR_BAR_LED_SEPARATION / 2, 0, 0},
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::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; Matrix44::FromMatrix33(Matrix33::RotateX(float(MathUtil::TAU / 4))) * transform;
struct CameraPoint
{
IRBasic::IRObject position;
u8 size = 0;
};
std::array<CameraPoint, leds.size()> camera_points; std::array<CameraPoint, leds.size()> camera_points;
std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) { 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); 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) 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();
return CameraPoint{{0xffff, 0xffff}, 0xff};
}); });
return camera_points;
}
void CameraLogic::Update(const std::array<CameraPoint, NUM_POINTS>& 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) switch (m_reg_data.mode)
{ {
case IR_MODE_BASIC: case IR_MODE_BASIC:

View File

@ -16,11 +16,26 @@ class Matrix44;
namespace WiimoteEmu namespace WiimoteEmu
{ {
using IRObject = Common::TVec2<u16>;
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 // Four bytes for two objects. Filled with 0xFF if empty
struct IRBasic struct IRBasic
{ {
using IRObject = Common::TVec2<u16>;
u8 x1; u8 x1;
u8 y1; u8 y1;
u8 x2hi : 2; u8 x2hi : 2;
@ -59,8 +74,8 @@ struct IRExtended
u8 xhi : 2; u8 xhi : 2;
u8 yhi : 2; u8 yhi : 2;
auto GetPosition() const { return IRBasic::IRObject(xhi << 8 | x, yhi << 8 | y); } auto GetPosition() const { return IRObject(xhi << 8 | x, yhi << 8 | y); }
void SetPosition(const IRBasic::IRObject& obj) void SetPosition(const IRObject& obj)
{ {
x = obj.x; x = obj.x;
xhi = obj.x >> 8; xhi = obj.x >> 8;
@ -109,9 +124,19 @@ public:
IR_MODE_FULL = 5, 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 Reset();
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
void Update(const Common::Matrix44& transform, Common::Vec2 field_of_view); static std::array<CameraPoint, NUM_POINTS> GetCameraPoints(const Common::Matrix44& transform,
Common::Vec2 field_of_view);
void Update(const std::array<CameraPoint, NUM_POINTS>& camera_points);
void SetEnabled(bool is_enabled); void SetEnabled(bool is_enabled);
static constexpr u8 I2C_ADDR = 0x58; static constexpr u8 I2C_ADDR = 0x58;

View File

@ -0,0 +1,328 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstring>
#include <optional>
#include <type_traits>
#include <variant>
#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<DesiredExtensionState::ExtensionData> <= (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<decltype(arg)>;
if constexpr (!std::is_same_v<std::monostate, T>)
{
static_assert(sizeof(arg) <= 6);
static_assert(std::is_trivially_copyable_v<T>);
std::memcpy(&s.data[s.length], &arg, sizeof(arg));
s.length += sizeof(arg);
}
},
state.extension.data);
}
return s;
}
template <typename T>
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<T>();
static_assert(std::is_trivially_copyable_v<T>);
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<u16>((accel_y_high << 1) | Common::ExtractBit<2>(accel_low), 1);
state->acceleration.value.z =
Common::ExpandValue<u16>((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<Nunchuk::DataFormat>(state, serialized, pos);
case ExtensionNumber::CLASSIC:
return DeserializeExtensionState<Classic::DataFormat>(state, serialized, pos);
case ExtensionNumber::GUITAR:
return DeserializeExtensionState<Guitar::DataFormat>(state, serialized, pos);
case ExtensionNumber::DRUMS:
return DeserializeExtensionState<Drums::DesiredState>(state, serialized, pos);
case ExtensionNumber::TURNTABLE:
return DeserializeExtensionState<Turntable::DataFormat>(state, serialized, pos);
case ExtensionNumber::UDRAW_TABLET:
return DeserializeExtensionState<UDrawTablet::DataFormat>(state, serialized, pos);
case ExtensionNumber::DRAWSOME_TABLET:
return DeserializeExtensionState<DrawsomeTablet::DataFormat>(state, serialized, pos);
case ExtensionNumber::TATACON:
return DeserializeExtensionState<TaTaCon::DataFormat>(state, serialized, pos);
case ExtensionNumber::SHINKANSEN:
return DeserializeExtensionState<Shinkansen::DesiredState>(state, serialized, pos);
default:
break;
}
return false;
}
} // namespace WiimoteEmu

View File

@ -0,0 +1,42 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#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<CameraPoint, 2> DEFAULT_CAMERA = {CameraPoint(), CameraPoint()};
WiimoteCommon::ButtonData buttons{}; // non-button state in this is ignored
WiimoteCommon::AccelData acceleration = DEFAULT_ACCELERATION;
std::array<CameraPoint, 2> camera_points = DEFAULT_CAMERA;
std::optional<MotionPlus::DataFormat::Data> motion_plus = std::nullopt;
DesiredExtensionState extension;
};
// For Netplay.
struct SerializedWiimoteState
{
u8 length;
std::array<u8, 24> data; // 12 bytes Wiimote, 6 bytes MotionPlus, 6 bytes Extension
};
SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state);
bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimoteState& serialized);
} // namespace WiimoteEmu

View File

@ -142,7 +142,8 @@ void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code)
InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); 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) if (WIIMOTE_BALANCE_BOARD == m_index)
{ {
@ -151,15 +152,13 @@ void Wiimote::HandleExtensionSwap()
return; return;
} }
ExtensionNumber desired_extension_number =
static_cast<ExtensionNumber>(m_attachments->GetSelectedAttachment());
const bool desired_motion_plus = m_motion_plus_setting.GetValue();
// FYI: AttachExtension also connects devices to the i2c bus // FYI: AttachExtension also connects devices to the i2c bus
if (m_is_motion_plus_attached && !desired_motion_plus) 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+ is attached and it's not wanted, so remove it.
m_extension_port.AttachExtension(GetNoneExtension()); m_extension_port.AttachExtension(GetNoneExtension());
m_is_motion_plus_attached = false; m_is_motion_plus_attached = false;
@ -184,6 +183,9 @@ void Wiimote::HandleExtensionSwap()
} }
else else
{ {
INFO_LOG_FMT(WIIMOTE, "Attaching Motion Plus (Wiimote {} in slot {})", m_index,
m_bt_device_index);
// No extension attached so attach M+. // No extension attached so attach M+.
m_is_motion_plus_attached = true; m_is_motion_plus_attached = true;
m_extension_port.AttachExtension(&m_motion_plus); 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) // A different extension is wanted (either by user or by the M+ logic above)
if (GetActiveExtensionNumber() != ExtensionNumber::NONE) 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. // First we must detach the current extension.
// The next call will change to the new extension if needed. // The next call will change to the new extension if needed.
m_active_extension = ExtensionNumber::NONE; m_active_extension = ExtensionNumber::NONE;
} }
else 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; m_active_extension = desired_extension_number;
} }

View File

@ -10,6 +10,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.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 = {}; DataFormat classic_data = {};
@ -149,7 +151,12 @@ void Classic::Update()
classic_data.SetButtons(buttons); classic_data.SetButtons(buttons);
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data; target_state->data = classic_data;
}
void Classic::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void Classic::Reset() void Classic::Reset()

View File

@ -178,7 +178,8 @@ public:
Classic(); Classic();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(ClassicGroup group); ControllerEmu::ControlGroup* GetGroup(ClassicGroup group);

View File

@ -0,0 +1,76 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include <variant>
#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<std::monostate, Nunchuk::DataFormat, Classic::DataFormat, Guitar::DataFormat,
Drums::DesiredState, Turntable::DataFormat, UDrawTablet::DataFormat,
DrawsomeTablet::DataFormat, TaTaCon::DataFormat, Shinkansen::DesiredState>;
ExtensionData data = std::monostate();
static_assert(std::is_same_v<std::monostate,
std::variant_alternative_t<ExtensionNumber::NONE, ExtensionData>>);
static_assert(
std::is_same_v<Nunchuk::DataFormat,
std::variant_alternative_t<ExtensionNumber::NUNCHUK, ExtensionData>>);
static_assert(
std::is_same_v<Classic::DataFormat,
std::variant_alternative_t<ExtensionNumber::CLASSIC, ExtensionData>>);
static_assert(std::is_same_v<Guitar::DataFormat,
std::variant_alternative_t<ExtensionNumber::GUITAR, ExtensionData>>);
static_assert(std::is_same_v<Drums::DesiredState,
std::variant_alternative_t<ExtensionNumber::DRUMS, ExtensionData>>);
static_assert(
std::is_same_v<Turntable::DataFormat,
std::variant_alternative_t<ExtensionNumber::TURNTABLE, ExtensionData>>);
static_assert(
std::is_same_v<UDrawTablet::DataFormat,
std::variant_alternative_t<ExtensionNumber::UDRAW_TABLET, ExtensionData>>);
static_assert(
std::is_same_v<DrawsomeTablet::DataFormat,
std::variant_alternative_t<ExtensionNumber::DRAWSOME_TABLET, ExtensionData>>);
static_assert(
std::is_same_v<TaTaCon::DataFormat,
std::variant_alternative_t<ExtensionNumber::TATACON, ExtensionData>>);
static_assert(
std::is_same_v<Shinkansen::DesiredState,
std::variant_alternative_t<ExtensionNumber::SHINKANSEN, ExtensionData>>);
static_assert(std::variant_size_v<DesiredExtensionState::ExtensionData> == ExtensionNumber::MAX);
};
template <typename T>
void DefaultExtensionUpdate(EncryptedExtension::Register* reg,
const DesiredExtensionState& target_state)
{
if (std::holds_alternative<T>(target_state.data))
{
Common::BitCastPtr<T>(&reg->controller_data) = std::get<T>(target_state.data);
}
else
{
Common::BitCastPtr<T>(&reg->controller_data) = T{};
}
}
} // namespace WiimoteEmu

View File

@ -9,6 +9,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h" #include "InputCommon/ControllerEmu/Control/Input.h"
@ -31,9 +33,9 @@ DrawsomeTablet::DrawsomeTablet() : Extension3rdParty("Drawsome", _trans("Drawsom
m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure")); 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<DataFormat>();
// Stylus X/Y (calibrated values): // Stylus X/Y (calibrated values):
constexpr u16 MIN_X = 0x0000; constexpr u16 MIN_X = 0x0000;
@ -77,8 +79,11 @@ void DrawsomeTablet::Update()
tablet_data.pressure1 = u8(pressure); tablet_data.pressure1 = u8(pressure);
tablet_data.pressure2 = u8(pressure >> 8); tablet_data.pressure2 = u8(pressure >> 8);
}
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data; void DrawsomeTablet::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void DrawsomeTablet::Reset() void DrawsomeTablet::Reset()

View File

@ -27,7 +27,8 @@ class DrawsomeTablet : public Extension3rdParty
public: public:
DrawsomeTablet(); DrawsomeTablet();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(DrawsomeTabletGroup group); ControllerEmu::ControlGroup* GetGroup(DrawsomeTabletGroup group);

View File

@ -9,6 +9,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h" #include "InputCommon/ControllerEmu/Control/Input.h"
@ -77,8 +79,43 @@ Drums::Drums() : Extension1stParty("Drums", _trans("Drum Kit"))
m_buttons->AddInput(ControllerEmu::DoNotTranslate, "+"); m_buttons->AddInput(ControllerEmu::DoNotTranslate, "+");
} }
void Drums::Update() void Drums::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{ {
DesiredState& state = target_state->data.emplace<DesiredState>();
{
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<DesiredState>(target_state.data))
{
desired_state = std::get<DesiredState>(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 = {}; DataFormat drum_data = {};
// The meaning of these bits are unknown but they are usually set. // 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.no_velocity_data_2 = 1;
drum_data.softness = 7; drum_data.softness = 7;
// Stick. drum_data.stick_x = desired_state.stick_x;
{ drum_data.stick_y = desired_state.stick_y;
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState(); drum_data.buttons = desired_state.buttons;
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 pads. // Drum pads.
u8 current_pad_input = 0; u8 current_pad_input = desired_state.drum_pads;
m_pads->GetState(&current_pad_input, drum_pad_bitmasks.data());
m_new_pad_hits |= ~m_prev_pad_input & current_pad_input; m_new_pad_hits |= ~m_prev_pad_input & current_pad_input;
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_1 = 0;
drum_data.no_velocity_data_2 = 0; drum_data.no_velocity_data_2 = 0;
// Set softness from user-configured hit strength setting. drum_data.softness = desired_state.softness;
drum_data.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));
// A drum-pad hit causes the relevent bit to be triggered for the next 10 frames. // A drum-pad hit causes the relevent bit to be triggered for the next 10 frames.
constexpr u8 HIT_FRAME_COUNT = 10; constexpr u8 HIT_FRAME_COUNT = 10;

View File

@ -28,6 +28,15 @@ enum class DrumsGroup
class Drums : public Extension1stParty class Drums : public Extension1stParty
{ {
public: 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 struct DataFormat
{ {
u8 stick_x : 6; u8 stick_x : 6;
@ -77,7 +86,8 @@ public:
Drums(); Drums();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(DrumsGroup group); ControllerEmu::ControlGroup* GetGroup(DrumsGroup group);

View File

@ -9,6 +9,8 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Inline.h" #include "Common/Inline.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -43,7 +45,12 @@ bool None::ReadDeviceDetectPin() const
return false; return false;
} }
void None::Update() void None::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
target_state->data.emplace<std::monostate>();
}
void None::Update(const DesiredExtensionState& target_state)
{ {
// Nothing needed. // Nothing needed.
} }

View File

@ -16,6 +16,8 @@
namespace WiimoteEmu namespace WiimoteEmu
{ {
struct DesiredExtensionState;
class Extension : public ControllerEmu::EmulatedController, public I2CSlave class Extension : public ControllerEmu::EmulatedController, public I2CSlave
{ {
public: public:
@ -32,7 +34,8 @@ public:
virtual void Reset() = 0; virtual void Reset() = 0;
virtual void DoState(PointerWrap& p) = 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: private:
const char* const m_config_name; const char* const m_config_name;
@ -46,7 +49,8 @@ public:
private: private:
bool ReadDeviceDetectPin() const override; bool ReadDeviceDetectPin() const override;
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
@ -67,7 +71,6 @@ public:
// TODO: TAS handles encryption poorly. // TODO: TAS handles encryption poorly.
EncryptionKey ext_key; EncryptionKey ext_key;
protected:
static constexpr int CALIBRATION_CHECKSUM_BYTES = 2; static constexpr int CALIBRATION_CHECKSUM_BYTES = 2;
#pragma pack(push, 1) #pragma pack(push, 1)
@ -97,6 +100,7 @@ protected:
static_assert(0x100 == sizeof(Register)); static_assert(0x100 == sizeof(Register));
protected:
Register m_reg = {}; Register m_reg = {};
void Reset() override; void Reset() override;

View File

@ -11,6 +11,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.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"))); groups.emplace_back(m_slider_bar = new ControllerEmu::Slider(_trans("Slider Bar")));
} }
void Guitar::Update() void Guitar::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{ {
DataFormat guitar_data = {}; DataFormat guitar_data = {};
@ -135,7 +137,12 @@ void Guitar::Update()
// flip button bits // flip button bits
guitar_data.bt ^= 0xFFFF; guitar_data.bt ^= 0xFFFF;
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = guitar_data; target_state->data = guitar_data;
}
void Guitar::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void Guitar::Reset() void Guitar::Reset()

View File

@ -50,7 +50,8 @@ public:
Guitar(); Guitar();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(GuitarGroup group); ControllerEmu::ControlGroup* GetGroup(GuitarGroup group);

View File

@ -12,7 +12,9 @@
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/MathUtil.h" #include "Common/MathUtil.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h" #include "InputCommon/ControllerEmu/Control/Input.h"
@ -60,7 +62,7 @@ Nunchuk::Nunchuk() : Extension1stParty(_trans("Nunchuk"))
"IMUAccelerometer", _trans("Accelerometer"))); "IMUAccelerometer", _trans("Accelerometer")));
} }
void Nunchuk::Update() void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{ {
DataFormat nc_data = {}; DataFormat nc_data = {};
@ -110,7 +112,12 @@ void Nunchuk::Update()
const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2); const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
nc_data.SetAccel(acc.value); nc_data.SetAccel(acc.value);
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data; target_state->data = nc_data;
}
void Nunchuk::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void Nunchuk::Reset() void Nunchuk::Reset()

View File

@ -149,7 +149,8 @@ public:
Nunchuk(); Nunchuk();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;

View File

@ -8,6 +8,8 @@
#include "Common/Assert.h" #include "Common/Assert.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.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")); 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<DesiredState>();
u16 digital = 0; u16 digital = 0;
const u16 lever_bitmasks[2] = {}; const u16 lever_bitmasks[2] = {};
@ -62,8 +64,8 @@ void Shinkansen::Update()
// guesses). // guesses).
const u8 brake_values[] = {0, 53, 79, 105, 132, 159, 187, 217, 250}; 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}; 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))]; state.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.power = power_values[size_t(analog[1] * (sizeof(power_values) - 1))];
// Note: This currently assumes a little-endian host. // Note: This currently assumes a little-endian host.
const u16 button_bitmasks[] = { const u16 button_bitmasks[] = {
@ -78,8 +80,27 @@ void Shinkansen::Update()
0x0010, // Select 0x0010, // Select
0x0004, // Start 0x0004, // Start
}; };
m_buttons->GetState(&ext_data.buttons, button_bitmasks); m_buttons->GetState(&state.buttons, button_bitmasks);
ext_data.buttons ^= 0xFFFF; }
void Shinkansen::Update(const DesiredExtensionState& target_state)
{
DesiredState desired_state;
if (std::holds_alternative<DesiredState>(target_state.data))
{
desired_state = std::get<DesiredState>(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<DataFormat>(&m_reg.controller_data) = ext_data; Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = ext_data;
const auto lock = GetStateLock(); const auto lock = GetStateLock();

View File

@ -24,9 +24,17 @@ enum class ShinkansenGroup
class Shinkansen : public Extension3rdParty class Shinkansen : public Extension3rdParty
{ {
public: public:
struct DesiredState
{
u8 brake;
u8 power;
u16 buttons;
};
Shinkansen(); Shinkansen();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(ShinkansenGroup group); ControllerEmu::ControlGroup* GetGroup(ShinkansenGroup group);

View File

@ -10,6 +10,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h" #include "InputCommon/ControllerEmu/Control/Input.h"
@ -48,7 +50,7 @@ TaTaCon::TaTaCon() : Extension3rdParty("TaTaCon", _trans("Taiko Drum"))
m_rim->AddInput(ControllerEmu::Translate, name); m_rim->AddInput(ControllerEmu::Translate, name);
} }
void TaTaCon::Update() void TaTaCon::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{ {
DataFormat tatacon_data = {}; DataFormat tatacon_data = {};
@ -58,7 +60,12 @@ void TaTaCon::Update()
// Flip button bits. // Flip button bits.
tatacon_data.state ^= 0xff; tatacon_data.state ^= 0xff;
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tatacon_data; target_state->data = tatacon_data;
}
void TaTaCon::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void TaTaCon::Reset() void TaTaCon::Reset()

View File

@ -31,7 +31,8 @@ public:
TaTaCon(); TaTaCon();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(TaTaConGroup group); ControllerEmu::ControlGroup* GetGroup(TaTaConGroup group);

View File

@ -10,6 +10,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.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"))); groups.emplace_back(m_crossfade = new ControllerEmu::Slider(_trans("Crossfade")));
} }
void Turntable::Update() void Turntable::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{ {
DataFormat tt_data = {}; 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 | 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); BUTTON_R_BLUE | BUTTON_MINUS | BUTTON_PLUS | BUTTON_EUPHORIA);
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tt_data; target_state->data = tt_data;
}
void Turntable::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void Turntable::Reset() void Turntable::Reset()

View File

@ -56,7 +56,8 @@ public:
Turntable(); Turntable();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(TurntableGroup group); ControllerEmu::ControlGroup* GetGroup(TurntableGroup group);

View File

@ -9,6 +9,8 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.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")); m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure"));
} }
void UDrawTablet::Update() void UDrawTablet::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{ {
DataFormat tablet_data = {}; DataFormat tablet_data = {};
@ -105,7 +107,12 @@ void UDrawTablet::Update()
// Always 0xff // Always 0xff
tablet_data.unk = 0xff; tablet_data.unk = 0xff;
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data; target_state->data = tablet_data;
}
void UDrawTablet::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
} }
void UDrawTablet::Reset() void UDrawTablet::Reset()

View File

@ -27,7 +27,8 @@ class UDrawTablet : public Extension3rdParty
public: public:
UDrawTablet(); UDrawTablet();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
ControllerEmu::ControlGroup* GetGroup(UDrawTabletGroup group); ControllerEmu::ControlGroup* GetGroup(UDrawTabletGroup group);

View File

@ -18,6 +18,7 @@
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/Dynamics.h" #include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
namespace 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) if (m_progress_timer)
--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. // This is something that is triggered by a read of 0x00 on real hardware.
// But we do it here for determinism reasons. // 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) if (GetActivationStatus() != ActivationStatus::Active)
return; 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 the above logic determined this should be M+ data, update it here.
if (mplus_data.is_mp_data) 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. mplus_data.yaw_slow = u8(yaw_slow);
constexpr float VALUE_SCALE = mplus_data.roll_slow = u8(roll_slow);
(CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)) / mplus_data.pitch_slow = u8(pitch_slow);
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);
// Bits 0-7 // Bits 0-7
mplus_data.yaw1 = u8(yaw_value); mplus_data.yaw1 = u8(yaw_value);

View File

@ -118,14 +118,18 @@ public:
MotionPlus(); MotionPlus();
void Update() override; void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override; void Reset() override;
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
ExtensionPort& GetExtPort(); ExtensionPort& GetExtPort();
// Vec3 is interpreted as radians/s about the x,y,z axes following the "right-hand rule". // 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. // Pointer to 6 bytes is expected.
static void ApplyPassthroughModifications(PassthroughMode, u8* data); static void ApplyPassthroughModifications(PassthroughMode, u8* data);
@ -218,6 +222,10 @@ private:
static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0; static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0;
static constexpr u16 CALIBRATION_SLOW_SCALE_DEGREES = 0x10e; 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 Activate();
void Deactivate(); void Deactivate();
void OnPassthroughModeWrite(); void OnPassthroughModeWrite();

View File

@ -21,11 +21,12 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/Movie.h" #include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.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/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h" #include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h"
#include "Core/HW/WiimoteEmu/Extension/Drums.h" #include "Core/HW/WiimoteEmu/Extension/Drums.h"
#include "Core/HW/WiimoteEmu/Extension/Guitar.h" #include "Core/HW/WiimoteEmu/Extension/Guitar.h"
@ -68,6 +69,8 @@ constexpr std::array<std::string_view, 7> named_buttons{
void Wiimote::Reset() void Wiimote::Reset()
{ {
const bool want_determinism = Core::WantsDeterminism();
SetRumble(false); SetRumble(false);
// Wiimote starts in non-continuous CORE mode: // Wiimote starts in non-continuous CORE mode:
@ -77,8 +80,12 @@ void Wiimote::Reset()
m_speaker_mute = false; m_speaker_mute = false;
// EEPROM // 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"); 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 // Write out existing EEPROM
INFO_LOG_FMT(WIIMOTE, "Wrote EEPROM for {}", GetName()); INFO_LOG_FMT(WIIMOTE, "Wrote EEPROM for {}", GetName());
@ -91,7 +98,7 @@ void Wiimote::Reset()
} }
m_eeprom = {}; m_eeprom = {};
if (File::Exists(eeprom_file)) if (!want_determinism && File::Exists(eeprom_file))
{ {
// Read existing EEPROM // Read existing EEPROM
std::ifstream file; std::ifstream file;
@ -171,18 +178,26 @@ void Wiimote::Reset()
m_extension_port.AttachExtension(GetNoneExtension()); m_extension_port.AttachExtension(GetNoneExtension());
m_motion_plus.GetExtPort().AttachExtension(GetNoneExtension()); m_motion_plus.GetExtPort().AttachExtension(GetNoneExtension());
// Switch to desired M+ status and extension (if any). if (!want_determinism)
// M+ and EXT are reset on attachment. {
HandleExtensionSwap(); // Switch to desired M+ status and extension (if any).
// M+ and EXT are reset on attachment.
HandleExtensionSwap(static_cast<ExtensionNumber>(m_attachments->GetSelectedAttachment()),
m_motion_plus_setting.GetValue());
}
// Reset sub-devices. // Reset sub-devices.
m_speaker_logic.Reset(); m_speaker_logic.Reset();
m_camera_logic.Reset(); m_camera_logic.Reset();
m_status = {}; 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. if (!want_determinism)
m_status.extension = m_extension_port.IsDeviceConnected(); {
// 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: // Dynamics:
m_swing_state = {}; m_swing_state = {};
@ -193,7 +208,7 @@ void Wiimote::Reset()
m_imu_cursor_state = {}; 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 // Buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons"))); groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@ -427,20 +442,13 @@ bool Wiimote::ProcessExtensionPortEvent()
return true; return true;
} }
// Update buttons in status struct from user input. void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state)
void Wiimote::UpdateButtonsStatus()
{ {
m_status.buttons.hex = 0; m_status.buttons.hex = target_state.buttons.hex & ButtonData::BUTTON_MASK;
m_buttons->GetState(&m_status.buttons.hex, button_bitmasks);
m_dpad->GetState(&m_status.buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks);
} }
// This is called every ::Wiimote::UPDATE_FREQ (200hz) void Wiimote::BuildDesiredWiimoteState(DesiredWiimoteState* target_state)
void Wiimote::Update()
{ {
const auto lock = GetStateLock();
// Hotkey / settings modifier // Hotkey / settings modifier
// Data is later accessed in IsSideways and IsUpright // Data is later accessed in IsSideways and IsUpright
m_hotkeys->UpdateState(); m_hotkeys->UpdateState();
@ -448,26 +456,70 @@ void Wiimote::Update()
// Update our motion simulations. // Update our motion simulations.
StepDynamics(); 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<Extension*>(
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. // Update buttons in the status struct which is sent in 99% of input reports.
// FYI: Movies only sync button updates in data reports. UpdateButtonsStatus(target_state);
if (!Core::WantsDeterminism())
{
UpdateButtonsStatus();
}
// If a new extension is requested in the GUI the change will happen here. // If a new extension is requested in the GUI the change will happen here.
HandleExtensionSwap(); HandleExtensionSwap(static_cast<ExtensionNumber>(target_state.extension.data.index()),
target_state.motion_plus.has_value());
// Allow extension to perform any regular duties it may need. // Prepare input data of the extension for reading.
// (e.g. Nunchuk motion simulation step) GetActiveExtension()->Update(target_state.extension);
// Input is prepared here too.
// TODO: Separate input preparation from Update.
GetActiveExtension()->Update();
if (m_is_motion_plus_attached) if (m_is_motion_plus_attached)
{ {
// M+ has some internal state that must processed. // 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. // Returns true if a report was sent.
@ -485,10 +537,10 @@ void Wiimote::Update()
return; return;
} }
SendDataReport(); SendDataReport(target_state);
} }
void Wiimote::SendDataReport() void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
{ {
Movie::SetPolledDevice(); Movie::SetPolledDevice();
@ -508,7 +560,8 @@ void Wiimote::SendDataReport()
DataReportBuilder rpt_builder(m_reporting_mode); DataReportBuilder rpt_builder(m_reporting_mode);
if (Movie::IsPlayingInput() && 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: // Update buttons in status struct from movie:
rpt_builder.GetCoreData(&m_status.buttons); rpt_builder.GetCoreData(&m_status.buttons);
@ -518,22 +571,13 @@ void Wiimote::SendDataReport()
// Core buttons: // Core buttons:
if (rpt_builder.HasCore()) 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); rpt_builder.SetCoreData(m_status.buttons);
} }
// Acceleration: // Acceleration:
if (rpt_builder.HasAccel()) if (rpt_builder.HasAccel())
{ {
// Calibration values are 8-bit but we want 10-bit precision, so << 2. rpt_builder.SetAccelData(target_state.acceleration);
AccelData accel =
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
rpt_builder.SetAccelData(accel);
} }
// IR Camera: // IR Camera:
@ -541,9 +585,7 @@ void Wiimote::SendDataReport()
{ {
// Note: Camera logic currently contains no changing state so we can just update it here. // 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(); // If that changes this should be moved to Wiimote::Update();
m_camera_logic.Update(GetTotalTransformation(), m_camera_logic.Update(target_state.camera_points);
Common::Vec2(m_fov_x_setting.GetValue(), m_fov_y_setting.GetValue()) /
360 * float(MathUtil::TAU));
// The real wiimote reads camera data from the i2c bus starting at offset 0x37: // The real wiimote reads camera data from the i2c bus starting at offset 0x37:
const u8 camera_data_offset = const u8 camera_data_offset =
@ -571,7 +613,9 @@ void Wiimote::SendDataReport()
if (m_is_motion_plus_attached) if (m_is_motion_plus_attached)
{ {
// TODO: Make input preparation triggered by bus read. // 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(); 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()) Movie::CheckWiimoteStatus(m_bt_device_index, rpt_builder, m_active_extension,
{ GetExtensionEncryptionKey());
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());
// Send the report: // Send the report:
InterruptDataInputCallback(rpt_builder.GetDataPtr(), rpt_builder.GetDataSize()); InterruptDataInputCallback(rpt_builder.GetDataPtr(), rpt_builder.GetDataSize());
@ -609,14 +646,15 @@ void Wiimote::SendDataReport()
m_reporting_mode = InputReportID::ReportInterleave1; m_reporting_mode = InputReportID::ReportInterleave1;
} }
bool Wiimote::IsButtonPressed() ButtonData Wiimote::GetCurrentlyPressedButtons()
{ {
u16 buttons = 0;
const auto lock = GetStateLock(); 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) void Wiimote::LoadDefaults(const ControllerInterface& ciface)

View File

@ -37,6 +37,9 @@ class Tilt;
namespace WiimoteEmu namespace WiimoteEmu
{ {
struct DesiredWiimoteState;
struct DesiredExtensionState;
enum class WiimoteGroup enum class WiimoteGroup
{ {
Buttons, Buttons,
@ -126,11 +129,15 @@ public:
ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const; ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const;
ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup 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 EventLinked() override;
void EventUnlinked() override; void EventUnlinked() override;
void InterruptDataOutput(const u8* data, u32 size) override; void InterruptDataOutput(const u8* data, u32 size) override;
bool IsButtonPressed() override; WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
void Reset(); void Reset();
@ -150,7 +157,8 @@ private:
void RefreshConfig(); void RefreshConfig();
void StepDynamics(); void StepDynamics();
void UpdateButtonsStatus(); void UpdateButtonsStatus(const DesiredWiimoteState& target_state);
void BuildDesiredWiimoteState(DesiredWiimoteState* target_state);
// Returns simulated accelerometer data in m/s^2. // Returns simulated accelerometer data in m/s^2.
Common::Vec3 GetAcceleration( Common::Vec3 GetAcceleration(
@ -187,9 +195,9 @@ private:
template <typename T, typename H> template <typename T, typename H>
void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size); void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size);
void HandleExtensionSwap(); void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus);
bool ProcessExtensionPortEvent(); bool ProcessExtensionPortEvent();
void SendDataReport(); void SendDataReport(const DesiredWiimoteState& target_state);
bool ProcessReadDataRequest(); bool ProcessReadDataRequest();
void SetRumble(bool on); void SetRumble(bool on);
@ -202,8 +210,6 @@ private:
Extension* GetActiveExtension() const; Extension* GetActiveExtension() const;
Extension* GetNoneExtension() const; Extension* GetNoneExtension() const;
bool NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode);
// TODO: Kill this nonsensical function used for TAS: // TODO: Kill this nonsensical function used for TAS:
EncryptionKey GetExtensionEncryptionKey() const; EncryptionKey GetExtensionEncryptionKey() const;
@ -277,9 +283,15 @@ private:
ExtensionPort m_extension_port{&m_i2c_bus}; 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; 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; WiimoteCommon::InputReportID m_reporting_mode;
bool m_reporting_continuous; bool m_reporting_continuous;

View File

@ -450,7 +450,22 @@ Report& Wiimote::ProcessReadQueue(bool repeat_last_data_report)
return m_last_input_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. // 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. // 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)); u32(rpt.size() - REPORT_HID_HEADER_SIZE));
} }
bool Wiimote::IsButtonPressed() ButtonData Wiimote::GetCurrentlyPressedButtons()
{ {
Report& rpt = m_last_input_report; Report& rpt = m_last_input_report;
if (rpt.size() >= 4) if (rpt.size() >= 4)
@ -489,10 +504,10 @@ bool Wiimote::IsButtonPressed()
ButtonData buttons = {}; ButtonData buttons = {};
builder->GetCoreData(&buttons); builder->GetCoreData(&buttons);
return buttons.hex != 0; return buttons;
} }
} }
return false; return ButtonData{};
} }
void Wiimote::Prepare() void Wiimote::Prepare()

View File

@ -62,10 +62,15 @@ public:
bool IsBalanceBoard(); bool IsBalanceBoard();
void InterruptDataOutput(const u8* data, const u32 size) override; 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 EventLinked() override;
void EventUnlinked() override; void EventUnlinked() override;
bool IsButtonPressed() override; WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
void EmuStop(); 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. // This is not enabled on all platforms as connecting a Wiimote can be a pain on some platforms.
bool m_really_disconnect = false; bool m_really_disconnect = false;
u8 m_bt_device_index = 0;
private: private:
void Read(); void Read();
bool Write(); bool Write();

View File

@ -19,8 +19,11 @@
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h" #include "Core/HW/SystemTimers.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
#include "Core/IOS/Device.h" #include "Core/IOS/Device.h"
#include "Core/IOS/IOS.h" #include "Core/IOS/IOS.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayProto.h"
#include "Core/SysConf.h" #include "Core/SysConf.h"
#include "InputCommon/ControllerInterface/ControllerInterface.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], 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]); tmp_bd[1], tmp_bd[2], tmp_bd[3], tmp_bd[4], tmp_bd[5]);
m_wiimotes.emplace_back(std::make_unique<WiimoteDevice>(this, i, tmp_bd)); const unsigned int hid_source_number =
NetPlay::IsNetPlayRunning() ? NetPlay::NetPlay_GetLocalWiimoteForSlot(i) : i;
m_wiimotes[i] = std::make_unique<WiimoteDevice>(this, tmp_bd, hid_source_number);
} }
bt_dinf.num_registered = MAX_BBMOTES; bt_dinf.num_registered = MAX_BBMOTES;
@ -340,8 +345,45 @@ void BluetoothEmuDevice::Update()
{ {
g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth); g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth);
g_controller_interface.UpdateInput(); g_controller_interface.UpdateInput();
for (auto& wiimote : m_wiimotes)
wiimote->UpdateInput(); std::array<WiimoteEmu::DesiredWiimoteState, MAX_BBMOTES> wiimote_states;
std::array<WiimoteDevice::NextUpdateInputCall, MAX_BBMOTES> 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<WiimoteEmu::SerializedWiimoteState, MAX_BBMOTES> serialized;
std::array<NetPlay::NetPlayClient::WiimoteDataBatchEntry, MAX_BBMOTES> 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<int>(i);
++batch_count;
}
if (batch_count > 0)
{
NetPlay::NetPlay_GetWiimoteData(
std::span<NetPlay::NetPlayClient::WiimoteDataBatchEntry>(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; m_last_ticks = now;
} }

View File

@ -60,7 +60,7 @@ public:
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
private: private:
std::vector<std::unique_ptr<WiimoteDevice>> m_wiimotes; std::array<std::unique_ptr<WiimoteDevice>, MAX_BBMOTES> m_wiimotes;
bdaddr_t m_controller_bd{{0x11, 0x02, 0x19, 0x79, 0x00, 0xff}}; bdaddr_t m_controller_bd{{0x11, 0x02, 0x19, 0x79, 0x00, 0xff}};

View File

@ -21,6 +21,7 @@
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
#include "Core/Host.h" #include "Core/Host.h"
#include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/IOS/USB/Bluetooth/WiimoteHIDAttr.h" #include "Core/IOS/USB/Bluetooth/WiimoteHIDAttr.h"
@ -54,24 +55,28 @@ private:
constexpr int CONNECTION_MESSAGE_TIME = 3000; 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_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_class = {0x00, 0x04, 0x48};
m_features = {0xBC, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00}; m_features = {0xBC, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00};
m_lmp_version = 0x2; m_lmp_version = 0x2;
m_lmp_subversion = 0x229; 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) if (hid_source)
{
hid_source->SetWiimoteDeviceIndex(GetNumber());
// UGLY: This prevents an OSD message in SetSource -> Activate.
SetBasebandState(BasebandState::RequestConnection); SetBasebandState(BasebandState::RequestConnection);
}
SetSource(hid_source); 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) if (m_connection_request_counter)
--m_connection_request_counter; --m_connection_request_counter;
if (!IsSourceValid()) if (!IsSourceValid())
return; return NextUpdateInputCall::None;
// Allow button press to trigger activation after a second of no connection activity. if (m_baseband_state == BasebandState::Inactive)
if (!m_connection_request_counter && m_baseband_state == BasebandState::Inactive)
{ {
if (Wiimote::NetPlay_GetButtonPress(GetNumber(), m_hid_source->IsButtonPressed())) // Allow button press to trigger activation after a second of no connection activity.
Activate(true); 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. // Verify interrupt channel is connected and configured.
const auto* channel = FindChannelWithPSM(L2CAP_PSM_HID_INTR); const auto* channel = FindChannelWithPSM(L2CAP_PSM_HID_INTR);
if (channel && channel->IsComplete()) 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 // This function receives L2CAP commands from the CPU

View File

@ -13,6 +13,11 @@
class PointerWrap; class PointerWrap;
namespace WiimoteEmu
{
struct DesiredWiimoteState;
}
namespace IOS::HLE namespace IOS::HLE
{ {
class BluetoothEmuDevice; class BluetoothEmuDevice;
@ -24,7 +29,7 @@ public:
using FeaturesType = std::array<u8, HCI_FEATURES_SIZE>; using FeaturesType = std::array<u8, HCI_FEATURES_SIZE>;
using LinkKeyType = std::array<u8, HCI_KEY_SIZE>; using LinkKeyType = std::array<u8, HCI_KEY_SIZE>;
WiimoteDevice(BluetoothEmuDevice* host, int number, bdaddr_t bd); WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int hid_source_number);
~WiimoteDevice(); ~WiimoteDevice();
WiimoteDevice(const WiimoteDevice&) = delete; WiimoteDevice(const WiimoteDevice&) = delete;
@ -38,7 +43,15 @@ public:
void Update(); void Update();
// Called every ~200hz. // 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); void DoState(PointerWrap& p);

View File

@ -10,8 +10,10 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <span>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
@ -54,6 +56,7 @@
#include "Core/HW/Sram.h" #include "Core/HW/Sram.h"
#include "Core/HW/WiiSave.h" #include "Core/HW/WiiSave.h"
#include "Core/HW/WiiSaveStructs.h" #include "Core/HW/WiiSaveStructs.h"
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/IOS/FS/FileSystem.h" #include "Core/IOS/FS/FileSystem.h"
@ -695,20 +698,29 @@ void NetPlayClient::OnPadHostData(sf::Packet& packet)
void NetPlayClient::OnWiimoteData(sf::Packet& packet) void NetPlayClient::OnWiimoteData(sf::Packet& packet)
{ {
PadIndex map; while (!packet.endOfPacket())
WiimoteInput nw; {
u8 size; 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); // Trusting server for good map value (>=0 && <4)
for (auto& byte : nw.data) // add to pad buffer
packet >> byte; m_wiimote_buffer.at(map).Push(pad);
m_wii_pad_event.Set();
// Trusting server for good map value (>=0 && <4) }
// add to Wiimote buffer
m_wiimote_buffer.at(map).Push(nw);
m_wii_pad_event.Set();
} }
void NetPlayClient::OnPadBuffer(sf::Packet& packet) 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.save_data_region;
packet >> m_net_settings.sync_codes; 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.golf_mode;
packet >> m_net_settings.use_fma; packet >> m_net_settings.use_fma;
packet >> m_net_settings.hide_remote_gbas; 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 // 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<PadIndex>(in_game_pad); packet << static_cast<PadIndex>(in_game_pad);
packet << static_cast<u8>(nw.report_id); packet << state.length;
packet << static_cast<u8>(nw.data.size()); for (size_t i = 0; i < state.length; ++i)
packet.append(nw.data.data(), nw.data.size()); packet << state.data[i];
SendAsync(std::move(packet));
} }
// called from ---GUI--- thread // called from ---GUI--- thread
@ -2042,79 +2050,42 @@ u64 NetPlayClient::GetInitialRTCValue() const
return m_initial_rtc; 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 // 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<WiimoteDataBatchEntry>& entries)
{ {
WiimoteInput nw; for (const WiimoteDataBatchEntry& entry : entries)
nw.report_id = reporting_mode;
{ {
std::lock_guard lkp(m_crit.players); const int local_wiimote = InGameWiimoteToLocalWiimote(entry.wiimote);
DEBUG_LOG_FMT(NETPLAY,
// Only send data, if this Wiimote is mapped to this player "Entering WiimoteUpdate() with wiimote {}, local_wiimote {}, state [{:02x}]",
if (m_wiimote_map[_number] == m_local_player->pid) 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? // Now, we either use the data pushed earlier, or wait for the
while (m_wiimote_buffer[_number].Size() <= m_target_buffer_size * 200 / 120) // 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; return false;
}
m_wiimote_buffer[_number].Pop(nw); m_wii_pad_event.Wait();
++tries;
if (tries > m_target_buffer_size * 200 / 120)
break;
} }
// If it still mismatches, it surely desynced m_wiimote_buffer[entry.wiimote].Pop(*entry.state);
if (nw.report_id != reporting_mode)
{ DEBUG_LOG_FMT(NETPLAY, "Exiting WiimoteUpdate() with wiimote {}, state [{:02x}]", entry.wiimote,
PanicAlertFmtT("Netplay has desynced. There is no way to recover from this."); fmt::join(std::span(entry.state->data.data(), entry.state->length), ", "));
return false;
}
} }
ASSERT(nw.data.size() == size);
std::copy(nw.data.begin(), nw.data.end(), data);
return true; return true;
} }
@ -2171,6 +2142,28 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
return data_added; 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) void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
{ {
// Here we handle polling for the Host Input Authority and Golf modes. Pad data is "polled" from // 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; }); [](auto mapping) { return mapping > 0; });
} }
int NetPlayClient::NumLocalPads() const static int CountLocalPads(const PadMappingArray& pad_map, const PlayerId& local_player_pid)
{ {
return static_cast<int>(std::count_if(m_pad_map.begin(), m_pad_map.end(), [this](auto mapping) { return static_cast<int>(
return mapping == m_local_player->pid; 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 // not our pad
if (m_pad_map[ingame_pad] != m_local_player->pid) if (pad_map[ingame_pad] != local_player_pid)
return 4; return 4;
int local_pad = 0; int local_pad = 0;
int 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++; local_pad++;
} }
return 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. // Figure out which in-game pad maps to which local pad.
// The logic we have here is that the local slots always // 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; int ingame_pad = 0;
for (; ingame_pad < 4; ingame_pad++) 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++; local_pad_count++;
if (local_pad_count == local_pad) if (local_pad_count == local_pad)
@ -2372,6 +2376,26 @@ int NetPlayClient::LocalPadToInGamePad(int local_pad) const
return ingame_pad; 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 bool NetPlayClient::PlayerHasControllerMapped(const PlayerId pid) const
{ {
const auto mapping_matches_player_id = [pid](const PlayerId& mapping) { return mapping == pid; }; 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; return pid == m_local_player->pid;
} }
const PlayerId& NetPlayClient::GetLocalPlayerId() const
{
return m_local_player->pid;
}
void NetPlayClient::SendGameStatus() void NetPlayClient::SendGameStatus()
{ {
sf::Packet packet; sf::Packet packet;
@ -2580,23 +2609,6 @@ void SendPowerButtonEvent()
netplay_client->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<ControllerEmu::Attachments*>(
static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(int(i)))
->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments))
->SetSelectedAttachment(netplay_settings.wiimote_extension[i]);
}
}
}
std::string GetGBASavePath(int pad_num) std::string GetGBASavePath(int pad_num)
{ {
std::lock_guard lk(crit_netplay_client); std::lock_guard lk(crit_netplay_client);
@ -2652,6 +2664,14 @@ PadDetails GetPadDetails(int pad_num)
return res; 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) void NetPlay_Enable(NetPlayClient* const np)
{ {
std::lock_guard lk(crit_netplay_client); std::lock_guard lk(crit_netplay_client);
@ -2679,37 +2699,51 @@ bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPa
return false; return false;
} }
bool WiimoteEmu::Wiimote::NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode) bool NetPlay::NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries)
{ {
std::lock_guard lk(NetPlay::crit_netplay_client); std::lock_guard lk(crit_netplay_client);
if (NetPlay::netplay_client) if (netplay_client)
return NetPlay::netplay_client->WiimoteUpdate(wiimote, data, size, reporting_mode); return netplay_client->WiimoteUpdate(entries);
return false; return false;
} }
// Sync the info whether a button was pressed or not. Used for the reconnect on button press feature unsigned int NetPlay::NetPlay_GetLocalWiimoteForSlot(unsigned int slot)
bool Wiimote::NetPlay_GetButtonPress(int wiimote, bool pressed)
{ {
std::lock_guard lk(NetPlay::crit_netplay_client); if (slot >= std::tuple_size_v<PadMappingArray>)
return slot;
// Use the reporting mode 0 for the button pressed event, the real ones start at RT_REPORT_CORE std::lock_guard lk(crit_netplay_client);
static const u8 BUTTON_PRESS_REPORTING_MODE = 0;
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<unsigned int, std::tuple_size_v<std::decay_t<decltype(mapping)>>> slot_map;
size_t player_count = 0;
for (size_t i = 0; i < mapping.size(); ++i)
{ {
std::array<u8, 1> data = {u8(pressed)}; if (mapping[i] == local_player_id)
if (NetPlay::netplay_client->WiimoteUpdate(wiimote, data.data(), data.size(),
BUTTON_PRESS_REPORTING_MODE))
{ {
return data[0]; slot_map[i] = static_cast<unsigned int>(player_count);
++player_count;
}
}
for (size_t i = 0; i < mapping.size(); ++i)
{
if (mapping[i] != local_player_id)
{
slot_map[i] = static_cast<unsigned int>(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 // called from ---CPU--- thread

View File

@ -9,6 +9,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <span>
#include <string> #include <string>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
@ -35,6 +36,11 @@ namespace UICommon
class GameFile; class GameFile;
} }
namespace WiimoteEmu
{
struct SerializedWiimoteState;
}
namespace NetPlay namespace NetPlay
{ {
class NetPlayUI class NetPlayUI
@ -129,7 +135,12 @@ public:
std::string GetCurrentGolfer(); std::string GetCurrentGolfer();
// Send and receive pads values // 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<WiimoteDataBatchEntry>& entries);
bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status); bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status);
u64 GetInitialRTCValue() const; u64 GetInitialRTCValue() const;
@ -140,13 +151,17 @@ public:
bool IsFirstInGamePad(int ingame_pad) const; bool IsFirstInGamePad(int ingame_pad) const;
int NumLocalPads() const; int NumLocalPads() const;
int NumLocalWiimotes() const;
int InGamePadToLocalPad(int ingame_pad) 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 PlayerHasControllerMapped(PlayerId pid) const;
bool LocalPlayerHasControllerMapped() const; bool LocalPlayerHasControllerMapped() const;
bool IsLocalPlayer(PlayerId pid) const; bool IsLocalPlayer(PlayerId pid) const;
const PlayerId& GetLocalPlayerId() const;
static void SendTimeBase(); static void SendTimeBase();
bool DoAllPlayersHaveGame(); bool DoAllPlayersHaveGame();
@ -182,7 +197,7 @@ protected:
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue; Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
std::array<Common::SPSCQueue<GCPadStatus>, 4> m_pad_buffer; std::array<Common::SPSCQueue<GCPadStatus>, 4> m_pad_buffer;
std::array<Common::SPSCQueue<WiimoteInput>, 4> m_wiimote_buffer; std::array<Common::SPSCQueue<WiimoteEmu::SerializedWiimoteState>, 4> m_wiimote_buffer;
std::array<GCPadStatus, 4> m_last_pad_status{}; std::array<GCPadStatus, 4> m_last_pad_status{};
std::array<bool, 4> m_first_pad_status_received{}; std::array<bool, 4> m_first_pad_status_received{};
@ -242,9 +257,13 @@ private:
bool PollLocalPad(int local_pad, sf::Packet& packet); bool PollLocalPad(int local_pad, sf::Packet& packet);
void SendPadHostPoll(PadIndex pad_num); void SendPadHostPoll(PadIndex pad_num);
bool AddLocalWiimoteToBuffer(int local_wiimote, const WiimoteEmu::SerializedWiimoteState& state,
sf::Packet& packet);
void UpdateDevices(); void UpdateDevices();
void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet); 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 Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
void Disconnect(); void Disconnect();
bool Connect(); bool Connect();
@ -253,8 +272,6 @@ private:
void DisplayPlayersPing(); void DisplayPlayersPing();
u32 GetPlayersMaxPing() const; u32 GetPlayersMaxPing() const;
bool WaitForWiimoteBuffer(int _number);
void OnData(sf::Packet& packet); void OnData(sf::Packet& packet);
void OnPlayerJoin(sf::Packet& packet); void OnPlayerJoin(sf::Packet& packet);
void OnPlayerLeave(sf::Packet& packet); void OnPlayerLeave(sf::Packet& packet);
@ -335,4 +352,6 @@ private:
void NetPlay_Enable(NetPlayClient* const np); void NetPlay_Enable(NetPlayClient* const np);
void NetPlay_Disable(); void NetPlay_Disable();
bool NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries);
unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot);
} // namespace NetPlay } // namespace NetPlay

View File

@ -101,7 +101,6 @@ struct NetSettings
bool strict_settings_sync = false; bool strict_settings_sync = false;
bool sync_codes = false; bool sync_codes = false;
std::string save_data_region; std::string save_data_region;
std::array<int, 4> wiimote_extension{};
bool golf_mode = false; bool golf_mode = false;
bool use_fma = false; bool use_fma = false;
bool hide_remote_gbas = false; bool hide_remote_gbas = false;
@ -228,11 +227,6 @@ enum : u8
CHANNEL_COUNT CHANNEL_COUNT
}; };
struct WiimoteInput
{
u8 report_id = 0;
std::vector<u8> data;
};
using PlayerId = u8; using PlayerId = u8;
using FrameNum = u32; using FrameNum = u32;
using PadIndex = s8; using PadIndex = s8;
@ -260,7 +254,7 @@ std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map,
bool IsNetPlayRunning(); bool IsNetPlayRunning();
void SetSIPollBatching(bool state); void SetSIPollBatching(bool state);
void SendPowerButtonEvent(); void SendPowerButtonEvent();
void SetupWiimotes();
std::string GetGBASavePath(int pad_num); std::string GetGBASavePath(int pad_num);
PadDetails GetPadDetails(int pad_num); PadDetails GetPadDetails(int pad_num);
int NumLocalWiimotes();
} // namespace NetPlay } // namespace NetPlay

View File

@ -52,6 +52,7 @@
#include "Core/HW/Sram.h" #include "Core/HW/Sram.h"
#include "Core/HW/WiiSave.h" #include "Core/HW/WiiSave.h"
#include "Core/HW/WiiSaveStructs.h" #include "Core/HW/WiiSaveStructs.h"
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/IOS/ES/ES.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) if (player.current_game != m_current_game)
break; break;
PadIndex map;
u8 size;
packet >> map >> size;
std::vector<u8> 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; sf::Packet spac;
spac << MessageID::WiimoteData; spac << MessageID::WiimoteData;
spac << map;
spac << size; while (!packet.endOfPacket())
for (const u8& byte : data) {
spac << byte; 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); SendToClients(spac, player.pid);
} }
@ -1518,16 +1525,6 @@ bool NetPlayServer::StartGame()
spac << region; spac << region;
spac << m_settings.sync_codes; spac << m_settings.sync_codes;
for (size_t i = 0; i < m_settings.wiimote_extension.size(); i++)
{
const int extension =
static_cast<ControllerEmu::Attachments*>(
static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(int(i)))
->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments))
->GetSelectedAttachment();
spac << extension;
}
spac << m_settings.golf_mode; spac << m_settings.golf_mode;
spac << m_settings.use_fma; spac << m_settings.use_fma;
spac << m_settings.hide_remote_gbas; spac << m_settings.hide_remote_gbas;

View File

@ -315,8 +315,10 @@
<ClInclude Include="Core\HW\WiimoteCommon\WiimoteReport.h" /> <ClInclude Include="Core\HW\WiimoteCommon\WiimoteReport.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Camera.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Camera.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Dynamics.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Dynamics.h" />
<ClInclude Include="Core\HW\WiimoteEmu\DesiredWiimoteState.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Encryption.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Encryption.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Extension\Classic.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Extension\Classic.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Extension\DesiredExtensionState.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Extension\DrawsomeTablet.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Extension\DrawsomeTablet.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Extension\Drums.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Extension\Drums.h" />
<ClInclude Include="Core\HW\WiimoteEmu\Extension\Extension.h" /> <ClInclude Include="Core\HW\WiimoteEmu\Extension\Extension.h" />
@ -931,6 +933,7 @@
<ClCompile Include="Core\HW\Wiimote.cpp" /> <ClCompile Include="Core\HW\Wiimote.cpp" />
<ClCompile Include="Core\HW\WiimoteCommon\DataReport.cpp" /> <ClCompile Include="Core\HW\WiimoteCommon\DataReport.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Camera.cpp" /> <ClCompile Include="Core\HW\WiimoteEmu\Camera.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\DesiredWiimoteState.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Dynamics.cpp" /> <ClCompile Include="Core\HW\WiimoteEmu\Dynamics.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\EmuSubroutines.cpp" /> <ClCompile Include="Core\HW\WiimoteEmu\EmuSubroutines.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Encryption.cpp" /> <ClCompile Include="Core\HW\WiimoteEmu\Encryption.cpp" />

View File

@ -18,6 +18,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_Device.h"
#include "Core/NetPlayProto.h"
#include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h" #include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h" #include "DolphinQt/Config/Mapping/MappingWindow.h"
@ -60,11 +61,13 @@ static SerialInterface::SIDevices FromGCMenuIndex(const int menudevice)
GamecubeControllersWidget::GamecubeControllersWidget(QWidget* parent) : QWidget(parent) GamecubeControllersWidget::GamecubeControllersWidget(QWidget* parent) : QWidget(parent)
{ {
CreateLayout(); CreateLayout();
LoadSettings();
ConnectWidgets(); ConnectWidgets();
connect(&Settings::Instance(), &Settings::ConfigChanged, this, 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() void GamecubeControllersWidget::CreateLayout()
@ -160,8 +163,9 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index)
window->show(); 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++) for (size_t i = 0; i < m_gc_groups.size(); i++)
{ {
const SerialInterface::SIDevices si_device = const SerialInterface::SIDevices si_device =
@ -170,6 +174,7 @@ void GamecubeControllersWidget::LoadSettings()
if (gc_index) if (gc_index)
{ {
SignalBlocking(m_gc_controller_boxes[i])->setCurrentIndex(*gc_index); SignalBlocking(m_gc_controller_boxes[i])->setCurrentIndex(*gc_index);
m_gc_controller_boxes[i]->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true);
OnGCTypeChanged(i); OnGCTypeChanged(i);
} }
} }

View File

@ -13,6 +13,11 @@ class QGridLayout;
class QGroupBox; class QGroupBox;
class QPushButton; class QPushButton;
namespace Core
{
enum class State;
}
class GamecubeControllersWidget final : public QWidget class GamecubeControllersWidget final : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -20,7 +25,7 @@ public:
explicit GamecubeControllersWidget(QWidget* parent); explicit GamecubeControllersWidget(QWidget* parent);
private: private:
void LoadSettings(); void LoadSettings(Core::State state);
void SaveSettings(); void SaveSettings();
void OnGCTypeChanged(size_t index); void OnGCTypeChanged(size_t index);

View File

@ -27,6 +27,7 @@
#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/IOS/IOS.h" #include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Bluetooth/BTReal.h" #include "Core/IOS/USB/Bluetooth/BTReal.h"
#include "Core/NetPlayProto.h"
#include "Core/WiiUtils.h" #include "Core/WiiUtils.h"
#include "DolphinQt/Config/Mapping/MappingWindow.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 running_gc = running && !SConfig::GetInstance().bWii;
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc; const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
const bool enable_emu_bt = !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_sync->setEnabled(enable_passthrough);
m_wiimote_reset->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) for (auto* pt_label : m_wiimote_pt_labels)
pt_label->setEnabled(enable_passthrough); 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++) for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{ {
m_wiimote_labels[i]->setEnabled(enable_emu_bt); 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; 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<int>(i) < num_local_wiimotes);
} }
m_wiimote_real_balance_board->setEnabled(enable_emu_bt); m_wiimote_real_balance_board->setEnabled(enable_emu_bt && !running_netplay);
m_wiimote_speaker_data->setEnabled(enable_emu_bt); m_wiimote_speaker_data->setEnabled(enable_emu_bt && !running_netplay);
const bool ciface_wiimotes = m_wiimote_ciface->isChecked(); const bool ciface_wiimotes = m_wiimote_ciface->isChecked();

View File

@ -360,8 +360,6 @@ void MainWindow::InitCoreCallbacks()
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) { connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
if (state == Core::State::Uninitialized) if (state == Core::State::Uninitialized)
OnStopComplete(); OnStopComplete();
if (state != Core::State::Uninitialized && NetPlay::IsNetPlayRunning() && m_controllers_window)
m_controllers_window->reject();
if (state == Core::State::Running && m_fullscreen_requested) if (state == Core::State::Running && m_fullscreen_requested)
{ {

View File

@ -131,9 +131,6 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
m_recording_play->setEnabled(m_game_selected && !running); m_recording_play->setEnabled(m_game_selected && !running);
m_recording_start->setEnabled((m_game_selected || running) && !Movie::IsPlayingInput()); m_recording_start->setEnabled((m_game_selected || running) && !Movie::IsPlayingInput());
// Options
m_controllers_action->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true);
// JIT // JIT
m_jit_interpreter_core->setEnabled(running); m_jit_interpreter_core->setEnabled(running);
m_jit_block_linking->setEnabled(!running); m_jit_block_linking->setEnabled(!running);

View File

@ -61,7 +61,6 @@ void ToolBar::OnEmulationStateChanged(Core::State state)
m_stop_action->setEnabled(running); m_stop_action->setEnabled(running);
m_fullscreen_action->setEnabled(running); m_fullscreen_action->setEnabled(running);
m_screenshot_action->setEnabled(running); m_screenshot_action->setEnabled(running);
m_controllers_action->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true);
bool playing = running && state != Core::State::Paused; bool playing = running && state != Core::State::Paused;
UpdatePausePlayButtonState(playing); UpdatePausePlayButtonState(playing);
@ -130,7 +129,6 @@ void ToolBar::MakeActions()
m_config_action = addAction(tr("Config"), this, &ToolBar::SettingsPressed); m_config_action = addAction(tr("Config"), this, &ToolBar::SettingsPressed);
m_graphics_action = addAction(tr("Graphics"), this, &ToolBar::GraphicsPressed); m_graphics_action = addAction(tr("Graphics"), this, &ToolBar::GraphicsPressed);
m_controllers_action = addAction(tr("Controllers"), this, &ToolBar::ControllersPressed); m_controllers_action = addAction(tr("Controllers"), this, &ToolBar::ControllersPressed);
m_controllers_action->setEnabled(true);
// Ensure every button has about the same width // Ensure every button has about the same width
std::vector<QWidget*> items; std::vector<QWidget*> items;

View File

@ -1256,7 +1256,7 @@ void Device::IRState::ProcessData(const std::array<WiimoteEmu::IRBasic, 2>& data
// A better implementation might extrapolate points when they fall out of camera view. // A better implementation might extrapolate points when they fall out of camera view.
// But just averaging visible points actually seems to work very well. // But just averaging visible points actually seems to work very well.
using IRObject = WiimoteEmu::IRBasic::IRObject; using IRObject = WiimoteEmu::IRObject;
MathUtil::RunningVariance<Common::Vec2> points; MathUtil::RunningVariance<Common::Vec2> points;