Core/DolphinQt: Implement Emulated Balance Board.

Co-authored-by: Jordan Woyak <jordan.woyak@gmail.com>
This commit is contained in:
Pokechu22 2025-02-06 23:31:35 -06:00 committed by Jordan Woyak
parent c770e7c276
commit cffda24a55
41 changed files with 1766 additions and 742 deletions

View File

@ -304,6 +304,8 @@ add_library(core
HW/WiimoteEmu/EmuSubroutines.cpp
HW/WiimoteEmu/Encryption.cpp
HW/WiimoteEmu/Encryption.h
HW/WiimoteEmu/Extension/BalanceBoard.cpp
HW/WiimoteEmu/Extension/BalanceBoard.h
HW/WiimoteEmu/Extension/Classic.cpp
HW/WiimoteEmu/Extension/Classic.h
HW/WiimoteEmu/Extension/DesiredExtensionState.h

View File

@ -713,7 +713,7 @@ void SetState(Core::System& system, State state, bool report_state_change,
// NOTE: GetState() will return State::Paused immediately, even before anything has
// stopped (including the CPU).
system.GetCPU().SetStepping(true); // Break
Wiimote::Pause();
WiimoteReal::Pause();
ResetRumble();
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().DoIdle();
@ -722,7 +722,7 @@ void SetState(Core::System& system, State state, bool report_state_change,
case State::Running:
{
system.GetCPU().SetStepping(false);
Wiimote::Resume();
WiimoteReal::Resume();
break;
}
default:

View File

@ -18,16 +18,12 @@
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/System.h"
#include "Core/WiiUtils.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/InputConfig.h"
// Limit the amount of wiimote connect requests, when a button is pressed in disconnected state
static std::array<u8, MAX_BBMOTES> s_last_connect_request_counter;
namespace
{
static std::array<std::atomic<WiimoteSource>, MAX_BBMOTES> s_wiimote_sources;
@ -60,6 +56,27 @@ void RefreshConfig()
OnSourceChanged(i, Config::Get(Config::GetInfoForWiimoteSource(i)));
}
void DoWiimoteSlotState(PointerWrap& p, int slot, ControllerEmu::EmulatedController* controller)
{
const WiimoteSource source = GetSource(slot);
auto state_wiimote_source = u8(source);
p.Do(state_wiimote_source);
if (WiimoteSource(state_wiimote_source) == WiimoteSource::Emulated)
{
// Sync complete state of emulated wiimotes.
static_cast<WiimoteEmu::WiimoteBase*>(controller)->DoState(p);
}
if (p.IsReadMode())
{
// If using a real wiimote or the save-state source does not match the current source,
// then force a reconnection on load.
if (source == WiimoteSource::Real || source != WiimoteSource(state_wiimote_source))
WiimoteCommon::UpdateSource(slot);
}
}
} // namespace
namespace WiimoteCommon
@ -80,7 +97,9 @@ HIDWiimote* GetHIDWiimoteSource(unsigned int index)
switch (GetSource(index))
{
case WiimoteSource::Emulated:
hid_source = static_cast<WiimoteEmu::Wiimote*>(::Wiimote::GetConfig()->GetController(index));
hid_source = static_cast<WiimoteEmu::WiimoteBase*>(
(index == WIIMOTE_BALANCE_BOARD) ? ::BalanceBoard::GetConfig()->GetController(0) :
::Wiimote::GetConfig()->GetController(index));
break;
case WiimoteSource::Real:
@ -96,10 +115,15 @@ HIDWiimote* GetHIDWiimoteSource(unsigned int index)
} // namespace WiimoteCommon
namespace
{
static InputConfig s_config(WIIMOTE_INI_NAME, _trans("Balance Board"), "BalanceBoard",
"BalanceBoard");
static InputConfig s_bb_config(WIIMOTE_INI_NAME, _trans("Wii Remote"), "Wiimote", "Wiimote");
} // namespace
namespace Wiimote
{
static InputConfig s_config(WIIMOTE_INI_NAME, _trans("Wii Remote"), "Wiimote", "Wiimote");
InputConfig* GetConfig()
{
return &s_config;
@ -164,6 +188,7 @@ void Shutdown()
{
s_config.UnregisterHotplugCallback();
s_bb_config.ClearControllers();
s_config.ClearControllers();
WiimoteReal::Stop();
@ -179,11 +204,14 @@ void Initialize(InitializeMode init_mode)
{
if (s_config.ControllersNeedToBeCreated())
{
for (unsigned int i = WIIMOTE_CHAN_0; i < MAX_BBMOTES; ++i)
for (unsigned int i = WIIMOTE_CHAN_0; i < MAX_WIIMOTES; ++i)
s_config.CreateController<WiimoteEmu::Wiimote>(i);
s_bb_config.CreateController<WiimoteEmu::BalanceBoard>(WIIMOTE_BALANCE_BOARD);
}
s_config.RegisterHotplugCallback();
s_bb_config.RegisterHotplugCallback();
LoadConfig();
@ -201,47 +229,41 @@ void Initialize(InitializeMode init_mode)
void ResetAllWiimotes()
{
for (int i = WIIMOTE_CHAN_0; i < MAX_BBMOTES; ++i)
for (int i = WIIMOTE_CHAN_0; i < MAX_WIIMOTES; ++i)
static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(i))->Reset();
static_cast<WiimoteEmu::BalanceBoard*>(s_bb_config.GetController(0))->Reset();
}
void LoadConfig()
{
s_config.LoadConfig();
s_last_connect_request_counter.fill(0);
}
void Resume()
{
WiimoteReal::Resume();
}
void Pause()
{
WiimoteReal::Pause();
}
void DoState(PointerWrap& p)
{
for (int i = 0; i < MAX_BBMOTES; ++i)
{
const WiimoteSource source = GetSource(i);
auto state_wiimote_source = u8(source);
p.Do(state_wiimote_source);
for (int slot = 0; slot < MAX_WIIMOTES; ++slot)
DoWiimoteSlotState(p, slot, s_config.GetController(slot));
if (WiimoteSource(state_wiimote_source) == WiimoteSource::Emulated)
{
// Sync complete state of emulated wiimotes.
static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(i))->DoState(p);
}
if (p.IsReadMode())
{
// If using a real wiimote or the save-state source does not match the current source,
// then force a reconnection on load.
if (source == WiimoteSource::Real || source != WiimoteSource(state_wiimote_source))
WiimoteCommon::UpdateSource(i);
}
}
DoWiimoteSlotState(p, WIIMOTE_BALANCE_BOARD, s_bb_config.GetController(0));
}
} // namespace Wiimote
namespace BalanceBoard
{
InputConfig* GetConfig()
{
return &s_bb_config;
}
void LoadConfig()
{
s_bb_config.LoadConfig();
}
ControllerEmu::ControlGroup* GetBalanceBoardGroup(int number, WiimoteEmu::BalanceBoardGroup group)
{
return static_cast<WiimoteEmu::BalanceBoard*>(s_bb_config.GetController(number))
->GetBalanceBoardGroup(group);
}
} // namespace BalanceBoard

View File

@ -3,10 +3,6 @@
#pragma once
#include <array>
#include <atomic>
#include "Common/Common.h"
#include "Common/CommonTypes.h"
class InputConfig;
@ -29,9 +25,10 @@ enum class UDrawTabletGroup;
enum class DrawsomeTabletGroup;
enum class TaTaConGroup;
enum class ShinkansenGroup;
enum class BalanceBoardGroup;
} // namespace WiimoteEmu
enum
enum : u8
{
WIIMOTE_CHAN_0 = 0,
WIIMOTE_CHAN_1,
@ -74,14 +71,13 @@ enum class InitializeMode
// The Real Wii Remote sends report every ~5ms (200 Hz).
constexpr int UPDATE_FREQ = 200;
void Shutdown();
// Note that these also handle the 5th-slot BalanceBoard, though it has a separate InputConfig.
void Initialize(InitializeMode init_mode);
void Shutdown();
void ResetAllWiimotes();
void LoadConfig();
void Resume();
void Pause();
void DoState(PointerWrap& p);
void LoadConfig();
InputConfig* GetConfig();
ControllerEmu::ControlGroup* GetWiimoteGroup(int number, WiimoteEmu::WiimoteGroup group);
ControllerEmu::ControlGroup* GetNunchukGroup(int number, WiimoteEmu::NunchukGroup group);
@ -96,6 +92,13 @@ ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGrou
ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group);
} // namespace Wiimote
namespace BalanceBoard
{
InputConfig* GetConfig();
void LoadConfig();
ControllerEmu::ControlGroup* GetBalanceBoardGroup(int number, WiimoteEmu::BalanceBoardGroup group);
} // namespace BalanceBoard
namespace WiimoteReal
{
void Initialize(::Wiimote::InitializeMode init_mode);
@ -104,5 +107,4 @@ void Shutdown();
void Resume();
void Pause();
void Refresh();
} // namespace WiimoteReal

View File

@ -214,7 +214,8 @@ struct InputReportStatus
private:
static constexpr auto BATTERY_MAX = std::numeric_limits<decltype(battery)>::max();
// Linear fit of battery level mid-point for charge bars in home menu.
// Linear fit of battery level mid-point for Wii Remote charge bars in home menu.
// Note that Balance Board battery values differ (probably from 2xAA vs 4xAA).
static constexpr float BATTERY_LEVEL_M = 2.46f;
static constexpr float BATTERY_LEVEL_B = -0.013f;
};

View File

@ -11,15 +11,15 @@
#include "Common/MathUtil.h"
#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
namespace WiimoteEmu
{
CameraLogic::CameraLogic(const WiimoteCommon::InputReportStatus* status) : m_wiimote_status{*status}
{
}
void CameraLogic::Reset()
{
m_reg_data = {};
m_is_enabled = false;
}
void CameraLogic::DoState(PointerWrap& p)
@ -34,7 +34,7 @@ int CameraLogic::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
if (I2C_ADDR != slave_addr)
return 0;
if (!m_is_enabled)
if (!IsEnabled())
return 0;
return RawRead(&m_reg_data, addr, count, data_out);
@ -45,7 +45,7 @@ int CameraLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
if (I2C_ADDR != slave_addr)
return 0;
if (!m_is_enabled)
if (!IsEnabled())
return 0;
return RawWrite(&m_reg_data, addr, count, data_in);
@ -179,9 +179,9 @@ void CameraLogic::Update(const std::array<CameraPoint, NUM_POINTS>& camera_point
}
}
void CameraLogic::SetEnabled(bool is_enabled)
bool CameraLogic::IsEnabled() const
{
m_is_enabled = is_enabled;
return m_wiimote_status.ir;
}
} // namespace WiimoteEmu

View File

@ -5,9 +5,10 @@
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
namespace Common
{
@ -104,6 +105,8 @@ static_assert(sizeof(IRFull) == 9, "Wrong size");
class CameraLogic : public I2CSlave
{
public:
CameraLogic(const WiimoteCommon::InputReportStatus* status);
// OEM sensor bar distance between LED clusters in meters.
static constexpr float SENSOR_BAR_LED_SEPARATION = 0.2f;
@ -136,7 +139,6 @@ public:
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;
static constexpr int CAMERA_DATA_BYTES = 36;
@ -176,13 +178,15 @@ public:
static const u8 REPORT_DATA_OFFSET = offsetof(Register, camera_data);
private:
// When disabled the camera does not respond on the bus.
// Change is triggered by wiimote report 0x13.
bool IsEnabled() const;
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override;
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override;
Register m_reg_data{};
// When disabled the camera does not respond on the bus.
// Change is triggered by wiimote report 0x13.
bool m_is_enabled = false;
const WiimoteCommon::InputReportStatus& m_wiimote_status;
};
} // namespace WiimoteEmu

View File

@ -3,6 +3,7 @@
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
#include <cassert>
#include <cstring>
#include <optional>
#include <type_traits>
@ -117,8 +118,9 @@ SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state)
using T = std::decay_t<decltype(arg)>;
if constexpr (!std::is_same_v<std::monostate, T>)
{
static_assert(sizeof(arg) <= 6);
static_assert(sizeof(arg) <= s.MAX_EXT_DATA_SIZE);
static_assert(std::is_trivially_copyable_v<T>);
assert(s.length + sizeof(arg) <= s.data.size());
std::memcpy(&s.data[s.length], &arg, sizeof(arg));
s.length += sizeof(arg);
}
@ -213,6 +215,9 @@ bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimote
case ExtensionNumber::SHINKANSEN:
s += sizeof(Shinkansen::DesiredState);
break;
case ExtensionNumber::BALANCE_BOARD:
s += sizeof(BalanceBoardExt::DataFormat);
break;
default:
break;
}
@ -225,6 +230,10 @@ bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimote
return false;
}
// Contriving data with MPlus + BBoard could create an oversided length.
if (expected_size > serialized.data.size())
return false;
size_t pos = 1;
if (has_buttons)
@ -319,6 +328,8 @@ bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimote
return DeserializeExtensionState<TaTaCon::DataFormat>(state, serialized, pos);
case ExtensionNumber::SHINKANSEN:
return DeserializeExtensionState<Shinkansen::DesiredState>(state, serialized, pos);
case ExtensionNumber::BALANCE_BOARD:
return DeserializeExtensionState<BalanceBoardExt::DataFormat>(state, serialized, pos);
default:
break;
}

View File

@ -35,7 +35,10 @@ struct DesiredWiimoteState
struct SerializedWiimoteState
{
u8 length;
std::array<u8, 30> data; // 18 bytes Wiimote, 6 bytes MotionPlus, 6 bytes Extension
// 18 bytes Wiimote + ((6 bytes MotionPlus + 6 bytes Extension) | 8 bytes BalanceBoardExt)
static constexpr size_t MAX_EXT_DATA_SIZE = std::max(6, 8);
std::array<u8, 18 + std::max(6 + 6, 8)> data;
};
SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state);

View File

