| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(®_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(®_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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(®_data, addr, count, data_out); | ||
| else | ||
| return 0; | ||
| } | ||
| else | ||
| { | ||
| if (INACTIVE_DEVICE_ADDR == slave_addr) | ||
| { | ||
| return RawRead(®_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(®_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(®_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 |