Large diffs are not rendered by default.

@@ -0,0 +1,112 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <array>
#include <memory>

#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"

namespace WiimoteCommon
{
// Interface for manipulating Wiimote "Data" reports
// If a report does not contain a particular feature the Get/Set is a no-op.
class DataReportManipulator
{
public:
virtual ~DataReportManipulator() = default;

// Accel data handled as if there were always 10 bits of precision.
struct AccelData
{
u16 x, y, z;
};

using CoreData = ButtonData;

virtual bool HasCore() const = 0;
virtual bool HasAccel() const = 0;
bool HasIR() const;
bool HasExt() const;

virtual void GetCoreData(CoreData*) const = 0;
virtual void GetAccelData(AccelData*) const = 0;

virtual void SetCoreData(const CoreData&) = 0;
virtual void SetAccelData(const AccelData&) = 0;

virtual u8* GetIRDataPtr() = 0;
virtual const u8* GetIRDataPtr() const = 0;
virtual u32 GetIRDataSize() const = 0;
virtual u32 GetIRDataFormatOffset() const = 0;

virtual u8* GetExtDataPtr() = 0;
virtual const u8* GetExtDataPtr() const = 0;
virtual u32 GetExtDataSize() const = 0;

u8* GetDataPtr();
const u8* GetDataPtr() const;

virtual u32 GetDataSize() const = 0;

u8* data_ptr;
};

std::unique_ptr<DataReportManipulator> MakeDataReportManipulator(InputReportID rpt_id,
u8* data_ptr);

class DataReportBuilder
{
public:
explicit DataReportBuilder(InputReportID rpt_id);

using CoreData = ButtonData;
using AccelData = DataReportManipulator::AccelData;

void SetMode(InputReportID rpt_id);
InputReportID GetMode() const;

static bool IsValidMode(InputReportID rpt_id);

bool HasCore() const;
bool HasAccel() const;
bool HasIR() const;
bool HasExt() const;

u32 GetIRDataSize() const;
u32 GetExtDataSize() const;

u32 GetIRDataFormatOffset() const;

void GetCoreData(CoreData*) const;
void GetAccelData(AccelData*) const;

void SetCoreData(const CoreData&);
void SetAccelData(const AccelData&);

u8* GetIRDataPtr();
const u8* GetIRDataPtr() const;
u8* GetExtDataPtr();
const u8* GetExtDataPtr() const;

u8* GetDataPtr();
const u8* GetDataPtr() const;

u32 GetDataSize() const;

private:
static constexpr int HEADER_SIZE = 2;

static constexpr int MAX_DATA_SIZE = MAX_PAYLOAD - 2;

TypedHIDInputData<std::array<u8, MAX_DATA_SIZE>> m_data;

std::unique_ptr<DataReportManipulator> m_manip;
};

} // namespace WiimoteCommon
@@ -4,55 +4,75 @@

#pragma once

// Wiimote internal codes
#include "Common/CommonTypes.h"

// Communication channels
enum WiimoteChannel
namespace WiimoteCommon
{
WC_OUTPUT = 0x11,
WC_INPUT = 0x13
};
constexpr u8 MAX_PAYLOAD = 23;

// The 4 most significant bits of the first byte of an outgoing command must be
// 0x50 if sending on the command channel and 0xA0 if sending on the interrupt
// channel. On Mac and Linux we use interrupt channel; on Windows, command.
enum WiimoteReport
enum class InputReportID : u8
{
#ifdef _WIN32
WR_SET_REPORT = 0x50
#else
WR_SET_REPORT = 0xA0
#endif
Status = 0x20,
ReadDataReply = 0x21,
Ack = 0x22,

// Not a real value on the wiimote, just a state to disable reports:
ReportDisabled = 0x00,

ReportCore = 0x30,
ReportCoreAccel = 0x31,
ReportCoreExt8 = 0x32,
ReportCoreAccelIR12 = 0x33,
ReportCoreExt19 = 0x34,
ReportCoreAccelExt16 = 0x35,
ReportCoreIR10Ext9 = 0x36,
ReportCoreAccelIR10Ext6 = 0x37,

ReportExt21 = 0x3d,
ReportInterleave1 = 0x3e,
ReportInterleave2 = 0x3f,
};

enum WiimoteBT
enum class OutputReportID : u8
{
BT_INPUT = 0x01,
BT_OUTPUT = 0x02
Rumble = 0x10,
LED = 0x11,
ReportMode = 0x12,
IRPixelClock = 0x13,
SpeakerEnable = 0x14,
RequestStatus = 0x15,
WriteData = 0x16,
ReadData = 0x17,
SpeakerData = 0x18,
SpeakerMute = 0x19,
IRLogic = 0x1a,
};

// LED bit masks
enum WiimoteLED
enum class LED : u8
{
LED_NONE = 0x00,
None = 0x00,
LED_1 = 0x10,
LED_2 = 0x20,
LED_3 = 0x40,
LED_4 = 0x80
};

enum WiimoteSpace
enum class AddressSpace : u8
{
WS_EEPROM = 0x00,
WS_REGS1 = 0x01,
WS_REGS2 = 0x02
// FYI: The EEPROM address space is offset 0x0070 on i2c slave 0x50.
// However attempting to access this device directly results in an error.
EEPROM = 0x00,
// 0x01 is never used but it does function on a real wiimote:
I2CBusAlt = 0x01,
I2CBus = 0x02,
};

enum WiimoteReadError
enum class ErrorCode : u8
{
RDERR_WOREG = 7,
RDERR_NOMEM = 8
Success = 0,
InvalidSpace = 6,
Nack = 7,
InvalidAddress = 8,
};

constexpr u8 MAX_PAYLOAD = 23;
constexpr u32 WIIMOTE_DEFAULT_TIMEOUT = 1000;
} // namespace WiimoteCommon
@@ -5,34 +5,71 @@
#pragma once

#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"

namespace WiimoteCommon
{
// Source: HID_010_SPC_PFL/1.0 (official HID specification)

constexpr u8 HID_TYPE_HANDSHAKE = 0;
constexpr u8 HID_TYPE_SET_REPORT = 5;
constexpr u8 HID_TYPE_DATA = 0xA;

constexpr u8 HID_HANDSHAKE_SUCCESS = 0;

constexpr u8 HID_PARAM_INPUT = 1;
constexpr u8 HID_PARAM_OUTPUT = 2;

#ifdef _MSC_VER
#pragma warning(push)
// Disable warning for zero-sized array:
#pragma warning(disable : 4200)
#endif

#pragma pack(push, 1)

// Source: HID_010_SPC_PFL/1.0 (official HID specification)

struct hid_packet
struct HIDPacket
{
static constexpr int HEADER_SIZE = 1;

u8 param : 4;
u8 type : 4;

u8 data[0];
};

constexpr u8 HID_TYPE_HANDSHAKE = 0;
constexpr u8 HID_TYPE_SET_REPORT = 5;
constexpr u8 HID_TYPE_DATA = 0xA;
template <typename T>
struct TypedHIDInputData
{
TypedHIDInputData(InputReportID _rpt_id)
: param(HID_PARAM_INPUT), type(HID_TYPE_DATA), report_id(_rpt_id)
{
}

constexpr u8 HID_HANDSHAKE_SUCCESS = 0;
u8 param : 4;
u8 type : 4;

constexpr u8 HID_PARAM_INPUT = 1;
constexpr u8 HID_PARAM_OUTPUT = 2;
InputReportID report_id;

T data;

static_assert(std::is_pod<T>());

u8* GetData() { return reinterpret_cast<u8*>(this); }
const u8* GetData() const { return reinterpret_cast<const u8*>(this); }

constexpr u32 GetSize() const
{
static_assert(sizeof(*this) == sizeof(T) + 2);
return sizeof(*this);
}
};

#pragma pack(pop)

#ifdef _MSC_VER
#pragma warning(pop)
#endif

} // namespace WiimoteCommon

Large diffs are not rendered by default.

This file was deleted.

This file was deleted.

@@ -0,0 +1,213 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Camera.h"

#include "Common/BitUtils.h"
#include "Common/ChunkFile.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/MatrixMath.h"

namespace WiimoteEmu
{
void CameraLogic::Reset()
{
reg_data = {};
}

void CameraLogic::DoState(PointerWrap& p)
{
p.Do(reg_data);
}

int CameraLogic::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
if (I2C_ADDR != slave_addr)
return 0;

return RawRead(&reg_data, addr, count, data_out);
}

int CameraLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
if (I2C_ADDR != slave_addr)
return 0;

return RawWrite(&reg_data, addr, count, data_in);
}