@ -4,18 +4,15 @@
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include <cmath>
#include <fstream>
#include <iterator>
#include "Common/BitUtils.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/EnumUtils.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h"
#include "Core/Core.h"
#include "Core/DolphinAnalytics.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
@ -25,7 +22,10 @@ namespace WiimoteEmu
{
using namespace WiimoteCommon;
void Wiimote::HandleReportMode(const OutputReportMode& dr)
// Used only for error generation:
static constexpr u8 EEPROM_I2C_ADDR = 0x50;
void WiimoteBase::HandleReportMode(const OutputReportMode& dr)
{
if (!DataReportBuilder::IsValidMode(dr.mode))
{
@ -47,7 +47,8 @@ void Wiimote::HandleReportMode(const OutputReportMode& dr)
// Tests that we have enough bytes for the report before we run the handler.
template <typename T, typename H>
void Wiimote::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size)
void WiimoteBase::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt,
u32 size)
{
if (size < sizeof(T))
{
@ -59,17 +60,17 @@ void Wiimote::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneri
(this->*handler)(Common::BitCastPtr<T>(&rpt.data[0]));
}
void Wiimote::EventLinked()
void WiimoteBase::EventLinked()
{
Reset();
}
void Wiimote::EventUnlinked()
void WiimoteBase::EventUnlinked()
{
Reset();
}
void Wiimote::InterruptDataOutput(const u8* data, u32 size)
void WiimoteBase::InterruptDataOutput(const u8* data, u32 size)
{
if (size == 0)
{
@ -88,7 +89,7 @@ void Wiimote::InterruptDataOutput(const u8* data, u32 size)
// WiiBrew:
// In every single Output Report, bit 0 (0x01) of the first byte controls the Rumble feature.
InvokeHandler<OutputReportRumble>(&Wiimote::HandleReportRumble, rpt, rpt_size);
InvokeHandler<OutputReportRumble>(&WiimoteBase::HandleReportRumble, rpt, rpt_size);
switch (rpt.rpt_id)
{
@ -96,34 +97,34 @@ void Wiimote::InterruptDataOutput(const u8* data, u32 size)
// This is handled above.
break;
case OutputReportID::LED:
InvokeHandler<OutputReportLeds>(&Wiimote::HandleReportLeds, rpt, rpt_size);
InvokeHandler<OutputReportLeds>(&WiimoteBase::HandleReportLeds, rpt, rpt_size);
break;
case OutputReportID::ReportMode:
InvokeHandler<OutputReportMode>(&Wiimote::HandleReportMode, rpt, rpt_size);
InvokeHandler<OutputReportMode>(&WiimoteBase::HandleReportMode, rpt, rpt_size);
break;
case OutputReportID::IRLogicEnable:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleIRLogicEnable, rpt, rpt_size);
InvokeHandler<OutputReportEnableFeature>(&WiimoteBase::HandleIRLogicEnable, rpt, rpt_size);
break;
case OutputReportID::SpeakerEnable:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleSpeakerEnable, rpt, rpt_size);
InvokeHandler<OutputReportEnableFeature>(&WiimoteBase::HandleSpeakerEnable, rpt, rpt_size);
break;
case OutputReportID::RequestStatus:
InvokeHandler<OutputReportRequestStatus>(&Wiimote::HandleRequestStatus, rpt, rpt_size);
InvokeHandler<OutputReportRequestStatus>(&WiimoteBase::HandleRequestStatus, rpt, rpt_size);
break;
case OutputReportID::WriteData:
InvokeHandler<OutputReportWriteData>(&Wiimote::HandleWriteData, rpt, rpt_size);
InvokeHandler<OutputReportWriteData>(&WiimoteBase::HandleWriteData, rpt, rpt_size);
break;
case OutputReportID::ReadData:
InvokeHandler<OutputReportReadData>(&Wiimote::HandleReadData, rpt, rpt_size);
InvokeHandler<OutputReportReadData>(&WiimoteBase::HandleReadData, rpt, rpt_size);
break;
case OutputReportID::SpeakerData:
InvokeHandler<OutputReportSpeakerData>(&Wiimote::HandleSpeakerData, rpt, rpt_size);
InvokeHandler<OutputReportSpeakerData>(&WiimoteBase::HandleSpeakerData, rpt, rpt_size);
break;
case OutputReportID::SpeakerMute:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleSpeakerMute, rpt, rpt_size);
InvokeHandler<OutputReportEnableFeature>(&WiimoteBase::HandleSpeakerMute, rpt, rpt_size);
break;
case OutputReportID::IRLogicEnable2:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleIRLogicEnable2, rpt, rpt_size);
InvokeHandler<OutputReportEnableFeature>(&WiimoteBase::HandleIRLogicEnable2, rpt, rpt_size);
break;
default:
PanicAlertFmt("HidOutputReport: Unknown report ID {:#04x}", static_cast<u8>(rpt.rpt_id));
@ -131,7 +132,7 @@ void Wiimote::InterruptDataOutput(const u8* data, u32 size)
}
}
void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code)
void WiimoteBase::SendAck(OutputReportID rpt_id, ErrorCode error_code)
{
TypedInputData<InputReportAck> rpt(InputReportID::Ack);
auto& ack = rpt.payload;
@ -146,13 +147,6 @@ void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code)
void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number,
bool desired_motion_plus)
{
if (WIIMOTE_BALANCE_BOARD == m_index)
{
// Prevent M+ or anything else silly from being attached to a balance board.
// In the future if we support an emulated balance board we can force the BB "extension" here.
return;
}
// FYI: AttachExtension also connects devices to the i2c bus
if (m_is_motion_plus_attached && !desired_motion_plus)
@ -231,29 +225,19 @@ void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number,
}
}
void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&)
void WiimoteBase::HandleRequestStatus(const OutputReportRequestStatus&)
{
// FYI: buttons are updated in Update() for determinism
// Update status struct
m_status.extension = m_extension_port.IsDeviceConnected();
m_status.SetEstimatedCharge(m_battery_setting.GetValue() / ciface::BATTERY_INPUT_MAX_VALUE);
if (Core::WantsDeterminism())
{
// One less thing to break determinism:
m_status.SetEstimatedCharge(1.f);
}
// Less than 0x20 triggers the low-battery flag:
m_status.battery_low = m_status.battery < 0x20;
TypedInputData<InputReportStatus> rpt(InputReportID::Status);
rpt.payload = m_status;
InterruptDataInputCallback(rpt.GetData(), rpt.GetSize());
}
void Wiimote::HandleWriteData(const OutputReportWriteData& wd)
void WiimoteBase::HandleWriteData(const OutputReportWriteData& wd)
{
if (m_read_request.size)
{
@ -332,7 +316,7 @@ void Wiimote::HandleReportRumble(const WiimoteCommon::OutputReportRumble& rpt)
// FYI: A real wiimote never seems to ACK a rumble report:
}
void Wiimote::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt)
void WiimoteBase::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt)
{
m_status.leds = rpt.leds;
@ -340,7 +324,7 @@ void Wiimote::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt)
SendAck(OutputReportID::LED, ErrorCode::Success);
}
void Wiimote::HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeature& rpt)
void WiimoteBase::HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
// FYI: We ignore this and update camera data regardless.
@ -348,7 +332,7 @@ void Wiimote::HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeatur
SendAck(OutputReportID::IRLogicEnable2, ErrorCode::Success);
}
void Wiimote::HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature& rpt)
void WiimoteBase::HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
// Note: Wiibrew currently refers to this report (0x13) as "Enable IR Pixel Clock"
// however my testing shows this affects the relevant status bit and whether or not
@ -356,8 +340,6 @@ void Wiimote::HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature
m_status.ir = rpt.enable;
m_camera_logic.SetEnabled(m_status.ir);
if (rpt.ack)
SendAck(OutputReportID::IRLogicEnable, ErrorCode::Success);
}
@ -370,7 +352,7 @@ void Wiimote::HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&
SendAck(OutputReportID::SpeakerMute, ErrorCode::Success);
}
void Wiimote::HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature& rpt)
void WiimoteBase::HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
m_status.speaker = rpt.enable;
@ -402,7 +384,7 @@ void Wiimote::HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData& rp
// More investigation is needed.
}
void Wiimote::HandleReadData(const OutputReportReadData& rd)
void WiimoteBase::HandleReadData(const OutputReportReadData& rd)
{
if (m_read_request.size)
{
@ -433,7 +415,7 @@ void Wiimote::HandleReadData(const OutputReportReadData& rd)
// FYI: No "ACK" is sent under normal situations.
}
bool Wiimote::ProcessReadDataRequest()
auto WiimoteBase::ProcessReadDataRequest() -> UpdateProgress
{
// Limit the amt to 16 bytes
// AyuanX: the MTU is 640B though... what a waste!
@ -442,7 +424,7 @@ bool Wiimote::ProcessReadDataRequest()
if (0 == bytes_to_read)
{
// No active request:
return false;
return UpdateProgress::Continue;
}
TypedInputData<InputReportReadDataReply> rpt(InputReportID::ReadDataReply);
@ -544,32 +526,29 @@ bool Wiimote::ProcessReadDataRequest()
InterruptDataInputCallback(rpt.GetData(), rpt.GetSize());
return true;
return UpdateProgress::DoNotContinue;
}
void Wiimote::DoState(PointerWrap& p)
void WiimoteBase::DoState(PointerWrap& p)
{
// No need to sync. Index will not change.
// p.Do(m_index);
// No need to sync. This is not wiimote state.
// p.Do(m_sensor_bar_on_top);
p.Do(m_reporting_mode);
p.Do(m_reporting_continuous);
p.Do(m_speaker_mute);
p.Do(m_status);
p.Do(m_eeprom);
p.Do(m_read_request);
// Sub-devices:
m_speaker_logic.DoState(p);
m_camera_logic.DoState(p);
p.DoMarker("WiimoteBase");
}
if (p.IsReadMode())
m_camera_logic.SetEnabled(m_status.ir);
void Wiimote::DoState(PointerWrap& p)
{
WiimoteBase::DoState(p);
p.Do(m_speaker_mute);
p.Do(m_is_motion_plus_attached);
p.Do(m_active_extension);
@ -585,6 +564,10 @@ void Wiimote::DoState(PointerWrap& p)
if (m_active_extension != ExtensionNumber::NONE)
GetActiveExtension()->DoState(p);
// Sub-devices:
m_speaker_logic.DoState(p);
m_camera_logic.DoState(p);
// Dynamics
p.Do(m_swing_state);
p.Do(m_tilt_state);
@ -597,9 +580,35 @@ void Wiimote::DoState(PointerWrap& p)
p.DoMarker("Wiimote");
}
ExtensionNumber Wiimote::GetActiveExtensionNumber() const
void BalanceBoard::DoState(PointerWrap& p)
{
return m_active_extension;
WiimoteBase::DoState(p);
m_ext.DoState(p);
p.DoMarker("BalanceBoard");
}
void BalanceBoard::HandleReportRumble(const WiimoteCommon::OutputReportRumble&)
{
// Behaves like a Wii Remote (no ACK).
}
void BalanceBoard::HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
// ACKs like Wii Remote.
if (rpt.ack)
SendAck(OutputReportID::SpeakerMute, ErrorCode::Success);
}
void BalanceBoard::HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&)
{
// Behaves like a Wii Remote (no ACK).
}
ControllerEmu::SubscribableSettingValue<int>& Wiimote::GetAttachmentSetting()
{
return static_cast<ControllerEmu::Attachments*>(
GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments))
->GetAttachmentSetting();
}
ControllerEmu::SubscribableSettingValue<bool>& Wiimote::GetMotionPlusSetting()

View File

@ -0,0 +1,174 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include <array>
#include <cstring>
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/Hash.h"
#include "Common/Swap.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
namespace WiimoteEmu
{
constexpr std::array<u8, 6> balance_board_id{{0x00, 0x00, 0xa4, 0x20, 0x04, 0x02}};
BalanceBoardExt::BalanceBoardExt(BalanceBoard* owner)
: Extension1stParty("BalanceBoard", _trans("Balance Board")), m_owner(owner)
{
}
// Use the same calibration data for all sensors.
// Wii Fit internally converts to grams, but using grams for the actual values leads to
// overflowing values, and also underflowing values when a sensor gets negative if balance is
// extremely tilted. Actual balance boards tend to have a sensitivity of about 10 grams.
// Real board values vary greatly but these nice values are very near those of a real board.
static constexpr u16 KG17_RANGE = 1700;
static constexpr u16 CALIBRATED_0_KG = 10000;
static constexpr u16 CALIBRATED_17_KG = CALIBRATED_0_KG + KG17_RANGE;
static constexpr u16 CALIBRATED_34_KG = CALIBRATED_17_KG + KG17_RANGE;
// WiiBrew: "always 0x69"
static constexpr u8 REFERENCE_BATTERY = 0x69;
// Chosen arbitrarily from the value for pokechu22's board. As long as the calibration and
// actual temperatures match, the value here doesn't matter.
static constexpr u8 REFERENCE_TEMPERATURE = 0x19;
void BalanceBoardExt::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
const ControllerEmu::AnalogStick::StateData balance_state = m_owner->m_balance->GetState();
const bool is_stepped_off = m_owner->m_options->controls[0]->GetState<bool>();
const double weight = is_stepped_off ? 0.0 : m_owner->m_weight_setting.GetValue();
auto [weight_tr, weight_br, weight_tl, weight_bl] =
CenterOfBalanceToSensors(balance_state, weight).data;
if (auto& func = m_input_override_function)
{
weight_tr = func(BALANCE_GROUP, SENSOR_TR, weight_tr).value_or(weight_tr);
weight_br = func(BALANCE_GROUP, SENSOR_BR, weight_br).value_or(weight_br);
weight_tl = func(BALANCE_GROUP, SENSOR_TL, weight_tl).value_or(weight_tl);
weight_bl = func(BALANCE_GROUP, SENSOR_BL, weight_bl).value_or(weight_bl);
}
DataFormat bb_data = {};
bb_data.sensor_tr = Common::swap16(ConvertToSensorWeight(weight_tr));
bb_data.sensor_br = Common::swap16(ConvertToSensorWeight(weight_br));
bb_data.sensor_tl = Common::swap16(ConvertToSensorWeight(weight_tl));
bb_data.sensor_bl = Common::swap16(ConvertToSensorWeight(weight_bl));
target_state->data = bb_data;
}
void BalanceBoardExt::Update(const DesiredExtensionState& target_state)
{
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
m_reg.controller_data[0x8] = REFERENCE_TEMPERATURE;
m_reg.controller_data[0x9] = 0x00;
// FYI: Real EXT battery byte doesn't exactly match status report battery byte.
// e.g. Seen: EXT:0x9e and Status:0xc6
// I'm guessing they just have separate ADCs.
m_reg.controller_data[0xa] = m_owner->m_status.battery;
}
void BalanceBoardExt::Reset()
{
EncryptedExtension::Reset();
m_reg.identifier = balance_board_id;
struct CalibrationData
{
u8 always_0x01 = 0x01;
u8 battery = REFERENCE_BATTERY;
u8 always_0x00[2] = {};
using SensorValue = Common::BigEndianValue<u16>;
// Each array is ordered: TR, BR, TL, BL
std::array<SensorValue, 4> kg0;
std::array<SensorValue, 4> kg17;
std::array<SensorValue, 4> kg34;
u8 checksum[4];
};
static_assert(sizeof(CalibrationData) == 0x20, "Wrong size");
CalibrationData cal_data{};
cal_data.kg0.fill(CalibrationData::SensorValue{CALIBRATED_0_KG});
cal_data.kg17.fill(CalibrationData::SensorValue{CALIBRATED_17_KG});
cal_data.kg34.fill(CalibrationData::SensorValue{CALIBRATED_34_KG});
std::copy_n(reinterpret_cast<char*>(&cal_data), 0x10, m_reg.calibration.data());
std::copy_n(reinterpret_cast<char*>(&cal_data) + 0x10, 0x10, m_reg.calibration2.data());
m_reg.calibration3 = {REFERENCE_TEMPERATURE, 0x01};
ComputeCalibrationChecksum();
}
u16 BalanceBoardExt::ConvertToSensorWeight(double weight_in_kilos)
{
return MapFloat((weight_in_kilos - 17.0) / 17.0, CALIBRATED_17_KG, CALIBRATED_0_KG,
CALIBRATED_34_KG);
}
double BalanceBoardExt::ConvertToKilograms(u16 sensor_weight)
{
const auto result =
MapToFloat<double>(sensor_weight, CALIBRATED_17_KG, CALIBRATED_0_KG, CALIBRATED_34_KG);
return result * 17.0 + 17.0;
}
void BalanceBoardExt::ComputeCalibrationChecksum()
{
u32 crc = Common::StartCRC32();
// Skip the first 4 bytes
crc = Common::UpdateCRC32(crc, &m_reg.calibration[4], 0xc);
// Skip the last 4 bytes (the CRC itself)
crc = Common::UpdateCRC32(crc, &m_reg.calibration2[0], 0xc);
// Hash 2 of the bytes skipped earlier
crc = Common::UpdateCRC32(crc, &m_reg.calibration[0], 2);
crc = Common::UpdateCRC32(crc, &m_reg.calibration3[0], 2);
m_reg.calibration2[0x0c] = u8(crc >> 24);
m_reg.calibration2[0x0d] = u8(crc >> 16);
m_reg.calibration2[0x0e] = u8(crc >> 8);
m_reg.calibration2[0x0f] = u8(crc);
}
Common::DVec2 BalanceBoardExt::SensorsToCenterOfBalance(Common::DVec4 sensors)
{
const double right = sensors.data[0] + sensors.data[1];
const double left = sensors.data[2] + sensors.data[3];
const double total = right + left;
if (!total)
return Common::DVec2{};
const double top = sensors.data[0] + sensors.data[2];
const double bottom = sensors.data[1] + sensors.data[3];
return Common::DVec2(right - left, top - bottom) / total;
}
Common::DVec4 BalanceBoardExt::CenterOfBalanceToSensors(Common::DVec2 balance, double total_weight)
{
return Common::DVec4{(1 + balance.x) * (1 + balance.y), (1 + balance.x) * (1 - balance.y),
(1 - balance.x) * (1 + balance.y), (1 - balance.x) * (1 - balance.y)} *
(total_weight * 0.25);
}
} // namespace WiimoteEmu

View File

@ -0,0 +1,59 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/Matrix.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
namespace WiimoteEmu
{
class BalanceBoard;
// NOTE: Although the balance board is implemented as an extension (which matches how the actual
// balance board works), actual controls are not registered in the same way as other extensions;
// WiimoteEmu::BalanceBoard has all of the controls and some are also used by the extension.
class BalanceBoardExt : public Extension1stParty
{
public:
struct DataFormat
{
// top-right, bottom-right, top-left, bottom-left
u16 sensor_tr;
u16 sensor_br;
u16 sensor_tl;
u16 sensor_bl;
// Note: temperature, padding, and battery bytes are not included.
// temperature change is not exposed, and arguably not important.
// battery level is pulled from the wii remote state.
};
static_assert(sizeof(DataFormat) == 8, "Wrong size");
static constexpr const char* BALANCE_GROUP = "Balance";
static constexpr const char* SENSOR_TR = "TR";
static constexpr const char* SENSOR_BR = "BR";
static constexpr const char* SENSOR_TL = "TL";
static constexpr const char* SENSOR_BL = "BL";
BalanceBoardExt(BalanceBoard* owner);
static constexpr float DEFAULT_WEIGHT = 60;
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
void Update(const DesiredExtensionState& target_state) override;
void Reset() override;
// Vec4 ordered: top-right, bottom-right, top-left, bottom-left
static Common::DVec2 SensorsToCenterOfBalance(Common::DVec4 sensors);
static Common::DVec4 CenterOfBalanceToSensors(Common::DVec2 balance, double total_weight);
static u16 ConvertToSensorWeight(double weight_in_kilos);
static double ConvertToKilograms(u16 sensor_weight);
private:
void ComputeCalibrationChecksum();
const BalanceBoard* m_owner;
};
} // namespace WiimoteEmu

View File

@ -8,6 +8,7 @@
#include "Common/BitUtils.h"
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h"
#include "Core/HW/WiimoteEmu/Extension/Drums.h"
@ -27,7 +28,8 @@ 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>;
DrawsomeTablet::DataFormat, TaTaCon::DataFormat, Shinkansen::DesiredState,
BalanceBoardExt::DataFormat>;
ExtensionData data = std::monostate();
static_assert(std::is_same_v<std::monostate,
@ -57,6 +59,9 @@ struct DesiredExtensionState
static_assert(
std::is_same_v<Shinkansen::DesiredState,
std::variant_alternative_t<ExtensionNumber::SHINKANSEN, ExtensionData>>);
static_assert(
std::is_same_v<BalanceBoardExt::DataFormat,
std::variant_alternative_t<ExtensionNumber::BALANCE_BOARD, ExtensionData>>);
static_assert(std::variant_size_v<DesiredExtensionState::ExtensionData> == ExtensionNumber::MAX);
};

View File

@ -64,6 +64,8 @@ private:
class EncryptedExtension : public Extension
{
public:
void DoState(PointerWrap& p) override;
static constexpr u8 I2C_ADDR = 0x52;
static constexpr int CONTROLLER_DATA_BYTES = 21;
@ -85,11 +87,14 @@ public:
// address 0x20
std::array<u8, 0x10> calibration;
u8 unknown3[0x10];
std::array<u8, 0x10> calibration2;
// address 0x40
std::array<u8, 0x10> encryption_key_data;
u8 unknown4[0xA0];
u8 unknown3[0x10];
// Address 0x60
std::array<u8, 2> calibration3;
u8 unknown4[0x8e];
// address 0xF0
u8 encryption;
@ -106,7 +111,6 @@ protected:
Register m_reg = {};
void Reset() override;
void DoState(PointerWrap& p) override;
virtual void UpdateEncryptionKey() = 0;

View File

@ -23,6 +23,8 @@ enum ExtensionNumber : u8
TATACON,
SHINKANSEN,
BALANCE_BOARD,
MAX
};

View File

