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
52 changed files with 1282 additions and 422 deletions
+3
View File
@@ -279,6 +279,8 @@ add_library(core
HW/WiimoteCommon/WiimoteReport.h
HW/WiimoteEmu/Camera.cpp
HW/WiimoteEmu/Camera.h
HW/WiimoteEmu/DesiredWiimoteState.cpp
HW/WiimoteEmu/DesiredWiimoteState.h
HW/WiimoteEmu/Dynamics.cpp
HW/WiimoteEmu/Dynamics.h
HW/WiimoteEmu/EmuSubroutines.cpp
@@ -286,6 +288,7 @@ add_library(core
HW/WiimoteEmu/Encryption.h
HW/WiimoteEmu/Extension/Classic.cpp
HW/WiimoteEmu/Extension/Classic.h
HW/WiimoteEmu/Extension/DesiredExtensionState.h
HW/WiimoteEmu/Extension/DrawsomeTablet.cpp
HW/WiimoteEmu/Extension/DrawsomeTablet.h
HW/WiimoteEmu/Extension/Drums.cpp
-3
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))
{
Wiimote::LoadConfig();
if (NetPlay::IsNetPlayRunning())
NetPlay::SetupWiimotes();
}
FreeLook::LoadInputConfig();
-2
View File
@@ -94,8 +94,6 @@ ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(int number,
WiimoteEmu::DrawsomeTabletGroup group);
ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGroup group);
ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group);
bool NetPlay_GetButtonPress(int wiimote, bool pressed);
} // namespace Wiimote
namespace WiimoteReal
+14 -3
View File
@@ -7,6 +7,11 @@
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
namespace WiimoteEmu
{
struct DesiredWiimoteState;
}
namespace WiimoteCommon
{
// Source: HID_010_SPC_PFL/1.0 (official HID specification)
@@ -30,8 +35,12 @@ public:
virtual void EventLinked() = 0;
virtual void EventUnlinked() = 0;
virtual u8 GetWiimoteDeviceIndex() const = 0;
virtual void SetWiimoteDeviceIndex(u8 index) = 0;
// Called every ~200hz after HID channels are established.
virtual void Update() = 0;
virtual void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) = 0;
virtual void Update(const WiimoteEmu::DesiredWiimoteState& target_state) = 0;
void SetInterruptCallback(InterruptCallbackType callback) { m_callback = std::move(callback); }
@@ -39,8 +48,10 @@ public:
// Does not include HID-type header.
virtual void InterruptDataOutput(const u8* data, u32 size) = 0;
// Used to connect a disconnected wii remote on button press.
virtual bool IsButtonPressed() = 0;
// Get a snapshot of the current state of the Wiimote's buttons.
// Note that only the button bits of the return value are meaningful, the rest should be ignored.
// This is used to query a disconnected Wiimote whether it wants to reconnect.
virtual ButtonData GetCurrentlyPressedButtons() = 0;
protected:
void InterruptDataInputCallback(const u8* data, u32 size)
+24 -33
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);
}
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::Matrix44;
using Common::Vec3;
using Common::Vec4;
// FYI: A real wiimote normally only returns 1 point for each LED cluster (2 total).
// Sending all 4 points can actually cause some stuttering issues.
constexpr int NUM_POINTS = 2;
// Range from 0-15. Small values (2-4) seem to be very typical.
// This is reduced based on distance from sensor bar.
constexpr int MAX_POINT_SIZE = 15;
const std::array<Vec3, NUM_POINTS> leds{
Vec3{-SENSOR_BAR_LED_SEPARATION / 2, 0, 0},
Vec3{SENSOR_BAR_LED_SEPARATION / 2, 0, 0},
@@ -91,12 +69,6 @@ void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_o
Matrix44::Perspective(field_of_view.y, field_of_view.x / field_of_view.y, 0.001f, 1000) *
Matrix44::FromMatrix33(Matrix33::RotateX(float(MathUtil::TAU / 4))) * transform;
struct CameraPoint
{
IRBasic::IRObject position;
u8 size = 0;
};
std::array<CameraPoint, leds.size()> camera_points;
std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) {
@@ -112,13 +84,32 @@ void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_o
const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);
if (x >= 0 && y >= 0 && x < CAMERA_RES_X && y < CAMERA_RES_Y)
return CameraPoint{{u16(x), u16(y)}, u8(point_size)};
return CameraPoint({u16(x), u16(y)}, u8(point_size));
}
// 0xFFFFs are interpreted as "not visible".
return CameraPoint{{0xffff, 0xffff}, 0xff};
return CameraPoint();
});
return camera_points;
}
void CameraLogic::Update(const std::array<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)
{
case IR_MODE_BASIC:
+30 -5
View File
@@ -16,11 +16,26 @@ class Matrix44;
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
struct IRBasic
{
using IRObject = Common::TVec2<u16>;
u8 x1;
u8 y1;
u8 x2hi : 2;
@@ -59,8 +74,8 @@ struct IRExtended
u8 xhi : 2;
u8 yhi : 2;
auto GetPosition() const { return IRBasic::IRObject(xhi << 8 | x, yhi << 8 | y); }
void SetPosition(const IRBasic::IRObject& obj)
auto GetPosition() const { return IRObject(xhi << 8 | x, yhi << 8 | y); }
void SetPosition(const IRObject& obj)
{
x = obj.x;
xhi = obj.x >> 8;
@@ -109,9 +124,19 @@ public:
IR_MODE_FULL = 5,
};
// FYI: A real wiimote normally only returns 1 point for each LED cluster (2 total).
// Sending all 4 points can actually cause some stuttering issues.
static constexpr int NUM_POINTS = 2;
// Range from 0-15. Small values (2-4) seem to be very typical.
// This is reduced based on distance from sensor bar.
static constexpr int MAX_POINT_SIZE = 15;
void Reset();
void DoState(PointerWrap& p);
void Update(const Common::Matrix44& transform, Common::Vec2 field_of_view);
static std::array<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);
static constexpr u8 I2C_ADDR = 0x58;
@@ -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
@@ -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
@@ -142,7 +142,8 @@ void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code)
InterruptDataInputCallback(rpt.GetData(), rpt.GetSize());
}
void Wiimote::HandleExtensionSwap()
void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number,
bool desired_motion_plus)
{
if (WIIMOTE_BALANCE_BOARD == m_index)
{
@@ -151,15 +152,13 @@ void Wiimote::HandleExtensionSwap()
return;
}
ExtensionNumber desired_extension_number =
static_cast<ExtensionNumber>(m_attachments->GetSelectedAttachment());
const bool desired_motion_plus = m_motion_plus_setting.GetValue();
// FYI: AttachExtension also connects devices to the i2c bus
if (m_is_motion_plus_attached && !desired_motion_plus)
{
INFO_LOG_FMT(WIIMOTE, "Detaching Motion Plus (Wiimote {} in slot {})", m_index,
m_bt_device_index);
// M+ is attached and it's not wanted, so remove it.
m_extension_port.AttachExtension(GetNoneExtension());
m_is_motion_plus_attached = false;
@@ -184,6 +183,9 @@ void Wiimote::HandleExtensionSwap()
}
else
{
INFO_LOG_FMT(WIIMOTE, "Attaching Motion Plus (Wiimote {} in slot {})", m_index,
m_bt_device_index);
// No extension attached so attach M+.
m_is_motion_plus_attached = true;
m_extension_port.AttachExtension(&m_motion_plus);
@@ -198,12 +200,18 @@ void Wiimote::HandleExtensionSwap()
// A different extension is wanted (either by user or by the M+ logic above)
if (GetActiveExtensionNumber() != ExtensionNumber::NONE)
{
INFO_LOG_FMT(WIIMOTE, "Detaching Extension (Wiimote {} in slot {})", m_index,
m_bt_device_index);
// First we must detach the current extension.
// The next call will change to the new extension if needed.
m_active_extension = ExtensionNumber::NONE;
}
else
{
INFO_LOG_FMT(WIIMOTE, "Switching to Extension {} (Wiimote {} in slot {})",
desired_extension_number, m_index, m_bt_device_index);
m_active_extension = desired_extension_number;
}
@@ -10,6 +10,8 @@
#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
@@ -105,7 +107,7 @@ Classic::Classic() : Extension1stParty("Classic", _trans("Classic Controller"))
}
}
void Classic::Update()
void Classic::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
DataFormat classic_data = {};
@@ -149,7 +151,12 @@ void Classic::Update()
classic_data.SetButtons(buttons);
Common::BitCastPtr<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()
@@ -178,7 +178,8 @@ public:
Classic();
void Update() override;
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override;
ControllerEmu::ControlGroup* GetGroup(ClassicGroup group);
@@ -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
@@ -9,6 +9,8 @@
#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
@@ -31,9 +33,9 @@ DrawsomeTablet::DrawsomeTablet() : Extension3rdParty("Drawsome", _trans("Drawsom
m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure"));
}
void DrawsomeTablet::Update()
void DrawsomeTablet::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
DataFormat tablet_data = {};
DataFormat& tablet_data = target_state->data.emplace<DataFormat>();
// Stylus X/Y (calibrated values):
constexpr u16 MIN_X = 0x0000;
@@ -77,8 +79,11 @@ void DrawsomeTablet::Update()
tablet_data.pressure1 = u8(pressure);
tablet_data.pressure2 = u8(pressure >> 8);
}
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data;
void DrawsomeTablet::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
}
void DrawsomeTablet::Reset()
@@ -27,7 +27,8 @@ class DrawsomeTablet : public Extension3rdParty
public:
DrawsomeTablet();
void Update() override;
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override;
ControllerEmu::ControlGroup* GetGroup(DrawsomeTabletGroup group);
@@ -9,6 +9,8 @@
#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
@@ -77,8 +79,43 @@ Drums::Drums() : Extension1stParty("Drums", _trans("Drum Kit"))
m_buttons->AddInput(ControllerEmu::DoNotTranslate, "+");
}
void Drums::Update()
void Drums::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
DesiredState& state = target_state->data.emplace<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 = {};
// The meaning of these bits are unknown but they are usually set.
@@ -94,20 +131,12 @@ void Drums::Update()
drum_data.no_velocity_data_2 = 1;
drum_data.softness = 7;
// Stick.
{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
drum_data.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
drum_data.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
}
// Buttons.
m_buttons->GetState(&drum_data.buttons, drum_button_bitmasks.data());
drum_data.stick_x = desired_state.stick_x;
drum_data.stick_y = desired_state.stick_y;
drum_data.buttons = desired_state.buttons;
// Drum pads.
u8 current_pad_input = 0;
m_pads->GetState(&current_pad_input, drum_pad_bitmasks.data());
u8 current_pad_input = desired_state.drum_pads;
m_new_pad_hits |= ~m_prev_pad_input & current_pad_input;
m_prev_pad_input = current_pad_input;
@@ -130,8 +159,7 @@ void Drums::Update()
drum_data.no_velocity_data_1 = 0;
drum_data.no_velocity_data_2 = 0;
// Set softness from user-configured hit strength setting.
drum_data.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));
drum_data.softness = desired_state.softness;
// A drum-pad hit causes the relevent bit to be triggered for the next 10 frames.
constexpr u8 HIT_FRAME_COUNT = 10;
@@ -28,6 +28,15 @@ enum class DrumsGroup
class Drums : public Extension1stParty
{
public:
struct DesiredState
{
u8 stick_x; // 6 bits
u8 stick_y; // 6 bits
u8 buttons; // 2 bits
u8 drum_pads; // 6 bits
u8 softness; // 3 bits
};
struct DataFormat
{
u8 stick_x : 6;
@@ -77,7 +86,8 @@ public:
Drums();
void Update() override;
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override;
ControllerEmu::ControlGroup* GetGroup(DrumsGroup group);
@@ -9,6 +9,8 @@
#include "Common/CommonTypes.h"
#include "Common/Inline.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Common/Logging/Log.h"
@@ -43,7 +45,12 @@ bool None::ReadDeviceDetectPin() const
return false;
}
void None::Update()
void None::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
target_state->data.emplace<std::monostate>();
}
void None::Update(const DesiredExtensionState& target_state)
{
// Nothing needed.
}
@@ -16,6 +16,8 @@
namespace WiimoteEmu
{
struct DesiredExtensionState;
class Extension : public ControllerEmu::EmulatedController, public I2CSlave
{
public:
@@ -32,7 +34,8 @@ public:
virtual void Reset() = 0;
virtual void DoState(PointerWrap& p) = 0;
virtual void Update() = 0;
virtual void BuildDesiredExtensionState(DesiredExtensionState* target_state) = 0;
virtual void Update(const DesiredExtensionState& target_state) = 0;
private:
const char* const m_config_name;
@@ -46,7 +49,8 @@ public:
private:
bool ReadDeviceDetectPin() const override;
void Update() override;
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override;
void DoState(PointerWrap& p) override;
@@ -67,7 +71,6 @@ public:
// TODO: TAS handles encryption poorly.
EncryptionKey ext_key;
protected:
static constexpr int CALIBRATION_CHECKSUM_BYTES = 2;
#pragma pack(push, 1)
@@ -97,6 +100,7 @@ protected:
static_assert(0x100 == sizeof(Register));
protected:
Register m_reg = {};
void Reset() override;
@@ -11,6 +11,8 @@
#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
@@ -93,7 +95,7 @@ Guitar::Guitar() : Extension1stParty(_trans("Guitar"))
groups.emplace_back(m_slider_bar = new ControllerEmu::Slider(_trans("Slider Bar")));
}
void Guitar::Update()
void Guitar::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
DataFormat guitar_data = {};
@@ -135,7 +137,12 @@ void Guitar::Update()
// flip button bits
guitar_data.bt ^= 0xFFFF;
Common::BitCastPtr<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()
@@ -50,7 +50,8 @@ public:
Guitar();
void Update() override;
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override;
ControllerEmu::ControlGroup* GetGroup(GuitarGroup group);

Some files were not shown because too many files have changed in this diff Show More