void CameraLogic::Update(const ControllerEmu::Cursor::StateData& cursor,
const NormalizedAccelData& accel, bool sensor_bar_on_top)
{
double nsin, ncos;

// Ugly code to figure out the wiimote's current angle.
// TODO: Kill this.
double ax = accel.x;
double az = accel.z;
const double len = sqrt(ax * ax + az * az);

if (len)
{
ax /= len;
az /= len; // normalizing the vector
nsin = ax;
ncos = az;
}
else
{
nsin = 0;
ncos = 1;
}

const double ir_sin = nsin;
const double ir_cos = ncos;

static constexpr int camWidth = 1024;
static constexpr int camHeight = 768;
static constexpr double bndleft = 0.78820266;
static constexpr double bndright = -0.78820266;
static constexpr double dist1 = 100.0 / camWidth; // this seems the optimal distance for zelda
static constexpr double dist2 = 1.2 * dist1;

constexpr int NUM_POINTS = 4;

std::array<Vertex, NUM_POINTS> v;

for (auto& vtx : v)
{
vtx.x = cursor.x * (bndright - bndleft) / 2 + (bndleft + bndright) / 2;

static constexpr double bndup = -0.315447;
static constexpr double bnddown = 0.85;

if (sensor_bar_on_top)
vtx.y = cursor.y * (bndup - bnddown) / 2 + (bndup + bnddown) / 2;
else
vtx.y = cursor.y * (bndup - bnddown) / 2 - (bndup + bnddown) / 2;

vtx.z = 0;
}

v[0].x -= (cursor.z * 0.5 + 1) * dist1;
v[1].x += (cursor.z * 0.5 + 1) * dist1;
v[2].x -= (cursor.z * 0.5 + 1) * dist2;
v[3].x += (cursor.z * 0.5 + 1) * dist2;

#define printmatrix(m) \
PanicAlert("%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n", m[0][0], m[0][1], m[0][2], \
m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], \
m[3][0], m[3][1], m[3][2], m[3][3])
Matrix rot, tot;
static Matrix scale;
MatrixScale(scale, 1, camWidth / camHeight, 1);
MatrixRotationByZ(rot, ir_sin, ir_cos);
MatrixMultiply(tot, scale, rot);

u16 x[NUM_POINTS], y[NUM_POINTS];
memset(x, 0xFF, sizeof(x));
memset(y, 0xFF, sizeof(y));

for (std::size_t i = 0; i < v.size(); i++)
{
MatrixTransformVertex(tot, v[i]);

if ((v[i].x < -1) || (v[i].x > 1) || (v[i].y < -1) || (v[i].y > 1))
continue;

x[i] = static_cast<u16>(lround((v[i].x + 1) / 2 * (camWidth - 1)));
y[i] = static_cast<u16>(lround((v[i].y + 1) / 2 * (camHeight - 1)));

if (x[i] >= camWidth || y[i] >= camHeight)
{
x[i] = -1;
y[i] = -1;
}
}

// IR data is read from offset 0x37 on real hardware
auto& data = reg_data.camera_data;
// A maximum of 36 bytes:
std::fill(std::begin(data), std::end(data), 0xff);

// Fill report with valid data when full handshake was done
// TODO: kill magic number:
if (reg_data.data[0x30])
{
switch (reg_data.mode)
{
case IR_MODE_BASIC:
for (unsigned int i = 0; i < 2; ++i)
{
IRBasic irdata = {};

irdata.x1 = static_cast<u8>(x[i * 2]);
irdata.x1hi = x[i * 2] >> 8;
irdata.y1 = static_cast<u8>(y[i * 2]);
irdata.y1hi = y[i * 2] >> 8;

irdata.x2 = static_cast<u8>(x[i * 2 + 1]);
irdata.x2hi = x[i * 2 + 1] >> 8;
irdata.y2 = static_cast<u8>(y[i * 2 + 1]);
irdata.y2hi = y[i * 2 + 1] >> 8;

Common::BitCastPtr<IRBasic>(data + i * sizeof(IRBasic)) = irdata;
}
break;
case IR_MODE_EXTENDED:
for (unsigned int i = 0; i < 4; ++i)
{
if (x[i] < camWidth)
{
IRExtended irdata = {};

irdata.x = static_cast<u8>(x[i]);
irdata.xhi = x[i] >> 8;

irdata.y = static_cast<u8>(y[i]);
irdata.yhi = y[i] >> 8;

irdata.size = 10;

Common::BitCastPtr<IRExtended>(data + i * sizeof(IRExtended)) = irdata;
}
}
break;
case IR_MODE_FULL:
for (unsigned int i = 0; i < 4; ++i)
{
if (x[i] < camWidth)
{
IRFull irdata = {};

irdata.x = static_cast<u8>(x[i]);
irdata.xhi = x[i] >> 8;

irdata.y = static_cast<u8>(y[i]);
irdata.yhi = y[i] >> 8;

irdata.size = 10;

// TODO: implement these sensibly:
// TODO: do high bits of x/y min/max need to be set to zero?
irdata.xmin = 0;
irdata.ymin = 0;
irdata.xmax = 0;
irdata.ymax = 0;
irdata.zero = 0;
irdata.intensity = 0;

Common::BitCastPtr<IRFull>(data + i * sizeof(IRFull)) = irdata;
}
}
break;
default:
// This seems to be fairly common, 0xff data is sent in this case:
// WARN_LOG(WIIMOTE, "Game is requesting IR data before setting IR mode.");
break;
}
}
}

} // namespace WiimoteEmu
@@ -0,0 +1,104 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"

namespace WiimoteEmu
{
// Four bytes for two objects. Filled with 0xFF if empty
struct IRBasic
{
u8 x1;
u8 y1;
u8 x2hi : 2;
u8 y2hi : 2;
u8 x1hi : 2;
u8 y1hi : 2;
u8 x2;
u8 y2;
};
static_assert(sizeof(IRBasic) == 5, "Wrong size");

// Three bytes for one object
struct IRExtended
{
u8 x;
u8 y;
u8 size : 4;
u8 xhi : 2;
u8 yhi : 2;
};
static_assert(sizeof(IRExtended) == 3, "Wrong size");

// Nine bytes for one object
// first 3 bytes are the same as extended
struct IRFull : IRExtended
{
u8 xmin : 7;
u8 : 1;
u8 ymin : 7;
u8 : 1;
u8 xmax : 7;
u8 : 1;
u8 ymax : 7;
u8 : 1;
u8 zero;
u8 intensity;
};
static_assert(sizeof(IRFull) == 9, "Wrong size");

class CameraLogic : public I2CSlave
{
public:
enum : u8
{
IR_MODE_BASIC = 1,
IR_MODE_EXTENDED = 3,
IR_MODE_FULL = 5,
};

void Reset();
void DoState(PointerWrap& p);
void Update(const ControllerEmu::Cursor::StateData& cursor, const NormalizedAccelData& accel,
bool sensor_bar_on_top);

static constexpr u8 I2C_ADDR = 0x58;

private:
// TODO: some of this memory is write-only and should return error 7.
#pragma pack(push, 1)
struct Register
{
// Contains sensitivity and other unknown data
// TODO: Do the IR and Camera enabling reports write to the i2c bus?
// TODO: Does disabling the camera peripheral reset the mode or sensitivity?
// TODO: Break out this "data" array into some known members
u8 data[0x33];
u8 mode;
u8 unk[3];
// addr: 0x37
u8 camera_data[36];
u8 unk2[165];
};
#pragma pack(pop)

static_assert(0x100 == sizeof(Register));

public:
// The real wiimote reads camera data from the i2c bus at offset 0x37:
static const u8 REPORT_DATA_OFFSET = offsetof(Register, camera_data);

private:
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 reg_data;
};
} // namespace WiimoteEmu
@@ -0,0 +1,264 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Dynamics.h"

#include <cmath>

#include "Common/MathUtil.h"
#include "Core/Config/WiimoteInputSettings.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/Tilt.h"