@ -17,7 +17,6 @@
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/MsgHandler.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
@ -28,6 +27,7 @@
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h"
@ -39,8 +39,7 @@
#include "Core/HW/WiimoteEmu/Extension/Turntable.h"
#include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/Control/Output.h"
#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
@ -67,18 +66,21 @@ static const u16 dpad_bitmasks[] = {Wiimote::PAD_UP, Wiimote::PAD_DOWN, Wiimote:
static const u16 dpad_sideways_bitmasks[] = {Wiimote::PAD_RIGHT, Wiimote::PAD_LEFT, Wiimote::PAD_UP,
Wiimote::PAD_DOWN};
void Wiimote::Reset()
static const u16 bboard_bitmasks[] = {BalanceBoard::BUTTON_POWER};
static constexpr u16 IR_LOW_X = 0x7F;
static constexpr u16 IR_LOW_Y = 0x5D;
static constexpr u16 IR_HIGH_X = 0x380;
static constexpr u16 IR_HIGH_Y = 0x2A2;
void WiimoteBase::Reset()
{
const bool want_determinism = Core::WantsDeterminism();
SetRumble(false);
// Wiimote starts in non-continuous CORE mode:
m_reporting_mode = InputReportID::ReportCore;
m_reporting_continuous = false;
m_speaker_mute = false;
// EEPROM
// TODO: This feels sketchy, this needs to properly handle the case where the load and the write
@ -108,69 +110,38 @@ void Wiimote::Reset()
}
else
{
// Load some default data.
// IR calibration:
std::array<u8, 11> ir_calibration = {
// Point 1
IR_LOW_X & 0xFF,
IR_LOW_Y & 0xFF,
// Mix
((IR_LOW_Y & 0x300) >> 2) | ((IR_LOW_X & 0x300) >> 4) | ((IR_LOW_Y & 0x300) >> 6) |
((IR_HIGH_X & 0x300) >> 8),
// Point 2
IR_HIGH_X & 0xFF,
IR_LOW_Y & 0xFF,
// Point 3
IR_HIGH_X & 0xFF,
IR_HIGH_Y & 0xFF,
// Mix
((IR_HIGH_Y & 0x300) >> 2) | ((IR_HIGH_X & 0x300) >> 4) | ((IR_HIGH_Y & 0x300) >> 6) |
((IR_LOW_X & 0x300) >> 8),
// Point 4
IR_LOW_X & 0xFF,
IR_HIGH_Y & 0xFF,
// Checksum
0x00,
};
UpdateCalibrationDataChecksum(ir_calibration, 1);
m_eeprom.ir_calibration_1 = ir_calibration;
m_eeprom.ir_calibration_2 = ir_calibration;
// Accel calibration:
// Last byte is a checksum.
std::array<u8, 10> accel_calibration = {
ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_ZERO_G, 0, ACCEL_ONE_G, ACCEL_ONE_G, ACCEL_ONE_G, 0, 0, 0,
};
UpdateCalibrationDataChecksum(accel_calibration, 1);
m_eeprom.accel_calibration_1 = accel_calibration;
m_eeprom.accel_calibration_2 = accel_calibration;
// TODO: Is this needed?
// Data of unknown purpose:
constexpr std::array<u8, 24> EEPROM_DATA_16D0 = {
0x00, 0x00, 0x00, 0xFF, 0x11, 0xEE, 0x00, 0x00, 0x33, 0xCC, 0x44, 0xBB,
0x00, 0x00, 0x66, 0x99, 0x77, 0x88, 0x00, 0x00, 0x2B, 0x01, 0xE8, 0x13};
m_eeprom.unk_2 = EEPROM_DATA_16D0;
std::string mii_file = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/mii.bin";
if (File::Exists(mii_file))
{
// Import from the existing mii.bin file, if present
std::ifstream file;
File::OpenFStream(file, mii_file, std::ios::binary | std::ios::in);
file.read(reinterpret_cast<char*>(m_eeprom.mii_data_1.data()), m_eeprom.mii_data_1.size());
m_eeprom.mii_data_2 = m_eeprom.mii_data_1;
file.close();
}
LoadDefaultEeprom();
}
m_read_request = {};
// Initialize i2c bus:
m_i2c_bus.Reset();
m_i2c_bus.AddSlave(&m_speaker_logic);
m_i2c_bus.AddSlave(&m_camera_logic);
m_status = {};
// A real wii remote does not normally send a status report on connection.
// But if an extension is already attached it does send one.
// Clearing this initially will simulate that on the first update cycle.
m_status.extension = 0;
}
u8 WiimoteBase::GetWiimoteDeviceIndex() const
{
return m_bt_device_index;
}
void WiimoteBase::SetWiimoteDeviceIndex(u8 index)
{
m_bt_device_index = index;
}
void Wiimote::Reset()
{
const bool want_determinism = Core::WantsDeterminism();
SetRumble(false);
m_speaker_mute = false;
// Reset extension connections to NONE:
m_is_motion_plus_attached = false;
@ -186,17 +157,16 @@ void Wiimote::Reset()
m_motion_plus_setting.GetValue());
}
WiimoteBase::Reset();
// Initialize i2c bus:
m_i2c_bus.AddSlave(&m_speaker_logic);
m_i2c_bus.AddSlave(&m_camera_logic);
// Reset sub-devices.
m_speaker_logic.Reset();
m_camera_logic.Reset();
m_status = {};
// A real wii remote does not normally send a status report on connection.
// But if an extension is already attached it does send one.
// Clearing this initially will simulate that on the first update cycle.
m_status.extension = 0;
// Dynamics:
m_swing_state = {};
m_tilt_state = {};
@ -206,7 +176,81 @@ void Wiimote::Reset()
m_imu_cursor_state = {};
}
Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(index)
void Wiimote::LoadDefaultEeprom()
{
// Load some default data.
// IR calibration:
std::array<u8, 11> ir_calibration = {
// Point 1
IR_LOW_X & 0xFF,
IR_LOW_Y & 0xFF,
// Mix
((IR_LOW_Y & 0x300) >> 2) | ((IR_LOW_X & 0x300) >> 4) | ((IR_LOW_Y & 0x300) >> 6) |
((IR_HIGH_X & 0x300) >> 8),
// Point 2
IR_HIGH_X & 0xFF,
IR_LOW_Y & 0xFF,
// Point 3
IR_HIGH_X & 0xFF,
IR_HIGH_Y & 0xFF,
// Mix
((IR_HIGH_Y & 0x300) >> 2) | ((IR_HIGH_X & 0x300) >> 4) | ((IR_HIGH_Y & 0x300) >> 6) |
((IR_LOW_X & 0x300) >> 8),
// Point 4
IR_LOW_X & 0xFF,
IR_HIGH_Y & 0xFF,
// Checksum
0x00,
};
UpdateCalibrationDataChecksum(ir_calibration, 1);
m_eeprom.ir_calibration_1 = ir_calibration;
m_eeprom.ir_calibration_2 = ir_calibration;
// Accel calibration:
// Last byte is a checksum.
std::array<u8, 10> accel_calibration = {
ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_ZERO_G, 0, ACCEL_ONE_G, ACCEL_ONE_G, ACCEL_ONE_G, 0, 0, 0,
};
UpdateCalibrationDataChecksum(accel_calibration, 1);
m_eeprom.accel_calibration_1 = accel_calibration;
m_eeprom.accel_calibration_2 = accel_calibration;
// TODO: Is this needed?
// Data of unknown purpose:
constexpr std::array<u8, 24> EEPROM_DATA_16D0 = {0x00, 0x00, 0x00, 0xFF, 0x11, 0xEE, 0x00, 0x00,
0x33, 0xCC, 0x44, 0xBB, 0x00, 0x00, 0x66, 0x99,
0x77, 0x88, 0x00, 0x00, 0x2B, 0x01, 0xE8, 0x13};
m_eeprom.unk_2 = EEPROM_DATA_16D0;
std::string mii_file = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/mii.bin";
if (File::Exists(mii_file))
{
// Import from the existing mii.bin file, if present
std::ifstream file;
File::OpenFStream(file, mii_file, std::ios::binary | std::ios::in);
file.read(reinterpret_cast<char*>(m_eeprom.mii_data_1.data()), m_eeprom.mii_data_1.size());
m_eeprom.mii_data_2 = m_eeprom.mii_data_1;
file.close();
}
}
void BalanceBoard::LoadDefaultEeprom()
{
// A real balance board starts with zero-filled EEPROM.
m_eeprom = {};
}
WiimoteBase::WiimoteBase(const u8 index) : m_index(index), m_bt_device_index(index)
{
}
InputConfig* WiimoteBase::GetConfig() const
{
return ::Wiimote::GetConfig();
}
Wiimote::Wiimote(const u8 index) : WiimoteBase(index)
{
using Translatability = ControllerEmu::Translatability;
@ -280,7 +324,6 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(i
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::DrawsomeTablet>());
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::TaTaCon>());
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::Shinkansen>());
m_attachments->AddSetting(&m_motion_plus_setting, {_trans("Attach MotionPlus")}, true);
// Rumble
@ -323,16 +366,9 @@ Wiimote::~Wiimote()
std::string Wiimote::GetName() const
{
if (m_index == WIIMOTE_BALANCE_BOARD)
return "BalanceBoard";
return fmt::format("Wiimote{}", 1 + m_index);
}
InputConfig* Wiimote::GetConfig() const
{
return ::Wiimote::GetConfig();
}
ControllerEmu::ControlGroup* Wiimote::GetWiimoteGroup(WiimoteGroup group) const
{
switch (group)
@ -429,13 +465,13 @@ ControllerEmu::ControlGroup* Wiimote::GetShinkansenGroup(ShinkansenGroup group)
->GetGroup(group);
}
bool Wiimote::ProcessExtensionPortEvent()
auto WiimoteBase::ProcessExtensionPortEvent() -> UpdateProgress
{
// WiiBrew: Following a connection or disconnection event on the Extension Port,
// data reporting is disabled and the Data Reporting Mode must be reset before new data can
// arrive.
if (m_extension_port.IsDeviceConnected() == m_status.extension)
return false;
return UpdateProgress::Continue;
// FYI: This happens even during a read request which continues after the status report is sent.
m_reporting_mode = InputReportID::ReportDisabled;
@ -444,7 +480,7 @@ bool Wiimote::ProcessExtensionPortEvent()
HandleRequestStatus(OutputReportRequestStatus{});
return true;
return UpdateProgress::DoNotContinue;
}
void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state)
@ -452,6 +488,14 @@ void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state)
m_status.buttons.hex = target_state.buttons.hex & ButtonData::BUTTON_MASK;
}
void Wiimote::UpdateBatteryStatus(double charge)
{
m_status.SetEstimatedCharge(charge);
// Less than 0x20 triggers the low-battery flag:
m_status.battery_low = m_status.battery < 0x20;
}
static std::array<CameraPoint, CameraLogic::NUM_POINTS>
GetPassthroughCameraPoints(ControllerEmu::IRPassthrough* ir_passthrough)
{
@ -533,61 +577,85 @@ void Wiimote::BuildDesiredWiimoteState(DesiredWiimoteState* target_state,
->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,
SensorBarState sensor_bar_state)
void WiimoteBase::PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state)
{
const auto lock = GetStateLock();
BuildDesiredWiimoteState(target_state, sensor_bar_state);
}
void Wiimote::Update(const WiimoteEmu::DesiredWiimoteState& target_state)
auto WiimoteBase::ProcessEvents() -> UpdateProgress
{
if (ProcessExtensionPortEvent() == UpdateProgress::DoNotContinue)
{
// Extension port event occurred.
// Don't send any other reports.
return UpdateProgress::DoNotContinue;
}
if (ProcessReadDataRequest() == UpdateProgress::DoNotContinue)
{
// Read requests suppress normal input reports
// Don't send any other reports
return UpdateProgress::DoNotContinue;
}
return UpdateProgress::Continue;
}
void WiimoteBase::Update(const WiimoteEmu::DesiredWiimoteState& target_state)
{
// Update buttons in the status struct which is sent in 99% of input reports.
UpdateButtonsStatus(target_state);
if (Core::WantsDeterminism())
{
// One less thing to break determinism:
UpdateBatteryStatus(1.0);
}
else
{
UpdateBatteryStatus(m_battery_setting.GetValue() / ciface::BATTERY_INPUT_MAX_VALUE);
}
SubDeviceUpdate(target_state);
if (ProcessEvents() == UpdateProgress::DoNotContinue)
return;
SendDataReport(target_state);
}
void Wiimote::SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state)
{
m_camera_logic.Update(target_state.camera_points);
// If a new extension is requested in the GUI the change will happen here.
HandleExtensionSwap(static_cast<ExtensionNumber>(target_state.extension.data.index()),
target_state.motion_plus.has_value());
// Prepare input data of the extension for reading.
// Prepare extension input first as motion-plus may read from it.
// FYI: This should only happen when trigged by EXT bus read.
// but it's really not going to break anything being here.
// and refactoring is needed to do it properly.
GetActiveExtension()->Update(target_state.extension);
if (m_is_motion_plus_attached)
{
// M+ has some internal state that must processed.
m_motion_plus.Update(target_state.extension);
}
// Returns true if a report was sent.
if (ProcessExtensionPortEvent())
{
// Extension port event occurred.
// Don't send any other reports.
return;
// FYI: This should really only happen when trigged by EXT bus read.
// refactoring is needed to do that properly.
m_motion_plus.PrepareInput(target_state.motion_plus.has_value() ?
target_state.motion_plus.value() :
MotionPlus::GetDefaultGyroscopeData());
}
if (ProcessReadDataRequest())
{
// Read requests suppress normal input reports
// Don't send any other reports
return;
}
SendDataReport(target_state);
}
void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
void WiimoteBase::SendDataReport(const DesiredWiimoteState& target_state)
{
auto& movie = Core::System::GetInstance().GetMovie();
movie.SetPolledDevice();
@ -607,8 +675,9 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
DataReportBuilder rpt_builder(m_reporting_mode);
if (movie.IsPlayingInput() && movie.PlayWiimote(m_bt_device_index, rpt_builder,
m_active_extension, GetExtensionEncryptionKey()))
if (movie.IsPlayingInput() &&
movie.PlayWiimote(m_bt_device_index, rpt_builder, GetActiveExtensionNumber(),
GetExtensionEncryptionKey()))
{
// Update buttons in status struct from movie:
rpt_builder.GetCoreData(&m_status.buttons);
@ -630,10 +699,6 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
// IR Camera:
if (rpt_builder.HasIR())
{
// Note: Camera logic currently contains no changing state so we can just update it here.
// If that changes this should be moved to Wiimote::Update();
m_camera_logic.Update(target_state.camera_points);
// The real wiimote reads camera data from the i2c bus starting at offset 0x37:
const u8 camera_data_offset =
CameraLogic::REPORT_DATA_OFFSET + rpt_builder.GetIRDataFormatOffset();
@ -652,19 +717,6 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
// Extension port:
if (rpt_builder.HasExt())
{
// Prepare extension input first as motion-plus may read from it.
// This currently happens in Wiimote::Update();
// TODO: Separate extension input data preparation from Update.
// GetActiveExtension()->PrepareInput();
if (m_is_motion_plus_attached)
{
// TODO: Make input preparation triggered by bus read.
m_motion_plus.PrepareInput(target_state.motion_plus.has_value() ?
target_state.motion_plus.value() :
MotionPlus::GetDefaultGyroscopeData());
}
u8* ext_data = rpt_builder.GetExtDataPtr();
const u8 ext_size = rpt_builder.GetExtDataSize();
@ -677,7 +729,7 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
}
}
movie.CheckWiimoteStatus(m_bt_device_index, rpt_builder, m_active_extension,
movie.CheckWiimoteStatus(m_bt_device_index, rpt_builder, GetActiveExtensionNumber(),
GetExtensionEncryptionKey());
// Send the report:
@ -741,15 +793,16 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface)
// B
m_buttons->SetControlExpression(1, "`Click 1`");
#endif
m_buttons->SetControlExpression(2, "`1`"); // 1
m_buttons->SetControlExpression(3, "`2`"); // 2
m_buttons->SetControlExpression(4, "Q"); // -
m_buttons->SetControlExpression(5, "E"); // +
// 1, 2, -, +
m_buttons->SetControlExpression(2, "`1`");
m_buttons->SetControlExpression(3, "`2`");
m_buttons->SetControlExpression(4, "Q");
m_buttons->SetControlExpression(5, "E");
#ifdef _WIN32
m_buttons->SetControlExpression(6, "RETURN"); // Home
#else
// Home
#ifdef _WIN32
m_buttons->SetControlExpression(6, "RETURN");
#else
m_buttons->SetControlExpression(6, "Return");
#endif
@ -769,20 +822,20 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface)
// DPad
#ifdef _WIN32
m_dpad->SetControlExpression(0, "UP"); // Up
m_dpad->SetControlExpression(1, "DOWN"); // Down
m_dpad->SetControlExpression(2, "LEFT"); // Left
m_dpad->SetControlExpression(3, "RIGHT"); // Right
m_dpad->SetControlExpression(0, "UP");
m_dpad->SetControlExpression(1, "DOWN");
m_dpad->SetControlExpression(2, "LEFT");
m_dpad->SetControlExpression(3, "RIGHT");
#elif __APPLE__
m_dpad->SetControlExpression(0, "`Up Arrow`"); // Up
m_dpad->SetControlExpression(1, "`Down Arrow`"); // Down
m_dpad->SetControlExpression(2, "`Left Arrow`"); // Left
m_dpad->SetControlExpression(3, "`Right Arrow`"); // Right
m_dpad->SetControlExpression(0, "`Up Arrow`");
m_dpad->SetControlExpression(1, "`Down Arrow`");
m_dpad->SetControlExpression(2, "`Left Arrow`");
m_dpad->SetControlExpression(3, "`Right Arrow`");
#else
m_dpad->SetControlExpression(0, "Up"); // Up
m_dpad->SetControlExpression(1, "Down"); // Down
m_dpad->SetControlExpression(2, "Left"); // Left
m_dpad->SetControlExpression(3, "Right"); // Right
m_dpad->SetControlExpression(0, "Up");
m_dpad->SetControlExpression(1, "Down");
m_dpad->SetControlExpression(2, "Left");
m_dpad->SetControlExpression(3, "Right");
#endif
// Motion Source
@ -817,12 +870,27 @@ Extension* Wiimote::GetNoneExtension() const
return static_cast<Extension*>(m_attachments->GetAttachmentList()[ExtensionNumber::NONE].get());
}
Extension* Wiimote::GetActiveExtension() const
Extension* Wiimote::GetActiveExtension()
{
return static_cast<Extension*>(m_attachments->GetAttachmentList()[m_active_extension].get());
}
EncryptionKey Wiimote::GetExtensionEncryptionKey() const
Extension* BalanceBoard::GetActiveExtension()
{
return &m_ext;
}
ExtensionNumber Wiimote::GetActiveExtensionNumber() const
{
return m_active_extension;
}
ExtensionNumber BalanceBoard::GetActiveExtensionNumber() const
{
return ExtensionNumber::BALANCE_BOARD;
}
EncryptionKey WiimoteBase::GetExtensionEncryptionKey()
{
if (ExtensionNumber::NONE == GetActiveExtensionNumber())
return {};
@ -991,4 +1059,123 @@ Common::Matrix44 Wiimote::GetTotalTransformation() const
Common::Quaternion::RotateX(m_imu_cursor_state.recentered_pitch)));
}
std::string BalanceBoard::GetName() const
{
return "BalanceBoard";
}
void BalanceBoard::LoadDefaults(const ControllerInterface& ciface)
{
EmulatedController::LoadDefaults(ciface);
// Power
m_buttons->SetControlExpression(0, "P");
// Balance: Up, Down, Left, Right
m_balance->SetControlExpression(0, "I");
m_balance->SetControlExpression(1, "K");
m_balance->SetControlExpression(2, "J");
m_balance->SetControlExpression(3, "L");
// Step Off
m_options->SetControlExpression(0, "O");
// Because our defaults use keyboard input, set calibration shape to a square.
m_balance->SetCalibrationFromGate(ControllerEmu::SquareStickGate(1.0));
}
void BalanceBoard::SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state)
{
m_ext.Update(target_state.extension);
}
void BalanceBoard::Reset()
{
WiimoteBase::Reset();
m_extension_port.AttachExtension(&m_ext);
m_ext.Reset();
}
BalanceBoard::BalanceBoard(const u8 index) : WiimoteBase(index)
{
using Translatability = ControllerEmu::Translatability;
// Buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(BUTTONS_GROUP_NAME));
m_buttons->AddInput(Translatability::Translate, BUTTON_POWER_NAME);
// Balance
groups.emplace_back(
m_balance = new ControllerEmu::AnalogStick(
_trans("Balance"), std::make_unique<ControllerEmu::SquareStickGate>(1.0)));
// Options
groups.emplace_back(m_options = new ControllerEmu::ControlGroup(_trans("Options")));
m_options->AddInput(Translatability::Translate, "Step Off");
m_options->AddSetting(&m_battery_setting,
{_trans("Battery"),
// i18n: The percent symbol.
_trans("%")},
95, 0, 100);
m_options->AddSetting(&m_weight_setting,
{_trans("Weight"),
// i18n: The abbreviation for kilograms.
_trans("kg")},
BalanceBoardExt::DEFAULT_WEIGHT, 0, 250);
Reset();
}
ControllerEmu::ControlGroup* BalanceBoard::GetBalanceBoardGroup(BalanceBoardGroup group) const
{
switch (group)
{
case BalanceBoardGroup::Buttons:
return m_buttons;
case BalanceBoardGroup::Options:
return m_options;
case BalanceBoardGroup::Balance:
return m_balance;
default:
ASSERT(false);
return nullptr;
}
}
ButtonData BalanceBoard::GetCurrentlyPressedButtons()
{
const auto lock = GetStateLock();
ButtonData buttons{};
m_buttons->GetState(&buttons.hex, bboard_bitmasks, m_input_override_function);
return buttons;
}
// Update buttons in status struct from user input.
void BalanceBoard::UpdateButtonsStatus(const DesiredWiimoteState& target_state)
{
m_status.buttons.hex = target_state.buttons.hex & BalanceBoard::BUTTON_POWER;
}
void BalanceBoard::UpdateBatteryStatus(double charge)
{
// Balance Board battery values are higher than that of a Wii Remote.
// This is a linear fit for the charge bars in the home menu.
m_status.battery = u8(std::lround((charge + 3.53) / 8.52 * 0xff));
// TODO: Verify this behavior on real hardware.
m_status.battery_low = m_status.battery <= 0x69;
}
void BalanceBoard::BuildDesiredWiimoteState(DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state)
{
m_buttons->GetState(&target_state->buttons.hex, bboard_bitmasks, m_input_override_function);
m_ext.BuildDesiredExtensionState(&target_state->extension);
}
} // namespace WiimoteEmu

