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