namespace WiimoteEmu
{
constexpr int SHAKE_FREQ = 6;
// Frame count of one up/down shake
// < 9 no shake detection in "Wario Land: Shake It"
constexpr int SHAKE_STEP_MAX = ::Wiimote::UPDATE_FREQ / SHAKE_FREQ;

void EmulateShake(NormalizedAccelData* const accel, ControllerEmu::Buttons* const buttons_group,
const double intensity, u8* const shake_step)
{
// shake is a bitfield of X,Y,Z shake button states
static const unsigned int btns[] = {0x01, 0x02, 0x04};
unsigned int shake = 0;
buttons_group->GetState(&shake, btns);

for (int i = 0; i != 3; ++i)
{
if (shake & (1 << i))
{
(&(accel->x))[i] += std::sin(MathUtil::TAU * shake_step[i] / SHAKE_STEP_MAX) * intensity;
shake_step[i] = (shake_step[i] + 1) % SHAKE_STEP_MAX;
}
else
{
shake_step[i] = 0;
}
}
}

void EmulateDynamicShake(NormalizedAccelData* const accel, DynamicData& dynamic_data,
ControllerEmu::Buttons* const buttons_group,
const DynamicConfiguration& config, u8* const shake_step)
{
// shake is a bitfield of X,Y,Z shake button states
static const unsigned int btns[] = {0x01, 0x02, 0x04};
unsigned int shake = 0;
buttons_group->GetState(&shake, btns);

for (int i = 0; i != 3; ++i)
{
if ((shake & (1 << i)) && dynamic_data.executing_frames_left[i] == 0)
{
dynamic_data.timing[i]++;
}
else if (dynamic_data.executing_frames_left[i] > 0)
{
(&(accel->x))[i] +=
std::sin(MathUtil::TAU * shake_step[i] / SHAKE_STEP_MAX) * dynamic_data.intensity[i];
shake_step[i] = (shake_step[i] + 1) % SHAKE_STEP_MAX;
dynamic_data.executing_frames_left[i]--;
}
else if (shake == 0 && dynamic_data.timing[i] > 0)
{
if (dynamic_data.timing[i] > config.frames_needed_for_high_intensity)
{
dynamic_data.intensity[i] = config.high_intensity;
}
else if (dynamic_data.timing[i] < config.frames_needed_for_low_intensity)
{
dynamic_data.intensity[i] = config.low_intensity;
}
else
{
dynamic_data.intensity[i] = config.med_intensity;
}
dynamic_data.timing[i] = 0;
dynamic_data.executing_frames_left[i] = config.frames_to_execute;
}
else
{
shake_step[i] = 0;
}
}
}

void EmulateTilt(NormalizedAccelData* const accel, ControllerEmu::Tilt* const tilt_group,
const bool sideways, const bool upright)
{
// 180 degrees
const ControllerEmu::Tilt::StateData state = tilt_group->GetState();
const ControlState roll = state.x * MathUtil::PI;
const ControlState pitch = state.y * MathUtil::PI;

// Some notes that no one will understand but me :p
// left, forward, up
// lr/ left == negative for all orientations
// ud/ up == negative for upright longways
// fb/ forward == positive for (sideways flat)

// Determine which axis is which direction
const u32 ud = upright ? (sideways ? 0 : 1) : 2;
const u32 lr = sideways;
const u32 fb = upright ? 2 : (sideways ? 0 : 1);

// Sign fix
std::array<int, 3> sgn{{-1, 1, 1}};
if (sideways && !upright)
sgn[fb] *= -1;
if (!sideways && upright)
sgn[ud] *= -1;

(&accel->x)[ud] = (sin((MathUtil::PI / 2) - std::max(fabs(roll), fabs(pitch)))) * sgn[ud];
(&accel->x)[lr] = -sin(roll) * sgn[lr];
(&accel->x)[fb] = sin(pitch) * sgn[fb];
}

void EmulateSwing(NormalizedAccelData* const accel, ControllerEmu::Force* const swing_group,
const double intensity, const bool sideways, const bool upright)
{
const ControllerEmu::Force::StateData swing = swing_group->GetState();

// Determine which axis is which direction
const std::array<int, 3> axis_map{{
upright ? (sideways ? 0 : 1) : 2, // up/down
sideways, // left/right
upright ? 2 : (sideways ? 0 : 1), // forward/backward
}};

// Some orientations have up as positive, some as negative
// same with forward
std::array<s8, 3> g_dir{{-1, -1, -1}};
if (sideways && !upright)
g_dir[axis_map[2]] *= -1;
if (!sideways && upright)
g_dir[axis_map[0]] *= -1;

for (std::size_t i = 0; i < swing.size(); ++i)
(&accel->x)[axis_map[i]] += swing[i] * g_dir[i] * intensity;
}

void EmulateDynamicSwing(NormalizedAccelData* const accel, DynamicData& dynamic_data,
ControllerEmu::Force* const swing_group,
const DynamicConfiguration& config, const bool sideways,
const bool upright)
{
const ControllerEmu::Force::StateData swing = swing_group->GetState();

// Determine which axis is which direction
const std::array<int, 3> axis_map{{
upright ? (sideways ? 0 : 1) : 2, // up/down
sideways, // left/right
upright ? 2 : (sideways ? 0 : 1), // forward/backward
}};

// Some orientations have up as positive, some as negative
// same with forward
std::array<s8, 3> g_dir{{-1, -1, -1}};
if (sideways && !upright)
g_dir[axis_map[2]] *= -1;
if (!sideways && upright)
g_dir[axis_map[0]] *= -1;

for (std::size_t i = 0; i < swing.size(); ++i)
{
if (swing[i] > 0 && dynamic_data.executing_frames_left[i] == 0)
{
dynamic_data.timing[i]++;
}
else if (dynamic_data.executing_frames_left[i] > 0)
{
(&accel->x)[axis_map[i]] += g_dir[i] * dynamic_data.intensity[i];
dynamic_data.executing_frames_left[i]--;
}
else if (swing[i] == 0 && dynamic_data.timing[i] > 0)
{
if (dynamic_data.timing[i] > config.frames_needed_for_high_intensity)
{
dynamic_data.intensity[i] = config.high_intensity;
}
else if (dynamic_data.timing[i] < config.frames_needed_for_low_intensity)
{
dynamic_data.intensity[i] = config.low_intensity;
}
else
{
dynamic_data.intensity[i] = config.med_intensity;
}
dynamic_data.timing[i] = 0;
dynamic_data.executing_frames_left[i] = config.frames_to_execute;
}
}
}

WiimoteCommon::DataReportBuilder::AccelData DenormalizeAccelData(const NormalizedAccelData& accel,
u16 zero_g, u16 one_g)
{
const u8 accel_range = one_g - zero_g;

const s32 unclamped_x = (s32)(accel.x * accel_range + zero_g);
const s32 unclamped_y = (s32)(accel.y * accel_range + zero_g);
const s32 unclamped_z = (s32)(accel.z * accel_range + zero_g);

WiimoteCommon::DataReportBuilder::AccelData result;

result.x = MathUtil::Clamp<u16>(unclamped_x, 0, 0x3ff);
result.y = MathUtil::Clamp<u16>(unclamped_y, 0, 0x3ff);
result.z = MathUtil::Clamp<u16>(unclamped_z, 0, 0x3ff);

return result;
}

void Wiimote::GetAccelData(NormalizedAccelData* accel)
{
const bool is_sideways = IsSideways();
const bool is_upright = IsUpright();

EmulateTilt(accel, m_tilt, is_sideways, is_upright);

DynamicConfiguration swing_config;
swing_config.low_intensity = Config::Get(Config::WIIMOTE_INPUT_SWING_INTENSITY_SLOW);
swing_config.med_intensity = Config::Get(Config::WIIMOTE_INPUT_SWING_INTENSITY_MEDIUM);
swing_config.high_intensity = Config::Get(Config::WIIMOTE_INPUT_SWING_INTENSITY_FAST);
swing_config.frames_needed_for_high_intensity =
Config::Get(Config::WIIMOTE_INPUT_SWING_DYNAMIC_FRAMES_HELD_FAST);
swing_config.frames_needed_for_low_intensity =
Config::Get(Config::WIIMOTE_INPUT_SWING_DYNAMIC_FRAMES_HELD_SLOW);
swing_config.frames_to_execute = Config::Get(Config::WIIMOTE_INPUT_SWING_DYNAMIC_FRAMES_LENGTH);

EmulateSwing(accel, m_swing, Config::Get(Config::WIIMOTE_INPUT_SWING_INTENSITY_MEDIUM),
is_sideways, is_upright);
EmulateSwing(accel, m_swing_slow, Config::Get(Config::WIIMOTE_INPUT_SWING_INTENSITY_SLOW),
is_sideways, is_upright);
EmulateSwing(accel, m_swing_fast, Config::Get(Config::WIIMOTE_INPUT_SWING_INTENSITY_FAST),
is_sideways, is_upright);
EmulateDynamicSwing(accel, m_swing_dynamic_data, m_swing_dynamic, swing_config, is_sideways,
is_upright);

DynamicConfiguration shake_config;
shake_config.low_intensity = Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_SOFT);
shake_config.med_intensity = Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_MEDIUM);
shake_config.high_intensity = Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_HARD);
shake_config.frames_needed_for_high_intensity =
Config::Get(Config::WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_HARD);
shake_config.frames_needed_for_low_intensity =
Config::Get(Config::WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_SOFT);
shake_config.frames_to_execute = Config::Get(Config::WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_LENGTH);

EmulateShake(accel, m_shake, Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_MEDIUM),
m_shake_step.data());
EmulateShake(accel, m_shake_soft, Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_SOFT),
m_shake_soft_step.data());
EmulateShake(accel, m_shake_hard, Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_HARD),
m_shake_hard_step.data());
EmulateDynamicShake(accel, m_shake_dynamic_data, m_shake_dynamic, shake_config,
m_shake_dynamic_step.data());
}

} // namespace WiimoteEmu
@@ -0,0 +1,66 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <array>

#include "Core/HW/WiimoteCommon/DataReport.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/Tilt.h"

namespace WiimoteEmu
{
struct NormalizedAccelData
{
// Unit is 1G
double x, y, z;
};

// Used for a dynamic swing or shake
struct DynamicData
{
std::array<int, 3> timing; // Hold length in frames for each axis
std::array<double, 3> intensity; // Swing or shake intensity
std::array<int, 3> executing_frames_left; // Number of frames to execute the intensity operation
};

// Used for a dynamic swing or shake.
// This is used to pass in data that defines the dynamic action
struct DynamicConfiguration
{
double low_intensity;
int frames_needed_for_low_intensity;

double med_intensity;
// Frames needed for med intensity can be calculated between high & low

double high_intensity;
int frames_needed_for_high_intensity;

int frames_to_execute; // How many frames should we execute the action for?
};

void EmulateShake(NormalizedAccelData* accel, ControllerEmu::Buttons* buttons_group,
double intensity, u8* shake_step);

void EmulateDynamicShake(NormalizedAccelData* accel, DynamicData& dynamic_data,
ControllerEmu::Buttons* buttons_group, const DynamicConfiguration& config,
u8* shake_step);

void EmulateTilt(NormalizedAccelData* accel, ControllerEmu::Tilt* tilt_group, bool sideways = false,
bool upright = false);

void EmulateSwing(NormalizedAccelData* accel, ControllerEmu::Force* swing_group, double intensity,
bool sideways = false, bool upright = false);

void EmulateDynamicSwing(NormalizedAccelData* accel, DynamicData& dynamic_data,
ControllerEmu::Force* swing_group, const DynamicConfiguration& config,
bool sideways = false, bool upright = false);

WiimoteCommon::DataReportBuilder::AccelData DenormalizeAccelData(const NormalizedAccelData& accel,
u16 zero_g, u16 one_g);

} // namespace WiimoteEmu