View File

@ -16,6 +16,7 @@
#include "Core/HW/WiimoteEmu/Camera.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
#include "Core/HW/WiimoteEmu/MotionPlus.h"
@ -25,6 +26,7 @@ class PointerWrap;
namespace ControllerEmu
{
class AnalogStick;
class Attachments;
class Buttons;
class ControlGroup;
@ -38,6 +40,7 @@ class IRPassthrough;
class ModifySettingsButton;
class Output;
class Tilt;
class Triggers;
} // namespace ControllerEmu
namespace WiimoteEmu
@ -63,6 +66,13 @@ enum class WiimoteGroup
IRPassthrough,
};
enum class BalanceBoardGroup
{
Buttons,
Balance,
Options,
};
enum class NunchukGroup;
enum class ClassicGroup;
enum class GuitarGroup;
@ -94,129 +104,43 @@ void UpdateCalibrationDataChecksum(T& data, int cksum_bytes)
}
}
class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::HIDWiimote
class WiimoteBase : public ControllerEmu::EmulatedController, public WiimoteCommon::HIDWiimote
{
public:
static constexpr u16 IR_LOW_X = 0x7F;
static constexpr u16 IR_LOW_Y = 0x5D;
static constexpr u16 IR_HIGH_X = 0x380;
static constexpr u16 IR_HIGH_Y = 0x2A2;
static constexpr u8 ACCEL_ZERO_G = 0x80;
static constexpr u8 ACCEL_ONE_G = 0x9A;
static constexpr u16 PAD_LEFT = 0x01;
static constexpr u16 PAD_RIGHT = 0x02;
static constexpr u16 PAD_DOWN = 0x04;
static constexpr u16 PAD_UP = 0x08;
static constexpr u16 BUTTON_PLUS = 0x10;
static constexpr u16 BUTTON_TWO = 0x0100;
static constexpr u16 BUTTON_ONE = 0x0200;
static constexpr u16 BUTTON_B = 0x0400;
static constexpr u16 BUTTON_A = 0x0800;
static constexpr u16 BUTTON_MINUS = 0x1000;
static constexpr u16 BUTTON_HOME = 0x8000;
static constexpr const char* BUTTONS_GROUP = _trans("Buttons");
static constexpr const char* DPAD_GROUP = _trans("D-Pad");
static constexpr const char* ACCELEROMETER_GROUP = "IMUAccelerometer";
static constexpr const char* GYROSCOPE_GROUP = "IMUGyroscope";
static constexpr const char* IR_GROUP = "IR";
static constexpr const char* IR_PASSTHROUGH_GROUP = "IRPassthrough";
static constexpr const char* A_BUTTON = "A";
static constexpr const char* B_BUTTON = "B";
static constexpr const char* ONE_BUTTON = "1";
static constexpr const char* TWO_BUTTON = "2";
static constexpr const char* MINUS_BUTTON = "-";
static constexpr const char* PLUS_BUTTON = "+";
static constexpr const char* HOME_BUTTON = "Home";
static constexpr const char* UPRIGHT_OPTION = "Upright Wiimote";
static constexpr const char* SIDEWAYS_OPTION = "Sideways Wiimote";
explicit Wiimote(unsigned int index);
~Wiimote();
std::string GetName() const override;
explicit WiimoteBase(u8 index);
InputConfig* GetConfig() const override;
void LoadDefaults(const ControllerInterface& ciface) override;
ControllerEmu::ControlGroup* GetWiimoteGroup(WiimoteGroup group) const;
ControllerEmu::ControlGroup* GetNunchukGroup(NunchukGroup group) const;
ControllerEmu::ControlGroup* GetClassicGroup(ClassicGroup group) const;
ControllerEmu::ControlGroup* GetGuitarGroup(GuitarGroup group) const;
ControllerEmu::ControlGroup* GetDrumsGroup(DrumsGroup group) const;
ControllerEmu::ControlGroup* GetTurntableGroup(TurntableGroup group) const;
ControllerEmu::ControlGroup* GetUDrawTabletGroup(UDrawTabletGroup group) const;
ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(DrawsomeTabletGroup group) const;
ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const;
ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup group) const;
u8 GetWiimoteDeviceIndex() const override;
void SetWiimoteDeviceIndex(u8 index) override;
void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state) override;
void Update(const WiimoteEmu::DesiredWiimoteState& target_state) override;
void EventLinked() override;
void EventUnlinked() override;
void InterruptDataOutput(const u8* data, u32 size) override;
WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
void Reset();
virtual void Reset();
virtual void LoadDefaultEeprom() = 0;
void DoState(PointerWrap& p);
virtual void DoState(PointerWrap& p);
virtual Extension* GetActiveExtension() = 0;
// Active extension number is exposed for TAS.
ExtensionNumber GetActiveExtensionNumber() const;
ControllerEmu::SubscribableSettingValue<bool>& GetMotionPlusSetting();
virtual ExtensionNumber GetActiveExtensionNumber() const = 0;
static Common::Vec3
OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec,
const ControllerEmu::InputOverrideFunction& input_override_function);
private:
// Used only for error generation:
static constexpr u8 EEPROM_I2C_ADDR = 0x50;
// static constexpr int EEPROM_SIZE = 16 * 1024;
// This is the region exposed over bluetooth:
protected:
// This is the region exposed over bluetooth, total size is 0x4000.
static constexpr int EEPROM_FREE_SIZE = 0x1700;
void RefreshConfig();
virtual void SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) = 0;
virtual void UpdateButtonsStatus(const DesiredWiimoteState& target_state) = 0;
virtual void UpdateBatteryStatus(double charge) = 0;
virtual void BuildDesiredWiimoteState(DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state) = 0;
void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state) override;
void StepDynamics();
void UpdateButtonsStatus(const DesiredWiimoteState& target_state);
void BuildDesiredWiimoteState(DesiredWiimoteState* target_state, SensorBarState sensor_bar_state);
// Returns simulated accelerometer data in m/s^2.
Common::Vec3 GetAcceleration(Common::Vec3 extra_acceleration) const;
// Returns simulated gyroscope data in radians/s.
Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity) const;
// Returns the transformation of the world around the wiimote.
// Used for simulating camera data and for rotating acceleration data.
// Does not include orientation transformations.
Common::Matrix44
GetTransformation(const Common::Matrix33& extra_rotation = Common::Matrix33::Identity()) const;
// Returns the world rotation from the effects of sideways/upright settings.
Common::Quaternion GetOrientation() const;
std::optional<Common::Vec3> OverrideVec3(const ControllerEmu::ControlGroup* control_group,
std::optional<Common::Vec3> optional_vec) const;
Common::Vec3 OverrideVec3(const ControllerEmu::ControlGroup* control_group,
Common::Vec3 vec) const;
Common::Vec3 GetTotalAcceleration() const;
Common::Vec3 GetTotalAngularVelocity() const;
Common::Matrix44 GetTotalTransformation() const;
void HandleReportRumble(const WiimoteCommon::OutputReportRumble&);
virtual void HandleReportRumble(const WiimoteCommon::OutputReportRumble&) = 0;
void HandleReportLeds(const WiimoteCommon::OutputReportLeds&);
void HandleReportMode(const WiimoteCommon::OutputReportMode&);
void HandleRequestStatus(const WiimoteCommon::OutputReportRequestStatus&);
@ -224,30 +148,28 @@ private:
void HandleWriteData(const WiimoteCommon::OutputReportWriteData&);
void HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature&);
void HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeature&);
void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&);
virtual void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&) = 0;
void HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature&);
void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&);
virtual void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) = 0;
template <typename T, typename H>
void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size);
void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus);
bool ProcessExtensionPortEvent();
void SendDataReport(const DesiredWiimoteState& target_state);
bool ProcessReadDataRequest();
void SetRumble(bool on);
void SendAck(WiimoteCommon::OutputReportID rpt_id, WiimoteCommon::ErrorCode err);
bool IsSideways() const;
bool IsUpright() const;
Extension* GetActiveExtension() const;
Extension* GetNoneExtension() const;
private:
enum class UpdateProgress
{
Continue,
DoNotContinue,
};
UpdateProgress ProcessEvents();
UpdateProgress ProcessExtensionPortEvent();
UpdateProgress ProcessReadDataRequest();
void SendDataReport(const DesiredWiimoteState& target_state);
// TODO: Kill this nonsensical function used for TAS:
EncryptionKey GetExtensionEncryptionKey() const;
EncryptionKey GetExtensionEncryptionKey();
struct ReadRequest
{
@ -289,6 +211,153 @@ private:
static_assert(EEPROM_FREE_SIZE == sizeof(UsableEEPROMData));
protected:
I2CBus m_i2c_bus;
ExtensionPort m_extension_port{&m_i2c_bus};
// Wiimote index, 0-3.
// Can also be 4 for Balance Board.
// This is used to look up the user button config.
const u8 m_index;
// The Bluetooth 'slot' this device is connected to.
// This is usually the same as m_index, but can differ during Netplay.
u8 m_bt_device_index;
ControllerEmu::SettingValue<double> m_battery_setting;
private:
WiimoteCommon::InputReportID m_reporting_mode = WiimoteCommon::InputReportID::ReportDisabled;
bool m_reporting_continuous = false;
protected:
WiimoteCommon::InputReportStatus m_status;
private:
bool m_eeprom_dirty = false;
ReadRequest m_read_request;
protected:
UsableEEPROMData m_eeprom;
};
class Wiimote final : public WiimoteBase
{
public:
static constexpr u8 ACCEL_ZERO_G = 0x80;
static constexpr u8 ACCEL_ONE_G = 0x9A;
static constexpr u16 PAD_LEFT = 0x01;
static constexpr u16 PAD_RIGHT = 0x02;
static constexpr u16 PAD_DOWN = 0x04;
static constexpr u16 PAD_UP = 0x08;
static constexpr u16 BUTTON_PLUS = 0x10;
static constexpr u16 BUTTON_TWO = 0x0100;
static constexpr u16 BUTTON_ONE = 0x0200;
static constexpr u16 BUTTON_B = 0x0400;
static constexpr u16 BUTTON_A = 0x0800;
static constexpr u16 BUTTON_MINUS = 0x1000;
static constexpr u16 BUTTON_HOME = 0x8000;
static constexpr const char* BUTTONS_GROUP = _trans("Buttons");
static constexpr const char* DPAD_GROUP = _trans("D-Pad");
static constexpr const char* ACCELEROMETER_GROUP = "IMUAccelerometer";
static constexpr const char* GYROSCOPE_GROUP = "IMUGyroscope";
static constexpr const char* IR_GROUP = "IR";
static constexpr const char* IR_PASSTHROUGH_GROUP = "IRPassthrough";
static constexpr const char* A_BUTTON = "A";
static constexpr const char* B_BUTTON = "B";
static constexpr const char* ONE_BUTTON = "1";
static constexpr const char* TWO_BUTTON = "2";
static constexpr const char* MINUS_BUTTON = "-";
static constexpr const char* PLUS_BUTTON = "+";
static constexpr const char* HOME_BUTTON = "Home";
static constexpr const char* UPRIGHT_OPTION = "Upright Wiimote";
static constexpr const char* SIDEWAYS_OPTION = "Sideways Wiimote";
explicit Wiimote(u8 index);
~Wiimote();
std::string GetName() const override;
void LoadDefaults(const ControllerInterface& ciface) override;
ControllerEmu::ControlGroup* GetWiimoteGroup(WiimoteGroup group) const;
ControllerEmu::ControlGroup* GetNunchukGroup(NunchukGroup group) const;
ControllerEmu::ControlGroup* GetClassicGroup(ClassicGroup group) const;
ControllerEmu::ControlGroup* GetGuitarGroup(GuitarGroup group) const;
ControllerEmu::ControlGroup* GetDrumsGroup(DrumsGroup group) const;
ControllerEmu::ControlGroup* GetTurntableGroup(TurntableGroup group) const;
ControllerEmu::ControlGroup* GetUDrawTabletGroup(UDrawTabletGroup group) const;
ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(DrawsomeTabletGroup group) const;
ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const;
ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup group) const;
void SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) override;
WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
void Reset() override;
void LoadDefaultEeprom() override;
void DoState(PointerWrap& p) override;
Extension* GetActiveExtension() override;
Extension* GetNoneExtension() const;
ExtensionNumber GetActiveExtensionNumber() const override;
ControllerEmu::SubscribableSettingValue<int>& GetAttachmentSetting();
ControllerEmu::SubscribableSettingValue<bool>& GetMotionPlusSetting();
static Common::Vec3
OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec,
const ControllerEmu::InputOverrideFunction& input_override_function);
protected:
void HandleReportRumble(const WiimoteCommon::OutputReportRumble&) override;
void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&) override;
void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) override;
void UpdateButtonsStatus(const DesiredWiimoteState& target_state) override;
void UpdateBatteryStatus(double charge) override;
void BuildDesiredWiimoteState(DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state) override;
std::optional<Common::Vec3> OverrideVec3(const ControllerEmu::ControlGroup* control_group,
std::optional<Common::Vec3> optional_vec) const;
Common::Vec3 OverrideVec3(const ControllerEmu::ControlGroup* control_group,
Common::Vec3 vec) const;
Common::Vec3 GetTotalAcceleration() const;
Common::Vec3 GetTotalAngularVelocity() const;
private:
void RefreshConfig();
void StepDynamics();
void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus);
// Returns simulated accelerometer data in m/s^2.
Common::Vec3 GetAcceleration(Common::Vec3 extra_acceleration) const;
// Returns simulated gyroscope data in radians/s.
Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity) const;
// Returns the transformation of the world around the wiimote.
// Used for simulating camera data and for rotating acceleration data.
// Does not include orientation transformations.
Common::Matrix44
GetTransformation(const Common::Matrix33& extra_rotation = Common::Matrix33::Identity()) const;
// Returns the world rotation from the effects of sideways/upright settings.
Common::Quaternion GetOrientation() const;
Common::Matrix44 GetTotalTransformation() const;
void SetRumble(bool on);
bool IsSideways() const;
bool IsUpright() const;
// Control groups for user input:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::Buttons* m_dpad;
@ -307,42 +376,18 @@ private:
ControllerEmu::SettingValue<bool> m_sideways_setting;
ControllerEmu::SettingValue<bool> m_upright_setting;
ControllerEmu::SettingValue<double> m_battery_setting;
ControllerEmu::SubscribableSettingValue<bool> m_motion_plus_setting;
ControllerEmu::SettingValue<double> m_fov_x_setting;
ControllerEmu::SettingValue<double> m_fov_y_setting;
SpeakerLogic m_speaker_logic;
MotionPlus m_motion_plus;
CameraLogic m_camera_logic;
I2CBus m_i2c_bus;
ExtensionPort m_extension_port{&m_i2c_bus};
// Wiimote index, 0-3.
// Can also be 4 for Balance Board.
// This is used to look up the user button config.
const u8 m_index;
// The Bluetooth 'slot' this device is connected to.
// This is usually the same as m_index, but can differ during Netplay.
u8 m_bt_device_index;
WiimoteCommon::InputReportID m_reporting_mode;
bool m_reporting_continuous;
CameraLogic m_camera_logic{&m_status};
bool m_speaker_mute;
WiimoteCommon::InputReportStatus m_status;
ExtensionNumber m_active_extension;
bool m_is_motion_plus_attached;
bool m_eeprom_dirty = false;
ReadRequest m_read_request;
UsableEEPROMData m_eeprom;
ExtensionNumber m_active_extension = NONE;
MotionPlus m_motion_plus;
bool m_is_motion_plus_attached = false;
// Dynamics:
MotionState m_swing_state;
@ -354,4 +399,51 @@ private:
Config::ConfigChangedCallbackID m_config_changed_callback_id;
};
class BalanceBoard final : public WiimoteBase
{
friend class BalanceBoardExt;
public:
static constexpr const char* BUTTONS_GROUP_NAME = _trans("Buttons");
static constexpr u16 BUTTON_POWER = Wiimote::BUTTON_A;
static constexpr const char* BUTTON_POWER_NAME = "Power";
explicit BalanceBoard(u8 index);
ControllerEmu::ControlGroup* GetBalanceBoardGroup(BalanceBoardGroup group) const;
std::string GetName() const override;
void LoadDefaults(const ControllerInterface& ciface) override;
void SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) override;
WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
void Reset() override;
void LoadDefaultEeprom() override;
void DoState(PointerWrap& p) override;
Extension* GetActiveExtension() override;
ExtensionNumber GetActiveExtensionNumber() const override;
protected:
void UpdateButtonsStatus(const DesiredWiimoteState& target_state) override;
void UpdateBatteryStatus(double charge) override;
void BuildDesiredWiimoteState(DesiredWiimoteState* target_state,
SensorBarState sensor_bar_state) override;
void HandleReportRumble(const WiimoteCommon::OutputReportRumble&) override;
void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&) override;
void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) override;
private:
BalanceBoardExt m_ext{this};
ControllerEmu::Buttons* m_buttons;
ControllerEmu::AnalogStick* m_balance;
ControllerEmu::ControlGroup* m_options;
ControllerEmu::SettingValue<double> m_weight_setting;
};
} // namespace WiimoteEmu

View File

@ -4,9 +4,7 @@
#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
#include <fmt/format.h>
@ -15,7 +13,6 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/Core.h"
#include "Core/HW/WII_IPC.h"
@ -62,7 +59,7 @@ WiimoteDevice::WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int
m_name(GetNumber() == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01")
{
INFO_LOG_FMT(IOS_WIIMOTE, "Wiimote: #{} Constructed", GetNumber());
INFO_LOG_FMT(IOS_WIIMOTE, "{} Constructed", GetDisplayName());
m_link_key.fill(0xa0 + GetNumber());
m_class = {0x00, 0x04, 0x48};
@ -224,8 +221,7 @@ void WiimoteDevice::Activate(bool connect)
{
SetBasebandState(BasebandState::RequestConnection);
Core::DisplayMessage(fmt::format("Wii Remote {} connected", GetNumber() + 1),
CONNECTION_MESSAGE_TIME);
Core::DisplayMessage(fmt::format("{} connected", GetDisplayName()), CONNECTION_MESSAGE_TIME);
}
else if (!connect && IsConnected())
{
@ -235,8 +231,7 @@ void WiimoteDevice::Activate(bool connect)
// Not doing that doesn't seem to break anything.
m_host->RemoteDisconnect(GetBD());
Core::DisplayMessage(fmt::format("Wii Remote {} disconnected", GetNumber() + 1),
CONNECTION_MESSAGE_TIME);
Core::DisplayMessage(fmt::format("{} disconnected", GetDisplayName()), CONNECTION_MESSAGE_TIME);
}
}
@ -275,7 +270,7 @@ void WiimoteDevice::EventDisconnect(u8 reason)
// FYI: It looks like reason is always 0x13 (User Ended Connection).
Core::DisplayMessage(
fmt::format("Wii Remote {} disconnected by emulated software", GetNumber() + 1),
fmt::format("{} disconnected by emulated software (0x{:02x})", GetDisplayName(), reason),
CONNECTION_MESSAGE_TIME);
Reset();
@ -1006,4 +1001,12 @@ void WiimoteDevice::InterruptDataInputCallback(u8 hid_type, const u8* data, u32
m_host->SendACLPacket(GetBD(), reinterpret_cast<const u8*>(&data_frame), data_frame_size);
}
std::string WiimoteDevice::GetDisplayName()
{
if (GetNumber() != WIIMOTE_BALANCE_BOARD)
return fmt::format("Wii Remote {}", GetNumber() + 1);
else
return "Balance Board";
}
} // namespace IOS::HLE

View File

@ -170,5 +170,7 @@ private:
void SDPSendServiceAttributeResponse(u16 cid, u16 transaction_id, u32 service_handle,
u16 start_attr_id, u16 end_attr_id,
u16 maximum_attribute_byte_count, u8* continuation_state);
std::string GetDisplayName();
};
} // namespace IOS::HLE

View File

@ -12,7 +12,6 @@
#include <mbedtls/config.h>
#include <mbedtls/md.h>
#include <mutex>
#include <sstream>
#include <thread>
#include <utility>
#include <variant>
@ -38,7 +37,6 @@
#include "Core/Boot/Boot.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h"
#include "Core/Config/WiimoteSettings.h"
#include "Core/ConfigLoaders/MovieConfigLoader.h"
#include "Core/ConfigManager.h"
@ -58,6 +56,7 @@
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
@ -69,8 +68,6 @@
#include "Core/System.h"
#include "Core/WiiUtils.h"
#include "DiscIO/Enums.h"
#include "InputCommon/GCPadStatus.h"
#include "VideoCommon/VideoBackendBase.h"
@ -130,10 +127,8 @@ std::string MovieManager::GetInputDisplay()
if (!IsMovieActive())
{
m_controllers = {};
m_wiimotes = {};
const auto& si = m_system.GetSerialInterface();
for (int i = 0; i < 4; ++i)
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
if (si.GetDeviceType(i) == SerialInterface::SIDEVICE_GC_GBA_EMULATED)
m_controllers[i] = ControllerType::GBA;
@ -141,6 +136,10 @@ std::string MovieManager::GetInputDisplay()
m_controllers[i] = ControllerType::GC;
else
m_controllers[i] = ControllerType::None;
}
m_wiimotes = {};
for (int i = 0; i < MAX_BBMOTES; ++i)
{
m_wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None;
}
}
@ -148,15 +147,15 @@ std::string MovieManager::GetInputDisplay()
std::string input_display;
{
std::lock_guard guard(m_input_display_lock);
for (int i = 0; i < 4; ++i)
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
if (IsUsingPad(i))
input_display += m_input_display[i] + '\n';
}
for (int i = 0; i < 4; ++i)
for (int i = 0; i < MAX_BBMOTES; ++i)
{
if (IsUsingWiimote(i))
input_display += m_input_display[i + 4] + '\n';
input_display += m_input_display[i + SerialInterface::MAX_SI_CHANNELS] + '\n';
}
}
return input_display;
@ -475,7 +474,7 @@ void MovieManager::ChangeWiiPads(bool instantly)
{
WiimoteEnabledArray wiimotes{};
for (int i = 0; i < MAX_WIIMOTES; ++i)
for (int i = 0; i < MAX_BBMOTES; ++i)
{
wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None;
}
@ -485,7 +484,7 @@ void MovieManager::ChangeWiiPads(bool instantly)
return;
const auto bt = WiiUtils::GetBluetoothEmuDevice();
for (int i = 0; i < MAX_WIIMOTES; ++i)
for (int i = 0; i < MAX_BBMOTES; ++i)
{
const bool is_using_wiimote = IsUsingWiimote(i);
@ -676,7 +675,8 @@ static std::string GenerateInputDisplayString(ControllerState padState, int cont
static std::string GenerateWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
ExtensionNumber ext, const EncryptionKey& key)
{
std::string display_str = fmt::format("R{}:", remoteID + 1);
std::string display_str =
(remoteID == WIIMOTE_BALANCE_BOARD ? "BB:" : fmt::format("R{}:", remoteID + 1));
if (rpt.HasCore())
{
@ -797,6 +797,24 @@ static std::string GenerateWiiInputDisplayString(int remoteID, const DataReportB
display_str += Analog2DToString(right_stick.x, right_stick.y, " R-ANA", 31);
}
// Balance board
if (rpt.HasExt() && ext == ExtensionNumber::BALANCE_BOARD)
{
const u8* const extData = rpt.GetExtDataPtr();
BalanceBoardExt::DataFormat bb;
memcpy(&bb, extData, sizeof(bb));
key.Decrypt((u8*)&bb, 0, sizeof(bb));
const double tr = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_tr));
const double br = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_br));
const double tl = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_tl));
const double bl = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_bl));
display_str +=
fmt::format(" TR:{:5.2f}kg BR:{:5.2f}kg TL:{:5.2f}kg BL:{:5.2f}kg", tr, br, tl, bl);
}
return display_str;
}
@ -864,7 +882,7 @@ void MovieManager::CheckWiimoteStatus(int wiimote, const DataReportBuilder& rpt,
std::string display_str = GenerateWiiInputDisplayString(wiimote, rpt, ext, key);
std::lock_guard guard(m_input_display_lock);
m_input_display[wiimote + 4] = std::move(display_str);
m_input_display[wiimote + SerialInterface::MAX_SI_CHANNELS] = std::move(display_str);
}
if (IsRecordingInput())
@ -886,7 +904,7 @@ void MovieManager::RecordWiimote(int wiimote, const u8* data, u8 size)
// NOTE: EmuThread / Host Thread
void MovieManager::ReadHeader()
{
for (int i = 0; i < 4; ++i)
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
if (m_temp_header.GBAControllers & (1 << i))
m_controllers[i] = ControllerType::GBA;
@ -894,8 +912,13 @@ void MovieManager::ReadHeader()
m_controllers[i] = ControllerType::GC;
else
m_controllers[i] = ControllerType::None;
m_wiimotes[i] = (m_temp_header.controllers & (1 << (i + 4))) != 0;
}
for (int i = 0; i < MAX_WIIMOTES; i++)
{
m_wiimotes[i] =
(m_temp_header.controllers & (1 << (i + SerialInterface::MAX_SI_CHANNELS))) != 0;
}
m_wiimotes[WIIMOTE_BALANCE_BOARD] = m_temp_header.bBalanceBoard;
m_recording_start_time = m_temp_header.recordingStartTime;
if (m_rerecords < m_temp_header.numRerecords)
m_rerecords = m_temp_header.numRerecords;
@ -1374,15 +1397,19 @@ void MovieManager::SaveRecording(const std::string& filename)
header.bWii = m_system.IsWii();
header.controllers = 0;
header.GBAControllers = 0;
for (int i = 0; i < 4; ++i)
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{
if (IsUsingGBA(i))
header.GBAControllers |= 1 << i;
if (IsUsingPad(i))
header.controllers |= 1 << i;
if (IsUsingWiimote(i) && m_system.IsWii())
header.controllers |= 1 << (i + 4);
}
for (int i = 0; i < MAX_WIIMOTES; i++)
{
if (IsUsingWiimote(i) && m_system.IsWii())
header.controllers |= 1 << (i + SerialInterface::MAX_SI_CHANNELS);
}
header.bBalanceBoard = IsUsingWiimote(WIIMOTE_BALANCE_BOARD);
header.bFromSaveState = m_recording_from_save_state;
header.frameCount = m_total_frames;

View File

@ -12,6 +12,8 @@
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/Wiimote.h"
struct BootParameters;
@ -50,8 +52,8 @@ enum class ControllerType
GC,
GBA,
};
using ControllerTypeArray = std::array<ControllerType, 4>;
using WiimoteEnabledArray = std::array<bool, 4>;
using ControllerTypeArray = std::array<ControllerType, SerialInterface::MAX_SI_CHANNELS>;
using WiimoteEnabledArray = std::array<bool, MAX_BBMOTES>;
// GameCube Controller State
#pragma pack(push, 1)
@ -130,10 +132,11 @@ struct DTMHeader
u8 reserved3;
bool bFollowBranch;
bool bUseFMA;
u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4)
bool bWidescreen; // true indicates SYSCONF aspect ratio is 16:9, false for 4:3
u8 countryCode; // SYSCONF country code
std::array<u8, 5> reserved; // Padding for any new config options
u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4)
bool bWidescreen; // true indicates SYSCONF aspect ratio is 16:9, false for 4:3
u8 countryCode; // SYSCONF country code
bool bBalanceBoard;
std::array<u8, 4> reserved; // Padding for any new config options
std::array<char, 40> discChange; // Name of iso file to switch to, for two disc games.
std::array<u8, 20> revision; // Git hash
u32 DSPiromHash;
@ -237,8 +240,8 @@ private:
u32 m_rerecords = 0;
PlayMode m_play_mode = PlayMode::None;
std::array<ControllerType, 4> m_controllers{};
std::array<bool, 4> m_wiimotes{};
ControllerTypeArray m_controllers{};
WiimoteEnabledArray m_wiimotes{};
ControllerState m_pad_state{};
DTMHeader m_temp_header{};
std::vector<u8> m_temp_input;
@ -273,7 +276,8 @@ private:
// m_input_display is used by both CPU and GPU (is mutable).
std::mutex m_input_display_lock;
std::array<std::string, 8> m_input_display;
std::array<std::string, static_cast<size_t>(SerialInterface::MAX_SI_CHANNELS) + MAX_BBMOTES>
m_input_display;
Core::System& m_system;
};

View File

@ -4,7 +4,6 @@
#include "Core/State.h"
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <filesystem>
#include <locale>
@ -12,7 +11,6 @@
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <utility>
#include <vector>
@ -31,12 +29,10 @@
#include "Common/MsgHandler.h"
#include "Common/Thread.h"
#include "Common/TimeUtil.h"
#include "Common/Timer.h"
#include "Common/Version.h"
#include "Common/WorkQueueThread.h"
#include "Core/AchievementManager.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
@ -46,7 +42,7 @@
#include "Core/HW/Wiimote.h"
#include "Core/Host.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayProto.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
@ -99,7 +95,7 @@ static size_t s_state_writes_in_queue;
static std::condition_variable s_state_write_queue_is_empty;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 169; // Last changed in PR 13074
constexpr u32 STATE_VERSION = 170; // Last changed in PR 13337
// Increase this if the StateExtendedHeader definition changes
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217

View File

@ -337,6 +337,7 @@
<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\Extension\BalanceBoard.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" />
@ -1001,6 +1002,7 @@
<ClCompile Include="Core\HW\WiimoteEmu\Dynamics.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\EmuSubroutines.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Encryption.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Extension\BalanceBoard.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Extension\Classic.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Extension\DrawsomeTablet.cpp" />
<ClCompile Include="Core\HW\WiimoteEmu\Extension\Drums.cpp" />

View File