Large diffs are not rendered by default.

@@ -241,8 +241,10 @@ static void GenerateTables(const u8* const rand, const u8* const key, const u8 i
sb[7] = sboxes[idx][rand[2]] ^ sboxes[(idx + 1) % 8][rand[6]];
}

/* Generate key from the 0x40-0x4c data in g_RegExt */
void WiimoteGenerateKey(wiimote_key* const key, const u8* const keydata)
namespace WiimoteEmu
{
// Generate key from the 0x40-0x4c data in g_RegExt
void EncryptionKey::Generate(const u8* const keydata)
{
u8 rand[10];
u8 skey[6];
@@ -262,22 +264,24 @@ void WiimoteGenerateKey(wiimote_key* const key, const u8* const keydata)
}
// default case is idx = 7 which is valid (homebrew uses it for the 0x17 case)

GenerateTables(rand, skey, idx, key->ft, key->sb);
GenerateTables(rand, skey, idx, ft, sb);

// for homebrew, ft and sb are all 0x97 which is equivalent to 0x17
}

// TODO: is there a reason these can only handle a length of 255?
/* Encrypt data */
void WiimoteEncrypt(const wiimote_key* const key, u8* const data, int addr, const u8 len)
// Question: Is there a reason these can only handle a length of 255?
// Answer: The i2c address space is only 8-bits so it really doesn't need to.
// Also, only 21 bytes are ever encrypted at most (6 in any normal game).
void EncryptionKey::Encrypt(u8* const data, int addr, const u8 len) const
{
for (int i = 0; i < len; ++i, ++addr)
data[i] = (data[i] - key->ft[addr % 8]) ^ key->sb[addr % 8];
data[i] = (data[i] - ft[addr % 8]) ^ sb[addr % 8];
}

/* Decrypt data */
void WiimoteDecrypt(const wiimote_key* const key, u8* const data, int addr, const u8 len)
void EncryptionKey::Decrypt(u8* const data, int addr, const u8 len) const
{
for (int i = 0; i < len; ++i, ++addr)
data[i] = (data[i] ^ key->sb[addr % 8]) + key->ft[addr % 8];
data[i] = (data[i] ^ sb[addr % 8]) + ft[addr % 8];
}

} // namespace WiimoteEmu
@@ -8,14 +8,19 @@

#include "Common/CommonTypes.h"

// The key structure to use with WiimoteGenerateKey()
struct wiimote_key
namespace WiimoteEmu
{
u8 ft[8];
u8 sb[8];
};
class EncryptionKey
{
public:
void Generate(const u8* keydata);

void WiimoteEncrypt(const wiimote_key* const key, u8* const data, int addr, const u8 len);
void WiimoteDecrypt(const wiimote_key* const key, u8* const data, int addr, const u8 len);
void Encrypt(u8* data, int addr, u8 len) const;
void Decrypt(u8* data, int addr, u8 len) const;

private:
u8 ft[8] = {};
u8 sb[8] = {};
};

void WiimoteGenerateKey(wiimote_key* const key, const u8* const keydata);
} // namespace WiimoteEmu
@@ -2,11 +2,12 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Attachment/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"

#include <array>
#include <cassert>

#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
@@ -71,7 +72,7 @@ constexpr std::array<u16, 4> classic_dpad_bitmasks{{
Classic::PAD_RIGHT,
}};

Classic::Classic(ExtensionReg& reg) : Attachment(_trans("Classic"), reg)
Classic::Classic() : EncryptedExtension(_trans("Classic"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -104,52 +105,20 @@ Classic::Classic(ExtensionReg& reg) : Attachment(_trans("Classic"), reg)
m_dpad->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::Translate, named_direction));
}

m_id = classic_id;

// Build calibration data:
m_calibration = {{
// Left Stick X max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Left Stick Y max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Right Stick X max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Right Stick Y max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Left/Right trigger range: (assumed based on real calibration data values)
LEFT_TRIGGER_RANGE,
RIGHT_TRIGGER_RANGE,
// 2 checksum bytes calculated below:
0x00,
0x00,
}};

UpdateCalibrationDataChecksum(m_calibration);
}

void Classic::GetState(u8* const data)
void Classic::Update()
{
wm_classic_extension classic_data = {};

// not using calibration data, o well
DataFormat classic_data = {};

// left stick
{
const ControllerEmu::AnalogStick::StateData left_stick_state = m_left_stick->GetState();

classic_data.regular_data.lx = static_cast<u8>(
Classic::LEFT_STICK_CENTER_X + (left_stick_state.x * Classic::LEFT_STICK_RADIUS));
classic_data.regular_data.ly = static_cast<u8>(
Classic::LEFT_STICK_CENTER_Y + (left_stick_state.y * Classic::LEFT_STICK_RADIUS));
classic_data.lx = static_cast<u8>(Classic::LEFT_STICK_CENTER_X +
(left_stick_state.x * Classic::LEFT_STICK_RADIUS));
classic_data.ly = static_cast<u8>(Classic::LEFT_STICK_CENTER_Y +
(left_stick_state.y * Classic::LEFT_STICK_RADIUS));
}

// right stick
@@ -188,7 +157,7 @@ void Classic::GetState(u8* const data)
// flip button bits
classic_data.bt.hex ^= 0xFFFF;

std::memcpy(data, &classic_data, sizeof(wm_classic_extension));
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data;
}

bool Classic::IsButtonPressed() const
@@ -201,6 +170,40 @@ bool Classic::IsButtonPressed() const
return buttons != 0;
}

void Classic::Reset()
{
m_reg = {};
m_reg.identifier = classic_id;

// Build calibration data:
m_reg.calibration = {{
// Left Stick X max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Left Stick Y max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Right Stick X max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Right Stick Y max,min,center:
CAL_STICK_CENTER + CAL_STICK_RANGE,
CAL_STICK_CENTER - CAL_STICK_RANGE,
CAL_STICK_CENTER,
// Left/Right trigger range: (assumed based on real calibration data values)
LEFT_TRIGGER_RANGE,
RIGHT_TRIGGER_RANGE,
// 2 checksum bytes calculated below:
0x00,
0x00,
}};

UpdateCalibrationDataChecksum(m_reg.calibration, CALIBRATION_CHECKSUM_BYTES);
}

ControllerEmu::ControlGroup* Classic::GetGroup(ClassicGroup group)
{
switch (group)
@@ -4,7 +4,8 @@

#pragma once

#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"

namespace ControllerEmu
{
@@ -16,15 +17,74 @@ class MixedTriggers;

namespace WiimoteEmu
{
enum class ClassicGroup;
struct ExtensionReg;
enum class ClassicGroup
{
Buttons,
Triggers,
DPad,
LeftStick,
RightStick
};

class Classic : public Attachment
class Classic : public EncryptedExtension
{
public:
explicit Classic(ExtensionReg& reg);
void GetState(u8* const data) override;
union ButtonFormat
{
u16 hex;

struct
{
u8 : 1;
u8 rt : 1; // right trigger
u8 plus : 1;
u8 home : 1;
u8 minus : 1;
u8 lt : 1; // left trigger
u8 dpad_down : 1;
u8 dpad_right : 1;

u8 dpad_up : 1;
u8 dpad_left : 1;
u8 zr : 1;
u8 x : 1;
u8 a : 1;
u8 y : 1;
u8 b : 1;
u8 zl : 1; // left z button
};
};
static_assert(sizeof(ButtonFormat) == 2, "Wrong size");

struct DataFormat
{
// lx/ly/lz; left joystick
// rx/ry/rz; right joystick
// lt; left trigger
// rt; left trigger

u8 lx : 6; // byte 0
u8 rx3 : 2;

u8 ly : 6; // byte 1
u8 rx2 : 2;

u8 ry : 5;
u8 lt2 : 2;
u8 rx1 : 1;

u8 rt : 5;
u8 lt1 : 3;

ButtonFormat bt; // byte 4, 5
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

Classic();

void Update() override;
bool IsButtonPressed() const override;
void Reset() override;

ControllerEmu::ControlGroup* GetGroup(ClassicGroup group);

@@ -2,12 +2,13 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Attachment/Drums.h"
#include "Core/HW/WiimoteEmu/Extension/Drums.h"

#include <array>
#include <cassert>
#include <cstring>

#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
@@ -43,7 +44,7 @@ constexpr std::array<u16, 2> drum_button_bitmasks{{
Drums::BUTTON_PLUS,
}};

Drums::Drums(ExtensionReg& reg) : Attachment(_trans("Drums"), reg)
Drums::Drums() : EncryptedExtension(_trans("Drums"))
{
// pads
groups.emplace_back(m_pads = new ControllerEmu::Buttons(_trans("Pads")));
@@ -62,16 +63,11 @@ Drums::Drums(ExtensionReg& reg) : Attachment(_trans("Drums"), reg)
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
m_buttons->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "-"));
m_buttons->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "+"));

// set up register
m_id = drums_id;
}

void Drums::GetState(u8* const data)
void Drums::Update()
{
wm_drums_extension drum_data = {};

// calibration data not figured out yet?
DataFormat drum_data = {};

// stick
{
@@ -81,9 +77,12 @@ void Drums::GetState(u8* const data)
drum_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
}

// TODO: softness maybe
data[2] = 0xFF;
data[3] = 0xFF;
// TODO: Implement these:
drum_data.which = 0x1F;
drum_data.none = 1;
drum_data.hhp = 1;
drum_data.velocity = 0xf;
drum_data.softness = 7;

// buttons
m_buttons->GetState(&drum_data.bt, drum_button_bitmasks.data());
@@ -94,7 +93,7 @@ void Drums::GetState(u8* const data)
// flip button bits
drum_data.bt ^= 0xFFFF;

std::memcpy(data, &drum_data, sizeof(wm_drums_extension));
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = drum_data;
}

bool Drums::IsButtonPressed() const
@@ -105,6 +104,14 @@ bool Drums::IsButtonPressed() const
return buttons != 0;
}