@ -113,6 +113,8 @@ add_executable(dolphin-emu
Config/LogConfigWidget.h
Config/LogWidget.cpp
Config/LogWidget.h
Config/Mapping/BalanceBoardGeneral.cpp
Config/Mapping/BalanceBoardGeneral.h
Config/Mapping/FreeLookGeneral.cpp
Config/Mapping/FreeLookGeneral.h
Config/Mapping/FreeLookRotation.cpp
@ -364,6 +366,8 @@ add_executable(dolphin-emu
SkylanderPortal/SkylanderModifyDialog.h
SkylanderPortal/SkylanderPortalWindow.cpp
SkylanderPortal/SkylanderPortalWindow.h
TAS/BalanceBoardWidget.cpp
TAS/BalanceBoardWidget.h
TAS/GCTASInputWindow.cpp
TAS/GCTASInputWindow.h
TAS/GBATASInputWindow.cpp

View File

@ -0,0 +1,47 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/Mapping/BalanceBoardGeneral.h"
#include <QComboBox>
#include <QFormLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "InputCommon/InputConfig.h"
BalanceBoardGeneral::BalanceBoardGeneral(MappingWindow* window) : MappingWidget(window)
{
auto* layout = new QHBoxLayout;
auto& get_group = BalanceBoard::GetBalanceBoardGroup;
using BBG = WiimoteEmu::BalanceBoardGroup;
layout->addWidget(CreateGroupBox(tr("Buttons"), get_group(GetPort(), BBG::Buttons)));
layout->addWidget(CreateGroupBox(tr("Balance"), get_group(GetPort(), BBG::Balance)));
layout->addWidget(CreateGroupBox(tr("Options"), get_group(GetPort(), BBG::Options)));
setLayout(layout);
}
void BalanceBoardGeneral::LoadSettings()
{
BalanceBoard::LoadConfig();
}
void BalanceBoardGeneral::SaveSettings()
{
BalanceBoard::GetConfig()->SaveConfig();
}
InputConfig* BalanceBoardGeneral::GetConfig()
{
return BalanceBoard::GetConfig();
}

View File

@ -0,0 +1,17 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "DolphinQt/Config/Mapping/MappingWidget.h"
class BalanceBoardGeneral final : public MappingWidget
{
Q_OBJECT
public:
explicit BalanceBoardGeneral(MappingWindow* window);
InputConfig* GetConfig() override;
void LoadSettings() override;
void SaveSettings() override;
};

View File

@ -24,6 +24,7 @@
#include "Common/IniFile.h"
#include "Common/StringUtil.h"
#include "DolphinQt/Config/Mapping/BalanceBoardGeneral.h"
#include "DolphinQt/Config/Mapping/FreeLookGeneral.h"
#include "DolphinQt/Config/Mapping/FreeLookRotation.h"
#include "DolphinQt/Config/Mapping/GBAPadEmu.h"
@ -448,6 +449,13 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
ShowExtensionMotionTabs(false);
break;
}
case Type::MAPPING_BALANCE_BOARD_EMU:
{
widget = new BalanceBoardGeneral(this);
setWindowTitle(tr("Balance Board"));
AddWidget(tr("General and Options"), widget);
break;
}
case Type::MAPPING_HOTKEYS:
{
widget = new HotkeyGeneral(this);

View File

@ -41,6 +41,7 @@ public:
MAPPING_GC_MICROPHONE,
// Wii
MAPPING_WIIMOTE_EMU,
MAPPING_BALANCE_BOARD_EMU,
// Hotkeys
MAPPING_HOTKEYS,
// Freelook

View File

@ -14,9 +14,6 @@
#include <QScreen>
#include <QVBoxLayout>
#include <map>
#include <optional>
#include "Common/Config/Config.h"
#include "Core/Config/MainSettings.h"
@ -26,7 +23,6 @@
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Bluetooth/BTReal.h"
#include "Core/NetPlayProto.h"
#include "Core/System.h"
#include "Core/WiiUtils.h"
@ -38,8 +34,6 @@
#include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h"
#include "UICommon/UICommon.h"
WiimoteControllersWidget::WiimoteControllersWidget(QWidget* parent) : QWidget(parent)
{
CreateLayout();
@ -111,7 +105,6 @@ void WiimoteControllersWidget::CreateLayout()
m_wiimote_pt_labels[1] = new QLabel(tr("Reset all saved Wii Remote pairings"));
m_wiimote_emu = new QRadioButton(tr("Emulate the Wii's Bluetooth adapter"));
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data"));
m_wiimote_ciface = new QCheckBox(tr("Connect Wii Remotes for Emulated Controllers"));
@ -136,12 +129,22 @@ void WiimoteControllersWidget::CreateLayout()
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
auto* wm_label = m_wiimote_labels[i] = new QLabel(tr("Wii Remote %1").arg(i + 1));
auto text = (i == WIIMOTE_BALANCE_BOARD ? tr("Balance Board") : tr("Wii Remote %1").arg(i + 1));
auto* wm_label = m_wiimote_labels[i] = new QLabel(text);
auto* wm_box = m_wiimote_boxes[i] = new QComboBox();
auto* wm_button = m_wiimote_buttons[i] = new NonDefaultQPushButton(tr("Configure"));
for (const auto& item : {tr("None"), tr("Emulated Wii Remote"), tr("Real Wii Remote")})
wm_box->addItem(item);
wm_box->addItem(tr("None"));
if (i == WIIMOTE_BALANCE_BOARD)
{
wm_box->addItem(tr("Emulated Balance Board"));
wm_box->addItem(tr("Real Balance Board"));
}
else
{
wm_box->addItem(tr("Emulated Wii Remote"));
wm_box->addItem(tr("Real Wii Remote"));
}
int wm_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(wm_label, wm_row, 1);
@ -149,7 +152,6 @@ void WiimoteControllersWidget::CreateLayout()
m_wiimote_layout->addWidget(wm_button, wm_row, 3);
}
m_wiimote_layout->addWidget(m_wiimote_real_balance_board, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_ciface, m_wiimote_layout->rowCount(), 0, 1, -1);
@ -185,8 +187,6 @@ void WiimoteControllersWidget::ConnectWidgets()
LoadSettings(Core::GetState(Core::System::GetInstance()));
});
connect(m_wiimote_real_balance_board, &QCheckBox::toggled, this,
&WiimoteControllersWidget::SaveSettings);
connect(m_wiimote_speaker_data, &QCheckBox::toggled, this,
&WiimoteControllersWidget::SaveSettings);
connect(m_wiimote_sync, &QPushButton::clicked, this,
@ -248,16 +248,15 @@ void WiimoteControllersWidget::OnWiimoteRefreshPressed()
void WiimoteControllersWidget::OnWiimoteConfigure(size_t index)
{
MappingWindow::Type type;
switch (m_wiimote_boxes[index]->currentIndex())
if (index == WIIMOTE_BALANCE_BOARD)
{
type = MappingWindow::Type::MAPPING_BALANCE_BOARD_EMU;
// Balance Board is a single entry its own InputConfig object.
index = 0;
}
else
{
case 0: // None
case 2: // Real Wii Remote
return;
case 1: // Emulated Wii Remote
type = MappingWindow::Type::MAPPING_WIIMOTE_EMU;
break;
default:
return;
}
MappingWindow* window = new MappingWindow(this, type, static_cast<int>(index));
@ -274,8 +273,6 @@ void WiimoteControllersWidget::LoadSettings(Core::State state)
SignalBlocking(m_wiimote_boxes[i])
->setCurrentIndex(int(Config::Get(Config::GetInfoForWiimoteSource(int(i)))));
}
SignalBlocking(m_wiimote_real_balance_board)
->setChecked(Config::Get(Config::WIIMOTE_BB_SOURCE) == WiimoteSource::Real);
SignalBlocking(m_wiimote_speaker_data)
->setChecked(Config::Get(Config::MAIN_WIIMOTE_ENABLE_SPEAKER));
SignalBlocking(m_wiimote_ciface)
@ -308,7 +305,7 @@ void WiimoteControllersWidget::LoadSettings(Core::State state)
for (auto* pt_label : m_wiimote_pt_labels)
pt_label->setEnabled(enable_passthrough);
const int num_local_wiimotes = is_netplay ? NetPlay::NumLocalWiimotes() : 4;
const int num_local_wiimotes = is_netplay ? NetPlay::NumLocalWiimotes() : MAX_BBMOTES;
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
m_wiimote_labels[i]->setEnabled(enable_emu_bt);
@ -319,7 +316,6 @@ void WiimoteControllersWidget::LoadSettings(Core::State state)
static_cast<int>(i) < num_local_wiimotes);
}
m_wiimote_real_balance_board->setEnabled(enable_emu_bt && !running_netplay);
m_wiimote_speaker_data->setEnabled(enable_emu_bt && !running_netplay);
const bool ciface_wiimotes = m_wiimote_ciface->isChecked();
@ -342,10 +338,6 @@ void WiimoteControllersWidget::SaveSettings()
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED,
m_wiimote_passthrough->isChecked());
const WiimoteSource bb_source =
m_wiimote_real_balance_board->isChecked() ? WiimoteSource::Real : WiimoteSource::None;
Config::SetBaseOrCurrent(Config::WIIMOTE_BB_SOURCE, bb_source);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const int index = m_wiimote_boxes[i]->currentIndex();

View File

@ -7,6 +7,8 @@
#include <array>
#include "Core/HW/Wiimote.h"
class QCheckBox;
class QComboBox;
class QHBoxLayout;
@ -42,10 +44,10 @@ private:
QGroupBox* m_wiimote_box;
QGridLayout* m_wiimote_layout;
std::array<QLabel*, 4> m_wiimote_labels;
std::array<QComboBox*, 4> m_wiimote_boxes;
std::array<QPushButton*, 4> m_wiimote_buttons;
std::array<QHBoxLayout*, 4> m_wiimote_groups;
std::array<QLabel*, MAX_BBMOTES> m_wiimote_labels;
std::array<QComboBox*, MAX_BBMOTES> m_wiimote_boxes;
std::array<QPushButton*, MAX_BBMOTES> m_wiimote_buttons;
std::array<QHBoxLayout*, MAX_BBMOTES> m_wiimote_groups;
std::array<QLabel*, 2> m_wiimote_pt_labels;
QRadioButton* m_wiimote_emu;
@ -53,7 +55,6 @@ private:
QPushButton* m_wiimote_sync;
QPushButton* m_wiimote_reset;
QCheckBox* m_wiimote_continuous_scanning;
QCheckBox* m_wiimote_real_balance_board;
QCheckBox* m_wiimote_speaker_data;
QCheckBox* m_wiimote_ciface;
QPushButton* m_wiimote_refresh;

View File

@ -90,6 +90,7 @@
<ClCompile Include="Config\InfoWidget.cpp" />
<ClCompile Include="Config\LogConfigWidget.cpp" />
<ClCompile Include="Config\LogWidget.cpp" />
<ClCompile Include="Config\Mapping\BalanceBoardGeneral.cpp" />
<ClCompile Include="Config\Mapping\FreeLookGeneral.cpp" />
<ClCompile Include="Config\Mapping\FreeLookRotation.cpp" />
<ClCompile Include="Config\Mapping\GBAPadEmu.cpp" />
@ -219,6 +220,7 @@
<ClCompile Include="Settings\WiiPane.cpp" />
<ClCompile Include="SkylanderPortal\SkylanderModifyDialog.cpp" />
<ClCompile Include="SkylanderPortal\SkylanderPortalWindow.cpp" />
<ClCompile Include="TAS\BalanceBoardWidget.cpp" />
<ClCompile Include="TAS\GCTASInputWindow.cpp" />
<ClCompile Include="TAS\GBATASInputWindow.cpp" />
<ClCompile Include="TAS\IRWidget.cpp" />
@ -312,6 +314,7 @@
<QtMoc Include="Config\InfoWidget.h" />
<QtMoc Include="Config\LogConfigWidget.h" />
<QtMoc Include="Config\LogWidget.h" />
<QtMoc Include="Config\Mapping\BalanceBoardGeneral.h" />
<QtMoc Include="Config\Mapping\FreeLookGeneral.h" />
<QtMoc Include="Config\Mapping\FreeLookRotation.h" />
<QtMoc Include="Config\Mapping\GBAPadEmu.h" />
@ -424,6 +427,7 @@
<QtMoc Include="Settings\USBDeviceAddToWhitelistDialog.h" />
<QtMoc Include="Settings\WiiPane.h" />
<QtMoc Include="SkylanderPortal\SkylanderPortalWindow.h" />
<QtMoc Include="TAS\BalanceBoardWidget.h" />
<QtMoc Include="TAS\GCTASInputWindow.h" />
<QtMoc Include="TAS\GBATASInputWindow.h" />
<QtMoc Include="TAS\IRWidget.h" />

View File

@ -42,7 +42,6 @@
#include "Core/Boot/Boot.h"
#include "Core/BootManager.h"
#include "Core/CommonTitles.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
#include "Core/Config/UISettings.h"
@ -56,10 +55,8 @@
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HotkeyManager.h"
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayProto.h"
@ -108,7 +105,6 @@
#include "DolphinQt/QtUtils/FileOpenEventFilter.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/ParallelProgressDialog.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/QtUtils/RunOnObject.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/QtUtils/WindowActivationEventFilter.h"
@ -126,18 +122,14 @@
#include "DolphinQt/WiiUpdate.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/GCAdapter.h"
#include "UICommon/DiscordPresence.h"
#include "UICommon/GameFile.h"
#include "UICommon/ResourcePack/Manager.h"
#include "UICommon/ResourcePack/Manifest.h"
#include "UICommon/ResourcePack/ResourcePack.h"
#include "UICommon/UICommon.h"
#include "VideoCommon/NetPlayChatUI.h"
#include "VideoCommon/VideoConfig.h"
#ifdef HAVE_XRANDR
#include "UICommon/X11Utils.h"
@ -350,12 +342,12 @@ MainWindow::~MainWindow()
delete m_render_widget;
delete m_netplay_dialog;
for (int i = 0; i < 4; i++)
{
delete m_gc_tas_input_windows[i];
delete m_gba_tas_input_windows[i];
delete m_wii_tas_input_windows[i];
}
for (auto& window : m_gc_tas_input_windows)
delete window;
for (auto& window : m_gba_tas_input_windows)
delete window;
for (auto& window : m_wii_tas_input_windows)
delete window;
ShutdownControllers();
@ -458,13 +450,17 @@ void MainWindow::CreateComponents()
m_render_widget = new RenderWidget;
m_stack = new QStackedWidget(this);
for (int i = 0; i < 4; i++)
for (int i = 0; i != num_gc_controllers; ++i)
{
m_gc_tas_input_windows[i] = new GCTASInputWindow(nullptr, i);
m_gba_tas_input_windows[i] = new GBATASInputWindow(nullptr, i);
m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i);
}
for (int i = 0; i != MAX_WIIMOTES; ++i)
m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i);
m_wii_tas_input_windows[WIIMOTE_BALANCE_BOARD] = new BalanceBoardTASInputWindow(nullptr);
m_jit_widget = new JITWidget(m_system, this);
m_log_widget = new LogWidget(this);
m_log_config_widget = new LogConfigWidget(this);
@ -1873,7 +1869,7 @@ void MainWindow::OnStartRecording()
Movie::ControllerTypeArray controllers{};
Movie::WiimoteEnabledArray wiimotes{};
for (int i = 0; i < 4; i++)
for (int i = 0; i < num_gc_controllers; i++)
{
const SerialInterface::SIDevices si_device = Config::Get(Config::GetInfoForSIDevice(i));
if (si_device == SerialInterface::SIDEVICE_GC_GBA_EMULATED)
@ -1882,9 +1878,11 @@ void MainWindow::OnStartRecording()
controllers[i] = Movie::ControllerType::GC;
else
controllers[i] = Movie::ControllerType::None;
wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None;
}
for (int i = 0; i != MAX_BBMOTES; ++i)
wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None;
if (movie.BeginRecordingInput(controllers, wiimotes))
{
emit RecordingStatusChanged(true);
@ -1949,7 +1947,7 @@ void MainWindow::ShowTASInput()
}
}
for (int i = 0; i < num_wii_controllers; i++)
for (int i = 0; i != MAX_BBMOTES; ++i)
{
if (Config::Get(Config::GetInfoForWiimoteSource(i)) == WiimoteSource::Emulated &&
(!Core::IsRunning(m_system) || m_system.IsWii()))

View File

@ -12,6 +12,8 @@
#include <string>
#include "Core/Boot/Boot.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/Wiimote.h"
class QMenu;
class QStackedWidget;
@ -51,7 +53,7 @@ class SkylanderPortalWindow;
class ThreadWidget;
class ToolBar;
class WatchWidget;
class WiiTASInputWindow;
class WiiBaseTASInputWindow;
struct WindowSystemInfo;
namespace Core
@ -252,11 +254,10 @@ private:
NetPlayDialog* m_netplay_dialog;
DiscordHandler* m_netplay_discord;
NetPlaySetupDialog* m_netplay_setup_dialog;
static constexpr int num_gc_controllers = 4;
static constexpr int num_gc_controllers = SerialInterface::MAX_SI_CHANNELS;
std::array<GCTASInputWindow*, num_gc_controllers> m_gc_tas_input_windows{};
std::array<GBATASInputWindow*, num_gc_controllers> m_gba_tas_input_windows{};
static constexpr int num_wii_controllers = 4;
std::array<WiiTASInputWindow*, num_wii_controllers> m_wii_tas_input_windows{};
std::array<QDialog*, MAX_BBMOTES> m_wii_tas_input_windows{};
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementsWindow* m_achievements_window = nullptr;

View File

@ -0,0 +1,137 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/TAS/BalanceBoardWidget.h"
#include <algorithm>
#include <cmath>
#include <QMouseEvent>
#include <QPainter>
#include <QSpinBox>
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
BalanceBoardWidget::BalanceBoardWidget(QWidget* parent, QDoubleSpinBox* total_weight_spinbox,
const std::array<QDoubleSpinBox*, 4>& sensors)
: QWidget(parent)
{
setMouseTracking(false);
setToolTip(tr("Left click to set the balance value.\n"
"Right click to return to perfect balance."));
using BW = BalanceBoardWidget;
auto const connect_sensor = [&](QDoubleSpinBox* spinbox, double* this_value) {
connect(this, &BW::UpdateSensorWidgets, this, [this, spinbox, this_value]() {
QSignalBlocker blocker{this};
spinbox->setValue(*this_value);
});
connect(spinbox, &QDoubleSpinBox::valueChanged, this,
[this, spinbox, this_value](double new_value) {
if (signalsBlocked())
return;
*this_value = spinbox->value();
update();
UpdateTotalWidget();
});
};
connect_sensor(sensors[0], &m_sensor_tr);
connect_sensor(sensors[1], &m_sensor_br);
connect_sensor(sensors[2], &m_sensor_tl);
connect_sensor(sensors[3], &m_sensor_bl);
connect(this, &BW::UpdateTotalWidget, this, [this, box = total_weight_spinbox]() {
QSignalBlocker blocker{this};
box->setValue(GetTotalWeight());
});
connect(total_weight_spinbox, &QDoubleSpinBox::valueChanged, this, [this](double new_total) {
if (signalsBlocked())
return;
SetTotal(new_total);
});
SetTotal(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT);
UpdateTotalWidget();
}
double BalanceBoardWidget::GetTotalWeight()
{
return m_sensor_tr + m_sensor_br + m_sensor_tl + m_sensor_bl;
}
void BalanceBoardWidget::SetTotal(double total)
{
const auto cob = GetCenterOfBalance();
const auto quarter_weight = total * 0.25;
m_sensor_tr = quarter_weight;
m_sensor_br = quarter_weight;
m_sensor_tl = quarter_weight;
m_sensor_bl = quarter_weight;
SetCenterOfBalance(cob);
}
void BalanceBoardWidget::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.setBrush(Qt::white);
painter.drawRect(0, 0, width() - 1, height() - 1);
painter.drawLine(0, height() / 2, width(), height() / 2);
painter.drawLine(width() / 2, 0, width() / 2, height());
auto cob = GetCenterOfBalance();
// Convert to widget space.
cob.x += 1;
cob.y = 1 - cob.y;
cob *= Common::DVec2(width(), height()) * 0.5;
painter.drawLine(width() / 2, height() / 2, cob.x, cob.y);
const int wh_avg = (width() + height()) / 2;
const int radius = wh_avg / 30;
painter.setBrush(Qt::blue);
painter.drawEllipse(QPointF{cob.x, cob.y}, radius, radius);
}
void BalanceBoardWidget::mousePressEvent(QMouseEvent* event)
{
if (event->button() != Qt::RightButton)
return;
SetCenterOfBalance({0, 0});
}
void BalanceBoardWidget::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() != Qt::LeftButton)
return;
// Convert from widget space to value space.
const double com_x = std::clamp((event->pos().x() * 2.0) / width() - 1, -1.0, 1.0);
const double com_y = std::clamp(1 - (event->pos().y() * 2.0) / height(), -1.0, 1.0);
SetCenterOfBalance({com_x, com_y});
}
void BalanceBoardWidget::SetCenterOfBalance(Common::DVec2 cob)
{
std::tie(m_sensor_tr, m_sensor_br, m_sensor_tl, m_sensor_bl) = std::tuple_cat(
WiimoteEmu::BalanceBoardExt::CenterOfBalanceToSensors(cob, GetTotalWeight()).data);
update();
UpdateSensorWidgets();
}
Common::DVec2 BalanceBoardWidget::GetCenterOfBalance() const
{
return WiimoteEmu::BalanceBoardExt::SensorsToCenterOfBalance(
{m_sensor_tr, m_sensor_br, m_sensor_tl, m_sensor_bl});
}

View File

@ -0,0 +1,41 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <QDialog>
#include "Common/Matrix.h"
class QDoubleSpinBox;
class BalanceBoardWidget : public QWidget
{
Q_OBJECT
signals:
void UpdateSensorWidgets();
void UpdateTotalWidget();
public:
explicit BalanceBoardWidget(QWidget* parent, QDoubleSpinBox* total,
const std::array<QDoubleSpinBox*, 4>& sensors);
void SetTotal(double total_weight);
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private:
double GetTotalWeight();
void SetCenterOfBalance(Common::DVec2);
Common::DVec2 GetCenterOfBalance() const;
double m_sensor_tr = 0;
double m_sensor_br = 0;
double m_sensor_tl = 0;
double m_sensor_bl = 0;
};

View File