void Drums::Reset()
{
m_reg = {};
m_reg.identifier = drums_id;

// TODO: Is there calibration data?
}

ControllerEmu::ControlGroup* Drums::GetGroup(DrumsGroup group)
{
switch (group)
@@ -4,26 +4,54 @@

#pragma once

#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"

namespace ControllerEmu
{
class AnalogStick;
class Buttons;
class ControlGroup;
}
} // namespace ControllerEmu

namespace WiimoteEmu
{
enum class DrumsGroup;
struct ExtensionReg;
enum class DrumsGroup
{
Buttons,
Pads,
Stick
};

class Drums : public Attachment
// TODO: Do the drums ever use encryption?
class Drums : public EncryptedExtension
{
public:
explicit Drums(ExtensionReg& reg);
void GetState(u8* const data) override;
struct DataFormat
{
u8 sx : 6;
u8 pad1 : 2; // always 0

u8 sy : 6;
u8 pad2 : 2; // always 0

u8 pad3 : 1; // unknown
u8 which : 5;
u8 none : 1;
u8 hhp : 1;

u8 pad4 : 1; // unknown
u8 velocity : 4; // unknown
u8 softness : 3;

u16 bt; // buttons
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

Drums();

void Update() override;
bool IsButtonPressed() const override;
void Reset() override;

ControllerEmu::ControlGroup* GetGroup(DrumsGroup group);

@@ -51,4 +79,4 @@ class Drums : public Attachment
ControllerEmu::Buttons* m_pads;
ControllerEmu::AnalogStick* m_stick;
};
}
} // namespace WiimoteEmu
@@ -0,0 +1,129 @@
// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Extension/Extension.h"

#include <algorithm>
#include <array>
#include <cstring>

#include "Common/CommonTypes.h"
#include "Common/Compiler.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"

namespace WiimoteEmu
{
Extension::Extension(const char* name) : m_name(name)
{
}

std::string Extension::GetName() const
{
return m_name;
}

None::None() : Extension("None")
{
}

bool None::ReadDeviceDetectPin() const
{
return false;
}

void None::Update()
{
// Nothing needed.
}

bool None::IsButtonPressed() const
{
return false;
}

void None::Reset()
{
// Nothing needed.
}

void None::DoState(PointerWrap& p)
{
// Nothing needed.
}

int None::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
return 0;
}

int None::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
return 0;
}

EncryptedExtension::EncryptedExtension(const char* name) : Extension(name)
{
}

bool EncryptedExtension::ReadDeviceDetectPin() const
{
return true;
}

int EncryptedExtension::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
if (I2C_ADDR != slave_addr)
return 0;

if (0x00 == addr)
{
// This is where real hardware would update controller data
// We do it in Update() for TAS determinism
// TAS code fails to sync data reads and such..
}

auto const result = RawRead(&m_reg, addr, count, data_out);

// Encrypt data read from extension register
if (ENCRYPTION_ENABLED == m_reg.encryption)
{
// INFO_LOG(WIIMOTE, "Encrypted read.");
ext_key.Encrypt(data_out, addr, (u8)count);
}
else
{
// INFO_LOG(WIIMOTE, "Unencrypted read.");
}

return result;
}

int EncryptedExtension::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
if (I2C_ADDR != slave_addr)
return 0;

auto const result = RawWrite(&m_reg, addr, count, data_in);

// TODO: make this check less ugly:
if (addr + count > 0x40 && addr < 0x50)
{
// Run the key generation on all writes in the key area, it doesn't matter
// that we send it parts of a key, only the last full key will have an effect
ext_key.Generate(m_reg.encryption_key_data);
}

return result;
}

void EncryptedExtension::DoState(PointerWrap& p)
{
p.Do(m_reg);

// No need to sync this when we can regenerate it:
if (p.GetMode() == PointerWrap::MODE_READ)
ext_key.Generate(m_reg.encryption_key_data);
}

} // namespace WiimoteEmu
@@ -0,0 +1,112 @@
// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include "Core/HW/WiimoteEmu/Extension/Extension.h"

#include <array>
#include <string>

#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"

namespace WiimoteEmu
{
class Extension : public ControllerEmu::EmulatedController, public I2CSlave
{
public:
explicit Extension(const char* name);

std::string GetName() const override;

// Used by the wiimote to detect extension changes.
// The normal extensions short this pin so it's always connected,
// but M+ does some tricks with it during activation.
virtual bool ReadDeviceDetectPin() const = 0;

virtual bool IsButtonPressed() const = 0;
virtual void Reset() = 0;
virtual void DoState(PointerWrap& p) = 0;
virtual void Update() = 0;

private:
const char* const m_name;
};

class None : public Extension
{
public:
explicit None();

private:
bool ReadDeviceDetectPin() const override;
void Update() override;
bool IsButtonPressed() const override;
void Reset() override;
void DoState(PointerWrap& p) override;

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;
};

// This class provides the encryption and initialization behavior of most extensions.
class EncryptedExtension : public Extension
{
public:
static constexpr u8 I2C_ADDR = 0x52;

EncryptedExtension(const char* name);

// TODO: This is public for TAS reasons.
// TODO: TAS handles encryption poorly.
WiimoteEmu::EncryptionKey ext_key = {};

protected:
static constexpr int CALIBRATION_CHECKSUM_BYTES = 2;

#pragma pack(push, 1)
struct Register
{
// 21 bytes of possible extension data
u8 controller_data[21];

u8 unknown2[11];

// address 0x20
std::array<u8, 0x10> calibration;
u8 unknown3[0x10];

// address 0x40
u8 encryption_key_data[0x10];
u8 unknown4[0xA0];

// address 0xF0
u8 encryption;
u8 unknown5[0x9];

// address 0xFA
std::array<u8, 6> identifier;
};
#pragma pack(pop)

static_assert(0x100 == sizeof(Register));

Register m_reg = {};

private:
static constexpr u8 ENCRYPTION_ENABLED = 0xaa;

bool ReadDeviceDetectPin() const override;

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;

void DoState(PointerWrap& p) override;
};

} // namespace WiimoteEmu
@@ -2,13 +2,14 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Attachment/Guitar.h"
#include "Core/HW/WiimoteEmu/Extension/Guitar.h"

#include <array>
#include <cassert>
#include <cstring>
#include <map>

#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
@@ -61,7 +62,7 @@ constexpr std::array<u16, 2> guitar_strum_bitmasks{{
Guitar::BAR_DOWN,
}};

Guitar::Guitar(ExtensionReg& reg) : Attachment(_trans("Guitar"), reg)
Guitar::Guitar() : EncryptedExtension(_trans("Guitar"))
{
// frets
groups.emplace_back(m_frets = new ControllerEmu::Buttons(_trans("Frets")));
@@ -94,16 +95,11 @@ Guitar::Guitar(ExtensionReg& reg) : Attachment(_trans("Guitar"), reg)

// slider bar
groups.emplace_back(m_slider_bar = new ControllerEmu::Slider(_trans("Slider Bar")));

// set up register
m_id = guitar_id;
}

void Guitar::GetState(u8* const data)
void Guitar::Update()
{
wm_guitar_extension guitar_data = {};

// calibration data not figured out yet?
DataFormat guitar_data = {};

// stick
{
@@ -142,7 +138,7 @@ void Guitar::GetState(u8* const data)
// flip button bits
guitar_data.bt ^= 0xFFFF;

std::memcpy(data, &guitar_data, sizeof(wm_guitar_extension));
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = guitar_data;
}

bool Guitar::IsButtonPressed() const
@@ -154,6 +150,14 @@ bool Guitar::IsButtonPressed() const
return buttons != 0;
}

void Guitar::Reset()
{
m_reg = {};
m_reg.identifier = guitar_id;

// TODO: Is there calibration data?
}

ControllerEmu::ControlGroup* Guitar::GetGroup(GuitarGroup group)
{
switch (group)
@@ -4,7 +4,7 @@

#pragma once

#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"

namespace ControllerEmu
{
@@ -13,19 +13,47 @@ class Buttons;
class ControlGroup;
class Triggers;
class Slider;
}
} // namespace ControllerEmu

namespace WiimoteEmu
{
enum class GuitarGroup;
struct ExtensionReg;
enum class GuitarGroup
{
Buttons,
Frets,
Strum,
Whammy,
Stick,
SliderBar
};

class Guitar : public Attachment
// TODO: Does the guitar ever use encryption?
class Guitar : public EncryptedExtension
{
public:
explicit Guitar(ExtensionReg& reg);
void GetState(u8* const data) override;
struct DataFormat
{
u8 sx : 6;
u8 pad1 : 2; // 1 on gh3, 0 on ghwt

u8 sy : 6;
u8 pad2 : 2; // 1 on gh3, 0 on ghwt

u8 sb : 5; // not used in gh3
u8 pad3 : 3; // always 0

u8 whammy : 5;
u8 pad4 : 3; // always 0

u16 bt; // buttons
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

Guitar();

void Update() override;
bool IsButtonPressed() const override;
void Reset() override;

ControllerEmu::ControlGroup* GetGroup(GuitarGroup group);

@@ -57,4 +85,4 @@ class Guitar : public Attachment
ControllerEmu::AnalogStick* m_stick;
ControllerEmu::Slider* m_slider_bar;
};
}
} // namespace WiimoteEmu
@@ -2,12 +2,13 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Attachment/Nunchuk.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"

#include <array>
#include <cassert>
#include <cstring>

#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
@@ -30,7 +31,7 @@ constexpr std::array<u8, 2> nunchuk_button_bitmasks{{
Nunchuk::BUTTON_Z,
}};

Nunchuk::Nunchuk(ExtensionReg& reg) : Attachment(_trans("Nunchuk"), reg)
Nunchuk::Nunchuk() : EncryptedExtension(_trans("Nunchuk"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -68,42 +69,11 @@ Nunchuk::Nunchuk(ExtensionReg& reg) : Attachment(_trans("Nunchuk"), reg)
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "X"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Y"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));

m_id = nunchuk_id;

// Build calibration data:
m_calibration = {{
// Accel Zero X,Y,Z:
ACCEL_ZERO_G,
ACCEL_ZERO_G,
ACCEL_ZERO_G,
// Possibly LSBs of zero values:
0x00,
// Accel 1G X,Y,Z:
ACCEL_ONE_G,
ACCEL_ONE_G,
ACCEL_ONE_G,
// Possibly LSBs of 1G values:
0x00,
// Stick X max,min,center:
STICK_CENTER + STICK_RADIUS,
STICK_CENTER - STICK_RADIUS,
STICK_CENTER,
// Stick Y max,min,center:
STICK_CENTER + STICK_RADIUS,
STICK_CENTER - STICK_RADIUS,
STICK_CENTER,
// 2 checksum bytes calculated below:
0x00,
0x00,
}};

UpdateCalibrationDataChecksum(m_calibration);
}

void Nunchuk::GetState(u8* const data)
void Nunchuk::Update()
{
wm_nc nc_data = {};
DataFormat nc_data = {};

// stick
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
@@ -126,7 +96,7 @@ void Nunchuk::GetState(u8* const data)
++nc_data.jy;
}

AccelData accel;
NormalizedAccelData accel;

// tilt
EmulateTilt(&accel, m_tilt);
@@ -150,23 +120,17 @@ void Nunchuk::GetState(u8* const data)
// flip the button bits :/
nc_data.bt.hex ^= 0x03;

// We now use 2 bits more precision, so multiply by 4 before converting to int
s16 accel_x = (s16)(4 * (accel.x * ACCEL_RANGE + ACCEL_ZERO_G));
s16 accel_y = (s16)(4 * (accel.y * ACCEL_RANGE + ACCEL_ZERO_G));
s16 accel_z = (s16)(4 * (accel.z * ACCEL_RANGE + ACCEL_ZERO_G));

accel_x = MathUtil::Clamp<s16>(accel_x, 0, 0x3ff);
accel_y = MathUtil::Clamp<s16>(accel_y, 0, 0x3ff);
accel_z = MathUtil::Clamp<s16>(accel_z, 0, 0x3ff);
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
auto acc = DenormalizeAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);

nc_data.ax = (accel_x >> 2) & 0xFF;
nc_data.ay = (accel_y >> 2) & 0xFF;
nc_data.az = (accel_z >> 2) & 0xFF;
nc_data.bt.acc_x_lsb = accel_x & 0x3;
nc_data.bt.acc_y_lsb = accel_y & 0x3;
nc_data.bt.acc_z_lsb = accel_z & 0x3;
nc_data.ax = (acc.x >> 2) & 0xFF;
nc_data.ay = (acc.y >> 2) & 0xFF;
nc_data.az = (acc.z >> 2) & 0xFF;
nc_data.bt.acc_x_lsb = acc.x & 0x3;
nc_data.bt.acc_y_lsb = acc.y & 0x3;
nc_data.bt.acc_z_lsb = acc.z & 0x3;

std::memcpy(data, &nc_data, sizeof(wm_nc));
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data;
}

bool Nunchuk::IsButtonPressed() const
@@ -176,6 +140,41 @@ bool Nunchuk::IsButtonPressed() const
return buttons != 0;
}

void Nunchuk::Reset()
{
m_reg = {};
m_reg.identifier = nunchuk_id;

// Build calibration data:
m_reg.calibration = {{
// Accel Zero X,Y,Z:
ACCEL_ZERO_G,
ACCEL_ZERO_G,
ACCEL_ZERO_G,
// Possibly LSBs of zero values:
0x00,
// Accel 1G X,Y,Z:
ACCEL_ONE_G,
ACCEL_ONE_G,
ACCEL_ONE_G,
// Possibly LSBs of 1G values:
0x00,
// Stick X max,min,center:
STICK_CENTER + STICK_RADIUS,
STICK_CENTER - STICK_RADIUS,
STICK_CENTER,
// Stick Y max,min,center:
STICK_CENTER + STICK_RADIUS,
STICK_CENTER - STICK_RADIUS,
STICK_CENTER,
// 2 checksum bytes calculated below:
0x00,
0x00,
}};

UpdateCalibrationDataChecksum(m_reg.calibration, CALIBRATION_CHECKSUM_BYTES);
}