@ -54,14 +54,14 @@ void IRWidget::paintEvent(QPaintEvent* event)
painter.drawLine(PADDING + w / 2, PADDING, PADDING + w / 2, PADDING + h);
// convert from value space to widget space
u16 x = PADDING + ((m_x * w) / IR_MAX_X);
u16 y = PADDING + (h - (m_y * h) / IR_MAX_Y);
const int x = PADDING + ((m_x * w) / IR_MAX_X);
const int y = PADDING + (h - (m_y * h) / IR_MAX_Y);
painter.drawLine(PADDING + w / 2, PADDING + h / 2, x, y);
painter.setBrush(Qt::blue);
int wh_avg = (w + h) / 2;
int radius = wh_avg / 30;
const int wh_avg = (w + h) / 2;
const int radius = wh_avg / 30;
painter.drawEllipse(x - radius, y - radius, radius * 2, radius * 2);
}
@ -90,8 +90,8 @@ void IRWidget::handleMouseEvent(QMouseEvent* event)
else
{
// convert from widget space to value space
int new_x = (event->pos().x() * IR_MAX_X) / width();
int new_y = IR_MAX_Y - (event->pos().y() * IR_MAX_Y) / height();
const int new_x = (event->pos().x() * IR_MAX_X) / width();
const int new_y = IR_MAX_Y - (event->pos().y() * IR_MAX_Y) / height();
m_x = std::max(0, std::min(static_cast<int>(IR_MAX_X), new_x));
m_y = std::max(0, std::min(static_cast<int>(IR_MAX_Y), new_y));

View File

@ -57,13 +57,13 @@ void StickWidget::paintEvent(QPaintEvent* event)
painter.drawLine(PADDING + diameter / 2, PADDING, PADDING + diameter / 2, PADDING + diameter);
// convert from value space to widget space
u16 x = PADDING + ((m_x * diameter) / m_max_x);
u16 y = PADDING + (diameter - (m_y * diameter) / m_max_y);
const int x = PADDING + ((m_x * diameter) / m_max_x);
const int y = PADDING + (diameter - (m_y * diameter) / m_max_y);
painter.drawLine(PADDING + diameter / 2, PADDING + diameter / 2, x, y);
painter.setBrush(Qt::blue);
int neutral_radius = diameter / 30;
const int neutral_radius = diameter / 30;
painter.drawEllipse(x - neutral_radius, y - neutral_radius, neutral_radius * 2,
neutral_radius * 2);
}
@ -93,8 +93,8 @@ void StickWidget::handleMouseEvent(QMouseEvent* event)
else
{
// convert from widget space to value space
int new_x = (event->pos().x() * m_max_x) / width();
int new_y = m_max_y - (event->pos().y() * m_max_y) / height();
const int new_x = (event->pos().x() * m_max_x) / width();
const int new_y = m_max_y - (event->pos().y() * m_max_y) / height();
m_x = std::max(0, std::min(static_cast<int>(m_max_x), new_x));
m_y = std::max(0, std::min(static_cast<int>(m_max_y), new_y));

View File

@ -8,6 +8,7 @@
#include <QApplication>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QEvent>
#include <QGroupBox>
#include <QHBoxLayout>
@ -17,8 +18,6 @@
#include <QSpinBox>
#include <QVBoxLayout>
#include "Common/CommonTypes.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/QtUtils/AspectRatioWidget.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
@ -202,7 +201,7 @@ TASSpinBox* TASInputWindow::CreateSliderValuePair(
}
// The shortcut_widget argument needs to specify the container widget that will be hidden/shown.
// This is done to avoid ambigous shortcuts
// This is done to avoid ambiguous shortcuts
TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, int max,
QKeySequence shortcut_key_sequence,
Qt::Orientation orientation,
@ -237,6 +236,60 @@ TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int defaul
return value;
}
QDoubleSpinBox* TASInputWindow::CreateWeightSliderValuePair(std::string_view group_name,
std::string_view control_name,
InputOverrider* overrider,
QBoxLayout* layout, int min, int max,
QKeySequence shortcut_key_sequence,
QWidget* shortcut_widget)
{
QDoubleSpinBox* value =
CreateWeightSliderValuePair(layout, min, max, shortcut_key_sequence, shortcut_widget);
InputOverrider::OverrideFunction func = [this, value](ControlState controller_state) {
return GetSpinBox(value, controller_state);
};
overrider->AddFunction(group_name, control_name, std::move(func));
return value;
}
// The shortcut_widget argument needs to specify the container widget that will be hidden/shown.
// This is done to avoid ambiguous shortcuts
QDoubleSpinBox* TASInputWindow::CreateWeightSliderValuePair(QBoxLayout* layout, int min, int max,
QKeySequence shortcut_key_sequence,
QWidget* shortcut_widget)
{
auto* value = new QDoubleSpinBox();
value->setRange(min, max);
value->setDecimals(2);
value->setSuffix(QStringLiteral("kg"));
auto* slider = new QSlider(Qt::Orientation::Horizontal);
slider->setRange(min * 100, max * 100);
slider->setFocusPolicy(Qt::ClickFocus);
slider->setSingleStep(100);
slider->setPageStep(1000);
slider->setTickPosition(QSlider::TickPosition::TicksBelow);
connect(slider, &QSlider::valueChanged, value, [value](int i) { value->setValue(i / 100.0); });
connect(value, &QDoubleSpinBox::valueChanged, slider, [slider](double d) {
QSignalBlocker blocker{slider};
slider->setValue((int)(d * 100));
});
auto* shortcut = new QShortcut(shortcut_key_sequence, shortcut_widget);
connect(shortcut, &QShortcut::activated, [value] {
value->setFocus();
value->selectAll();
});
layout->addWidget(slider);
layout->addWidget(value);
return value;
}
std::optional<ControlState> TASInputWindow::GetButton(TASCheckBox* checkbox,
ControlState controller_state)
{
@ -272,6 +325,27 @@ std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, int zer
return (spin->GetValue() - zero) / scale;
}
std::optional<ControlState> TASInputWindow::GetSpinBox(QDoubleSpinBox* spin,
ControlState controller_state)
{
if (m_use_controller->isChecked())
{
if (!m_spinbox_most_recent_values_double.count(spin) ||
m_spinbox_most_recent_values_double[spin] != controller_state)
{
QueueOnObjectBlocking(spin, [spin, controller_state] { spin->setValue(controller_state); });
}
m_spinbox_most_recent_values_double[spin] = controller_state;
}
else
{
m_spinbox_most_recent_values_double.clear();
}
return spin->value();
}
void TASInputWindow::changeEvent(QEvent* const event)
{
if (event->type() == QEvent::ActivationChange)

View File

@ -18,6 +18,7 @@
class QBoxLayout;
class QCheckBox;
class QDialog;
class QDoubleSpinBox;
class QEvent;
class QGroupBox;
class QSpinBox;
@ -68,6 +69,14 @@ protected:
TASSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, int max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget);
QDoubleSpinBox* CreateWeightSliderValuePair(std::string_view group_name,
std::string_view control_name,
InputOverrider* overrider, QBoxLayout* layout,
int min, int max, QKeySequence shortcut_key_sequence,
QWidget* shortcut_widget);
QDoubleSpinBox* CreateWeightSliderValuePair(QBoxLayout* layout, int min, int max,
QKeySequence shortcut_key_sequence,
QWidget* shortcut_widget);
void changeEvent(QEvent* event) override;
@ -82,4 +91,7 @@ private:
ControlState controller_state);
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, int zero, ControlState controller_state,
ControlState scale);
std::optional<ControlState> GetSpinBox(QDoubleSpinBox* spin, ControlState controller_state);
std::map<QDoubleSpinBox*, u16> m_spinbox_most_recent_values_double;
};

View File