ControllerEmu::ControlGroup* Nunchuk::GetGroup(NunchukGroup group)
{
switch (group)
@@ -5,7 +5,9 @@
#pragma once

#include <array>
#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"

#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"

namespace ControllerEmu
{
@@ -18,16 +20,59 @@ class Tilt;

namespace WiimoteEmu
{
enum class NunchukGroup;
struct ExtensionReg;
enum class NunchukGroup
{
Buttons,
Stick,
Tilt,
Swing,
Shake
};

class Nunchuk : public Attachment
class Nunchuk : public EncryptedExtension
{
public:
explicit Nunchuk(ExtensionReg& reg);
union ButtonFormat
{
u8 hex;

struct
{
u8 z : 1;
u8 c : 1;

// LSBs of accelerometer
u8 acc_x_lsb : 2;
u8 acc_y_lsb : 2;
u8 acc_z_lsb : 2;
};
};
static_assert(sizeof(ButtonFormat) == 1, "Wrong size");

void GetState(u8* const data) override;
union DataFormat
{
struct
{
// joystick x, y
u8 jx;
u8 jy;

// accelerometer
u8 ax;
u8 ay;
u8 az;

// buttons + accelerometer LSBs
ButtonFormat bt;
};
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

Nunchuk();

void Update() override;
bool IsButtonPressed() const override;
void Reset() override;

ControllerEmu::ControlGroup* GetGroup(NunchukGroup group);

@@ -41,7 +86,6 @@ class Nunchuk : public Attachment
{
ACCEL_ZERO_G = 0x80,
ACCEL_ONE_G = 0xB3,
ACCEL_RANGE = (ACCEL_ONE_G - ACCEL_ZERO_G),
};

enum
@@ -55,6 +99,7 @@ class Nunchuk : public Attachment

private:
ControllerEmu::Tilt* m_tilt;

ControllerEmu::Force* m_swing;
ControllerEmu::Force* m_swing_slow;
ControllerEmu::Force* m_swing_fast;
@@ -2,12 +2,13 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/Attachment/Turntable.h"
#include "Core/HW/WiimoteEmu/Extension/Turntable.h"

#include <array>
#include <cassert>
#include <cstring>

#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
@@ -44,7 +45,7 @@ constexpr std::array<const char*, 6> turntable_button_names{{
_trans("Blue Right"),
}};

Turntable::Turntable(ExtensionReg& reg) : Attachment(_trans("Turntable"), reg)
Turntable::Turntable() : EncryptedExtension(_trans("Turntable"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -80,14 +81,11 @@ Turntable::Turntable(ExtensionReg& reg) : Attachment(_trans("Turntable"), reg)

// crossfade
groups.emplace_back(m_crossfade = new ControllerEmu::Slider(_trans("Crossfade")));

// set up register
m_id = turntable_id;
}

void Turntable::GetState(u8* const data)
void Turntable::Update()
{
wm_turntable_extension tt_data = {};
DataFormat tt_data = {};

// stick
{
@@ -140,7 +138,7 @@ void Turntable::GetState(u8* const data)
tt_data.bt ^= (BUTTON_L_GREEN | BUTTON_L_RED | BUTTON_L_BLUE | BUTTON_R_GREEN | BUTTON_R_RED |
BUTTON_R_BLUE | BUTTON_MINUS | BUTTON_PLUS | BUTTON_EUPHORIA);

std::memcpy(data, &tt_data, sizeof(wm_turntable_extension));
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tt_data;
}

bool Turntable::IsButtonPressed() const
@@ -150,6 +148,14 @@ bool Turntable::IsButtonPressed() const
return buttons != 0;
}

void Turntable::Reset()
{
m_reg = {};
m_reg.identifier = turntable_id;

// TODO: Is there calibration data?
}

ControllerEmu::ControlGroup* Turntable::GetGroup(TurntableGroup group)
{
switch (group)
@@ -4,7 +4,7 @@

#pragma once

#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"

namespace ControllerEmu
{
@@ -13,19 +13,53 @@ class Buttons;
class ControlGroup;
class Slider;
class Triggers;
}
} // namespace ControllerEmu

namespace WiimoteEmu
{
enum class TurntableGroup;
struct ExtensionReg;
enum class TurntableGroup
{
Buttons,
Stick,
EffectDial,
LeftTable,
RightTable,
Crossfade
};

class Turntable : public Attachment
// TODO: Does the turntable ever use encryption?
class Turntable : public EncryptedExtension
{
public:
explicit Turntable(ExtensionReg& reg);
void GetState(u8* const data) override;
struct DataFormat
{
u8 sx : 6;
u8 rtable3 : 2;

u8 sy : 6;
u8 rtable2 : 2;

u8 rtable4 : 1;
u8 slider : 4;
u8 dial2 : 2;
u8 rtable1 : 1;

u8 ltable1 : 5;
u8 dial1 : 3;

union
{
u16 ltable2 : 1;
u16 bt; // buttons
};
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

Turntable();

void Update() override;
bool IsButtonPressed() const override;
void Reset() override;

ControllerEmu::ControlGroup* GetGroup(TurntableGroup group);

@@ -59,4 +93,4 @@ class Turntable : public Attachment
ControllerEmu::Slider* m_right_table;
ControllerEmu::Slider* m_crossfade;
};
}
} // namespace WiimoteEmu
@@ -0,0 +1,29 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/ExtensionPort.h"

#include "Common/ChunkFile.h"

namespace WiimoteEmu
{
ExtensionPort::ExtensionPort(I2CBus* i2c_bus) : m_i2c_bus(*i2c_bus)
{
}

bool ExtensionPort::IsDeviceConnected() const
{
return m_extension->ReadDeviceDetectPin();
}

void ExtensionPort::AttachExtension(Extension* ext)
{
m_i2c_bus.RemoveSlave(m_extension);

m_extension = ext;
m_i2c_bus.AddSlave(m_extension);
;
}

} // namespace WiimoteEmu
@@ -0,0 +1,43 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include "Common/ChunkFile.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"

namespace WiimoteEmu
{
enum ExtensionNumber : u8
{
NONE,

NUNCHUK,
CLASSIC,
GUITAR,
DRUMS,
TURNTABLE,
};

// FYI: An extension must be attached.
// Attach "None" for no extension.
class ExtensionPort
{
public:
// The real wiimote reads extension data from i2c slave 0x52 addres 0x00:
static constexpr u8 REPORT_I2C_SLAVE = 0x52;
static constexpr u8 REPORT_I2C_ADDR = 0x00;

explicit ExtensionPort(I2CBus* i2c_bus);

bool IsDeviceConnected() const;
void AttachExtension(Extension* dev);

private:
Extension* m_extension = nullptr;
I2CBus& m_i2c_bus;
};

} // namespace WiimoteEmu
@@ -0,0 +1,56 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/I2CBus.h"

#include <algorithm>

namespace WiimoteEmu
{
void I2CBus::AddSlave(I2CSlave* slave)
{
m_slaves.emplace_back(slave);
}

void I2CBus::RemoveSlave(I2CSlave* slave)
{
m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end());
}

void I2CBus::Reset()
{
m_slaves.clear();
}

int I2CBus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
// INFO_LOG(WIIMOTE, "i2c bus read: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count);
for (auto& slave : m_slaves)
{
auto const bytes_read = slave->BusRead(slave_addr, addr, count, data_out);

// A slave responded, we are done.
if (bytes_read)
return bytes_read;
}

return 0;
}

int I2CBus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
// INFO_LOG(WIIMOTE, "i2c bus write: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count);
for (auto& slave : m_slaves)
{
auto const bytes_written = slave->BusWrite(slave_addr, addr, count, data_in);

// A slave responded, we are done.
if (bytes_written)
return bytes_written;
}

return 0;
}

} // namespace WiimoteEmu
@@ -0,0 +1,76 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <algorithm>
#include <vector>

#include "Common/CommonTypes.h"

namespace WiimoteEmu
{
class I2CBus;

class I2CSlave
{
friend I2CBus;

protected:
virtual ~I2CSlave() = default;

virtual int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) = 0;
virtual int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) = 0;

template <typename T>
static int RawRead(T* reg_data, u8 addr, int count, u8* data_out)
{
static_assert(std::is_pod<T>::value);
static_assert(0x100 == sizeof(T));

// TODO: addr wraps around after 0xff

u8* src = reinterpret_cast<u8*>(reg_data) + addr;
count = std::min(count, int(reinterpret_cast<u8*>(reg_data + 1) - src));

std::copy_n(src, count, data_out);

return count;
}

template <typename T>
static int RawWrite(T* reg_data, u8 addr, int count, const u8* data_in)
{
static_assert(std::is_pod<T>::value);
static_assert(0x100 == sizeof(T));

// TODO: addr wraps around after 0xff

u8* dst = reinterpret_cast<u8*>(reg_data) + addr;
count = std::min(count, int(reinterpret_cast<u8*>(reg_data + 1) - dst));

std::copy_n(data_in, count, dst);

return count;
}
};

class I2CBus
{
public:
void AddSlave(I2CSlave* slave);
void RemoveSlave(I2CSlave* slave);

void Reset();

// TODO: change int to u16 or something
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out);
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in);

private:
// Pointers are unowned:
std::vector<I2CSlave*> m_slaves;
};

} // namespace WiimoteEmu
@@ -6,6 +6,8 @@

#include <cmath>

// TODO: kill this whole file, we have Matrix functions in MathUtil.h

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
@@ -0,0 +1,381 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Core/HW/WiimoteEmu/MotionPlus.h"

#include "Common/BitUtils.h"
#include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"

namespace WiimoteEmu
{
MotionPlus::MotionPlus() : Extension("MotionPlus")
{
}

void MotionPlus::Reset()
{
reg_data = {};

constexpr std::array<u8, 6> initial_id = {0x00, 0x00, 0xA6, 0x20, 0x00, 0x05};

// FYI: This ID changes on activation
std::copy(std::begin(initial_id), std::end(initial_id), reg_data.ext_identifier);

// TODO: determine meaning of calibration data:
constexpr std::array<u8, 32> cdata = {
0x78, 0xd9, 0x78, 0x38, 0x77, 0x9d, 0x2f, 0x0c, 0xcf, 0xf0, 0x31,
0xad, 0xc8, 0x0b, 0x5e, 0x39, 0x6f, 0x81, 0x7b, 0x89, 0x78, 0x51,
0x33, 0x60, 0xc9, 0xf5, 0x37, 0xc1, 0x2d, 0xe9, 0x15, 0x8d,
};

std::copy(std::begin(cdata), std::end(cdata), reg_data.calibration_data);

// TODO: determine the meaning behind this:
constexpr std::array<u8, 64> cert = {
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,
};

std::copy(std::begin(cert), std::end(cert), reg_data.cert_data);
}

void MotionPlus::DoState(PointerWrap& p)
{
p.Do(reg_data);
}

bool MotionPlus::IsActive() const
{
return (ACTIVE_DEVICE_ADDR << 1) == reg_data.ext_identifier[2];
}

MotionPlus::PassthroughMode MotionPlus::GetPassthroughMode() const
{
return static_cast<PassthroughMode>(reg_data.ext_identifier[4]);
}

ExtensionPort& MotionPlus::GetExtPort()
{
return m_extension_port;
}

int MotionPlus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
if (IsActive())
{
// FYI: Motion plus does not respond to 0x53 when activated

if (ACTIVE_DEVICE_ADDR == slave_addr)
return RawRead(&reg_data, addr, count, data_out);
else
return 0;
}
else
{
if (INACTIVE_DEVICE_ADDR == slave_addr)
{
return RawRead(&reg_data, addr, count, data_out);
}
else
{
// Passthrough to the connected extension (if any)
return i2c_bus.BusRead(slave_addr, addr, count, data_out);
}
}
}