@ -19,22 +19,22 @@
#include "Core/Core.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/MotionPlus.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/System.h"
#include "DolphinQt/QtUtils/AspectRatioWidget.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/TAS/BalanceBoardWidget.h"
#include "DolphinQt/TAS/IRWidget.h"
#include "DolphinQt/TAS/TASCheckBox.h"
#include "DolphinQt/TAS/TASSpinBox.h"
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/StickGate.h"
#include "InputCommon/InputConfig.h"
@ -55,26 +55,26 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
const int ir_y_center = static_cast<int>(std::round(IRWidget::IR_MAX_Y / 2.));
auto* x_layout = new QHBoxLayout;
m_ir_x_value = CreateSliderValuePair(
auto* const ir_x_value = CreateSliderValuePair(
WiimoteEmu::Wiimote::IR_GROUP, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE,
&m_wiimote_overrider, x_layout, ir_x_center, ir_x_center, IRWidget::IR_MIN_X,
IRWidget::IR_MAX_X, ir_x_shortcut_key_sequence, Qt::Horizontal, m_ir_box);
auto* y_layout = new QVBoxLayout;
m_ir_y_value = CreateSliderValuePair(
auto* const ir_y_value = CreateSliderValuePair(
WiimoteEmu::Wiimote::IR_GROUP, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE,
&m_wiimote_overrider, y_layout, ir_y_center, ir_y_center, IRWidget::IR_MIN_Y,
IRWidget::IR_MAX_Y, ir_y_shortcut_key_sequence, Qt::Vertical, m_ir_box);
m_ir_y_value->setMaximumWidth(60);
ir_y_value->setMaximumWidth(60);
auto* visual = new IRWidget(this);
visual->SetX(ir_x_center);
visual->SetY(ir_y_center);
connect(m_ir_x_value, &QSpinBox::valueChanged, visual, &IRWidget::SetX);
connect(m_ir_y_value, &QSpinBox::valueChanged, visual, &IRWidget::SetY);
connect(visual, &IRWidget::ChangedX, m_ir_x_value, &QSpinBox::setValue);
connect(visual, &IRWidget::ChangedY, m_ir_y_value, &QSpinBox::setValue);
connect(ir_x_value, &QSpinBox::valueChanged, visual, &IRWidget::SetX);
connect(ir_y_value, &QSpinBox::valueChanged, visual, &IRWidget::SetY);
connect(visual, &IRWidget::ChangedX, ir_x_value, &QSpinBox::setValue);
connect(visual, &IRWidget::ChangedY, ir_y_value, &QSpinBox::setValue);
auto* visual_ar = new AspectRatioWidget(visual, IRWidget::IR_MAX_X, IRWidget::IR_MAX_Y);
@ -223,48 +223,49 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
triggers_layout->addLayout(r_trigger_layout);
m_triggers_box->setLayout(triggers_layout);
m_a_button = CreateButton(QStringLiteral("&A"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::A_BUTTON, &m_wiimote_overrider);
m_b_button = CreateButton(QStringLiteral("&B"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::B_BUTTON, &m_wiimote_overrider);
m_1_button = CreateButton(QStringLiteral("&1"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::ONE_BUTTON, &m_wiimote_overrider);
m_2_button = CreateButton(QStringLiteral("&2"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::TWO_BUTTON, &m_wiimote_overrider);
m_plus_button = CreateButton(QStringLiteral("&+"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::PLUS_BUTTON, &m_wiimote_overrider);
m_minus_button = CreateButton(QStringLiteral("&-"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::MINUS_BUTTON, &m_wiimote_overrider);
m_home_button = CreateButton(QStringLiteral("&HOME"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::HOME_BUTTON, &m_wiimote_overrider);
auto* const a_button = CreateButton(QStringLiteral("&A"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::A_BUTTON, &m_wiimote_overrider);
auto* const b_button = CreateButton(QStringLiteral("&B"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::B_BUTTON, &m_wiimote_overrider);
auto* const button_1 = CreateButton(QStringLiteral("&1"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::ONE_BUTTON, &m_wiimote_overrider);
auto* const button_2 = CreateButton(QStringLiteral("&2"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::TWO_BUTTON, &m_wiimote_overrider);
auto* const plus_button = CreateButton(QStringLiteral("&+"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::PLUS_BUTTON, &m_wiimote_overrider);
auto* const minus_button = CreateButton(QStringLiteral("&-"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::MINUS_BUTTON, &m_wiimote_overrider);
auto* const home_button =
CreateButton(QStringLiteral("&HOME"), WiimoteEmu::Wiimote::BUTTONS_GROUP,
WiimoteEmu::Wiimote::HOME_BUTTON, &m_wiimote_overrider);
m_left_button = CreateButton(QStringLiteral("&Left"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_LEFT, &m_wiimote_overrider);
m_up_button = CreateButton(QStringLiteral("&Up"), WiimoteEmu::Wiimote::DPAD_GROUP, DIRECTION_UP,
&m_wiimote_overrider);
m_down_button = CreateButton(QStringLiteral("&Down"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_DOWN, &m_wiimote_overrider);
m_right_button = CreateButton(QStringLiteral("&Right"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_RIGHT, &m_wiimote_overrider);
auto* const left_button = CreateButton(QStringLiteral("&Left"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_LEFT, &m_wiimote_overrider);
auto* const up_button = CreateButton(QStringLiteral("&Up"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_UP, &m_wiimote_overrider);
auto* const down_button = CreateButton(QStringLiteral("&Down"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_DOWN, &m_wiimote_overrider);
auto* const right_button = CreateButton(QStringLiteral("&Right"), WiimoteEmu::Wiimote::DPAD_GROUP,
DIRECTION_RIGHT, &m_wiimote_overrider);
m_c_button = CreateButton(QStringLiteral("&C"), WiimoteEmu::Nunchuk::BUTTONS_GROUP,
WiimoteEmu::Nunchuk::C_BUTTON, &m_nunchuk_overrider);
m_z_button = CreateButton(QStringLiteral("&Z"), WiimoteEmu::Nunchuk::BUTTONS_GROUP,
WiimoteEmu::Nunchuk::Z_BUTTON, &m_nunchuk_overrider);
auto* const c_button = CreateButton(QStringLiteral("&C"), WiimoteEmu::Nunchuk::BUTTONS_GROUP,
WiimoteEmu::Nunchuk::C_BUTTON, &m_nunchuk_overrider);
auto* const z_button = CreateButton(QStringLiteral("&Z"), WiimoteEmu::Nunchuk::BUTTONS_GROUP,
WiimoteEmu::Nunchuk::Z_BUTTON, &m_nunchuk_overrider);
auto* buttons_layout = new QGridLayout;
buttons_layout->addWidget(m_a_button, 0, 0);
buttons_layout->addWidget(m_b_button, 0, 1);
buttons_layout->addWidget(m_1_button, 0, 2);
buttons_layout->addWidget(m_2_button, 0, 3);
buttons_layout->addWidget(m_plus_button, 0, 4);
buttons_layout->addWidget(m_minus_button, 0, 5);
buttons_layout->addWidget(a_button, 0, 0);
buttons_layout->addWidget(b_button, 0, 1);
buttons_layout->addWidget(button_1, 0, 2);
buttons_layout->addWidget(button_2, 0, 3);
buttons_layout->addWidget(plus_button, 0, 4);
buttons_layout->addWidget(minus_button, 0, 5);
buttons_layout->addWidget(m_home_button, 1, 0);
buttons_layout->addWidget(m_left_button, 1, 1);
buttons_layout->addWidget(m_up_button, 1, 2);
buttons_layout->addWidget(m_down_button, 1, 3);
buttons_layout->addWidget(m_right_button, 1, 4);
buttons_layout->addWidget(home_button, 1, 0);
buttons_layout->addWidget(left_button, 1, 1);
buttons_layout->addWidget(up_button, 1, 2);
buttons_layout->addWidget(down_button, 1, 3);
buttons_layout->addWidget(right_button, 1, 4);
buttons_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding), 0, 7);
@ -272,63 +273,70 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
m_remote_buttons_box->setLayout(buttons_layout);
auto* nunchuk_buttons_layout = new QHBoxLayout;
nunchuk_buttons_layout->addWidget(m_c_button);
nunchuk_buttons_layout->addWidget(m_z_button);
nunchuk_buttons_layout->addWidget(c_button);
nunchuk_buttons_layout->addWidget(z_button);
nunchuk_buttons_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding));
m_nunchuk_buttons_box = new QGroupBox(tr("Nunchuk Buttons"));
m_nunchuk_buttons_box->setLayout(nunchuk_buttons_layout);
m_classic_a_button = CreateButton(QStringLiteral("&A"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::A_BUTTON, &m_classic_overrider);
m_classic_b_button = CreateButton(QStringLiteral("&B"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::B_BUTTON, &m_classic_overrider);
m_classic_x_button = CreateButton(QStringLiteral("&X"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::X_BUTTON, &m_classic_overrider);
m_classic_y_button = CreateButton(QStringLiteral("&Y"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::Y_BUTTON, &m_classic_overrider);
m_classic_zl_button = CreateButton(QStringLiteral("&ZL"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::ZL_BUTTON, &m_classic_overrider);
m_classic_zr_button = CreateButton(QStringLiteral("ZR"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::ZR_BUTTON, &m_classic_overrider);
m_classic_plus_button = CreateButton(QStringLiteral("&+"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::PLUS_BUTTON, &m_classic_overrider);
m_classic_minus_button = CreateButton(QStringLiteral("&-"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::MINUS_BUTTON, &m_classic_overrider);
m_classic_home_button = CreateButton(QStringLiteral("&HOME"), WiimoteEmu::Classic::BUTTONS_GROUP,
WiimoteEmu::Classic::HOME_BUTTON, &m_classic_overrider);
auto& wcbg = WiimoteEmu::Classic::BUTTONS_GROUP;
using WiimoteEmu::Classic;
auto* const classic_a_button =
CreateButton(QStringLiteral("&A"), wcbg, Classic::A_BUTTON, &m_classic_overrider);
auto* const classic_b_button =
CreateButton(QStringLiteral("&B"), wcbg, Classic::B_BUTTON, &m_classic_overrider);
auto* const classic_x_button =
CreateButton(QStringLiteral("&X"), wcbg, Classic::X_BUTTON, &m_classic_overrider);
auto* const classic_y_button =
CreateButton(QStringLiteral("&Y"), wcbg, Classic::Y_BUTTON, &m_classic_overrider);
auto* const classic_zl_button =
CreateButton(QStringLiteral("&ZL"), wcbg, Classic::ZL_BUTTON, &m_classic_overrider);
auto* const classic_zr_button =
CreateButton(QStringLiteral("ZR"), wcbg, Classic::ZR_BUTTON, &m_classic_overrider);
auto* const classic_plus_button =
CreateButton(QStringLiteral("&+"), wcbg, Classic::PLUS_BUTTON, &m_classic_overrider);
auto* const classic_minus_button =
CreateButton(QStringLiteral("&-"), wcbg, Classic::MINUS_BUTTON, &m_classic_overrider);
auto* const classic_home_button =
CreateButton(QStringLiteral("&HOME"), wcbg, Classic::HOME_BUTTON, &m_classic_overrider);
m_classic_l_button = CreateButton(QStringLiteral("&L"), WiimoteEmu::Classic::TRIGGERS_GROUP,
WiimoteEmu::Classic::L_DIGITAL, &m_classic_overrider);
m_classic_r_button = CreateButton(QStringLiteral("&R"), WiimoteEmu::Classic::TRIGGERS_GROUP,
WiimoteEmu::Classic::R_DIGITAL, &m_classic_overrider);
auto* const classic_l_button =
CreateButton(QStringLiteral("&L"), WiimoteEmu::Classic::TRIGGERS_GROUP,
WiimoteEmu::Classic::L_DIGITAL, &m_classic_overrider);
auto* const classic_r_button =
CreateButton(QStringLiteral("&R"), WiimoteEmu::Classic::TRIGGERS_GROUP,
WiimoteEmu::Classic::R_DIGITAL, &m_classic_overrider);
m_classic_left_button = CreateButton(QStringLiteral("L&eft"), WiimoteEmu::Classic::DPAD_GROUP,
DIRECTION_LEFT, &m_classic_overrider);
m_classic_up_button = CreateButton(QStringLiteral("&Up"), WiimoteEmu::Classic::DPAD_GROUP,
DIRECTION_UP, &m_classic_overrider);
m_classic_down_button = CreateButton(QStringLiteral("&Down"), WiimoteEmu::Classic::DPAD_GROUP,
DIRECTION_DOWN, &m_classic_overrider);
m_classic_right_button = CreateButton(QStringLiteral("R&ight"), WiimoteEmu::Classic::DPAD_GROUP,
DIRECTION_RIGHT, &m_classic_overrider);
auto* const classic_left_button =
CreateButton(QStringLiteral("L&eft"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_LEFT,
&m_classic_overrider);
auto* const classic_up_button = CreateButton(
QStringLiteral("&Up"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_UP, &m_classic_overrider);
auto* const classic_down_button =
CreateButton(QStringLiteral("&Down"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_DOWN,
&m_classic_overrider);
auto* const classic_right_button =
CreateButton(QStringLiteral("R&ight"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_RIGHT,
&m_classic_overrider);
auto* classic_buttons_layout = new QGridLayout;
classic_buttons_layout->addWidget(m_classic_a_button, 0, 0);
classic_buttons_layout->addWidget(m_classic_b_button, 0, 1);
classic_buttons_layout->addWidget(m_classic_x_button, 0, 2);
classic_buttons_layout->addWidget(m_classic_y_button, 0, 3);
classic_buttons_layout->addWidget(m_classic_l_button, 0, 4);
classic_buttons_layout->addWidget(m_classic_r_button, 0, 5);
classic_buttons_layout->addWidget(m_classic_zl_button, 0, 6);
classic_buttons_layout->addWidget(m_classic_zr_button, 0, 7);
classic_buttons_layout->addWidget(classic_a_button, 0, 0);
classic_buttons_layout->addWidget(classic_b_button, 0, 1);
classic_buttons_layout->addWidget(classic_x_button, 0, 2);
classic_buttons_layout->addWidget(classic_y_button, 0, 3);
classic_buttons_layout->addWidget(classic_l_button, 0, 4);
classic_buttons_layout->addWidget(classic_r_button, 0, 5);
classic_buttons_layout->addWidget(classic_zl_button, 0, 6);
classic_buttons_layout->addWidget(classic_zr_button, 0, 7);
classic_buttons_layout->addWidget(m_classic_plus_button, 1, 0);
classic_buttons_layout->addWidget(m_classic_minus_button, 1, 1);
classic_buttons_layout->addWidget(m_classic_home_button, 1, 2);
classic_buttons_layout->addWidget(m_classic_left_button, 1, 3);
classic_buttons_layout->addWidget(m_classic_up_button, 1, 4);
classic_buttons_layout->addWidget(m_classic_down_button, 1, 5);
classic_buttons_layout->addWidget(m_classic_right_button, 1, 6);
classic_buttons_layout->addWidget(classic_plus_button, 1, 0);
classic_buttons_layout->addWidget(classic_minus_button, 1, 1);
classic_buttons_layout->addWidget(classic_home_button, 1, 2);
classic_buttons_layout->addWidget(classic_left_button, 1, 3);
classic_buttons_layout->addWidget(classic_up_button, 1, 4);
classic_buttons_layout->addWidget(classic_down_button, 1, 5);
classic_buttons_layout->addWidget(classic_right_button, 1, 6);
classic_buttons_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding), 0, 8);
@ -349,21 +357,96 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
setLayout(layout);
}
BalanceBoardTASInputWindow::BalanceBoardTASInputWindow(QWidget* parent) : TASInputWindow{parent}
{
setWindowTitle(tr("Wii TAS Input %1 - Balance Board").arg(WIIMOTE_BALANCE_BOARD + 1));
const QKeySequence balance_tl_keyseq = QKeySequence(Qt::ALT | Qt::Key_L);
const QKeySequence balance_tr_keyseq = QKeySequence(Qt::ALT | Qt::Key_R);
const QKeySequence balance_bl_keyseq = QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_L);
const QKeySequence balance_br_keyseq = QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_R);
const QKeySequence balance_weight_keyseq = QKeySequence(Qt::ALT | Qt::Key_W);
auto* const balance_board_box =
new QGroupBox(QStringLiteral("%1 (%2/%3/%4)")
.arg(tr("Balance"), balance_tl_keyseq.toString(QKeySequence::NativeText),
balance_tr_keyseq.toString(QKeySequence::NativeText),
balance_weight_keyseq.toString(QKeySequence::NativeText)));
SetQWidgetWindowDecorations(balance_board_box);
// How heavy do the TASers want to pretend to be?
constexpr int max_total_kg = 136;
// FYI, a lifted board shows about -4kg (the weight of the board itself).
// But I don't think TASers necessarily need to simulate that.
// They'd probably prefer the bottom of the slider be 0kg.
constexpr int min_total_kg = 0;
constexpr int max_sensor_value = max_total_kg;
constexpr int min_sensor_value = min_total_kg / 4;
using BBExt = WiimoteEmu::BalanceBoardExt;
auto& balance_group = BBExt::BALANCE_GROUP;
auto* bal_top_layout = new QHBoxLayout;
auto* const balance_value_tl = CreateWeightSliderValuePair(
balance_group, BBExt::SENSOR_TL, &m_balance_board_overrider, bal_top_layout, min_sensor_value,
max_sensor_value, balance_tl_keyseq, balance_board_box);
auto* const balance_value_tr = CreateWeightSliderValuePair(
balance_group, BBExt::SENSOR_TR, &m_balance_board_overrider, bal_top_layout, min_sensor_value,
max_sensor_value, balance_tr_keyseq, balance_board_box);
auto* bal_bottom_layout = new QHBoxLayout;
auto* const balance_value_bl = CreateWeightSliderValuePair(
balance_group, BBExt::SENSOR_BL, &m_balance_board_overrider, bal_bottom_layout,
min_sensor_value, max_sensor_value, balance_bl_keyseq, balance_board_box);
auto* const balance_value_br = CreateWeightSliderValuePair(
balance_group, BBExt::SENSOR_BR, &m_balance_board_overrider, bal_bottom_layout,
min_sensor_value, max_sensor_value, balance_br_keyseq, balance_board_box);
auto* bal_weight_layout = new QHBoxLayout;
auto* const total_weight_value = CreateWeightSliderValuePair(
bal_weight_layout, min_total_kg, max_total_kg, balance_weight_keyseq, balance_board_box);
auto* bal_visual = new BalanceBoardWidget(
this, total_weight_value,
{balance_value_tr, balance_value_br, balance_value_tl, balance_value_bl});
auto* bal_ar = new AspectRatioWidget(bal_visual, 20, 12);
bal_ar->setMinimumHeight(120);
auto* bal_visual_layout = new QHBoxLayout;
bal_visual_layout->addWidget(bal_ar);
auto* const bboard_power_button =
CreateButton(QStringLiteral("&Power Button"), WiimoteEmu::BalanceBoard::BUTTONS_GROUP_NAME,
WiimoteEmu::BalanceBoard::BUTTON_POWER_NAME, &m_wiimote_overrider);
auto* bal_layout = new QVBoxLayout;
bal_layout->addLayout(bal_top_layout);
bal_layout->addLayout(bal_visual_layout);
bal_layout->addLayout(bal_bottom_layout);
bal_layout->addLayout(bal_weight_layout);
bal_layout->addWidget(bboard_power_button);
balance_board_box->setLayout(bal_layout);
auto* top_layout = new QHBoxLayout;
top_layout->addWidget(balance_board_box);
auto* layout = new QVBoxLayout;
layout->addLayout(top_layout);
layout->addWidget(m_settings_box);
setLayout(layout);
adjustSize();
}
WiimoteEmu::Wiimote* WiiTASInputWindow::GetWiimote()
{
return static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(m_num));
}
ControllerEmu::Attachments* WiiTASInputWindow::GetAttachments()
{
return static_cast<ControllerEmu::Attachments*>(
GetWiimote()->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments));
}
WiimoteEmu::Extension* WiiTASInputWindow::GetExtension()
{
return static_cast<WiimoteEmu::Extension*>(
GetAttachments()->GetAttachmentList()[m_active_extension].get());
return GetWiimote()->GetActiveExtension();
}
void WiiTASInputWindow::UpdateExtension(const int extension)
@ -425,69 +508,59 @@ void WiiTASInputWindow::LoadExtensionAndMotionPlus()
QueueOnObject(this, [this, attached] { UpdateMotionPlus(attached); });
});
m_attachment_callback_id =
GetAttachments()->GetAttachmentSetting().AddCallback([this](const int extension_index) {
wiimote->GetAttachmentSetting().AddCallback([this](const int extension_index) {
QueueOnObject(this, [this, extension_index] { UpdateExtension(extension_index); });
});
}
void WiiTASInputWindow::UpdateControlVisibility()
{
if (m_active_extension == WiimoteEmu::ExtensionNumber::NUNCHUK)
{
setWindowTitle(tr("Wii TAS Input %1 - Wii Remote + Nunchuk").arg(m_num + 1));
SetQWidgetWindowDecorations(m_ir_box);
m_ir_box->show();
SetQWidgetWindowDecorations(m_nunchuk_stick_box);
m_nunchuk_stick_box->show();
m_classic_right_stick_box->hide();
m_classic_left_stick_box->hide();
SetQWidgetWindowDecorations(m_remote_accelerometer_box);
m_remote_accelerometer_box->show();
m_remote_gyroscope_box->setVisible(m_is_motion_plus_attached);
SetQWidgetWindowDecorations(m_nunchuk_accelerometer_box);
m_nunchuk_accelerometer_box->show();
m_triggers_box->hide();
SetQWidgetWindowDecorations(m_nunchuk_buttons_box);
m_nunchuk_buttons_box->show();
SetQWidgetWindowDecorations(m_remote_buttons_box);
m_remote_buttons_box->show();
m_classic_buttons_box->hide();
}
else if (m_active_extension == WiimoteEmu::ExtensionNumber::CLASSIC)
m_ir_box->hide();
m_remote_accelerometer_box->hide();
m_remote_gyroscope_box->hide();
m_nunchuk_buttons_box->hide();
m_remote_buttons_box->hide();
m_nunchuk_accelerometer_box->hide();
m_nunchuk_stick_box->hide();
m_classic_right_stick_box->hide();
m_classic_left_stick_box->hide();
m_classic_buttons_box->hide();
m_triggers_box->hide();
const auto show_box = [](QGroupBox* box) {
SetQWidgetWindowDecorations(box);
box->show();
};
if (m_active_extension == WiimoteEmu::ExtensionNumber::CLASSIC)
{
setWindowTitle(tr("Wii TAS Input %1 - Classic Controller").arg(m_num + 1));
m_ir_box->hide();
m_nunchuk_stick_box->hide();
SetQWidgetWindowDecorations(m_classic_right_stick_box);
m_classic_right_stick_box->show();
SetQWidgetWindowDecorations(m_classic_left_stick_box);
m_classic_left_stick_box->show();
m_remote_accelerometer_box->hide();
m_remote_gyroscope_box->hide();
m_nunchuk_accelerometer_box->hide();
SetQWidgetWindowDecorations(m_triggers_box);
m_triggers_box->show();
m_remote_buttons_box->hide();
m_nunchuk_buttons_box->hide();
SetQWidgetWindowDecorations(m_classic_buttons_box);
m_classic_buttons_box->show();
show_box(m_classic_right_stick_box);
show_box(m_classic_left_stick_box);
show_box(m_triggers_box);
show_box(m_classic_buttons_box);
}
else
{
setWindowTitle(tr("Wii TAS Input %1 - Wii Remote").arg(m_num + 1));
m_ir_box->show();
m_nunchuk_stick_box->hide();
m_classic_right_stick_box->hide();
m_classic_left_stick_box->hide();
SetQWidgetWindowDecorations(m_remote_accelerometer_box);
m_remote_accelerometer_box->show();
m_remote_gyroscope_box->setVisible(m_is_motion_plus_attached);
m_nunchuk_accelerometer_box->hide();
m_triggers_box->hide();
SetQWidgetWindowDecorations(m_remote_buttons_box);
m_remote_buttons_box->show();
m_nunchuk_buttons_box->hide();
m_classic_buttons_box->hide();
show_box(m_ir_box);
show_box(m_remote_accelerometer_box);
show_box(m_remote_buttons_box);
if (m_is_motion_plus_attached)
show_box(m_remote_gyroscope_box);
if (m_active_extension == WiimoteEmu::ExtensionNumber::NUNCHUK)
{
setWindowTitle(tr("Wii TAS Input %1 - Wii Remote + Nunchuk").arg(m_num + 1));
show_box(m_nunchuk_stick_box);
show_box(m_nunchuk_accelerometer_box);
show_box(m_nunchuk_buttons_box);
}
else
{
setWindowTitle(tr("Wii TAS Input %1 - Wii Remote").arg(m_num + 1));
}
}
// Without these calls, switching between attachments can result in the Stick/IRWidgets being
@ -501,10 +574,7 @@ void WiiTASInputWindow::hideEvent(QHideEvent* const event)
WiimoteEmu::Wiimote* const wiimote = GetWiimote();
wiimote->ClearInputOverrideFunction();
wiimote->GetMotionPlusSetting().RemoveCallback(m_motion_plus_callback_id);
GetExtension()->ClearInputOverrideFunction();
GetAttachments()->GetAttachmentSetting().RemoveCallback(m_attachment_callback_id);
TASInputWindow::hideEvent(event);
}
@ -516,6 +586,32 @@ void WiiTASInputWindow::showEvent(QShowEvent* const event)
TASInputWindow::showEvent(event);
}
WiimoteEmu::BalanceBoard* BalanceBoardTASInputWindow::GetBalanceBoard() const
{
return static_cast<WiimoteEmu::BalanceBoard*>(BalanceBoard::GetConfig()->GetController(0));
}
void BalanceBoardTASInputWindow::hideEvent(QHideEvent* const event)
{
auto* const board = GetBalanceBoard();
board->ClearInputOverrideFunction();
board->GetActiveExtension()->ClearInputOverrideFunction();
TASInputWindow::hideEvent(event);
}
void BalanceBoardTASInputWindow::showEvent(QShowEvent* const event)
{
auto* const board = GetBalanceBoard();
board->SetInputOverrideFunction(m_wiimote_overrider.GetInputOverrideFunction());
board->GetActiveExtension()->SetInputOverrideFunction(
m_balance_board_overrider.GetInputOverrideFunction());
TASInputWindow::showEvent(event);
}
void WiiTASInputWindow::UpdateInputOverrideFunction()
{
WiimoteEmu::Wiimote* const wiimote = GetWiimote();

View File

@ -18,13 +18,9 @@ namespace WiimoteEmu
{
class Extension;
class Wiimote;
class BalanceBoard;
} // namespace WiimoteEmu
namespace ControllerEmu
{
class Attachments;
}
class WiiTASInputWindow : public TASInputWindow
{
Q_OBJECT
@ -39,7 +35,6 @@ public:
private:
WiimoteEmu::Wiimote* GetWiimote();
ControllerEmu::Attachments* GetAttachments();
WiimoteEmu::Extension* GetExtension();
void LoadExtensionAndMotionPlus();
@ -56,36 +51,6 @@ private:
InputOverrider m_nunchuk_overrider;
InputOverrider m_classic_overrider;
TASCheckBox* m_a_button;
TASCheckBox* m_b_button;
TASCheckBox* m_1_button;
TASCheckBox* m_2_button;
TASCheckBox* m_plus_button;
TASCheckBox* m_minus_button;
TASCheckBox* m_home_button;
TASCheckBox* m_left_button;
TASCheckBox* m_up_button;
TASCheckBox* m_down_button;
TASCheckBox* m_right_button;
TASCheckBox* m_c_button;
TASCheckBox* m_z_button;
TASCheckBox* m_classic_a_button;
TASCheckBox* m_classic_b_button;
TASCheckBox* m_classic_x_button;
TASCheckBox* m_classic_y_button;
TASCheckBox* m_classic_plus_button;
TASCheckBox* m_classic_minus_button;
TASCheckBox* m_classic_l_button;
TASCheckBox* m_classic_r_button;
TASCheckBox* m_classic_zl_button;
TASCheckBox* m_classic_zr_button;
TASCheckBox* m_classic_home_button;
TASCheckBox* m_classic_left_button;
TASCheckBox* m_classic_up_button;
TASCheckBox* m_classic_down_button;
TASCheckBox* m_classic_right_button;
TASSpinBox* m_ir_x_value;
TASSpinBox* m_ir_y_value;
QGroupBox* m_remote_accelerometer_box;
QGroupBox* m_remote_gyroscope_box;
QGroupBox* m_nunchuk_accelerometer_box;
@ -98,3 +63,18 @@ private:
QGroupBox* m_classic_buttons_box;
QGroupBox* m_triggers_box;
};
class BalanceBoardTASInputWindow : public TASInputWindow
{
public:
explicit BalanceBoardTASInputWindow(QWidget* parent);
private:
void hideEvent(QHideEvent* event) override;
void showEvent(QShowEvent* event) override;
WiimoteEmu::BalanceBoard* GetBalanceBoard() const;
InputOverrider m_wiimote_overrider;
InputOverrider m_balance_board_overrider;
};