int MotionPlus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
if (IsActive())
{
// Motion plus does not respond to 0x53 when activated
if (ACTIVE_DEVICE_ADDR == slave_addr)
{
auto const result = RawWrite(&reg_data, addr, count, data_in);

// It seems a write of any value triggers deactivation.
// TODO: kill magic number
if (0xf0 == addr)
{
// Deactivate motion plus:
reg_data.ext_identifier[2] = INACTIVE_DEVICE_ADDR << 1;
reg_data.cert_ready = 0x0;

// Pass through the activation write to the attached extension:
// The M+ deactivation signal is cleverly the same as EXT activation:
i2c_bus.BusWrite(slave_addr, addr, count, data_in);
}
// TODO: kill magic number
else if (0xf1 == addr)
{
INFO_LOG(WIIMOTE, "M+ cert activation: 0x%x", reg_data.cert_enable);
// 0x14,0x18 is also a valid value
// 0x1a is final value
reg_data.cert_ready = 0x18;
}
// TODO: kill magic number
else if (0xf2 == addr)
{
INFO_LOG(WIIMOTE, "M+ calibration ?? : 0x%x", reg_data.unknown_0xf2[0]);
}

return result;
}
else
{
// No i2c passthrough when activated.
return 0;
}
}
else
{
if (INACTIVE_DEVICE_ADDR == slave_addr)
{
auto const result = RawWrite(&reg_data, addr, count, data_in);

// It seems a write of any value triggers activation.
if (0xfe == addr)
{
INFO_LOG(WIIMOTE, "M+ has been activated: %d", data_in[0]);

// Activate motion plus:
reg_data.ext_identifier[2] = ACTIVE_DEVICE_ADDR << 1;
// TODO: kill magic number
// reg_data.cert_ready = 0x2;

// A real M+ is unresponsive on the bus for some time during activation
// Reads fail to ack and ext data gets filled with 0xff for a frame or two
// I don't think we need to emulate that.

// TODO: activate extension and disable encrption
// also do this if an extension is attached after activation.
std::array<u8, 1> data = {0x55};
i2c_bus.BusWrite(ACTIVE_DEVICE_ADDR, 0xf0, (int)data.size(), data.data());
}

return result;
}
else
{
// Passthrough to the connected extension (if any)
return i2c_bus.BusWrite(slave_addr, addr, count, data_in);
}
}
}

bool MotionPlus::ReadDeviceDetectPin() const
{
if (IsActive())
{
return true;
}
else
{
// When inactive the device detect pin reads from the ext port:
return m_extension_port.IsDeviceConnected();
}
}

bool MotionPlus::IsButtonPressed() const
{
return false;
}

void MotionPlus::Update()
{
if (!IsActive())
{
return;
}

auto& data = reg_data.controller_data;

if (0x0 == reg_data.cert_ready)
{
// Without sending this nonsense, inputs are unresponsive.. even regular buttons
// Device still operates when changing the data slightly so its not any sort of encrpytion
// It even works when removing the is_mp_data bit in the last byte
// My M+ non-inside gives: 61,46,45,aa,0,2 or b6,46,45,9a,0,2
// static const u8 init_data[6] = {0x8e, 0xb0, 0x4f, 0x5a, 0xfc | 0x01, 0x02};
constexpr std::array<u8, 6> init_data = {0x81, 0x46, 0x46, 0xb6, 0x01, 0x02};
std::copy(std::begin(init_data), std::end(init_data), data);
reg_data.cert_ready = 0x2;
return;
}

if (0x2 == reg_data.cert_ready)
{
constexpr std::array<u8, 6> init_data = {0x7f, 0xcf, 0xdf, 0x8b, 0x4f, 0x82};
std::copy(std::begin(init_data), std::end(init_data), data);
reg_data.cert_ready = 0x8;
return;
}

if (0x8 == reg_data.cert_ready)
{
// A real wiimote takes about 2 seconds to reach this state:
reg_data.cert_ready = 0xe;
}

if (0x18 == reg_data.cert_ready)
{
// TODO: determine the meaning of this
constexpr std::array<u8, 64> mp_cert2 = {
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,
};

std::copy(std::begin(mp_cert2), std::end(mp_cert2), reg_data.cert_data);

if (0x01 != reg_data.cert_enable)
{
PanicAlert("M+ Failure! Game requested cert2 with value other than 0x01. M+ will disconnect "
"shortly unfortunately. Reconnect wiimote and hope for the best.");
}

// A real wiimote takes about 2 seconds to reach this state:
reg_data.cert_ready = 0x1a;
INFO_LOG(WIIMOTE, "M+ cert 2 ready!");
}

// TODO: make sure a motion plus report is sent first after init

// On real mplus:
// For some reason the first read seems to have garbage data
// is_mp_data and extension_connected are set, but the data is junk
// it does seem to have some sort of pattern though, byte 5 is always 2
// something like: d5, b0, 4e, 6e, fc, 2
// When a passthrough mode is set:
// the second read is valid mplus data, which then triggers a read from the extension
// the third read is finally extension data
// If an extension is not attached the data is always mplus data
// even when passthrough is enabled

// Real M+ seems to only ever read 6 bytes from the extension.
// Data after 6 bytes seems to be zero-filled.
// After reading, the real M+ uses that data for the next frame.
// But we are going to use it for the current frame instead.
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;

// Try to alternate between M+ and EXT data:
DataFormat mplus_data = Common::BitCastPtr<DataFormat>(data);
mplus_data.is_mp_data ^= true;

// hax!!!
// static const u8 hacky_mp_data[6] = {0x1d, 0x91, 0x49, 0x87, 0x73, 0x7a};
// static const u8 hacky_nc_data[6] = {0x79, 0x7f, 0x4b, 0x83, 0x8b, 0xec};
// auto& hacky_ptr = mplus_data.is_mp_data ? hacky_mp_data : hacky_nc_data;
// std::copy(std::begin(hacky_ptr), std::end(hacky_ptr), data);
// return;

// If the last frame had M+ data try to send some non-M+ data:
if (!mplus_data.is_mp_data)
{
switch (GetPassthroughMode())
{
case PassthroughMode::Disabled:
{
// Passthrough disabled, always send M+ data:
mplus_data.is_mp_data = true;
break;
}
case PassthroughMode::Nunchuk:
{
if (EXT_AMT == i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
{
// Passthrough data modifications via wiibrew.org
// Data passing through drops the least significant bit of the three accelerometer values
// Bit 7 of byte 5 is moved to bit 6 of byte 5, overwriting it
Common::SetBit(data[5], 6, Common::ExtractBit(data[5], 7));
// Bit 0 of byte 4 is moved to bit 7 of byte 5
Common::SetBit(data[5], 7, Common::ExtractBit(data[4], 0));
// Bit 3 of byte 5 is moved to bit 4 of byte 5, overwriting it
Common::SetBit(data[5], 4, Common::ExtractBit(data[5], 3));
// Bit 1 of byte 5 is moved to bit 3 of byte 5
Common::SetBit(data[5], 3, Common::ExtractBit(data[5], 1));
// Bit 0 of byte 5 is moved to bit 2 of byte 5, overwriting it
Common::SetBit(data[5], 2, Common::ExtractBit(data[5], 0));

// Bit 0 and 1 of byte 5 contain a M+ flag and a zero bit which is set below.
mplus_data.is_mp_data = false;
}
else
{
// Read failed (extension unplugged), Send M+ data instead
mplus_data.is_mp_data = true;
}
break;
}
case PassthroughMode::Classic:
{
if (EXT_AMT == i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
{
// Passthrough data modifications via wiibrew.org
// Data passing through drops the least significant bit of the axes of the left (or only)
// joystick Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] Bits 0 and 1
// of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before
Common::SetBit(data[0], 0, Common::ExtractBit(data[5], 0));
Common::SetBit(data[1], 0, Common::ExtractBit(data[5], 1));

// Bit 0 and 1 of byte 5 contain a M+ flag and a zero bit which is set below.
mplus_data.is_mp_data = false;
}
else
{
// Read failed (extension unplugged), Send M+ data instead
mplus_data.is_mp_data = true;
}
break;
}
default:
PanicAlert("MotionPlus unknown passthrough-mode %d", (int)GetPassthroughMode());
break;
}
}

// If the above logic determined this should be M+ data, update it here
if (mplus_data.is_mp_data)
{
// Wiibrew: "While the Wiimote is still, the values will be about 0x1F7F (8,063)"
// high-velocity range should be about +/- 1500 or 1600 dps
// low-velocity range should be about +/- 400 dps
// Wiibrew implies it shoould be +/- 595 and 2700

u16 yaw_value = 0x2000;
u16 roll_value = 0x2000;
u16 pitch_value = 0x2000;

mplus_data.yaw_slow = 1;
mplus_data.roll_slow = 1;
mplus_data.pitch_slow = 1;

// Bits 0-7
mplus_data.yaw1 = yaw_value & 0xff;
mplus_data.roll1 = roll_value & 0xff;
mplus_data.pitch1 = pitch_value & 0xff;

// Bits 8-13
mplus_data.yaw2 = yaw_value >> 8;
mplus_data.roll2 = roll_value >> 8;
mplus_data.pitch2 = pitch_value >> 8;
}

mplus_data.extension_connected = m_extension_port.IsDeviceConnected();
mplus_data.zero = 0;

Common::BitCastPtr<DataFormat>(data) = mplus_data;
}

} // namespace WiimoteEmu