WiimoteEmu: Major renaming and cleanup.

This commit is contained in:
Jordan Woyak 2019-01-01 08:32:39 -06:00
parent b1f350ab1c
commit 0d1fbe7bbc
64 changed files with 4140 additions and 3185 deletions

View File

@ -199,4 +199,14 @@ inline To BitCast(const From& source) noexcept
std::memcpy(&storage, &source, sizeof(storage));
return reinterpret_cast<To&>(storage);
}
template <typename T>
void SetBit(T& value, size_t bit_number, bool bit_value)
{
if (bit_value)
value |= (1 << bit_number);
else
value &= ~(1 << bit_number);
}
} // namespace Common

View File

@ -133,16 +133,22 @@ add_library(core
HW/VideoInterface.cpp
HW/WII_IPC.cpp
HW/Wiimote.cpp
HW/WiimoteCommon/DataReport.cpp
HW/WiimoteEmu/WiimoteEmu.cpp
HW/WiimoteEmu/Attachment/Classic.cpp
HW/WiimoteEmu/Attachment/Attachment.cpp
HW/WiimoteEmu/Attachment/Nunchuk.cpp
HW/WiimoteEmu/Attachment/Drums.cpp
HW/WiimoteEmu/Attachment/Guitar.cpp
HW/WiimoteEmu/Attachment/Turntable.cpp
HW/WiimoteEmu/Camera.cpp
HW/WiimoteEmu/Dynamics.cpp
HW/WiimoteEmu/EmuSubroutines.cpp
HW/WiimoteEmu/Encryption.cpp
HW/WiimoteEmu/ExtensionPort.cpp
HW/WiimoteEmu/I2CBus.cpp
HW/WiimoteEmu/MotionPlus.cpp
HW/WiimoteEmu/Speaker.cpp
HW/WiimoteEmu/Extension/Classic.cpp
HW/WiimoteEmu/Extension/Extension.cpp
HW/WiimoteEmu/Extension/Nunchuk.cpp
HW/WiimoteEmu/Extension/Drums.cpp
HW/WiimoteEmu/Extension/Guitar.cpp
HW/WiimoteEmu/Extension/Turntable.cpp
HW/WiimoteReal/WiimoteReal.cpp
HW/WiiSave.cpp
IOS/Device.cpp

View File

@ -171,14 +171,20 @@
<ClCompile Include="HW\SystemTimers.cpp" />
<ClCompile Include="HW\VideoInterface.cpp" />
<ClCompile Include="HW\Wiimote.cpp" />
<ClCompile Include="HW\WiimoteEmu\Attachment\Attachment.cpp" />
<ClCompile Include="HW\WiimoteEmu\Attachment\Classic.cpp" />
<ClCompile Include="HW\WiimoteEmu\Attachment\Drums.cpp" />
<ClCompile Include="HW\WiimoteEmu\Attachment\Guitar.cpp" />
<ClCompile Include="HW\WiimoteEmu\Attachment\Nunchuk.cpp" />
<ClCompile Include="HW\WiimoteEmu\Attachment\Turntable.cpp" />
<ClCompile Include="HW\WiimoteCommon\DataReport.cpp" />
<ClCompile Include="HW\WiimoteEmu\Camera.cpp" />
<ClCompile Include="HW\WiimoteEmu\Dynamics.cpp" />
<ClCompile Include="HW\WiimoteEmu\EmuSubroutines.cpp" />
<ClCompile Include="HW\WiimoteEmu\Encryption.cpp" />
<ClCompile Include="HW\WiimoteEmu\ExtensionPort.cpp" />
<ClCompile Include="HW\WiimoteEmu\Extension\Classic.cpp" />
<ClCompile Include="HW\WiimoteEmu\Extension\Drums.cpp" />
<ClCompile Include="HW\WiimoteEmu\Extension\Extension.cpp" />
<ClCompile Include="HW\WiimoteEmu\Extension\Guitar.cpp" />
<ClCompile Include="HW\WiimoteEmu\Extension\Nunchuk.cpp" />
<ClCompile Include="HW\WiimoteEmu\Extension\Turntable.cpp" />
<ClCompile Include="HW\WiimoteEmu\I2CBus.cpp" />
<ClCompile Include="HW\WiimoteEmu\MotionPlus.cpp" />
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp" />
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp" />
<ClCompile Include="HW\WiimoteReal\IOWin.cpp" />
@ -430,17 +436,24 @@
<ClInclude Include="HW\SystemTimers.h" />
<ClInclude Include="HW\VideoInterface.h" />
<ClInclude Include="HW\Wiimote.h" />
<ClInclude Include="HW\WiimoteCommon\DataReport.h" />
<ClInclude Include="HW\WiimoteCommon\WiimoteConstants.h" />
<ClInclude Include="HW\WiimoteCommon\WiimoteHid.h" />
<ClInclude Include="HW\WiimoteCommon\WiimoteReport.h" />
<ClInclude Include="HW\WiimoteEmu\Attachment\Attachment.h" />
<ClInclude Include="HW\WiimoteEmu\Attachment\Classic.h" />
<ClInclude Include="HW\WiimoteEmu\Attachment\Drums.h" />
<ClInclude Include="HW\WiimoteEmu\Attachment\Guitar.h" />
<ClInclude Include="HW\WiimoteEmu\Attachment\Nunchuk.h" />
<ClInclude Include="HW\WiimoteEmu\Attachment\Turntable.h" />
<ClInclude Include="HW\WiimoteEmu\Camera.h" />
<ClInclude Include="HW\WiimoteEmu\Dynamics.h" />
<ClInclude Include="HW\WiimoteEmu\Encryption.h" />
<ClInclude Include="HW\WiimoteEmu\ExtensionPort.h" />
<ClInclude Include="HW\WiimoteEmu\Extension\Classic.h" />
<ClInclude Include="HW\WiimoteEmu\Extension\Drums.h" />
<ClInclude Include="HW\WiimoteEmu\Extension\Extension.h" />
<ClInclude Include="HW\WiimoteEmu\Extension\Guitar.h" />
<ClInclude Include="HW\WiimoteEmu\Extension\Nunchuk.h" />
<ClInclude Include="HW\WiimoteEmu\Extension\Turntable.h" />
<ClInclude Include="HW\WiimoteEmu\I2CBus.h" />
<ClInclude Include="HW\WiimoteEmu\MatrixMath.h" />
<ClInclude Include="HW\WiimoteEmu\MotionPlus.h" />
<ClInclude Include="HW\WiimoteEmu\Speaker.h" />
<ClInclude Include="HW\WiimoteEmu\WiimoteEmu.h" />
<ClInclude Include="HW\WiimoteReal\WiimoteReal.h" />
<ClInclude Include="HW\WiimoteReal\WiimoteRealBase.h" />

View File

@ -79,9 +79,6 @@
<Filter Include="HW %28Flipper/Hollywood%29\Wiimote\Real">
<UniqueIdentifier>{bc3e845a-3d01-4713-aa32-f27110838d0c}</UniqueIdentifier>
</Filter>
<Filter Include="HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment">
<UniqueIdentifier>{cdbd65da-541f-47d2-8fdc-e99e73e98e69}</UniqueIdentifier>
</Filter>
<Filter Include="HW %28Flipper/Hollywood%29\EXI - Expansion Interface">
<UniqueIdentifier>{d19f1218-0e28-4f24-a4b3-33fac750a899}</UniqueIdentifier>
</Filter>
@ -163,9 +160,12 @@
<Filter Include="PowerPC\SignatureDB">
<UniqueIdentifier>{f0b52c84-49f4-470a-b037-edeea5634b9e}</UniqueIdentifier>
</Filter>
<Filter Include="HW %28Flipper/Hollywood%29\WiimoteCommon">
<Filter Include="HW %28Flipper/Hollywood%29\Wiimote\Common">
<UniqueIdentifier>{ee6645da-3ad9-4fe7-809f-e4646d0b0ca5}</UniqueIdentifier>
</Filter>
<Filter Include="HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension">
<UniqueIdentifier>{68c09d7e-4f5a-435d-a0d2-7eb7a74d7054}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BootManager.cpp" />
@ -516,33 +516,12 @@
<ClCompile Include="HW\VideoInterface.cpp">
<Filter>HW %28Flipper/Hollywood%29\VI - Video Interface</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Attachment\Attachment.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Attachment\Classic.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Attachment\Drums.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Attachment\Guitar.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Attachment\Nunchuk.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Attachment\Turntable.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\EmuSubroutines.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Encryption.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\WiimoteEmu.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
@ -901,6 +880,45 @@
<ClCompile Include="PowerPC\Jit64\RegCache\FPURegCache.cpp" />
<ClCompile Include="PowerPC\Jit64\RegCache\GPRRegCache.cpp" />
<ClCompile Include="PowerPC\Jit64\RegCache\JitRegCache.cpp" />
<ClCompile Include="HW\WiimoteEmu\I2CBus.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\MotionPlus.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Camera.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Dynamics.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\Classic.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\Drums.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\Guitar.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\Nunchuk.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\Turntable.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\Extension.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\ExtensionPort.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteCommon\DataReport.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Common</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="BootManager.h" />
@ -1200,24 +1218,6 @@
<ClInclude Include="HW\VideoInterface.h">
<Filter>HW %28Flipper/Hollywood%29\VI - Video Interface</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Attachment\Attachment.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Attachment\Classic.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Attachment\Drums.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Attachment\Guitar.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Attachment\Nunchuk.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Attachment\Turntable.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Attachment</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Encryption.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
@ -1584,13 +1584,13 @@
<Filter>IOS\Network\NCD</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteCommon\WiimoteConstants.h">
<Filter>HW %28Flipper/Hollywood%29\WiimoteCommon</Filter>
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Common</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteCommon\WiimoteHid.h">
<Filter>HW %28Flipper/Hollywood%29\WiimoteCommon</Filter>
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Common</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteCommon\WiimoteReport.h">
<Filter>HW %28Flipper/Hollywood%29\WiimoteCommon</Filter>
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Common</Filter>
</ClInclude>
<ClInclude Include="Config\UISettings.h">
<Filter>Config</Filter>
@ -1598,6 +1598,45 @@
<ClInclude Include="Config\WiimoteInputSettings.h">
<Filter>Config</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\I2CBus.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\MotionPlus.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Camera.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Speaker.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Dynamics.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\Classic.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\Drums.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\Guitar.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\Nunchuk.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\Turntable.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\Extension.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\ExtensionPort.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteCommon\DataReport.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Common</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />

View File

@ -0,0 +1,425 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <cassert>
#include "Core/HW/WiimoteCommon/DataReport.h"
namespace WiimoteCommon
{
bool DataReportManipulator::HasIR() const
{
return 0 != GetIRDataSize();
}
bool DataReportManipulator::HasExt() const
{
return 0 != GetExtDataSize();
}
u8* DataReportManipulator::GetDataPtr()
{
return data_ptr;
}
const u8* DataReportManipulator::GetDataPtr() const
{
return data_ptr;
}
struct IncludeCore : virtual DataReportManipulator
{
bool HasCore() const override { return true; }
void GetCoreData(CoreData* result) const override
{
*result = *reinterpret_cast<const CoreData*>(data_ptr);
// Remove accel LSBs.
result->hex &= CoreData::BUTTON_MASK;
}
void SetCoreData(const CoreData& new_core) override
{
auto& core = *reinterpret_cast<CoreData*>(data_ptr);
// Don't overwrite accel LSBs.
core.hex &= ~CoreData::BUTTON_MASK;
core.hex |= new_core.hex & CoreData::BUTTON_MASK;
}
};
struct NoCore : virtual DataReportManipulator
{
bool HasCore() const override { return false; }
void GetCoreData(CoreData*) const override {}
void SetCoreData(const CoreData&) override {}
};
struct NoAccel : virtual DataReportManipulator
{
bool HasAccel() const override { return false; }
void GetAccelData(AccelData* accel) const override {}
void SetAccelData(const AccelData& accel) override {}
};
// Handles typical non-interleaved accel data:
struct IncludeAccel : virtual DataReportManipulator
{
void GetAccelData(AccelData* result) const override
{
const auto& accel = *reinterpret_cast<AccelMSB*>(data_ptr + 2);
result->x = accel.x << 2;
result->y = accel.y << 2;
result->z = accel.z << 2;
// LSBs
const auto& core = *reinterpret_cast<CoreData*>(data_ptr);
result->x |= core.acc_bits & 0b11;
result->y |= (core.acc_bits2 & 0b1) << 1;
result->z |= core.acc_bits2 & 0b10;
}
void SetAccelData(const AccelData& new_accel) override
{
auto& accel = *reinterpret_cast<AccelMSB*>(data_ptr + 2);
accel.x = new_accel.x >> 2;
accel.y = new_accel.y >> 2;
accel.z = new_accel.z >> 2;
// LSBs
auto& core = *reinterpret_cast<CoreData*>(data_ptr);
core.acc_bits = (new_accel.x >> 0) & 0b11;
core.acc_bits2 = (new_accel.y >> 1) & 0x1;
core.acc_bits2 |= (new_accel.z & 0xb10);
}
bool HasAccel() const override { return true; }
private:
struct AccelMSB
{
u8 x, y, z;
};
static_assert(sizeof(AccelMSB) == 3, "Wrong size");
};
template <u32 Offset, u32 Length>
struct IncludeExt : virtual DataReportManipulator
{
u32 GetExtDataSize() const override { return Length; }
const u8* GetExtDataPtr() const override { return data_ptr + Offset; }
u8* GetExtDataPtr() override { return data_ptr + Offset; }
// Any report that has Extension data has it last.
u32 GetDataSize() const override { return Offset + Length; }
};
struct NoExt : virtual DataReportManipulator
{
u32 GetExtDataSize() const override { return 0; }
const u8* GetExtDataPtr() const override { return nullptr; }
u8* GetExtDataPtr() override { return nullptr; }
};
template <u32 Offset, u32 Length, u32 DataOffset = 0>
struct IncludeIR : virtual DataReportManipulator
{
u32 GetIRDataSize() const override { return Length; }
const u8* GetIRDataPtr() const override { return data_ptr + Offset; }
u8* GetIRDataPtr() override { return data_ptr + Offset; }
u32 GetIRDataFormatOffset() const override { return DataOffset; }
};
struct NoIR : virtual DataReportManipulator
{
u32 GetIRDataSize() const override { return 0; }
const u8* GetIRDataPtr() const override { return nullptr; }
u8* GetIRDataPtr() override { return nullptr; }
u32 GetIRDataFormatOffset() const override { return 0; }
};
#ifdef _MSC_VER
#pragma warning(push)
// Disable warning for inheritance via dominance
#pragma warning(disable : 4250)
#endif
struct ReportCore : IncludeCore, NoAccel, NoIR, NoExt
{
u32 GetDataSize() const override { return 2; }
};
struct ReportCoreAccel : IncludeCore, IncludeAccel, NoIR, NoExt
{
u32 GetDataSize() const override { return 5; }
};
struct ReportCoreExt8 : IncludeCore, NoAccel, NoIR, IncludeExt<5, 8>
{
};
struct ReportCoreAccelIR12 : IncludeCore, IncludeAccel, IncludeIR<5, 12>, NoExt
{
u32 GetDataSize() const override { return 17; }
};
struct ReportCoreExt19 : IncludeCore, NoAccel, NoIR, IncludeExt<2, 19>
{
};
struct ReportCoreAccelExt16 : IncludeCore, IncludeAccel, NoIR, IncludeExt<5, 16>
{
};
struct ReportCoreIR10Ext9 : IncludeCore, NoAccel, IncludeIR<2, 10>, IncludeExt<12, 9>
{
};
struct ReportCoreAccelIR10Ext6 : IncludeCore, IncludeAccel, IncludeIR<5, 10>, IncludeExt<15, 6>
{
};
struct ReportExt21 : NoCore, NoAccel, NoIR, IncludeExt<0, 21>
{
};
struct ReportInterleave1 : IncludeCore, IncludeIR<3, 18, 0>, NoExt
{
// FYI: Only 8-bits of precision in this report, and no Y axis.
// Only contains 4 MSB of Z axis.
void GetAccelData(AccelData* accel) const override
{
accel->x = data_ptr[2] << 2;
// Retain lower 6LSBs.
accel->z &= 0b111111;
const auto& core = *reinterpret_cast<CoreData*>(data_ptr);
accel->z |= (core.acc_bits << 6) | (core.acc_bits2 << 8);
}
void SetAccelData(const AccelData& accel) override
{
data_ptr[2] = accel.x >> 2;
auto& core = *reinterpret_cast<CoreData*>(data_ptr);
core.acc_bits = (accel.z >> 6) & 0b11;
core.acc_bits2 = (accel.z >> 8) & 0b11;
}
bool HasAccel() const override { return true; }
u32 GetDataSize() const override { return 21; }
};
struct ReportInterleave2 : IncludeCore, IncludeIR<3, 18, 18>, NoExt
{
// FYI: Only 8-bits of precision in this report, and no X axis.
// Only contains 4 LSB of Z axis.
void GetAccelData(AccelData* accel) const override
{
accel->y = data_ptr[2] << 2;
// Retain upper 4MSBs.
accel->z &= ~0b111111;
const auto& core = *reinterpret_cast<CoreData*>(data_ptr);
accel->z |= (core.acc_bits << 2) | (core.acc_bits2 << 4);
}
void SetAccelData(const AccelData& accel) override
{
data_ptr[2] = accel.y >> 2;
auto& core = *reinterpret_cast<CoreData*>(data_ptr);
core.acc_bits = (accel.z >> 2) & 0b11;
core.acc_bits2 = (accel.z >> 4) & 0b11;
}
bool HasAccel() const override { return true; }
u32 GetDataSize() const override { return 21; }
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
std::unique_ptr<DataReportManipulator> MakeDataReportManipulator(InputReportID rpt_id, u8* data_ptr)
{
std::unique_ptr<DataReportManipulator> ptr;
switch (rpt_id)
{
case InputReportID::REPORT_CORE:
// 0x30: Core Buttons
ptr = std::make_unique<ReportCore>();
break;
case InputReportID::REPORT_CORE_ACCEL:
// 0x31: Core Buttons and Accelerometer
ptr = std::make_unique<ReportCoreAccel>();
break;
case InputReportID::REPORT_CORE_EXT8:
// 0x32: Core Buttons with 8 Extension bytes
ptr = std::make_unique<ReportCoreExt8>();
break;
case InputReportID::REPORT_CORE_ACCEL_IR12:
// 0x33: Core Buttons and Accelerometer with 12 IR bytes
ptr = std::make_unique<ReportCoreAccelIR12>();
break;
case InputReportID::REPORT_CORE_EXT19:
// 0x34: Core Buttons with 19 Extension bytes
ptr = std::make_unique<ReportCoreExt19>();
break;
case InputReportID::REPORT_CORE_ACCEL_EXT16:
// 0x35: Core Buttons and Accelerometer with 16 Extension Bytes
ptr = std::make_unique<ReportCoreAccelExt16>();
break;
case InputReportID::REPORT_CORE_IR10_EXT9:
// 0x36: Core Buttons with 10 IR bytes and 9 Extension Bytes
ptr = std::make_unique<ReportCoreIR10Ext9>();
break;
case InputReportID::REPORT_CORE_ACCEL_IR10_EXT6:
// 0x37: Core Buttons and Accelerometer with 10 IR bytes and 6 Extension Bytes
ptr = std::make_unique<ReportCoreAccelIR10Ext6>();
break;
case InputReportID::REPORT_EXT21:
// 0x3d: 21 Extension Bytes
ptr = std::make_unique<ReportExt21>();
break;
case InputReportID::REPORT_INTERLEAVE1:
// 0x3e - 0x3f: Interleaved Core Buttons and Accelerometer with 36 IR bytes
ptr = std::make_unique<ReportInterleave1>();
break;
case InputReportID::REPORT_INTERLEAVE2:
ptr = std::make_unique<ReportInterleave2>();
break;
default:
assert(false);
break;
}
ptr->data_ptr = data_ptr;
return ptr;
}
DataReportBuilder::DataReportBuilder(InputReportID rpt_id) : m_data(rpt_id)
{
SetMode(rpt_id);
}
void DataReportBuilder::SetMode(InputReportID rpt_id)
{
m_data.report_id = rpt_id;
m_manip = MakeDataReportManipulator(rpt_id, GetDataPtr() + HEADER_SIZE);
}
InputReportID DataReportBuilder::GetMode() const
{
return m_data.report_id;
}
bool DataReportBuilder::IsValidMode(InputReportID mode)
{
return (mode >= InputReportID::REPORT_CORE &&
mode <= InputReportID::REPORT_CORE_ACCEL_IR10_EXT6) ||
(mode >= InputReportID::REPORT_EXT21 && InputReportID::REPORT_INTERLEAVE2 <= mode);
}
bool DataReportBuilder::HasCore() const
{
return m_manip->HasCore();
}
bool DataReportBuilder::HasAccel() const
{
return m_manip->HasAccel();
}
bool DataReportBuilder::HasIR() const
{
return m_manip->HasIR();
}
bool DataReportBuilder::HasExt() const
{
return m_manip->HasExt();
}
u32 DataReportBuilder::GetIRDataSize() const
{
return m_manip->GetIRDataSize();
}
u32 DataReportBuilder::GetExtDataSize() const
{
return m_manip->GetExtDataSize();
}
u32 DataReportBuilder::GetIRDataFormatOffset() const
{
return m_manip->GetIRDataFormatOffset();
}
void DataReportBuilder::GetCoreData(CoreData* core) const
{
m_manip->GetCoreData(core);
}
void DataReportBuilder::SetCoreData(const CoreData& core)
{
m_manip->SetCoreData(core);
}
void DataReportBuilder::GetAccelData(AccelData* accel) const
{
m_manip->GetAccelData(accel);
}
void DataReportBuilder::SetAccelData(const AccelData& accel)
{
m_manip->SetAccelData(accel);
}
const u8* DataReportBuilder::GetDataPtr() const
{
return m_data.GetData();
}
u8* DataReportBuilder::GetDataPtr()
{
return m_data.GetData();
}
u32 DataReportBuilder::GetDataSize() const
{
return m_manip->GetDataSize() + HEADER_SIZE;
}
u8* DataReportBuilder::GetIRDataPtr()
{
return m_manip->GetIRDataPtr();
}
const u8* DataReportBuilder::GetIRDataPtr() const
{
return m_manip->GetIRDataPtr();
}
u8* DataReportBuilder::GetExtDataPtr()
{
return m_manip->GetExtDataPtr();
}
const u8* DataReportBuilder::GetExtDataPtr() const
{
return m_manip->GetExtDataPtr();
}
} // namespace WiimoteCommon

View File

@ -0,0 +1,115 @@
// 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;
};
typedef ButtonData CoreData;
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:
DataReportBuilder(InputReportID rpt_id);
typedef ButtonData CoreData;
typedef DataReportManipulator::AccelData AccelData;
typedef std::vector<u8> IRData;
typedef std::vector<u8> ExtData;
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

View File

@ -6,58 +6,76 @@
#include "Common/CommonTypes.h"
// Wiimote internal codes
// Communication channels
enum WiimoteChannel
namespace WiimoteCommon
{
WC_OUTPUT = 0x11,
WC_INPUT = 0x13
constexpr u8 MAX_PAYLOAD = 23;
enum class InputReportID : u8
{
STATUS = 0x20,
READ_DATA_REPLY = 0x21,
ACK = 0x22,
// Not a real value on the wiimote, just a state to disable reports:
REPORT_DISABLED = 0x00,
REPORT_CORE = 0x30,
REPORT_CORE_ACCEL = 0x31,
REPORT_CORE_EXT8 = 0x32,
REPORT_CORE_ACCEL_IR12 = 0x33,
REPORT_CORE_EXT19 = 0x34,
REPORT_CORE_ACCEL_EXT16 = 0x35,
REPORT_CORE_IR10_EXT9 = 0x36,
REPORT_CORE_ACCEL_IR10_EXT6 = 0x37,
REPORT_EXT21 = 0x3d,
REPORT_INTERLEAVE1 = 0x3e,
REPORT_INTERLEAVE2 = 0x3f,
};
// 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 OutputReportID : u8
{
#ifdef _WIN32
WR_SET_REPORT = 0x50
#else
WR_SET_REPORT = 0xA0
#endif
RUMBLE = 0x10,
LEDS = 0x11,
REPORT_MODE = 0x12,
IR_PIXEL_CLOCK = 0x13,
SPEAKER_ENABLE = 0x14,
REQUEST_STATUS = 0x15,
WRITE_DATA = 0x16,
READ_DATA = 0x17,
SPEAKER_DATA = 0x18,
SPEAKER_MUTE = 0x19,
IR_LOGIC = 0x1A,
};
enum WiimoteBT
enum class LED : u8
{
BT_INPUT = 0x01,
BT_OUTPUT = 0x02
};
// LED bit masks
enum WiimoteLED
{
LED_NONE = 0x00,
NONE = 0x00,
LED_1 = 0x10,
LED_2 = 0x20,
LED_3 = 0x40,
LED_4 = 0x80
};
enum class WiimoteAddressSpace : u8
enum class AddressSpace : u8
{
// 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:
I2C_BUS_ALT = 0x01,
I2C_BUS = 0x02,
};
enum class WiimoteErrorCode : u8
enum class ErrorCode : u8
{
SUCCESS = 0,
INVALID_SPACE = 6,
NACK = 7,
INVALID_ADDRESS = 8,
// Not a real value:
DO_NOT_SEND_ACK = 0xff,
};
constexpr u8 MAX_PAYLOAD = 23;
constexpr u32 WIIMOTE_DEFAULT_TIMEOUT = 1000;
} // namespace WiimoteCommon

View File

@ -5,44 +5,13 @@
#pragma once
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4200)
#endif
#pragma pack(push, 1)
namespace WiimoteCommon
{
// Source: HID_010_SPC_PFL/1.0 (official HID specification)
struct hid_packet
{
u8 param : 4;
u8 type : 4;
u8 data[0];
};
template <typename T>
struct TypedHidPacket
{
u8 param : 4;
u8 type : 4;
// TODO: auto set this from the T-type:
u8 report_id;
T data;
static_assert(std::is_pod<T>::value);
const u8* GetData() const { return reinterpret_cast<const u8*>(this); }
u32 GetSize() const
{
static_assert(sizeof(*this) == sizeof(T) + 2);
return sizeof(*this);
}
};
constexpr u8 HID_TYPE_HANDSHAKE = 0;
constexpr u8 HID_TYPE_SET_REPORT = 5;
constexpr u8 HID_TYPE_DATA = 0xA;
@ -52,8 +21,55 @@ 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)
struct HIDPacket
{
static constexpr int HEADER_SIZE = 1;
u8 param : 4;
u8 type : 4;
u8 data[0];
};
template <typename T>
struct TypedHIDInputData
{
TypedHIDInputData(InputReportID _rpt_id)
: param(HID_PARAM_INPUT), type(HID_TYPE_DATA), report_id(_rpt_id)
{
}
u8 param : 4;
u8 type : 4;
InputReportID report_id;
T data;
static_assert(std::is_pod<T>::value);
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

View File

@ -7,54 +7,124 @@
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#ifdef _MSC_VER
#pragma warning(push)
// Disable warning for zero-sized array:
#pragma warning(disable : 4200)
#endif
typedef std::vector<u8> Report;
// Report defines
// TODO: enum classes
enum ReportType
namespace WiimoteCommon
{
RT_RUMBLE = 0x10,
RT_LEDS = 0x11,
RT_REPORT_MODE = 0x12,
RT_IR_PIXEL_CLOCK = 0x13,
RT_SPEAKER_ENABLE = 0x14,
RT_REQUEST_STATUS = 0x15,
RT_WRITE_DATA = 0x16,
RT_READ_DATA = 0x17,
RT_WRITE_SPEAKER_DATA = 0x18,
RT_SPEAKER_MUTE = 0x19,
RT_IR_LOGIC = 0x1A,
RT_STATUS_REPORT = 0x20,
RT_READ_DATA_REPLY = 0x21,
RT_ACK_DATA = 0x22,
// Not a real value on the wiimote, just a state to disable reports:
RT_REPORT_DISABLED = 0x00,
RT_REPORT_CORE = 0x30,
RT_REPORT_CORE_ACCEL = 0x31,
RT_REPORT_CORE_EXT8 = 0x32,
RT_REPORT_CORE_ACCEL_IR12 = 0x33,
RT_REPORT_CORE_EXT19 = 0x34,
RT_REPORT_CORE_ACCEL_EXT16 = 0x35,
RT_REPORT_CORE_IR10_EXT9 = 0x36,
RT_REPORT_CORE_ACCEL_IR10_EXT6 = 0x37,
RT_REPORT_EXT21 = 0x3d,
RT_REPORT_INTERLEAVE1 = 0x3e,
RT_REPORT_INTERLEAVE2 = 0x3f
};
// Source: http://wiibrew.org/wiki/Wiimote
// Custom structs
#pragma pack(push, 1)
union wm_buttons // also just called "core data"
struct OutputReportGeneric
{
OutputReportID rpt_id;
static constexpr int HEADER_SIZE = sizeof(OutputReportID);
union
{
u8 data[0];
struct
{
// Enable/disable rumble. (Valid for ALL output reports)
u8 rumble : 1;
};
};
};
static_assert(sizeof(OutputReportGeneric) == 2, "Wrong size");
// TODO: The following structs don't have the report_id header byte.
// This is fine but the naming conventions are poor.
struct OutputReportRumble
{
u8 rumble : 1;
};
static_assert(sizeof(OutputReportRumble) == 1, "Wrong size");
struct OutputReportEnableFeature
{
u8 rumble : 1;
// Respond with an ack.
u8 ack : 1;
// Enable/disable certain feature.
u8 enable : 1;
};
static_assert(sizeof(OutputReportEnableFeature) == 1, "Wrong size");
struct OutputReportLeds
{
u8 rumble : 1;
u8 ack : 1;
u8 : 2;
u8 leds : 4;
};
static_assert(sizeof(OutputReportLeds) == 1, "Wrong size");
struct OutputReportMode
{
u8 rumble : 1;
u8 ack : 1;
u8 continuous : 1;
u8 : 5;
InputReportID mode;
};
static_assert(sizeof(OutputReportMode) == 2, "Wrong size");
struct OutputReportRequestStatus
{
u8 rumble : 1;
u8 : 7;
};
static_assert(sizeof(OutputReportRequestStatus) == 1, "Wrong size");
struct OutputReportWriteData
{
u8 rumble : 1;
u8 space : 2;
u8 : 5;
// A real wiimote ignores the i2c read/write bit.
u8 i2c_rw_ignored : 1;
// Used only for register space (i2c bus) (7-bits):
u8 slave_address : 7;
// big endian:
u8 address[2];
u8 size;
u8 data[16];
};
static_assert(sizeof(OutputReportWriteData) == 21, "Wrong size");
struct OutputReportReadData
{
u8 rumble : 1;
u8 space : 2;
u8 : 5;
// A real wiimote ignores the i2c read/write bit.
u8 i2c_rw_ignored : 1;
// Used only for register space (i2c bus) (7-bits):
u8 slave_address : 7;
// big endian:
u8 address[2];
u8 size[2];
};
static_assert(sizeof(OutputReportReadData) == 6, "Wrong size");
struct OutputReportSpeakerData
{
u8 unknown : 3;
u8 length : 5;
u8 data[20];
};
static_assert(sizeof(OutputReportSpeakerData) == 21, "Wrong size");
// FYI: Also contains LSB of accel data:
union ButtonData
{
static constexpr u16 BUTTON_MASK = ~0x6060;
u16 hex;
struct
@ -80,342 +150,11 @@ union wm_buttons // also just called "core data"
u8 home : 1;
};
};
static_assert(sizeof(wm_buttons) == 2, "Wrong size");
static_assert(sizeof(ButtonData) == 2, "Wrong size");
struct wm_accel
struct InputReportStatus
{
u8 x, y, z;
};
static_assert(sizeof(wm_accel) == 3, "Wrong size");
// Four bytes for two objects. Filled with 0xFF if empty
struct wm_ir_basic
{
u8 x1;
u8 y1;
u8 x2hi : 2;
u8 y2hi : 2;
u8 x1hi : 2;
u8 y1hi : 2;
u8 x2;
u8 y2;
};
static_assert(sizeof(wm_ir_basic) == 5, "Wrong size");
// Three bytes for one object
struct wm_ir_extended
{
u8 x;
u8 y;
u8 size : 4;
u8 xhi : 2;
u8 yhi : 2;
};
static_assert(sizeof(wm_ir_extended) == 3, "Wrong size");
// Nine bytes for one object
// first 3 bytes are the same as extended
struct wm_ir_full : wm_ir_extended
{
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(wm_ir_full) == 9, "Wrong size");
// Nunchuk
union wm_nc_core
{
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(wm_nc_core) == 1, "Wrong size");
union wm_nc
{
struct
{
// joystick x, y
u8 jx;
u8 jy;
// accelerometer
u8 ax;
u8 ay;
u8 az;
wm_nc_core bt; // buttons + accelerometer LSBs
}; // regular data
struct
{
u8 reserved[4]; // jx, jy, ax and ay as in regular case
u8 extension_connected : 1;
u8 acc_z : 7; // MSBs of accelerometer data
u8 unknown : 1; // always 0?
u8 report_type : 1; // 1: report contains M+ data, 0: report contains extension data
u8 z : 1;
u8 c : 1;
// LSBs of accelerometer - starting from bit 1!
u8 acc_x_lsb : 1;
u8 acc_y_lsb : 1;
u8 acc_z_lsb : 2;
} passthrough_data;
};
static_assert(sizeof(wm_nc) == 6, "Wrong size");
// TODO: kill/move these:
union wm_classic_extension_buttons
{
u16 hex;
struct
{
u8 extension_connected : 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 : 2; // cf. extension_data and passthrough_data
u8 zr : 1; // right z button
u8 x : 1;
u8 a : 1;
u8 y : 1;
u8 b : 1;
u8 zl : 1; // left z button
}; // common data
// M+ pass-through mode slightly differs from the regular data.
// Refer to the common data for unnamed fields
struct
{
u8 : 8;
u8 dpad_up : 1;
u8 dpad_left : 1;
u8 : 6;
} regular_data;
struct
{
u8 : 8;
u8 unknown : 1; // always 0?
u8 report_type : 1; // 1: report contains M+ data, 0: report contains extension data
u8 : 6;
} passthrough_data;
};
static_assert(sizeof(wm_classic_extension_buttons) == 2, "Wrong size");
union wm_classic_extension
{
// lx/ly/lz; left joystick
// rx/ry/rz; right joystick
// lt; left trigger
// rt; left trigger
struct
{
u8 : 6;
u8 rx3 : 2;
u8 : 6;
u8 rx2 : 2;
u8 ry : 5;
u8 lt2 : 2;
u8 rx1 : 1;
u8 rt : 5;
u8 lt1 : 3;
wm_classic_extension_buttons bt; // byte 4, 5
};
struct
{
u8 lx : 6; // byte 0
u8 : 2;
u8 ly : 6; // byte 1
u8 : 2;
unsigned : 32;
} regular_data;
struct
{
u8 dpad_up : 1;
u8 lx : 5; // Bits 1-5
u8 : 2;
u8 dpad_left : 1;
u8 ly : 5; // Bits 1-5
u8 : 2;
unsigned : 32;
} passthrough_data;
};
static_assert(sizeof(wm_classic_extension) == 6, "Wrong size");
struct wm_guitar_extension
{
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(wm_guitar_extension) == 6, "Wrong size");
struct wm_drums_extension
{
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(wm_drums_extension) == 6, "Wrong size");
struct wm_turntable_extension
{
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(wm_turntable_extension) == 6, "Wrong size");
struct wm_motionplus_data
{
// yaw1, roll1, pitch1: Bits 0-7
// yaw2, roll2, pitch2: Bits 8-13
u8 yaw1;
u8 roll1;
u8 pitch1;
u8 pitch_slow : 1;
u8 yaw_slow : 1;
u8 yaw2 : 6;
u8 extension_connected : 1;
u8 roll_slow : 1;
u8 roll2 : 6;
u8 zero : 1;
u8 is_mp_data : 1;
u8 pitch2 : 6;
};
static_assert(sizeof(wm_motionplus_data) == 6, "Wrong size");
struct wm_report
{
u8 wm;
union
{
u8 data[0];
struct
{
// Enable/disable rumble. (Valid for ALL output reports)
u8 rumble : 1;
// Respond with an ack. (Only valid for certain reports)
u8 ack : 1;
// Enable/disable certain features. (Only valid for certain reports)
u8 enable : 1;
};
};
};
static_assert(sizeof(wm_report) == 2, "Wrong size");
struct wm_leds
{
u8 rumble : 1;
u8 : 3;
u8 leds : 4;
};
static_assert(sizeof(wm_leds) == 1, "Wrong size");
struct wm_report_mode
{
u8 rumble : 1;
u8 : 1;
u8 continuous : 1;
u8 : 5;
u8 mode;
};
static_assert(sizeof(wm_report_mode) == 2, "Wrong size");
struct wm_request_status
{
u8 rumble : 1;
u8 : 7;
};
static_assert(sizeof(wm_request_status) == 1, "Wrong size");
struct wm_status_report
{
wm_buttons buttons;
ButtonData buttons;
u8 battery_low : 1;
u8 extension : 1;
u8 speaker : 1;
@ -424,121 +163,29 @@ struct wm_status_report
u8 padding2[2];
u8 battery;
};
static_assert(sizeof(wm_status_report) == 6, "Wrong size");
static_assert(sizeof(InputReportStatus) == 6, "Wrong size");
struct wm_write_data
struct InputReportAck
{
u8 rumble : 1;
u8 space : 2;
u8 : 5;
// A real wiimote ignores the i2c read/write bit.
u8 i2c_rw_ignored : 1;
// Used only for register space (i2c bus) (7-bits):
u8 slave_address : 7;
// big endian:
u8 address[2];
u8 size;
u8 data[16];
ButtonData buttons;
OutputReportID rpt_id;
ErrorCode error_code;
};
static_assert(sizeof(wm_write_data) == 21, "Wrong size");
static_assert(sizeof(InputReportAck) == 4, "Wrong size");
struct wm_acknowledge
struct InputReportReadDataReply
{
wm_buttons buttons;
u8 reportID;
u8 errorID;
};
static_assert(sizeof(wm_acknowledge) == 4, "Wrong size");
struct wm_read_data
{
u8 rumble : 1;
u8 space : 2;
u8 : 5;
// A real wiimote ignores the i2c read/write bit.
u8 i2c_rw_ignored : 1;
// Used only for register space (i2c bus) (7-bits):
u8 slave_address : 7;
// big endian:
u8 address[2];
u8 size[2];
};
static_assert(sizeof(wm_read_data) == 6, "Wrong size");
struct wm_read_data_reply
{
wm_buttons buttons;
ButtonData buttons;
u8 error : 4;
u8 size_minus_one : 4;
// big endian:
u16 address;
u8 data[16];
};
static_assert(sizeof(wm_read_data_reply) == 21, "Wrong size");
static_assert(sizeof(InputReportReadDataReply) == 21, "Wrong size");
// Data reports
} // namespace WiimoteCommon
struct wm_report_core
{
wm_buttons c;
};
static_assert(sizeof(wm_report_core) == 2, "Wrong size");
struct wm_report_core_accel
{
wm_buttons c;
wm_accel a;
};
static_assert(sizeof(wm_report_core_accel) == 5, "Wrong size");
struct wm_report_core_ext8
{
wm_buttons c;
u8 ext[8];
};
static_assert(sizeof(wm_report_core_ext8) == 10, "Wrong size");
struct wm_report_core_accel_ir12
{
wm_buttons c;
wm_accel a;
wm_ir_extended ir[4];
};
static_assert(sizeof(wm_report_core_accel_ir12) == 17, "Wrong size");
struct wm_report_core_accel_ext16
{
wm_buttons c;
wm_accel a;
wm_nc ext; // TODO: Does this make any sense? Shouldn't it be just a general "extension" field?
// wm_ir_basic ir[2];
u8 pad[10];
};
static_assert(sizeof(wm_report_core_accel_ext16) == 21, "Wrong size");
struct wm_report_core_accel_ir10_ext6
{
wm_buttons c;
wm_accel a;
wm_ir_basic ir[2];
// u8 ext[6];
wm_nc ext; // TODO: Does this make any sense? Shouldn't it be just a general "extension" field?
};
static_assert(sizeof(wm_report_core_accel_ir10_ext6) == 21, "Wrong size");
struct wm_report_ext21
{
u8 ext[21];
};
static_assert(sizeof(wm_report_ext21) == 21, "Wrong size");
struct wm_speaker_data
{
u8 unknown : 3;
u8 length : 5;
u8 data[20];
};
static_assert(sizeof(wm_speaker_data) == 21, "Wrong size");
#pragma pack(pop)
#ifdef _MSC_VER

View File

@ -1,79 +0,0 @@
// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"
#include <algorithm>
#include <array>
#include <cstring>
#include "Common/CommonTypes.h"
#include "Common/Compiler.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
namespace WiimoteEmu
{
// Extension device IDs to be written to the last bytes of the extension reg
// The id for nothing inserted
constexpr std::array<u8, 6> nothing_id{{0x00, 0x00, 0x00, 0x00, 0x2e, 0x2e}};
// The id for a partially inserted extension (currently unused)
DOLPHIN_UNUSED constexpr std::array<u8, 6> partially_id{{0x00, 0x00, 0x00, 0x00, 0xff, 0xff}};
Attachment::Attachment(const char* const name, ExtensionReg& reg) : m_name(name), m_reg(reg)
{
}
None::None(ExtensionReg& reg) : Attachment("None", reg)
{
// set up register
m_id = nothing_id;
}
void Attachment::GetState(u8* const data)
{
}
bool Attachment::IsButtonPressed() const
{
return false;
}
std::string Attachment::GetName() const
{
return m_name;
}
void Attachment::Reset()
{
// set up register
m_reg = {};
std::copy(m_id.cbegin(), m_id.cend(), m_reg.constant_id);
std::copy(m_calibration.cbegin(), m_calibration.cend(), m_reg.calibration);
}
} // namespace WiimoteEmu
namespace ControllerEmu
{
void Extension::GetState(u8* const data)
{
static_cast<WiimoteEmu::Attachment*>(attachments[active_extension].get())->GetState(data);
}
bool Extension::IsButtonPressed() const
{
// Extension == 0 means no Extension, > 0 means one is connected
// Since we want to use this to know if disconnected Wiimotes want to be connected, and
// disconnected
// Wiimotes (can? always?) have their active_extension set to -1, we also have to check the
// switch_extension
if (active_extension > 0)
return static_cast<WiimoteEmu::Attachment*>(attachments[active_extension].get())
->IsButtonPressed();
if (switch_extension > 0)
return static_cast<WiimoteEmu::Attachment*>(attachments[switch_extension].get())
->IsButtonPressed();
return false;
}
} // namespace ControllerEmu

View File

@ -1,41 +0,0 @@
// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <string>
#include "Common/CommonTypes.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
namespace WiimoteEmu
{
struct ExtensionReg;
class Attachment : public ControllerEmu::EmulatedController
{
public:
Attachment(const char* const name, ExtensionReg& reg);
virtual void GetState(u8* const data);
virtual bool IsButtonPressed() const;
void Reset();
std::string GetName() const override;
protected:
std::array<u8, 6> m_id{};
std::array<u8, 0x10> m_calibration{};
private:
const char* const m_name;
ExtensionReg& m_reg;
};
class None : public Attachment
{
public:
explicit None(ExtensionReg& reg);
};
} // namespace WiimoteEmu

View File

@ -0,0 +1,202 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/HW/WiimoteEmu/Camera.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)
{
u16 x[4], y[4];
memset(x, 0xFF, sizeof(x));
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;
std::array<Vertex, 4> 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);
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)));
}
// 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:
{
auto* const irdata = reinterpret_cast<IRBasic*>(data);
for (unsigned int i = 0; i < 2; ++i)
{
if (x[i * 2] < 1024 && y[i * 2] < 768)
{
irdata[i].x1 = static_cast<u8>(x[i * 2]);
irdata[i].x1hi = x[i * 2] >> 8;
irdata[i].y1 = static_cast<u8>(y[i * 2]);
irdata[i].y1hi = y[i * 2] >> 8;
}
if (x[i * 2 + 1] < 1024 && y[i * 2 + 1] < 768)
{
irdata[i].x2 = static_cast<u8>(x[i * 2 + 1]);
irdata[i].x2hi = x[i * 2 + 1] >> 8;
irdata[i].y2 = static_cast<u8>(y[i * 2 + 1]);
irdata[i].y2hi = y[i * 2 + 1] >> 8;
}
}
break;
}
case IR_MODE_EXTENDED:
{
auto* const irdata = reinterpret_cast<IRExtended*>(data);
for (unsigned int i = 0; i < 4; ++i)
if (x[i] < 1024 && y[i] < 768)
{
irdata[i].x = static_cast<u8>(x[i]);
irdata[i].xhi = x[i] >> 8;
irdata[i].y = static_cast<u8>(y[i]);
irdata[i].yhi = y[i] >> 8;
irdata[i].size = 10;
}
break;
}
case IR_MODE_FULL:
{
auto* const irdata = reinterpret_cast<IRFull*>(data);
for (unsigned int i = 0; i < 4; ++i)
if (x[i] < 1024 && y[i] < 768)
{
irdata[i].x = static_cast<u8>(x[i]);
irdata[i].xhi = x[i] >> 8;
irdata[i].y = static_cast<u8>(y[i]);
irdata[i].yhi = y[i] >> 8;
irdata[i].size = 10;
// TODO: implement these sensibly:
// TODO: do high bits of x/y min/max need to be set to zero?
irdata[i].xmin = 0;
irdata[i].ymin = 0;
irdata[i].xmax = 0;
irdata[i].ymax = 0;
irdata[i].zero = 0;
irdata[i].intensity = 0;
}
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

View File

@ -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,
};
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:
static constexpr u8 I2C_ADDR = 0x58;
// The real wiimote reads camera data from the i2c bus at offset 0x37:
static const u8 REPORT_DATA_OFFSET = offsetof(Register, camera_data);
void Reset();
void DoState(PointerWrap& p);
void Update(const ControllerEmu::Cursor::StateData& cursor, const NormalizedAccelData& accel,
bool sensor_bar_on_top);
private:
Register reg_data;
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;
};
} // namespace WiimoteEmu

View File

@ -0,0 +1,262 @@
// 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

View File

@ -0,0 +1,68 @@
// 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
{
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

View File

@ -2,24 +2,7 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
/* HID reports access guide. */
/* 0x10 - 0x1a Output EmuMain.cpp: HidOutputReport()
0x10 - 0x14: General
0x15: Status report request from the Wii
0x16 and 0x17: Write and read memory or registers
0x19 and 0x1a: General
0x20 - 0x22 Input EmuMain.cpp: HidOutputReport() to the destination
0x15 leads to a 0x20 Input report
0x17 leads to a 0x21 Input report
0x10 - 0x1a leads to a 0x22 Input report
0x30 - 0x3f Input This file: Update() */
#include <fstream>
#include <mutex>
#include <queue>
#include <string>
#include <vector>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
@ -29,21 +12,21 @@
#include "Common/Swap.h"
#include "Core/Core.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteEmu/Attachment/Attachment.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.h"
namespace WiimoteEmu
{
void Wiimote::ReportMode(const wm_report_mode* const dr)
using namespace WiimoteCommon;
void Wiimote::HandleReportMode(const OutputReportMode& dr)
{
if (dr->mode < RT_REPORT_CORE || dr->mode > RT_REPORT_INTERLEAVE2 ||
(dr->mode > RT_REPORT_CORE_ACCEL_IR10_EXT6 && dr->mode < RT_REPORT_EXT21))
if (!DataReportBuilder::IsValidMode(dr.mode))
{
// A real wiimote ignores the entire message if the mode is invalid.
WARN_LOG(WIIMOTE, "Game requested invalid report mode: 0x%02x", dr->mode);
WARN_LOG(WIIMOTE, "Game requested invalid report mode: 0x%02x", int(dr.mode));
return;
}
@ -51,229 +34,192 @@ void Wiimote::ReportMode(const wm_report_mode* const dr)
// even on REPORT_CORE and continuous off when the buttons haven't changed.
// But.. it is sent after the ACK
// INFO_LOG(WIIMOTE, "Set data report mode");
// DEBUG_LOG(WIIMOTE, "Set data report mode");
// DEBUG_LOG(WIIMOTE, " Rumble: %x", dr->rumble);
// DEBUG_LOG(WIIMOTE, " Continuous: %x", dr->continuous);
// DEBUG_LOG(WIIMOTE, " Mode: 0x%02x", dr->mode);
m_reporting_auto = dr->continuous;
m_reporting_mode = dr->mode;
m_reporting_continuous = dr.continuous;
m_reporting_mode = dr.mode;
SendAck(OutputReportID::REPORT_MODE, ErrorCode::SUCCESS);
}
/* Here we process the Output Reports that the Wii sends. Our response will be
an Input Report back to the Wii. Input and Output is from the Wii's
perspective, Output means data to the Wiimote (from the Wii), Input means
data from the Wiimote.
The call browser:
1. Wiimote_InterruptChannel > InterruptChannel > HidOutputReport
2. Wiimote_ControlChannel > ControlChannel > HidOutputReport
The IR enable/disable and speaker enable/disable and mute/unmute values are
bit2: 0 = Disable (0x02), 1 = Enable (0x06)
*/
void Wiimote::HidOutputReport(const wm_report* const sr, const bool send_ack)
// Tests that we have enough bytes for the report before we run the handler.
template <typename T, typename H>
void Wiimote::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size)
{
DEBUG_LOG(WIIMOTE, "HidOutputReport (page: %i, cid: 0x%02x, wm: 0x%02x)", m_index,
m_reporting_channel, sr->wm);
if (size < sizeof(T))
{
ERROR_LOG(WIIMOTE, "InvokeHandler: report: 0x%02x invalid size: %d", int(rpt.rpt_id), size);
}
(this->*handler)(*reinterpret_cast<const T*>(rpt.data));
}
// Here we process the Output Reports that the Wii sends. Our response will be
// an Input Report back to the Wii. Input and Output is from the Wii's
// perspective, Output means data to the Wiimote (from the Wii), Input means
// data from the Wiimote.
//
// The call browser:
//
// 1. Wiimote_InterruptChannel > InterruptChannel > HIDOutputReport
// 2. Wiimote_ControlChannel > ControlChannel > HIDOutputReport
void Wiimote::HIDOutputReport(const void* data, u32 size)
{
if (!size)
{
ERROR_LOG(WIIMOTE, "HIDOutputReport: zero sized data");
return;
}
auto& rpt = *static_cast<const OutputReportGeneric*>(data);
const int rpt_size = size - rpt.HEADER_SIZE;
DEBUG_LOG(WIIMOTE, "HIDOutputReport (page: %i, cid: 0x%02x, wm: 0x%02x)", m_index,
m_reporting_channel, int(rpt.rpt_id));
// WiiBrew:
// In every single Output Report, bit 0 (0x01) of the first byte controls the Rumble feature.
m_rumble_on = sr->rumble;
InvokeHandler<OutputReportRumble>(&Wiimote::HandleReportRumble, rpt, rpt_size);
switch (sr->wm)
switch (rpt.rpt_id)
{
case RT_RUMBLE:
case OutputReportID::RUMBLE:
// This is handled above.
// A real wiimote never ACKs a rumble report:
return;
break;
case RT_LEDS:
// INFO_LOG(WIIMOTE, "Set LEDs: 0x%02x", sr->data[0]);
m_status.leds = sr->data[0] >> 4;
case OutputReportID::LEDS:
InvokeHandler<OutputReportLeds>(&Wiimote::HandleReportLeds, rpt, rpt_size);
break;
case RT_REPORT_MODE:
ReportMode(reinterpret_cast<const wm_report_mode*>(sr->data));
case OutputReportID::REPORT_MODE:
InvokeHandler<OutputReportMode>(&Wiimote::HandleReportMode, rpt, rpt_size);
break;
case RT_IR_PIXEL_CLOCK:
// INFO_LOG(WIIMOTE, "WM IR Clock: 0x%02x", sr->data[0]);
// Camera data is currently always updated. Ignoring pixel clock status.
case OutputReportID::IR_PIXEL_CLOCK:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleIRPixelClock, rpt, rpt_size);
break;
case RT_SPEAKER_ENABLE:
// INFO_LOG(WIIMOTE, "WM Speaker Enable: %02x", sr->enable);
m_status.speaker = sr->enable;
case OutputReportID::SPEAKER_ENABLE:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleSpeakerEnable, rpt, rpt_size);
break;
case RT_REQUEST_STATUS:
RequestStatus(reinterpret_cast<const wm_request_status*>(sr->data));
// No ACK:
return;
case OutputReportID::REQUEST_STATUS:
InvokeHandler<OutputReportRequestStatus>(&Wiimote::HandleRequestStatus, rpt, rpt_size);
break;
case RT_WRITE_DATA:
WriteData(reinterpret_cast<const wm_write_data*>(sr->data));
// Sends it's own ACK conditionally:
return;
case OutputReportID::WRITE_DATA:
InvokeHandler<OutputReportWriteData>(&Wiimote::HandleWriteData, rpt, rpt_size);
break;
case RT_READ_DATA:
ReadData(reinterpret_cast<const wm_read_data*>(sr->data));
// No ACK:
return;
case OutputReportID::READ_DATA:
InvokeHandler<OutputReportReadData>(&Wiimote::HandleReadData, rpt, rpt_size);
break;
case RT_WRITE_SPEAKER_DATA:
// TODO: Does speaker mute stop speaker data processing?
// (important to keep decoder in proper state)
if (!m_speaker_mute)
{
auto sd = reinterpret_cast<const wm_speaker_data*>(sr->data);
if (sd->length > ArraySize(sd->data))
{
ERROR_LOG(WIIMOTE, "Bad speaker data length: %d", sd->length);
}
else
{
SpeakerData(sd->data, sd->length);
}
}
// No ACK:
return;
case OutputReportID::SPEAKER_DATA:
InvokeHandler<OutputReportSpeakerData>(&Wiimote::HandleSpeakerData, rpt, rpt_size);
break;
case RT_SPEAKER_MUTE:
m_speaker_mute = sr->enable;
case OutputReportID::SPEAKER_MUTE:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleSpeakerMute, rpt, rpt_size);
break;
case RT_IR_LOGIC:
// Camera data is currently always updated. Just saving this for status requests.
m_status.ir = sr->enable;
case OutputReportID::IR_LOGIC:
InvokeHandler<OutputReportEnableFeature>(&Wiimote::HandleIRLogic, rpt, rpt_size);
break;
default:
PanicAlert("HidOutputReport: Unknown channel 0x%02x", sr->wm);
return;
PanicAlert("HidOutputReport: Unknown report ID 0x%02x", int(rpt.rpt_id));
break;
}
if (sr->ack)
SendAck(sr->wm);
}
/* This will generate the 0x22 acknowledgement for most Input reports.
It has the form of "a1 22 00 00 _reportID 00".
The first two bytes are the core buttons data,
00 00 means nothing is pressed.
The last byte is the success code 00. */
void Wiimote::SendAck(u8 report_id, WiimoteErrorCode error_code)
void Wiimote::CallbackInterruptChannel(const u8* data, u32 size)
{
TypedHidPacket<wm_acknowledge> rpt;
rpt.type = HID_TYPE_DATA;
rpt.param = HID_PARAM_INPUT;
rpt.report_id = RT_ACK_DATA;
Core::Callback_WiimoteInterruptChannel(m_index, m_reporting_channel, data, size);
}
void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code)
{
TypedHIDInputData<InputReportAck> rpt(InputReportID::ACK);
auto& ack = rpt.data;
ack.buttons = m_status.buttons;
ack.reportID = report_id;
ack.errorID = static_cast<u8>(error_code);
ack.rpt_id = rpt_id;
ack.error_code = error_code;
Core::Callback_WiimoteInterruptChannel(m_index, m_reporting_channel, rpt.GetData(),
rpt.GetSize());
CallbackInterruptChannel(rpt.GetData(), rpt.GetSize());
}
void Wiimote::HandleExtensionSwap()
{
// handle switch extension
if (m_extension->active_extension != m_extension->switch_extension)
const ExtensionNumber desired_extension =
static_cast<ExtensionNumber>(m_attachments->GetSelectedAttachment());
if (GetActiveExtensionNumber() != desired_extension)
{
// if an extension is currently connected and we want to switch to a different extension
if ((m_extension->active_extension > 0) && m_extension->switch_extension)
if (GetActiveExtensionNumber())
{
// detach extension first, wait til next Update() or RequestStatus() call to change to the new
// extension
m_extension->active_extension = 0;
// First we must detach the current extension.
// The next call will change to the new extension if needed.
m_active_extension = ExtensionNumber::NONE;
}
else
{
// set the wanted extension
m_extension->active_extension = m_extension->switch_extension;
m_active_extension = desired_extension;
}
// TODO: this is a bit ugly:
if (m_extension->active_extension != 0)
m_motion_plus_logic.extension_port.SetAttachment(&m_ext_logic);
else
m_motion_plus_logic.extension_port.SetAttachment(nullptr);
// reset register
((WiimoteEmu::Attachment*)m_extension->attachments[m_extension->active_extension].get())
->Reset();
// TODO: Attach directly when not using M+.
m_motion_plus.AttachExtension(GetActiveExtension());
GetActiveExtension()->Reset();
}
}
void Wiimote::RequestStatus(const wm_request_status* const rs)
void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&)
{
// update status struct
// FYI: buttons are updated in Update() for determinism
// Update status struct
m_status.extension = m_extension_port.IsDeviceConnected();
// TODO: Battery level will break determinism in TAS/Netplay
// Battery levels in voltage
// 0x00 - 0x32: level 1
// 0x33 - 0x43: level 2
// 0x33 - 0x54: level 3
// 0x55 - 0xff: level 4
m_status.battery = (u8)(m_battery_setting->GetValue() * 0xff);
// Less than 0x20 triggers the low-battery flag:
m_status.battery_low = m_status.battery < 0x20;
// set up report
TypedHidPacket<wm_status_report> rpt;
rpt.type = HID_TYPE_DATA;
rpt.param = HID_PARAM_INPUT;
rpt.report_id = RT_STATUS_REPORT;
// status values
TypedHIDInputData<InputReportStatus> rpt(InputReportID::STATUS);
rpt.data = m_status;
// send report
Core::Callback_WiimoteInterruptChannel(m_index, m_reporting_channel, rpt.GetData(),
rpt.GetSize());
CallbackInterruptChannel(rpt.GetData(), rpt.GetSize());
}
/* Write data to Wiimote and Extensions registers. */
void Wiimote::WriteData(const wm_write_data* const wd)
void Wiimote::HandleWriteData(const OutputReportWriteData& wd)
{
u16 address = Common::swap16(wd->address);
// TODO: Are writes ignored during an active read request?
INFO_LOG(WIIMOTE, "Wiimote::WriteData: 0x%02x @ 0x%02x @ 0x%02x (%d)", wd->space,
wd->slave_address, address, wd->size);
u16 address = Common::swap16(wd.address);
if (0 == wd->size || wd->size > 16)
DEBUG_LOG(WIIMOTE, "Wiimote::WriteData: 0x%02x @ 0x%02x @ 0x%02x (%d)", wd.space,
wd.slave_address, address, wd.size);
if (0 == wd.size || wd.size > 16)
{
WARN_LOG(WIIMOTE, "WriteData: invalid size: %d", wd->size);
WARN_LOG(WIIMOTE, "WriteData: invalid size: %d", wd.size);
// A real wiimote silently ignores such a request:
return;
}
WiimoteErrorCode error_code = WiimoteErrorCode::SUCCESS;
ErrorCode error_code = ErrorCode::SUCCESS;
switch (static_cast<WiimoteAddressSpace>(wd->space))
switch (static_cast<AddressSpace>(wd.space))
{
case WiimoteAddressSpace::EEPROM:
case AddressSpace::EEPROM:
{
// Write to EEPROM
if (address + wd->size > WIIMOTE_EEPROM_SIZE)
if (address + wd.size > EEPROM_FREE_SIZE)
{
WARN_LOG(WIIMOTE, "WriteData: address + size out of bounds!");
error_code = WiimoteErrorCode::INVALID_ADDRESS;
error_code = ErrorCode::INVALID_ADDRESS;
}
else
{
std::copy_n(wd->data, wd->size, m_eeprom + address);
std::copy_n(wd.data, wd.size, m_eeprom.data.data() + address);
// Write mii data to file
if (address >= 0x0FCA && address < 0x12C0)
@ -282,61 +228,145 @@ void Wiimote::WriteData(const wm_write_data* const wd)
std::ofstream file;
File::OpenFStream(file, File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/mii.bin",
std::ios::binary | std::ios::out);
file.write((char*)m_eeprom + 0x0FCA, 0x02f0);
file.write((char*)m_eeprom.data.data() + 0x0FCA, 0x02f0);
file.close();
}
}
}
break;
case WiimoteAddressSpace::I2C_BUS:
case WiimoteAddressSpace::I2C_BUS_ALT:
case AddressSpace::I2C_BUS:
case AddressSpace::I2C_BUS_ALT:
{
// Write to Control Register
// Attempting to access the EEPROM directly over i2c results in error 8.
if (EEPROM_I2C_ADDR == m_read_request.slave_address)
{
WARN_LOG(WIIMOTE, "Attempt to write EEPROM directly.");
error_code = ErrorCode::INVALID_ADDRESS;
break;
}
// Top byte of address is ignored on the bus.
auto const bytes_written =
m_i2c_bus.BusWrite(wd->slave_address, (u8)address, wd->size, wd->data);
if (bytes_written != wd->size)
auto const bytes_written = m_i2c_bus.BusWrite(wd.slave_address, (u8)address, wd.size, wd.data);
if (bytes_written != wd.size)
{
// A real wiimote gives error 7 for failed write to i2c bus (mainly a non-existant slave)
error_code = WiimoteErrorCode::NACK;
error_code = ErrorCode::NACK;
}
}
break;
default:
WARN_LOG(WIIMOTE, "WriteData: invalid address space: 0x%x", wd->space);
WARN_LOG(WIIMOTE, "WriteData: invalid address space: 0x%x", wd.space);
// A real wiimote gives error 6:
error_code = WiimoteErrorCode::INVALID_SPACE;
error_code = ErrorCode::INVALID_SPACE;
break;
}
SendAck(RT_WRITE_DATA, error_code);
SendAck(OutputReportID::WRITE_DATA, error_code);
}
/* Read data from Wiimote and Extensions registers. */
void Wiimote::ReadData(const wm_read_data* const rd)
void Wiimote::HandleReportRumble(const WiimoteCommon::OutputReportRumble& rpt)
{
SetRumble(rpt.rumble);
// FYI: A real wiimote never seems to ACK a rumble report:
}
void Wiimote::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt)
{
m_status.leds = rpt.leds;
if (rpt.ack)
SendAck(OutputReportID::LEDS, ErrorCode::SUCCESS);
}
void Wiimote::HandleIRPixelClock(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
// INFO_LOG(WIIMOTE, "WM IR Clock: %02x", erpt.enable);
// FYI: Camera data is currently always updated. Ignoring pixel clock status.
if (rpt.ack)
SendAck(OutputReportID::IR_PIXEL_CLOCK, ErrorCode::SUCCESS);
}
void Wiimote::HandleIRLogic(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
// FYI: Camera data is currently always updated. We just save this for status reports.
m_status.ir = rpt.enable;
if (rpt.ack)
SendAck(OutputReportID::IR_LOGIC, ErrorCode::SUCCESS);
}
void Wiimote::HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
m_speaker_mute = rpt.enable;
if (rpt.ack)
SendAck(OutputReportID::SPEAKER_MUTE, ErrorCode::SUCCESS);
}
void Wiimote::HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature& rpt)
{
// INFO_LOG(WIIMOTE, "WM Speaker Enable: %02x", erpt.enable);
m_status.speaker = rpt.enable;
if (rpt.ack)
SendAck(OutputReportID::SPEAKER_ENABLE, ErrorCode::SUCCESS);
}
void Wiimote::HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData& rpt)
{
// TODO: Does speaker_mute stop speaker data processing?
// and what about speaker_enable?
// (important to keep decoder in proper state)
if (!m_speaker_mute)
{
if (rpt.length > ArraySize(rpt.data))
{
ERROR_LOG(WIIMOTE, "Bad speaker data length: %d", rpt.length);
}
else
{
// Speaker Pan
// GUI clamps pan setting from -127 to 127. Why?
const auto pan = int(m_options->numeric_settings[0]->GetValue() * 100);
m_speaker_logic.SpeakerData(rpt.data, rpt.length, pan);
}
}
// FYI: Speaker data reports normally do not ACK but I have seen them ACK with error codes
// It seems some wiimotes do this when receiving data too quickly.
// More investigation is needed.
}
void Wiimote::HandleReadData(const OutputReportReadData& rd)
{
if (m_read_request.size)
{
// There is already an active read request.
// A real wiimote ignores the new one.
WARN_LOG(WIIMOTE, "ReadData: ignoring read during active request.");
return;
}
// Save the request and process it on the next "Update()" calls
m_read_request.space = static_cast<WiimoteAddressSpace>(rd->space);
m_read_request.slave_address = rd->slave_address;
m_read_request.address = Common::swap16(rd->address);
// Save the request and process it on the next "Update()" call(s)
m_read_request.space = static_cast<AddressSpace>(rd.space);
m_read_request.slave_address = rd.slave_address;
m_read_request.address = Common::swap16(rd.address);
// A zero size request is just ignored, like on the real wiimote.
m_read_request.size = Common::swap16(rd->size);
m_read_request.size = Common::swap16(rd.size);
INFO_LOG(WIIMOTE, "Wiimote::ReadData: %d @ 0x%02x @ 0x%02x (%d)", m_read_request.space,
m_read_request.slave_address, m_read_request.address, m_read_request.size);
DEBUG_LOG(WIIMOTE, "Wiimote::ReadData: %d @ 0x%02x @ 0x%02x (%d)", int(m_read_request.space),
m_read_request.slave_address, m_read_request.address, m_read_request.size);
// Send up to one read-data-reply.
// If more data needs to be sent it will happen on the next "Update()"
// TODO: should this be removed and let Update() take care of it?
ProcessReadDataRequest();
}
@ -352,41 +382,30 @@ bool Wiimote::ProcessReadDataRequest()
return false;
}
TypedHidPacket<wm_read_data_reply> rpt;
rpt.type = HID_TYPE_DATA;
rpt.param = HID_PARAM_INPUT;
rpt.report_id = RT_READ_DATA_REPLY;
TypedHIDInputData<InputReportReadDataReply> rpt(InputReportID::READ_DATA_REPLY);
auto& reply = rpt.data;
auto reply = &rpt.data;
reply->buttons = m_status.buttons;
reply->address = Common::swap16(m_read_request.address);
reply.buttons = m_status.buttons;
reply.address = Common::swap16(m_read_request.address);
// Pre-fill with zeros in case of read-error or read < 16-bytes:
std::fill(std::begin(reply->data), std::end(reply->data), 0x00);
std::fill(std::begin(reply.data), std::end(reply.data), 0x00);
WiimoteErrorCode error_code = WiimoteErrorCode::SUCCESS;
ErrorCode error_code = ErrorCode::SUCCESS;
switch (m_read_request.space)
{
case WiimoteAddressSpace::EEPROM:
case AddressSpace::EEPROM:
{
// Read from EEPROM
if (m_read_request.address + m_read_request.size > WIIMOTE_EEPROM_FREE_SIZE)
if (m_read_request.address + m_read_request.size > EEPROM_FREE_SIZE)
{
if (m_read_request.address + m_read_request.size > WIIMOTE_EEPROM_SIZE)
{
WARN_LOG(WIIMOTE, "ReadData: address + size out of bounds!");
}
// Generate a read error. Even if the start of the block is readable a real wiimote just sends
// error code 8
// The real Wiimote generate an error for the first
// request to 0x1770 if we dont't replicate that the game will never
// read the calibration data at the beginning of Eeprom. I think this
// error is supposed to occur when we try to read above the freely
// usable space that ends at 0x16ff.
error_code = WiimoteErrorCode::INVALID_ADDRESS;
// read the calibration data at the beginning of Eeprom.
error_code = ErrorCode::INVALID_ADDRESS;
}
else
{
@ -398,50 +417,57 @@ bool Wiimote::ProcessReadDataRequest()
std::ifstream file;
File::OpenFStream(file, (File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/mii.bin").c_str(),
std::ios::binary | std::ios::in);
file.read((char*)m_eeprom + 0x0FCA, 0x02f0);
file.read((char*)m_eeprom.data.data() + 0x0FCA, 0x02f0);
file.close();
}
// read memory to be sent to Wii
std::copy_n(m_eeprom + m_read_request.address, bytes_to_read, reply->data);
reply->size_minus_one = bytes_to_read - 1;
// Read memory to be sent to Wii
std::copy_n(m_eeprom.data.data() + m_read_request.address, bytes_to_read, reply.data);
reply.size_minus_one = bytes_to_read - 1;
}
}
break;
case WiimoteAddressSpace::I2C_BUS:
case WiimoteAddressSpace::I2C_BUS_ALT:
case AddressSpace::I2C_BUS:
case AddressSpace::I2C_BUS_ALT:
{
// Read from I2C bus
// Attempting to access the EEPROM directly over i2c results in error 8.
if (EEPROM_I2C_ADDR == m_read_request.slave_address)
{
WARN_LOG(WIIMOTE, "Attempt to read EEPROM directly.");
error_code = ErrorCode::INVALID_ADDRESS;
break;
}
// Top byte of address is ignored on the bus, but it IS maintained in the read-reply.
auto const bytes_read = m_i2c_bus.BusRead(
m_read_request.slave_address, (u8)m_read_request.address, bytes_to_read, reply->data);
reply->size_minus_one = bytes_read - 1;
m_read_request.slave_address, (u8)m_read_request.address, bytes_to_read, reply.data);
if (bytes_read != bytes_to_read)
{
// generate read error, 7 == no such slave (no ack)
INFO_LOG(WIIMOTE, "Responding with read error 7 @ 0x%x @ 0x%x (%d)",
m_read_request.slave_address, m_read_request.address, m_read_request.size);
error_code = WiimoteErrorCode::NACK;
DEBUG_LOG(WIIMOTE, "Responding with read error 7 @ 0x%x @ 0x%x (%d)",
m_read_request.slave_address, m_read_request.address, m_read_request.size);
error_code = ErrorCode::NACK;
break;
}
reply.size_minus_one = bytes_read - 1;
}
break;
default:
WARN_LOG(WIIMOTE, "ReadData: invalid address space: 0x%x", m_read_request.space);
WARN_LOG(WIIMOTE, "ReadData: invalid address space: 0x%x", int(m_read_request.space));
// A real wiimote gives error 6:
error_code = WiimoteErrorCode::INVALID_SPACE;
error_code = ErrorCode::INVALID_SPACE;
break;
}
if (WiimoteErrorCode::SUCCESS != error_code)
if (ErrorCode::SUCCESS != error_code)
{
// Stop processing request on read error:
m_read_request.size = 0;
// Real wiimote seems to set size to max value on read errors:
reply->size_minus_one = 0xf;
reply.size_minus_one = 0xf;
}
else
{
@ -450,49 +476,57 @@ bool Wiimote::ProcessReadDataRequest()
m_read_request.size -= bytes_to_read;
}
reply->error = static_cast<u8>(error_code);
reply.error = static_cast<u8>(error_code);
// Send the data
Core::Callback_WiimoteInterruptChannel(m_index, m_reporting_channel, rpt.GetData(),
rpt.GetSize());
CallbackInterruptChannel(rpt.GetData(), rpt.GetSize());
return true;
}
void Wiimote::DoState(PointerWrap& p)
{
p.Do(m_extension->active_extension);
p.Do(m_extension->switch_extension);
// No need to sync. Index will not change.
// p.Do(m_index);
// No need to sync. This is not wiimote state.
// p.Do(m_sensor_bar_on_top);
p.Do(m_accel);
p.Do(m_index);
p.Do(ir_sin);
p.Do(ir_cos);
p.Do(m_rumble_on);
p.Do(m_speaker_mute);
p.Do(m_reporting_auto);
p.Do(m_reporting_mode);
p.Do(m_reporting_channel);
p.Do(m_shake_step);
p.Do(m_sensor_bar_on_top);
p.Do(m_reporting_mode);
p.Do(m_reporting_continuous);
p.Do(m_speaker_mute);
p.Do(m_status);
p.Do(m_speaker_logic.adpcm_state);
p.Do(m_ext_logic.ext_key);
p.DoArray(m_eeprom);
p.Do(m_motion_plus_logic.reg_data);
p.Do(m_camera_logic.reg_data);
p.Do(m_ext_logic.reg_data);
p.Do(m_speaker_logic.reg_data);
p.Do(m_eeprom);
p.Do(m_read_request);
// Sub-devices:
m_speaker_logic.DoState(p);
m_motion_plus.DoState(p);
m_camera_logic.DoState(p);
p.Do(m_active_extension);
GetActiveExtension()->DoState(p);
// TODO: Handle motion plus being disabled.
m_motion_plus.AttachExtension(GetActiveExtension());
// Dynamics
// TODO: clean this up:
p.Do(m_shake_step);
p.DoMarker("Wiimote");
if (p.GetMode() == PointerWrap::MODE_READ)
RealState();
// TODO: rebuild i2c bus state after state-change
}
// load real Wiimote state
ExtensionNumber Wiimote::GetActiveExtensionNumber() const
{
return m_active_extension;
}
void Wiimote::RealState()
{
using namespace WiimoteReal;
@ -500,7 +534,7 @@ void Wiimote::RealState()
if (g_wiimotes[m_index])
{
g_wiimotes[m_index]->SetChannel(m_reporting_channel);
g_wiimotes[m_index]->EnableDataReporting(m_reporting_mode);
}
}
} // namespace WiimoteEmu

View File

@ -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

View File

@ -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 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 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 WiimoteGenerateKey(wiimote_key* const key, const u8* const keydata);
} // namespace WiimoteEmu

View File

@ -2,7 +2,7 @@
// 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>
@ -71,7 +71,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 +104,21 @@ 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
auto& classic_data = *reinterpret_cast<DataFormat*>(&m_reg.controller_data);
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
@ -187,8 +156,6 @@ void Classic::GetState(u8* const data)
// flip button bits
classic_data.bt.hex ^= 0xFFFF;
std::memcpy(data, &classic_data, sizeof(wm_classic_extension));
}
bool Classic::IsButtonPressed() const
@ -201,6 +168,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)

View File

@ -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);

View File

@ -2,7 +2,7 @@
// 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>
@ -43,7 +43,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 +62,12 @@ 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?
auto& drum_data = reinterpret_cast<DataFormat&>(m_reg.controller_data);
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());
@ -93,8 +92,6 @@ void Drums::GetState(u8* const data)
// flip button bits
drum_data.bt ^= 0xFFFF;
std::memcpy(data, &drum_data, sizeof(wm_drums_extension));
}
bool Drums::IsButtonPressed() const
@ -105,6 +102,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)

View File

@ -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 @@ private:
ControllerEmu::Buttons* m_pads;
ControllerEmu::AnalogStick* m_stick;
};
}
} // namespace WiimoteEmu

View File

@ -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

View File

@ -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:
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

View File

@ -2,7 +2,7 @@
// 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>
@ -61,7 +61,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 +94,12 @@ 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?
auto& guitar_data = reinterpret_cast<DataFormat&>(m_reg.controller_data);
guitar_data = {};
// stick
{
@ -141,8 +137,6 @@ void Guitar::GetState(u8* const data)
// flip button bits
guitar_data.bt ^= 0xFFFF;
std::memcpy(data, &guitar_data, sizeof(wm_guitar_extension));
}
bool Guitar::IsButtonPressed() const
@ -154,6 +148,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)

View File

@ -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 @@ private:
ControllerEmu::AnalogStick* m_stick;
ControllerEmu::Slider* m_slider_bar;
};
}
} // namespace WiimoteEmu

View File

@ -2,7 +2,7 @@
// 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>
@ -30,7 +30,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,11 +68,83 @@ 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;
void Nunchuk::Update()
{
auto& nc_data = *reinterpret_cast<DataFormat*>(&m_reg.controller_data);
nc_data = {};
// stick
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
nc_data.jx = u8(STICK_CENTER + stick_state.x * STICK_RADIUS);
nc_data.jy = u8(STICK_CENTER + stick_state.y * STICK_RADIUS);
// Some terribly coded games check whether to move with a check like
//
// if (x != 0 && y != 0)
// do_movement(x, y);
//
// With keyboard controls, these games break if you simply hit
// of the axes. Adjust this if you're hitting one of the axes so that
// we slightly tweak the other axis.
if (nc_data.jx != STICK_CENTER || nc_data.jy != STICK_CENTER)
{
if (nc_data.jx == STICK_CENTER)
++nc_data.jx;
if (nc_data.jy == STICK_CENTER)
++nc_data.jy;
}
NormalizedAccelData accel;
// tilt
EmulateTilt(&accel, m_tilt);
// swing
EmulateSwing(&accel, m_swing, Config::Get(Config::NUNCHUK_INPUT_SWING_INTENSITY_MEDIUM));
EmulateSwing(&accel, m_swing_slow, Config::Get(Config::NUNCHUK_INPUT_SWING_INTENSITY_SLOW));
EmulateSwing(&accel, m_swing_fast, Config::Get(Config::NUNCHUK_INPUT_SWING_INTENSITY_FAST));
// shake
EmulateShake(&accel, m_shake, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_MEDIUM),
m_shake_step.data());
EmulateShake(&accel, m_shake_soft, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_SOFT),
m_shake_soft_step.data());
EmulateShake(&accel, m_shake_hard, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_HARD),
m_shake_hard_step.data());
// buttons
m_buttons->GetState(&nc_data.bt.hex, nunchuk_button_bitmasks.data());
// flip the button bits :/
nc_data.bt.hex ^= 0x03;
// 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 = (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;
}
bool Nunchuk::IsButtonPressed() const
{
u8 buttons = 0;
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data());
return buttons != 0;
}
void Nunchuk::Reset()
{
m_reg = {};
m_reg.identifier = nunchuk_id;
// Build calibration data:
m_calibration = {{
m_reg.calibration = {{
// Accel Zero X,Y,Z:
ACCEL_ZERO_G,
ACCEL_ZERO_G,
@ -98,82 +170,7 @@ Nunchuk::Nunchuk(ExtensionReg& reg) : Attachment(_trans("Nunchuk"), reg)
0x00,
}};
UpdateCalibrationDataChecksum(m_calibration);
}
void Nunchuk::GetState(u8* const data)
{
wm_nc nc_data = {};
// stick
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
nc_data.jx = u8(STICK_CENTER + stick_state.x * STICK_RADIUS);
nc_data.jy = u8(STICK_CENTER + stick_state.y * STICK_RADIUS);
// Some terribly coded games check whether to move with a check like
//
// if (x != 0 && y != 0)
// do_movement(x, y);
//
// With keyboard controls, these games break if you simply hit
// of the axes. Adjust this if you're hitting one of the axes so that
// we slightly tweak the other axis.
if (nc_data.jx != STICK_CENTER || nc_data.jy != STICK_CENTER)
{
if (nc_data.jx == STICK_CENTER)
++nc_data.jx;
if (nc_data.jy == STICK_CENTER)
++nc_data.jy;
}
AccelData accel;
// tilt
EmulateTilt(&accel, m_tilt);
// swing
EmulateSwing(&accel, m_swing, Config::Get(Config::NUNCHUK_INPUT_SWING_INTENSITY_MEDIUM));
EmulateSwing(&accel, m_swing_slow, Config::Get(Config::NUNCHUK_INPUT_SWING_INTENSITY_SLOW));
EmulateSwing(&accel, m_swing_fast, Config::Get(Config::NUNCHUK_INPUT_SWING_INTENSITY_FAST));
// shake
EmulateShake(&accel, m_shake, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_MEDIUM),
m_shake_step.data());
EmulateShake(&accel, m_shake_soft, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_SOFT),
m_shake_soft_step.data());
EmulateShake(&accel, m_shake_hard, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_HARD),
m_shake_hard_step.data());
// buttons
m_buttons->GetState(&nc_data.bt.hex, nunchuk_button_bitmasks.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);
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;
std::memcpy(data, &nc_data, sizeof(wm_nc));
}
bool Nunchuk::IsButtonPressed() const
{
u8 buttons = 0;
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data());
return buttons != 0;
UpdateCalibrationDataChecksum(m_reg.calibration, CALIBRATION_CHECKSUM_BYTES);
}
ControllerEmu::ControlGroup* Nunchuk::GetGroup(NunchukGroup group)

View File

@ -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;
void GetState(u8* const data) override;
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");
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 @@ public:
{
ACCEL_ZERO_G = 0x80,
ACCEL_ONE_G = 0xB3,
ACCEL_RANGE = (ACCEL_ONE_G - ACCEL_ZERO_G),
};
enum
@ -55,6 +99,7 @@ public:
private:
ControllerEmu::Tilt* m_tilt;
ControllerEmu::Force* m_swing;
ControllerEmu::Force* m_swing_slow;
ControllerEmu::Force* m_swing_fast;

View File

@ -2,7 +2,7 @@
// 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>
@ -44,7 +44,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 +80,12 @@ 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 = {};
auto& tt_data = reinterpret_cast<DataFormat&>(m_reg.controller_data);
tt_data = {};
// stick
{
@ -139,8 +137,6 @@ void Turntable::GetState(u8* const data)
// flip button bits :/
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));
}
bool Turntable::IsButtonPressed() const
@ -150,6 +146,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)

View File

@ -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 @@ private:
ControllerEmu::Slider* m_right_table;
ControllerEmu::Slider* m_crossfade;
};
}
} // namespace WiimoteEmu

View File

@ -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

View File

@ -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;
ExtensionPort(I2CBus* i2c_bus);
bool IsDeviceConnected() const;
void AttachExtension(Extension* dev);
private:
Extension* m_extension = nullptr;
I2CBus& m_i2c_bus;
};
} // namespace WiimoteEmu

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,376 @@
// 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:
static const u8 cdata[32] = {
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:
static const u8 cert[64] = {
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);
}
void MotionPlus::AttachExtension(Extension* ext)
{
extension_port.AttachExtension(ext);
}
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]);
}
int MotionPlus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
if (IsActive())
{
// 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 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};
static const u8 init_data[6] = {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)
{
static const u8 init_data[6] = {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
const u8 mp_cert2[64] = {
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:
auto& mplus_data = *reinterpret_cast<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 = extension_port.IsDeviceConnected();
mplus_data.zero = 0;
}
} // namespace WiimoteEmu

View File

@ -0,0 +1,131 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
namespace WiimoteEmu
{
struct MotionPlus : public Extension
{
public:
MotionPlus();
void Update() override;
void Reset() override;
void DoState(PointerWrap& p) override;
void AttachExtension(Extension* ext);
private:
#pragma pack(push, 1)
struct DataFormat
{
// yaw1, roll1, pitch1: Bits 0-7
// yaw2, roll2, pitch2: Bits 8-13
u8 yaw1;
u8 roll1;
u8 pitch1;
u8 pitch_slow : 1;
u8 yaw_slow : 1;
u8 yaw2 : 6;
u8 extension_connected : 1;
u8 roll_slow : 1;
u8 roll2 : 6;
u8 zero : 1;
u8 is_mp_data : 1;
u8 pitch2 : 6;
};
struct Register
{
u8 controller_data[21];
u8 unknown_0x15[11];
// address 0x20
u8 calibration_data[0x20];
u8 unknown_0x40[0x10];
// address 0x50
u8 cert_data[0x40];
u8 unknown_0x90[0x60];
// address 0xF0
u8 initialized;
// address 0xF1
u8 cert_enable;
// Conduit 2 writes 1 byte to 0xf2 on calibration screen
u8 unknown_0xf2[5];
// address 0xf7
// Wii Sports Resort reads regularly
// Value starts at 0x00 and goes up after activation (not initialization)
// Immediately returns 0x02, even still after 15 and 30 seconds
// After the first data read the value seems to progress to 0x4,0x8,0xc,0xe
// More typical seems to be 2,8,c,e
// A value of 0xe triggers the game to read 64 bytes from 0x50
// The game claims M+ is disconnected after this read of unsatisfactory data
u8 cert_ready;
u8 unknown_0xf8[2];
// address 0xFA
u8 ext_identifier[6];
};
#pragma pack(pop)
static_assert(sizeof(DataFormat) == 6, "Wrong size");
static_assert(0x100 == sizeof(Register));
static const u8 INACTIVE_DEVICE_ADDR = 0x53;
static const u8 ACTIVE_DEVICE_ADDR = 0x52;
enum class PassthroughMode : u8
{
DISABLED = 0x04,
NUNCHUK = 0x05,
CLASSIC = 0x07,
};
bool IsActive() const;
PassthroughMode GetPassthroughMode() const;
// TODO: when activated it seems the motion plus reactivates the extension
// It sends 0x55 to 0xf0
// It also writes 0x00 to slave:0x52 addr:0xfa for some reason
// And starts a write to 0xfa but never writes bytes..
// It tries to read data at 0x00 for 3 times (failing)
// then it reads the 16 bytes of calibration at 0x20 and stops
// TODO: if an extension is attached after activation, it also does this.
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;
bool ReadDeviceDetectPin() const override;
bool IsButtonPressed() const override;
// TODO: rename m_
Register reg_data = {};
// The port on the end of the motion plus:
I2CBus i2c_bus;
ExtensionPort extension_port{&i2c_bus};
};
} // namespace WiimoteEmu

View File

@ -2,6 +2,8 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/HW/WiimoteEmu/Speaker.h"
#include <memory>
#include "AudioCommon/AudioCommon.h"
@ -69,13 +71,13 @@ void stopdamnwav()
}
#endif
void Wiimote::SpeakerData(const u8* data, int length)
void SpeakerLogic::SpeakerData(const u8* data, int length, int speaker_pan)
{
// TODO: should we still process samples for the decoder state?
if (!SConfig::GetInstance().m_WiimoteEnableSpeaker)
return;
if (m_speaker_logic.reg_data.sample_rate == 0 || length == 0)
if (reg_data.sample_rate == 0 || length == 0)
return;
// Even if volume is zero we process samples to maintain proper decoder state.
@ -86,7 +88,7 @@ void Wiimote::SpeakerData(const u8* data, int length)
unsigned int sample_rate_dividend, sample_length;
u8 volume_divisor;
if (m_speaker_logic.reg_data.format == SpeakerLogic::DATA_FORMAT_PCM)
if (reg_data.format == SpeakerLogic::DATA_FORMAT_PCM)
{
// 8 bit PCM
for (int i = 0; i < length; ++i)
@ -99,14 +101,13 @@ void Wiimote::SpeakerData(const u8* data, int length)
volume_divisor = 0xff;
sample_length = (unsigned int)length;
}
else if (m_speaker_logic.reg_data.format == SpeakerLogic::DATA_FORMAT_ADPCM)
else if (reg_data.format == SpeakerLogic::DATA_FORMAT_ADPCM)
{
// 4 bit Yamaha ADPCM (same as dreamcast)
for (int i = 0; i < length; ++i)
{
samples[i * 2] =
adpcm_yamaha_expand_nibble(m_speaker_logic.adpcm_state, (data[i] >> 4) & 0xf);
samples[i * 2 + 1] = adpcm_yamaha_expand_nibble(m_speaker_logic.adpcm_state, data[i] & 0xf);
samples[i * 2] = adpcm_yamaha_expand_nibble(adpcm_state, (data[i] >> 4) & 0xf);
samples[i * 2 + 1] = adpcm_yamaha_expand_nibble(adpcm_state, data[i] & 0xf);
}
// Following details from http://wiibrew.org/wiki/Wiimote#Speaker
@ -116,27 +117,25 @@ void Wiimote::SpeakerData(const u8* data, int length)
}
else
{
ERROR_LOG(IOS_WIIMOTE, "Unknown speaker format %x", m_speaker_logic.reg_data.format);
ERROR_LOG(IOS_WIIMOTE, "Unknown speaker format %x", reg_data.format);
return;
}
if (m_speaker_logic.reg_data.volume > volume_divisor)
if (reg_data.volume > volume_divisor)
{
DEBUG_LOG(IOS_WIIMOTE, "Wiimote volume is higher than suspected maximum!");
volume_divisor = m_speaker_logic.reg_data.volume;
volume_divisor = reg_data.volume;
}
// Speaker Pan
// GUI clamps pan setting from -127 to 127. Why?
const auto pan = (unsigned int)(m_options->numeric_settings[0]->GetValue() * 100);
// TODO: use speaker pan law
const unsigned int sample_rate = sample_rate_dividend / m_speaker_logic.reg_data.sample_rate;
float speaker_volume_ratio = (float)m_speaker_logic.reg_data.volume / volume_divisor;
const unsigned int sample_rate = sample_rate_dividend / reg_data.sample_rate;
float speaker_volume_ratio = (float)reg_data.volume / volume_divisor;
// Sloppy math:
unsigned int left_volume =
MathUtil::Clamp<unsigned int>((0xff + (2 * pan)) * speaker_volume_ratio, 0, 0xff);
MathUtil::Clamp<unsigned int>((0xff + (2 * speaker_pan)) * speaker_volume_ratio, 0, 0xff);
unsigned int right_volume =
MathUtil::Clamp<unsigned int>((0xff - (2 * pan)) * speaker_volume_ratio, 0, 0xff);
MathUtil::Clamp<unsigned int>((0xff - (2 * speaker_pan)) * speaker_volume_ratio, 0, 0xff);
g_sound_stream->GetMixer()->SetWiimoteSpeakerVolume(left_volume, right_volume);
@ -166,4 +165,46 @@ void Wiimote::SpeakerData(const u8* data, int length)
num++;
#endif
}
void SpeakerLogic::Reset()
{
reg_data = {};
// Yamaha ADPCM state initialize
adpcm_state.predictor = 0;
adpcm_state.step = 127;
}
void SpeakerLogic::DoState(PointerWrap& p)
{
p.Do(adpcm_state);
p.Do(reg_data);
}
int SpeakerLogic::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 SpeakerLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
if (I2C_ADDR != slave_addr)
return 0;
if (0x00 == addr)
{
ERROR_LOG(WIIMOTE, "Writing of speaker data to address 0x00 is unimplemented!");
return count;
}
else
{
// TODO: Does writing immediately change the decoder config even when active
// or does a write to 0x08 activate the new configuration or something?
return RawWrite(&reg_data, addr, count, data_in);
}
}
} // namespace WiimoteEmu

View File

@ -0,0 +1,67 @@
// 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/I2CBus.h"
namespace WiimoteEmu
{
struct ADPCMState
{
s32 predictor, step;
};
class SpeakerLogic : public I2CSlave
{
public:
static const u8 I2C_ADDR = 0x51;
void Reset();
void DoState(PointerWrap& p);
void SpeakerData(const u8* data, int length, int speaker_pan);
private:
// TODO: enum class
static const u8 DATA_FORMAT_ADPCM = 0x00;
static const u8 DATA_FORMAT_PCM = 0x40;
// TODO: It seems reading address 0x00 should always return 0xff.
#pragma pack(push, 1)
struct Register
{
// Speaker reports result in a write of samples to addr 0x00 (which also plays sound)
u8 speaker_data;
u8 unk_1;
u8 format;
// seems to always play at 6khz no matter what this is set to?
// or maybe it only applies to pcm input
// Little-endian:
u16 sample_rate;
u8 volume;
u8 unk_5;
u8 unk_6;
// Reading this byte on real hardware seems to return 0x09:
u8 unk_7;
u8 unk_8;
u8 unknown[0xf6];
};
#pragma pack(pop)
static_assert(0x100 == sizeof(Register));
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;
// TODO: What actions reset this state?
// Is this actually in the register somewhere?
ADPCMState adpcm_state;
};
} // namespace WiimoteEmu

File diff suppressed because it is too large Load Diff

View File

@ -4,25 +4,25 @@
#pragma once
#include <algorithm>
#include <array>
#include <numeric>
#include <string>
#include "Common/Logging/Log.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
// Registry sizes
#define WIIMOTE_EEPROM_SIZE (16 * 1024)
#define WIIMOTE_EEPROM_FREE_SIZE 0x1700
#include "Core/HW/WiimoteEmu/Camera.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
#include "Core/HW/WiimoteEmu/MotionPlus.h"
#include "Core/HW/WiimoteEmu/Speaker.h"
class PointerWrap;
namespace ControllerEmu
{
class Attachments;
class BooleanSetting;
class Buttons;
class ControlGroup;
@ -35,10 +35,12 @@ class Output;
class Tilt;
} // namespace ControllerEmu
// Needed for friendship:
namespace WiimoteReal
{
class Wiimote;
}
} // namespace WiimoteReal
namespace WiimoteEmu
{
enum class WiimoteGroup
@ -50,309 +52,51 @@ enum class WiimoteGroup
Tilt,
Swing,
Rumble,
Extension,
Attachments,
Options,
Hotkeys
};
enum
enum class NunchukGroup;
enum class ClassicGroup;
enum class GuitarGroup;
enum class DrumsGroup;
enum class TurntableGroup;
template <typename T>
void UpdateCalibrationDataChecksum(T& data, int cksum_bytes)
{
EXT_NONE,
constexpr u8 CALIBRATION_MAGIC_NUMBER = 0x55;
EXT_NUNCHUK,
EXT_CLASSIC,
EXT_GUITAR,
EXT_DRUMS,
EXT_TURNTABLE
};
static_assert(std::is_same<decltype(data[0]), u8&>::value, "Only sane for containers of u8!");
enum class NunchukGroup
{
Buttons,
Stick,
Tilt,
Swing,
Shake
};
auto cksum_start = std::end(data) - cksum_bytes;
enum class ClassicGroup
{
Buttons,
Triggers,
DPad,
LeftStick,
RightStick
};
// Checksum is a sum of the previous bytes plus a magic value (0x55).
// Extension calibration data has a 2nd checksum byte which is
// the magic value (0x55) added to the previous checksum byte.
u8 checksum = std::accumulate(std::begin(data), cksum_start, CALIBRATION_MAGIC_NUMBER);
enum class GuitarGroup
{
Buttons,
Frets,
Strum,
Whammy,
Stick,
SliderBar
};
enum class DrumsGroup
{
Buttons,
Pads,
Stick
};
enum class TurntableGroup
{
Buttons,
Stick,
EffectDial,
LeftTable,
RightTable,
Crossfade
};
#pragma pack(push, 1)
struct ReportFeatures
{
// Byte counts:
// Features are always in the following order in an input report:
u8 core_size, accel_size, ir_size, ext_size, total_size;
int GetCoreOffset() const { return 2; }
int GetAccelOffset() const { return GetCoreOffset() + core_size; }
int GetIROffset() const { return GetAccelOffset() + accel_size; }
int GetExtOffset() const { return GetIROffset() + ir_size; }
};
struct AccelData
{
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?
};
struct ADPCMState
{
s32 predictor, step;
};
struct ExtensionReg
{
// 16 bytes of possible extension data
u8 controller_data[0x10];
u8 unknown2[0x10];
// address 0x20
u8 calibration[0x10];
u8 unknown3[0x10];
// address 0x40
u8 encryption_key[0x10];
u8 unknown4[0xA0];
// address 0xF0
u8 encryption;
u8 unknown5[0x9];
// address 0xFA
u8 constant_id[6];
};
#pragma pack(pop)
static_assert(0x100 == sizeof(ExtensionReg));
void UpdateCalibrationDataChecksum(std::array<u8, 0x10>& data);
void EmulateShake(AccelData* accel, ControllerEmu::Buttons* buttons_group, double intensity,
u8* shake_step);
void EmulateDynamicShake(AccelData* accel, DynamicData& dynamic_data,
ControllerEmu::Buttons* buttons_group, const DynamicConfiguration& config,
u8* shake_step);
void EmulateTilt(AccelData* accel, ControllerEmu::Tilt* tilt_group, bool sideways = false,
bool upright = false);
void EmulateSwing(AccelData* accel, ControllerEmu::Force* swing_group, double intensity,
bool sideways = false, bool upright = false);
void EmulateDynamicSwing(AccelData* accel, DynamicData& dynamic_data,
ControllerEmu::Force* swing_group, const DynamicConfiguration& config,
bool sideways = false, bool upright = false);
enum
{
ACCEL_ZERO_G = 0x80,
ACCEL_ONE_G = 0x9A,
ACCEL_RANGE = (ACCEL_ONE_G - ACCEL_ZERO_G),
};
class I2CSlave
{
public:
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)
for (auto& i = cksum_start; i != std::end(data); ++i)
{
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;
*i = checksum;
checksum += CALIBRATION_MAGIC_NUMBER;
}
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) { m_slaves.emplace_back(slave); }
void RemoveSlave(I2CSlave* slave)
{
m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end());
}
void Reset() { m_slaves.clear(); }
// TODO: change int to u16 or something
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
{
// TODO: the real bus seems to read in blocks of 8.
// peripherals NACK after each 8 bytes.
// 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;
}
// TODO: change int to u16 or something
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
{
// TODO: write in blocks of 6 to simulate the real bus
// sometimes it writes in blocks of 8, (speaker data)
// this might trigger activation writes more accurately
// 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;
}
private:
// Pointers are unowned:
std::vector<I2CSlave*> m_slaves;
};
class ExtensionAttachment : public I2CSlave
{
public:
virtual bool ReadDeviceDetectPin() = 0;
};
class ExtensionPort
{
public:
ExtensionPort(I2CBus& _i2c_bus) : m_i2c_bus(_i2c_bus) {}
// Simulates the "device-detect" pin.
// Wiimote uses this to detect extension change..
// and then send a status report..
bool IsDeviceConnected()
{
if (m_attachment)
return m_attachment->ReadDeviceDetectPin();
else
return false;
}
void SetAttachment(ExtensionAttachment* dev)
{
m_i2c_bus.RemoveSlave(m_attachment);
m_attachment = dev;
if (dev)
m_i2c_bus.AddSlave(dev);
}
private:
ExtensionAttachment* m_attachment;
I2CBus& m_i2c_bus;
};
}
class Wiimote : public ControllerEmu::EmulatedController
{
friend class WiimoteReal::Wiimote;
public:
enum
enum : u8
{
ACCEL_ZERO_G = 0x80,
ACCEL_ONE_G = 0x9A,
};
enum : u16
{
PAD_LEFT = 0x01,
PAD_RIGHT = 0x02,
@ -369,7 +113,10 @@ public:
};
explicit Wiimote(unsigned int index);
std::string GetName() const override;
void LoadDefaults(const ControllerInterface& ciface) override;
ControllerEmu::ControlGroup* GetWiimoteGroup(WiimoteGroup group);
ControllerEmu::ControlGroup* GetNunchukGroup(NunchukGroup group);
ControllerEmu::ControlGroup* GetClassicGroup(ClassicGroup group);
@ -378,409 +125,109 @@ public:
ControllerEmu::ControlGroup* GetTurntableGroup(TurntableGroup group);
void Update();
void InterruptChannel(u16 channel_id, const void* data, u32 size);
void ControlChannel(u16 channel_id, const void* data, u32 size);
void SpeakerData(const u8* data, int length);
bool CheckForButtonPress();
void Reset();
void DoState(PointerWrap& p);
void RealState();
void LoadDefaults(const ControllerInterface& ciface) override;
int CurrentExtension() const;
protected:
bool Step();
void HidOutputReport(const wm_report* sr, bool send_ack = true);
void HandleExtensionSwap();
void UpdateButtonsStatus();
void GetButtonData(u8* data);
void GetAccelData(s16* x, s16* y, s16* z);
void UpdateIRData(bool use_accel);
// Active extension number is exposed for TAS.
ExtensionNumber GetActiveExtensionNumber() const;
private:
I2CBus m_i2c_bus;
// Used only for error generation:
static constexpr u8 EEPROM_I2C_ADDR = 0x50;
ExtensionPort m_extension_port{m_i2c_bus};
// static constexpr int EEPROM_SIZE = 16 * 1024;
// This is the region exposed over bluetooth:
static constexpr int EEPROM_FREE_SIZE = 0x1700;
struct IRCameraLogic : public I2CSlave
{
// TODO: some of this memory is write-only and should return error 7.
void UpdateButtonsStatus();
struct RegData
{
// 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];
} reg_data;
void GetAccelData(NormalizedAccelData* accel);
static_assert(0x100 == sizeof(reg_data));
void HIDOutputReport(const void* data, u32 size);
static const u8 DEVICE_ADDR = 0x58;
void HandleReportRumble(const WiimoteCommon::OutputReportRumble&);
void HandleReportLeds(const WiimoteCommon::OutputReportLeds&);
void HandleReportMode(const WiimoteCommon::OutputReportMode&);
void HandleRequestStatus(const WiimoteCommon::OutputReportRequestStatus&);
void HandleReadData(const WiimoteCommon::OutputReportReadData&);
void HandleWriteData(const WiimoteCommon::OutputReportWriteData&);
void HandleIRPixelClock(const WiimoteCommon::OutputReportEnableFeature&);
void HandleIRLogic(const WiimoteCommon::OutputReportEnableFeature&);
void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&);
void HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature&);
void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&);
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override
{
if (DEVICE_ADDR != slave_addr)
return 0;
template <typename T, typename H>
void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size);
return RawRead(&reg_data, addr, count, data_out);
}
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override
{
if (DEVICE_ADDR != slave_addr)
return 0;
return RawWrite(&reg_data, addr, count, data_in);
}
} m_camera_logic;
class ExtensionLogic : public ExtensionAttachment
{
public:
ExtensionReg reg_data;
wiimote_key ext_key;
ControllerEmu::Extension* extension;
static const u8 DEVICE_ADDR = 0x52;
void Update();
private:
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override
{
if (DEVICE_ADDR != slave_addr)
return 0;
if (0x00 == addr)
{
// Here we should sample user input and update controller data
// Moved into Update() function for TAS determinism
// TAS code fails to sync data reads and such..
// extension->GetState(reg_data.controller_data);
}
auto const result = RawRead(&reg_data, addr, count, data_out);
// Encrypt data read from extension register
// Check if encrypted reads is on
if (0xaa == reg_data.encryption)
{
// INFO_LOG(WIIMOTE, "Encrypted read.");
WiimoteEncrypt(&ext_key, data_out, addr, (u8)count);
}
else
{
// INFO_LOG(WIIMOTE, "Unencrypted read.");
}
return result;
}
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override
{
if (DEVICE_ADDR != slave_addr)
return 0;
auto const result = RawWrite(&reg_data, addr, count, data_in);
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
WiimoteGenerateKey(&ext_key, reg_data.encryption_key);
}
return result;
}
bool ReadDeviceDetectPin() override;
} m_ext_logic;
struct SpeakerLogic : public I2CSlave
{
// TODO: enum class
static const u8 DATA_FORMAT_ADPCM = 0x00;
static const u8 DATA_FORMAT_PCM = 0x40;
// TODO: It seems reading address 0x00 should always return 0xff.
#pragma pack(push, 1)
struct
{
// Speaker reports result in a write of samples to addr 0x00 (which also plays sound)
u8 speaker_data;
u8 unk_1;
u8 format;
// seems to always play at 6khz no matter what this is set to?
// or maybe it only applies to pcm input
// Little-endian:
u16 sample_rate;
u8 volume;
u8 unk_5;
u8 unk_6;
// Reading this byte on real hardware seems to return 0x09:
u8 unk_7;
u8 unk_8;
u8 unknown[0xf6];
} reg_data;
#pragma pack(pop)
static_assert(0x100 == sizeof(reg_data));
// TODO: What actions should reset this state?
ADPCMState adpcm_state;
static const u8 DEVICE_ADDR = 0x51;
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override
{
if (DEVICE_ADDR != slave_addr)
return 0;
return RawRead(&reg_data, addr, count, data_out);
}
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override
{
if (DEVICE_ADDR != slave_addr)
return 0;
if (0x00 == addr)
{
ERROR_LOG(WIIMOTE, "Writing of speaker data to address 0x00 is unimplemented!");
return count;
}
else
{
// TODO: Does writing immediately change the decoder config even when active
// or does a write to 0x08 activate the new configuration or something?
return RawWrite(&reg_data, addr, count, data_in);
}
}
} m_speaker_logic;
struct MotionPlusLogic : public ExtensionAttachment
{
private:
// The bus on the end of the motion plus:
I2CBus i2c_bus;
public:
// The port on the end of the motion plus:
ExtensionPort extension_port{i2c_bus};
void Update();
#pragma pack(push, 1)
struct MotionPlusRegister
{
u8 controller_data[21];
u8 unknown_0x15[11];
// address 0x20
u8 calibration_data[0x20];
u8 unknown_0x40[0x10];
// address 0x50
u8 cert_data[0x40];
u8 unknown_0x90[0x60];
// address 0xF0
u8 initialized;
// address 0xF1
u8 cert_enable;
// Conduit 2 writes 1 byte to 0xf2 on calibration screen
u8 unknown_0xf2[5];
// address 0xf7
// Wii Sports Resort reads regularly
// Value starts at 0x00 and goes up after activation (not initialization)
// Immediately returns 0x02, even still after 15 and 30 seconds
// After the first data read the value seems to progress to 0x4,0x8,0xc,0xe
// More typical seems to be 2,8,c,e
// A value of 0xe triggers the game to read 64 bytes from 0x50
// The game claims M+ is disconnected after this read of unsatisfactory data
u8 cert_ready;
u8 unknown_0xf8[2];
// address 0xFA
u8 ext_identifier[6];
} reg_data;
#pragma pack(pop)
static_assert(0x100 == sizeof(reg_data));
private:
static const u8 INACTIVE_DEVICE_ADDR = 0x53;
static const u8 ACTIVE_DEVICE_ADDR = 0x52;
enum class PassthroughMode : u8
{
DISABLED = 0x04,
NUNCHUK = 0x05,
CLASSIC = 0x07,
};
bool IsActive() const { return ACTIVE_DEVICE_ADDR << 1 == reg_data.ext_identifier[2]; }
PassthroughMode GetPassthroughMode() const
{
return static_cast<PassthroughMode>(reg_data.ext_identifier[4]);
}
// TODO: when activated it seems the motion plus reactivates the extension
// It sends 0x55 to 0xf0
// It also writes 0x00 to slave:0x52 addr:0xfa for some reason
// And starts a write to 0xfa but never writes bytes..
// It tries to read data at 0x00 for 3 times (failing)
// then it reads the 16 bytes of calibration at 0x20 and stops
// TODO: if an extension is attached after activation, it also does this.
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override
{
if (IsActive())
{
// 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 BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override
{
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);
}
}
}
private:
bool ReadDeviceDetectPin() override
{
if (IsActive())
{
return true;
}
else
{
// When inactive the device detect pin reads from ext port:
return extension_port.IsDeviceConnected();
}
}
} m_motion_plus_logic;
void ReportMode(const wm_report_mode* dr);
void SendAck(u8 report_id, WiimoteErrorCode error_code = WiimoteErrorCode::SUCCESS);
void RequestStatus(const wm_request_status* rs = nullptr);
void ReadData(const wm_read_data* rd);
void WriteData(const wm_write_data* wd);
void HandleExtensionSwap();
bool ProcessExtensionPortEvent();
void SendDataReport();
bool ProcessReadDataRequest();
void RealState();
void SetRumble(bool on);
void CallbackInterruptChannel(const u8* data, u32 size);
void SendAck(WiimoteCommon::OutputReportID rpt_id, WiimoteCommon::ErrorCode err);
bool IsSideways() const;
bool IsUpright() const;
Extension* GetActiveExtension() const;
bool NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode);
// control groups
// TODO: Kill this nonsensical function used for TAS:
EncryptionKey GetExtensionEncryptionKey() const;
struct ReadRequest
{
WiimoteCommon::AddressSpace space;
u8 slave_address;
u16 address;
u16 size;
};
// This is just the usable 0x1700 bytes:
union UsableEEPROMData
{
struct
{
// addr: 0x0000
std::array<u8, 11> ir_calibration_1;
std::array<u8, 11> ir_calibration_2;
std::array<u8, 10> accel_calibration_1;
std::array<u8, 10> accel_calibration_2;
// addr: 0x002A
std::array<u8, 0x0FA0> user_data;
// addr: 0x0FCA
std::array<u8, 0x02f0> mii_data_1;
std::array<u8, 0x02f0> mii_data_2;
// addr: 0x15AA
std::array<u8, 0x0126> unk_1;
// addr: 0x16D0
std::array<u8, 24> unk_2;
std::array<u8, 24> unk_3;
};
std::array<u8, EEPROM_FREE_SIZE> data;
};
static_assert(EEPROM_FREE_SIZE == sizeof(UsableEEPROMData));
// Control groups for user input:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::Buttons* m_dpad;
ControllerEmu::Buttons* m_shake;
@ -795,49 +242,46 @@ private:
ControllerEmu::Force* m_swing_dynamic;
ControllerEmu::ControlGroup* m_rumble;
ControllerEmu::Output* m_motor;
ControllerEmu::Extension* m_extension;
ControllerEmu::Attachments* m_attachments;
ControllerEmu::ControlGroup* m_options;
ControllerEmu::BooleanSetting* m_sideways_setting;
ControllerEmu::BooleanSetting* m_upright_setting;
ControllerEmu::NumericSetting* m_battery_setting;
ControllerEmu::ModifySettingsButton* m_hotkeys;
DynamicData m_swing_dynamic_data;
DynamicData m_shake_dynamic_data;
SpeakerLogic m_speaker_logic;
MotionPlus m_motion_plus;
CameraLogic m_camera_logic;
// Wiimote accel data
// TODO: can this member be eliminated?
AccelData m_accel;
I2CBus m_i2c_bus;
ExtensionPort m_extension_port{&m_i2c_bus};
// Wiimote index, 0-3
const u8 m_index;
double ir_sin, ir_cos; // for the low pass filter
u16 m_reporting_channel;
WiimoteCommon::InputReportID m_reporting_mode;
bool m_reporting_continuous;
bool m_rumble_on;
bool m_speaker_mute;
bool m_reporting_auto;
u8 m_reporting_mode;
u16 m_reporting_channel;
// This is just for the IR Camera to compensate for the sensor bar position.
bool m_sensor_bar_on_top;
WiimoteCommon::InputReportStatus m_status;
ExtensionNumber m_active_extension;
ReadRequest m_read_request;
UsableEEPROMData m_eeprom;
// Dynamics:
std::array<u8, 3> m_shake_step{};
std::array<u8, 3> m_shake_soft_step{};
std::array<u8, 3> m_shake_hard_step{};
std::array<u8, 3> m_shake_dynamic_step{};
bool m_sensor_bar_on_top;
wm_status_report m_status;
struct ReadRequest
{
WiimoteAddressSpace space;
u8 slave_address;
u16 address;
u16 size;
} m_read_request;
u8 m_eeprom[WIIMOTE_EEPROM_SIZE];
DynamicData m_swing_dynamic_data;
DynamicData m_shake_dynamic_data;
};
} // namespace WiimoteEmu

View File

@ -28,6 +28,8 @@
#include "Common/ScopeGuard.h"
#include "Common/Thread.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteReal/IOWin.h"
// Create func_t function pointer type and declare a nullptr-initialized static variable of that
@ -436,6 +438,8 @@ int ReadFromHandle(HANDLE& dev_handle, u8* buf)
bool IsWiimote(const std::basic_string<TCHAR>& device_path, WinWriteMethod& method)
{
using namespace WiimoteCommon;
HANDLE dev_handle = CreateFile(device_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr);
@ -445,7 +449,7 @@ bool IsWiimote(const std::basic_string<TCHAR>& device_path, WinWriteMethod& meth
Common::ScopeGuard handle_guard{[&dev_handle] { CloseHandle(dev_handle); }};
u8 buf[MAX_PAYLOAD];
u8 const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT, RT_REQUEST_STATUS, 0};
u8 const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::REQUEST_STATUS), 0};
int invalid_report_count = 0;
int rc = WriteToHandle(dev_handle, method, req_status_report, sizeof(req_status_report));
while (rc > 0)
@ -454,9 +458,9 @@ bool IsWiimote(const std::basic_string<TCHAR>& device_path, WinWriteMethod& meth
if (rc <= 0)
break;
switch (buf[1])
switch (InputReportID(buf[1]))
{
case RT_STATUS_REPORT:
case InputReportID::STATUS:
return true;
default:
WARN_LOG(WIIMOTE, "IsWiimote(): Received unexpected report %02x", buf[1]);
@ -691,34 +695,25 @@ bool WiimoteWindows::IsConnected() const
}
// See http://wiibrew.org/wiki/Wiimote for the Report IDs and its sizes
size_t GetReportSize(u8 report_id)
size_t GetReportSize(u8 rid)
{
using namespace WiimoteCommon;
const auto report_id = static_cast<InputReportID>(rid);
switch (report_id)
{
case RT_STATUS_REPORT:
return sizeof(wm_status_report);
case RT_READ_DATA_REPLY:
return sizeof(wm_read_data_reply);
case RT_ACK_DATA:
return sizeof(wm_acknowledge);
case RT_REPORT_CORE:
return sizeof(wm_report_core);
case RT_REPORT_CORE_ACCEL:
return sizeof(wm_report_core_accel);
case RT_REPORT_CORE_EXT8:
return sizeof(wm_report_core_ext8);
case RT_REPORT_CORE_ACCEL_IR12:
return sizeof(wm_report_core_accel_ir12);
case RT_REPORT_CORE_EXT19:
case RT_REPORT_CORE_ACCEL_EXT16:
case RT_REPORT_CORE_IR10_EXT9:
case RT_REPORT_CORE_ACCEL_IR10_EXT6:
case RT_REPORT_EXT21:
case RT_REPORT_INTERLEAVE1:
case RT_REPORT_INTERLEAVE2:
return sizeof(wm_report_ext21);
case InputReportID::STATUS:
return sizeof(InputReportStatus);
case InputReportID::READ_DATA_REPLY:
return sizeof(InputReportReadDataReply);
case InputReportID::ACK:
return sizeof(InputReportAck);
default:
return 0;
if (DataReportBuilder::IsValidMode(report_id))
return MakeDataReportManipulator(report_id, nullptr)->GetDataSize();
else
return 0;
}
}
@ -1004,4 +999,4 @@ bool ForgetWiimote(BLUETOOTH_DEVICE_INFO_STRUCT& btdi)
return false;
}
}
} // namespace WiimoteReal

View File

@ -298,7 +298,7 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
return;
}
if (length > MAX_PAYLOAD)
if (length > WiimoteCommon::MAX_PAYLOAD)
{
WARN_LOG(WIIMOTE, "Dropping packet for Wiimote %i, too large", wm->GetIndex() + 1);
return;

View File

@ -10,6 +10,9 @@
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteReal/IOhidapi.h"
using namespace WiimoteCommon;
using namespace WiimoteReal;
static bool IsDeviceUsable(const std::string& device_path)
{
hid_device* handle = hid_open_path(device_path.c_str());
@ -24,7 +27,7 @@ static bool IsDeviceUsable(const std::string& device_path)
// Some third-party adapters (DolphinBar) always expose all four Wii Remotes as HIDs
// even when they are not connected, which causes an endless error loop when we try to use them.
// Try to write a report to the device to see if this Wii Remote is really usable.
static const u8 report[] = {WR_SET_REPORT | BT_OUTPUT, RT_REQUEST_STATUS, 0};
static const u8 report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::REQUEST_STATUS), 0};
const int result = hid_write(handle, report, sizeof(report));
// The DolphinBar uses EPIPE to signal the absence of a Wii Remote connected to this HID.
if (result == -1 && errno != EPIPE)
@ -145,4 +148,4 @@ int WiimoteHidapi::IOWrite(const u8* buf, size_t len)
}
return (result == 0) ? 1 : result;
}
}; // WiimoteReal
}; // namespace WiimoteReal

View File

@ -34,6 +34,8 @@ unsigned int g_wiimote_sources[MAX_BBMOTES];
namespace WiimoteReal
{
using namespace WiimoteCommon;
static void TryToConnectBalanceBoard(Wiimote*);
static void TryToConnectWiimote(Wiimote*);
static void HandleWiimoteDisconnect(int index);
@ -69,7 +71,7 @@ void Wiimote::WriteReport(Report rpt)
bool const new_rumble_state = (rpt[2] & 0x1) != 0;
// If this is a rumble report and the rumble state didn't change, ignore.
if (rpt[1] == RT_RUMBLE && new_rumble_state == m_rumble_state)
if (rpt[1] == u8(OutputReportID::RUMBLE) && new_rumble_state == m_rumble_state)
return;
m_rumble_state = new_rumble_state;
@ -95,22 +97,22 @@ void Wiimote::DisableDataReporting()
{
m_last_input_report.clear();
// This probably accomplishes nothing.
wm_report_mode rpt = {};
rpt.mode = RT_REPORT_CORE;
// This accomplishes very little:
OutputReportMode rpt = {};
rpt.mode = InputReportID::REPORT_CORE;
rpt.continuous = 0;
rpt.rumble = 0;
QueueReport(RT_REPORT_MODE, &rpt, sizeof(rpt));
QueueReport(u8(OutputReportID::REPORT_MODE), &rpt, sizeof(rpt));
}
void Wiimote::EnableDataReporting(u8 mode)
{
m_last_input_report.clear();
wm_report_mode rpt = {};
rpt.mode = mode;
OutputReportMode rpt = {};
rpt.mode = InputReportID(mode);
rpt.continuous = 1;
QueueReport(RT_REPORT_MODE, &rpt, sizeof(rpt));
QueueReport(u8(OutputReportID::REPORT_MODE), &rpt, sizeof(rpt));
}
void Wiimote::SetChannel(u16 channel)
@ -139,11 +141,11 @@ void Wiimote::ControlChannel(const u16 channel, const void* const data, const u3
else
{
InterruptChannel(channel, data, size);
const hid_packet* const hidp = reinterpret_cast<const hid_packet*>(data);
if (hidp->type == HID_TYPE_SET_REPORT)
const auto& hidp = *static_cast<const HIDPacket*>(data);
if (hidp.type == HID_TYPE_SET_REPORT)
{
u8 handshake_ok = HID_HANDSHAKE_SUCCESS;
Core::Callback_WiimoteInterruptChannel(m_index, channel, &handshake_ok, sizeof(handshake_ok));
u8 handshake = HID_HANDSHAKE_SUCCESS;
Core::Callback_WiimoteInterruptChannel(m_index, channel, &handshake, sizeof(handshake));
}
}
}
@ -175,20 +177,21 @@ void Wiimote::InterruptChannel(const u16 channel, const void* const data, const
// Disallow games from turning off all of the LEDs.
// It makes Wiimote connection status confusing.
if (rpt[1] == RT_LEDS)
if (rpt[1] == u8(OutputReportID::LEDS))
{
auto& leds_rpt = *reinterpret_cast<wm_leds*>(&rpt[2]);
auto& leds_rpt = *reinterpret_cast<OutputReportLeds*>(&rpt[2]);
if (0 == leds_rpt.leds)
{
// Turn on ALL of the LEDs.
leds_rpt.leds = 0xf;
}
}
else if (rpt[1] == RT_WRITE_SPEAKER_DATA && (!SConfig::GetInstance().m_WiimoteEnableSpeaker ||
(!wm->m_status.speaker || wm->m_speaker_mute)))
else if (rpt[1] == u8(OutputReportID::SPEAKER_DATA) &&
(!SConfig::GetInstance().m_WiimoteEnableSpeaker ||
(!wm->m_status.speaker || wm->m_speaker_mute)))
{
// Translate speaker data reports into rumble reports.
rpt[1] = RT_RUMBLE;
rpt[1] = u8(OutputReportID::RUMBLE);
// Keep only the rumble bit.
rpt[2] &= 0x1;
rpt.resize(3);
@ -251,11 +254,25 @@ bool Wiimote::IsBalanceBoard()
if (!ConnectInternal())
return false;
// Initialise the extension by writing 0x55 to 0xa400f0, then writing 0x00 to 0xa400fb.
static const u8 init_extension_rpt1[MAX_PAYLOAD] = {
WR_SET_REPORT | BT_OUTPUT, RT_WRITE_DATA, 0x04, 0xa4, 0x00, 0xf0, 0x01, 0x55};
static const u8 init_extension_rpt2[MAX_PAYLOAD] = {
WR_SET_REPORT | BT_OUTPUT, RT_WRITE_DATA, 0x04, 0xa4, 0x00, 0xfb, 0x01, 0x00};
static const u8 status_report[] = {WR_SET_REPORT | BT_OUTPUT, RT_REQUEST_STATUS, 0};
// TODO: Use the structs for building these reports..
static const u8 init_extension_rpt1[MAX_PAYLOAD] = {WR_SET_REPORT | BT_OUTPUT,
u8(OutputReportID::WRITE_DATA),
0x04,
0xa4,
0x00,
0xf0,
0x01,
0x55};
static const u8 init_extension_rpt2[MAX_PAYLOAD] = {WR_SET_REPORT | BT_OUTPUT,
u8(OutputReportID::WRITE_DATA),
0x04,
0xa4,
0x00,
0xfb,
0x01,
0x00};
static const u8 status_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::REQUEST_STATUS),
0};
if (!IOWrite(init_extension_rpt1, sizeof(init_extension_rpt1)) ||
!IOWrite(init_extension_rpt2, sizeof(init_extension_rpt2)))
{
@ -271,23 +288,29 @@ bool Wiimote::IsBalanceBoard()
if (ret == -1)
continue;
switch (buf[1])
switch (InputReportID(buf[1]))
{
case RT_STATUS_REPORT:
case InputReportID::STATUS:
{
const auto* status = reinterpret_cast<wm_status_report*>(&buf[2]);
const auto* status = reinterpret_cast<InputReportStatus*>(&buf[2]);
// A Balance Board has a Balance Board extension.
if (!status->extension)
return false;
// Read two bytes from 0xa400fe to identify the extension.
static const u8 identify_ext_rpt[] = {
WR_SET_REPORT | BT_OUTPUT, RT_READ_DATA, 0x04, 0xa4, 0x00, 0xfe, 0x02, 0x00};
static const u8 identify_ext_rpt[] = {WR_SET_REPORT | BT_OUTPUT,
u8(OutputReportID::READ_DATA),
0x04,
0xa4,
0x00,
0xfe,
0x02,
0x00};
ret = IOWrite(identify_ext_rpt, sizeof(identify_ext_rpt));
break;
}
case RT_READ_DATA_REPLY:
case InputReportID::READ_DATA_REPLY:
{
const auto* reply = reinterpret_cast<wm_read_data_reply*>(&buf[2]);
const auto* reply = reinterpret_cast<InputReportReadDataReply*>(&buf[2]);
if (Common::swap16(reply->address) != 0x00fe)
{
ERROR_LOG(WIIMOTE, "IsBalanceBoard(): Received unexpected data reply for address %X",
@ -297,15 +320,17 @@ bool Wiimote::IsBalanceBoard()
// A Balance Board ext can be identified by checking for 0x0402.
return reply->data[0] == 0x04 && reply->data[1] == 0x02;
}
case RT_ACK_DATA:
case InputReportID::ACK:
{
const auto* ack = reinterpret_cast<wm_acknowledge*>(&buf[2]);
if (ack->reportID == RT_READ_DATA && ack->errorID != 0x00)
const auto* ack = reinterpret_cast<InputReportAck*>(&buf[2]);
if (ack->rpt_id == OutputReportID::READ_DATA && ack->error_code != ErrorCode::SUCCESS)
{
WARN_LOG(WIIMOTE, "Failed to read from 0xa400fe, assuming Wiimote is not a Balance Board.");
return false;
}
}
default:
break;
}
}
return false;
@ -313,7 +338,7 @@ bool Wiimote::IsBalanceBoard()
static bool IsDataReport(const Report& rpt)
{
return rpt.size() >= 2 && rpt[1] >= RT_REPORT_CORE;
return rpt.size() >= 2 && rpt[1] >= u8(InputReportID::REPORT_CORE);
}
// Returns the next report that should be sent
@ -365,19 +390,20 @@ bool Wiimote::CheckForButtonPress()
const Report& rpt = ProcessReadQueue();
if (rpt.size() >= 4)
{
switch (rpt[1])
switch (InputReportID(rpt[1]))
{
case RT_REPORT_CORE:
case RT_REPORT_CORE_ACCEL:
case RT_REPORT_CORE_EXT8:
case RT_REPORT_CORE_ACCEL_IR12:
case RT_REPORT_CORE_EXT19:
case RT_REPORT_CORE_ACCEL_EXT16:
case RT_REPORT_CORE_IR10_EXT9:
case RT_REPORT_CORE_ACCEL_IR10_EXT6:
case RT_REPORT_INTERLEAVE1:
case RT_REPORT_INTERLEAVE2:
case InputReportID::REPORT_CORE:
case InputReportID::REPORT_CORE_ACCEL:
case InputReportID::REPORT_CORE_EXT8:
case InputReportID::REPORT_CORE_ACCEL_IR12:
case InputReportID::REPORT_CORE_EXT19:
case InputReportID::REPORT_CORE_ACCEL_EXT16:
case InputReportID::REPORT_CORE_IR10_EXT9:
case InputReportID::REPORT_CORE_ACCEL_IR10_EXT6:
case InputReportID::REPORT_INTERLEAVE1:
case InputReportID::REPORT_INTERLEAVE2:
// check any button without checking accelerometer data
// TODO: use the structs!
if ((rpt[2] & 0x1F) != 0 || (rpt[3] & 0x9F) != 0)
{
return true;
@ -399,17 +425,20 @@ void Wiimote::Prepare()
bool Wiimote::PrepareOnThread()
{
// core buttons, no continuous reporting
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, RT_REPORT_MODE, 0, RT_REPORT_CORE};
// TODO: use the structs..
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::REPORT_MODE), 0,
u8(InputReportID::REPORT_CORE)};
// Set the active LEDs and turn on rumble.
u8 static led_report[] = {WR_SET_REPORT | BT_OUTPUT, RT_LEDS, 0};
led_report[2] = u8(WiimoteLED::LED_1 << (m_index % WIIMOTE_BALANCE_BOARD) | 0x1);
u8 static led_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::LEDS), 0};
led_report[2] = u8(u8(LED::LED_1) << (m_index % WIIMOTE_BALANCE_BOARD) | 0x1);
// Turn off rumble
u8 static const rumble_report[] = {WR_SET_REPORT | BT_OUTPUT, RT_RUMBLE, 0};
u8 static const rumble_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::RUMBLE), 0};
// Request status report
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT, RT_REQUEST_STATUS, 0};
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT,
u8(OutputReportID::REQUEST_STATUS), 0};
// TODO: check for sane response?
return (IOWrite(mode_report, sizeof(mode_report)) && IOWrite(led_report, sizeof(led_report)) &&
@ -441,10 +470,10 @@ void Wiimote::EmuResume()
m_last_input_report.clear();
wm_report_mode rpt = {};
OutputReportMode rpt = {};
rpt.mode = wm->m_reporting_mode;
rpt.continuous = 1;
QueueReport(RT_REPORT_MODE, &rpt, sizeof(rpt));
QueueReport(u8(OutputReportID::REPORT_MODE), &rpt, sizeof(rpt));
NOTICE_LOG(WIIMOTE, "Resuming Wiimote data reporting.");
@ -455,10 +484,10 @@ void Wiimote::EmuPause()
{
m_last_input_report.clear();
wm_report_mode rpt = {};
rpt.mode = RT_REPORT_CORE;
OutputReportMode rpt = {};
rpt.mode = InputReportID::REPORT_CORE;
rpt.continuous = 0;
QueueReport(RT_REPORT_MODE, &rpt, sizeof(rpt));
QueueReport(u8(OutputReportID::REPORT_MODE), &rpt, sizeof(rpt));
NOTICE_LOG(WIIMOTE, "Pausing Wiimote data reporting.");

View File

@ -23,6 +23,37 @@ class PointerWrap;
namespace WiimoteReal
{
using WiimoteCommon::MAX_PAYLOAD;
typedef std::vector<u8> Report;
constexpr u32 WIIMOTE_DEFAULT_TIMEOUT = 1000;
// Communication channels
enum WiimoteChannel
{
WC_OUTPUT = 0x11,
WC_INPUT = 0x13,
};
// 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
{
#ifdef _WIN32
WR_SET_REPORT = 0x50
#else
WR_SET_REPORT = 0xA0
#endif
};
enum WiimoteBT
{
BT_INPUT = 0x01,
BT_OUTPUT = 0x02
};
class Wiimote
{
public:
@ -174,4 +205,4 @@ bool IsNewWiimote(const std::string& identifier);
void InitAdapterClass();
#endif
} // WiimoteReal
} // namespace WiimoteReal

View File

@ -45,8 +45,13 @@
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
#include "Core/NetPlayProto.h"
@ -64,6 +69,9 @@
namespace Movie
{
using namespace WiimoteCommon;
using namespace WiimoteEmu;
static bool s_bReadOnly = true;
static u32 s_rerecords = 0;
static PlayMode s_playMode = MODE_NONE;
@ -640,23 +648,17 @@ static void SetInputDisplayString(ControllerState padState, int controllerID)
}
// NOTE: CPU Thread
static void SetWiiInputDisplayString(int remoteID, const u8* const data,
const WiimoteEmu::ReportFeatures& rptf, int ext,
const wiimote_key key)
static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt, int ext,
const EncryptionKey& key)
{
int controllerID = remoteID + 4;
std::string display_str = StringFromFormat("R%d:", remoteID + 1);
const u8* const coreData = rptf.core_size ? (data + rptf.GetCoreOffset()) : nullptr;
const u8* const accelData = rptf.accel_size ? (data + rptf.GetAccelOffset()) : nullptr;
const u8* const irData = rptf.ir_size ? (data + rptf.GetIROffset()) : nullptr;
const u8* const extData = rptf.ext_size ? (data + rptf.GetExtOffset()) : nullptr;
if (coreData)
if (rpt.HasCore())
{
wm_buttons buttons;
std::memcpy(&buttons, coreData, sizeof(buttons));
ButtonData buttons;
rpt.GetCoreData(&buttons);
if (buttons.left)
display_str += " LEFT";
@ -680,31 +682,37 @@ static void SetWiiInputDisplayString(int remoteID, const u8* const data,
display_str += " 2";
if (buttons.home)
display_str += " HOME";
// A few bits of accelData are actually inside the coreData struct.
if (accelData)
{
wm_accel dt;
std::memcpy(&dt, accelData, sizeof(dt));
display_str +=
StringFromFormat(" ACC:%d,%d,%d (LSB not shown)", dt.x << 2, dt.y << 2, dt.z << 2);
}
}
if (irData)
if (rpt.HasAccel())
{
DataReportBuilder::AccelData accel_data;
rpt.GetAccelData(&accel_data);
// FYI: This will only print partial data for interleaved reports.
display_str += StringFromFormat(" ACC:%d,%d,%d", accel_data.x, accel_data.y, accel_data.z);
}
if (rpt.HasIR())
{
const u8* const irData = rpt.GetIRDataPtr();
// TODO: This does not handle the different IR formats.
u16 x = irData[0] | ((irData[2] >> 4 & 0x3) << 8);
u16 y = irData[1] | ((irData[2] >> 6 & 0x3) << 8);
display_str += StringFromFormat(" IR:%d,%d", x, y);
}
// Nunchuk
if (extData && ext == 1)
if (rpt.HasExt() && ext == 1)
{
wm_nc nunchuk;
memcpy(&nunchuk, extData, sizeof(wm_nc));
WiimoteDecrypt(&key, (u8*)&nunchuk, 0, sizeof(wm_nc));
const u8* const extData = rpt.GetExtDataPtr();
Nunchuk::DataFormat nunchuk;
memcpy(&nunchuk, extData, sizeof(nunchuk));
key.Decrypt((u8*)&nunchuk, 0, sizeof(nunchuk));
nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;
std::string accel = StringFromFormat(
@ -720,20 +728,22 @@ static void SetWiiInputDisplayString(int remoteID, const u8* const data,
}
// Classic controller
if (extData && ext == 2)
if (rpt.HasExt() && ext == 2)
{
wm_classic_extension cc;
memcpy(&cc, extData, sizeof(wm_classic_extension));
WiimoteDecrypt(&key, (u8*)&cc, 0, sizeof(wm_classic_extension));
const u8* const extData = rpt.GetExtDataPtr();
Classic::DataFormat cc;
memcpy(&cc, extData, sizeof(cc));
key.Decrypt((u8*)&cc, 0, sizeof(cc));
cc.bt.hex = cc.bt.hex ^ 0xFFFF;
if (cc.bt.regular_data.dpad_left)
if (cc.bt.dpad_left)
display_str += " LEFT";
if (cc.bt.dpad_right)
display_str += " RIGHT";
if (cc.bt.dpad_down)
display_str += " DOWN";
if (cc.bt.regular_data.dpad_up)
if (cc.bt.dpad_up)
display_str += " UP";
if (cc.bt.a)
display_str += " A";
@ -756,7 +766,7 @@ static void SetWiiInputDisplayString(int remoteID, const u8* const data,
display_str += Analog1DToString(cc.lt1 | (cc.lt2 << 3), " L", 31);
display_str += Analog1DToString(cc.rt, " R", 31);
display_str += Analog2DToString(cc.regular_data.lx, cc.regular_data.ly, " ANA", 63);
display_str += Analog2DToString(cc.lx, cc.ly, " ANA", 63);
display_str += Analog2DToString(cc.rx1 | (cc.rx2 << 1) | (cc.rx3 << 3), cc.ry, " R-ANA", 31);
}
@ -814,13 +824,13 @@ void RecordInput(const GCPadStatus* PadStatus, int controllerID)
}
// NOTE: CPU Thread
void CheckWiimoteStatus(int wiimote, const u8* data, const WiimoteEmu::ReportFeatures& rptf,
int ext, const wiimote_key key)
void CheckWiimoteStatus(int wiimote, const DataReportBuilder& rpt, int ext,
const EncryptionKey& key)
{
SetWiiInputDisplayString(wiimote, data, rptf, ext, key);
SetWiiInputDisplayString(wiimote, rpt, ext, key);
if (IsRecordingInput())
RecordWiimote(wiimote, data, rptf.total_size);
RecordWiimote(wiimote, rpt.GetDataPtr(), rpt.GetDataSize());
}
void RecordWiimote(int wiimote, const u8* data, u8 size)
@ -1189,8 +1199,8 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
}
// NOTE: CPU Thread
bool PlayWiimote(int wiimote, u8* data, const WiimoteEmu::ReportFeatures& rptf, int ext,
const wiimote_key key)
bool PlayWiimote(int wiimote, WiimoteCommon::DataReportBuilder& rpt, int ext,
const EncryptionKey& key)
{
if (!IsPlayingInput() || !IsUsingWiimote(wiimote) || s_temp_input.empty())
return false;
@ -1203,9 +1213,8 @@ bool PlayWiimote(int wiimote, u8* data, const WiimoteEmu::ReportFeatures& rptf,
return false;
}
u8 size = rptf.total_size;
u8 sizeInMovie = s_temp_input[s_currentByte];
const u8 size = rpt.GetDataSize();
const u8 sizeInMovie = s_temp_input[s_currentByte];
if (size != sizeInMovie)
{
@ -1229,7 +1238,7 @@ bool PlayWiimote(int wiimote, u8* data, const WiimoteEmu::ReportFeatures& rptf,
return false;
}
memcpy(data, &s_temp_input[s_currentByte], size);
memcpy(rpt.GetDataPtr(), &s_temp_input[s_currentByte], size);
s_currentByte += size;
s_currentInputCount++;
@ -1348,11 +1357,10 @@ void CallGCInputManip(GCPadStatus* PadStatus, int controllerID)
s_gc_manip_func(PadStatus, controllerID);
}
// NOTE: CPU Thread
void CallWiiInputManip(u8* data, WiimoteEmu::ReportFeatures rptf, int controllerID, int ext,
const wiimote_key key)
void CallWiiInputManip(DataReportBuilder& rpt, int controllerID, int ext, const EncryptionKey& key)
{
if (s_wii_manip_func)
s_wii_manip_func(data, rptf, controllerID, ext, key);
s_wii_manip_func(rpt, controllerID, ext, key);
}
// NOTE: GPU Thread

View File

@ -10,17 +10,13 @@
#include <string>
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
struct BootParameters;
struct GCPadStatus;
class PointerWrap;
struct wiimote_key;
namespace WiimoteEmu
{
struct ReportFeatures;
}
// Per-(video )Movie actions
@ -165,27 +161,27 @@ bool PlayInput(const std::string& movie_path, std::optional<std::string>* savest
void LoadInput(const std::string& movie_path);
void ReadHeader();
void PlayController(GCPadStatus* PadStatus, int controllerID);
bool PlayWiimote(int wiimote, u8* data, const struct WiimoteEmu::ReportFeatures& rptf, int ext,
const wiimote_key key);
bool PlayWiimote(int wiimote, WiimoteCommon::DataReportBuilder& rpt, int ext,
const WiimoteEmu::EncryptionKey& key);
void EndPlayInput(bool cont);
void SaveRecording(const std::string& filename);
void DoState(PointerWrap& p);
void Shutdown();
void CheckPadStatus(const GCPadStatus* PadStatus, int controllerID);
void CheckWiimoteStatus(int wiimote, const u8* data, const struct WiimoteEmu::ReportFeatures& rptf,
int ext, const wiimote_key key);
void CheckWiimoteStatus(int wiimote, const WiimoteCommon::DataReportBuilder& rpt, int ext,
const WiimoteEmu::EncryptionKey& key);
std::string GetInputDisplay();
std::string GetRTCDisplay();
// Done this way to avoid mixing of core and gui code
using GCManipFunction = std::function<void(GCPadStatus*, int)>;
using WiiManipFunction =
std::function<void(u8*, WiimoteEmu::ReportFeatures, int, int, wiimote_key)>;
using WiiManipFunction = std::function<void(WiimoteCommon::DataReportBuilder&, int, int,
const WiimoteEmu::EncryptionKey&)>;
void SetGCInputManip(GCManipFunction);
void SetWiiInputManip(WiiManipFunction);
void CallGCInputManip(GCPadStatus* PadStatus, int controllerID);
void CallWiiInputManip(u8* core, WiimoteEmu::ReportFeatures rptf, int controllerID, int ext,
const wiimote_key key);
void CallWiiInputManip(WiimoteCommon::DataReportBuilder& rpt, int controllerID, int ext,
const WiimoteEmu::EncryptionKey& key);
} // namespace Movie

View File

@ -43,6 +43,7 @@
#include "Core/HW/Sram.h"
#include "Core/HW/WiiSave.h"
#include "Core/HW/WiiSaveStructs.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/IOS/FS/FileSystem.h"
@ -61,6 +62,8 @@
namespace NetPlay
{
using namespace WiimoteCommon;
static std::mutex crit_netplay_client;
static NetPlayClient* netplay_client = nullptr;
static std::unique_ptr<IOS::HLE::FS::FileSystem> s_wii_sync_fs;

View File

@ -9,6 +9,11 @@
#include <QLabel>
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Drums.h"
#include "Core/HW/WiimoteEmu/Extension/Guitar.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/Extension/Turntable.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/InputConfig.h"

View File

@ -16,7 +16,7 @@
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
#include "InputCommon/InputConfig.h"
@ -40,12 +40,13 @@ void WiimoteEmuGeneral::CreateMainLayout()
m_main_layout->addWidget(CreateGroupBox(
tr("Hotkeys"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Hotkeys)));
auto* extension_group = Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Extension);
auto* extension_group =
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Attachments);
auto* extension = CreateGroupBox(tr("Extension"), extension_group);
auto* ce_extension = static_cast<ControllerEmu::Extension*>(extension_group);
auto* ce_extension = static_cast<ControllerEmu::Attachments*>(extension_group);
m_extension_combo = new QComboBox();
for (const auto& attachment : ce_extension->attachments)
for (const auto& attachment : ce_extension->GetAttachmentList())
{
// TODO: Figure out how to localize this
m_extension_combo->addItem(QString::fromStdString(attachment->GetName()));
@ -88,18 +89,18 @@ void WiimoteEmuGeneral::OnAttachmentChanged(int extension)
m_extension_widget->ChangeExtensionType(value_map[value]);
auto* ce_extension = static_cast<ControllerEmu::Extension*>(
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Extension));
ce_extension->switch_extension = extension;
auto* ce_extension = static_cast<ControllerEmu::Attachments*>(
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Attachments));
ce_extension->SetSelectedAttachment(extension);
SaveSettings();
}
void WiimoteEmuGeneral::Update()
{
auto* ce_extension = static_cast<ControllerEmu::Extension*>(
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Extension));
auto* ce_extension = static_cast<ControllerEmu::Attachments*>(
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Attachments));
m_extension_combo->setCurrentIndex(ce_extension->switch_extension);
m_extension_combo->setCurrentIndex(ce_extension->GetSelectedAttachment());
}
void WiimoteEmuGeneral::LoadSettings()

View File

@ -350,9 +350,9 @@ void MainWindow::CreateComponents()
m_gc_tas_input_windows[controller_id]->GetValues(pad_status);
});
Movie::SetWiiInputManip([this](u8* input_data, WiimoteEmu::ReportFeatures rptf, int controller_id,
int ext, wiimote_key key) {
m_wii_tas_input_windows[controller_id]->GetValues(input_data, rptf, ext, key);
Movie::SetWiiInputManip([this](WiimoteCommon::DataReportBuilder& rpt, int controller_id, int ext,
const WiimoteEmu::EncryptionKey& key) {
m_wii_tas_input_windows[controller_id]->GetValues(rpt, ext, key);
});
m_jit_widget = new JITWidget(this);

View File

@ -14,10 +14,16 @@
#include "Common/FileUtil.h"
#include "Core/Core.h"
#include "Core/HW/WiimoteEmu/Attachment/Classic.h"
#include "Core/HW/WiimoteEmu/Attachment/Nunchuk.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteEmu/Camera.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "DolphinQt/QtUtils/AspectRatioWidget.h"
@ -27,6 +33,8 @@
#include "InputCommon/InputConfig.h"
using namespace WiimoteCommon;
WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(parent), m_num(num)
{
const QKeySequence ir_x_shortcut_key_sequence = QKeySequence(Qt::ALT + Qt::Key_F);
@ -255,7 +263,7 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
if (Core::IsRunning())
{
ext = static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(num))
->CurrentExtension();
->GetActiveExtensionNumber();
}
else
{
@ -318,22 +326,20 @@ void WiiTASInputWindow::UpdateExt(u8 ext)
}
}
void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rptf, int ext,
wiimote_key key)
void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
const WiimoteEmu::EncryptionKey& key)
{
if (!isVisible())
return;
UpdateExt(ext);
u8* const buttons_data = rptf.core_size ? (report_data + rptf.GetCoreOffset()) : nullptr;
u8* const accel_data = rptf.accel_size ? (report_data + rptf.GetAccelOffset()) : nullptr;
u8* const ir_data = rptf.ir_size ? (report_data + rptf.GetIROffset()) : nullptr;
u8* const ext_data = rptf.ext_size ? (report_data + rptf.GetExtOffset()) : nullptr;
if (m_remote_buttons_box->isVisible() && buttons_data)
if (m_remote_buttons_box->isVisible() && rpt.HasCore())
{
u16& buttons = (reinterpret_cast<wm_buttons*>(buttons_data))->hex;
DataReportBuilder::CoreData core;
rpt.GetCoreData(&core);
u16& buttons = core.hex;
GetButton<u16>(m_a_button, buttons, WiimoteEmu::Wiimote::BUTTON_A);
GetButton<u16>(m_b_button, buttons, WiimoteEmu::Wiimote::BUTTON_B);
GetButton<u16>(m_1_button, buttons, WiimoteEmu::Wiimote::BUTTON_ONE);
@ -345,34 +351,28 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
GetButton<u16>(m_up_button, buttons, WiimoteEmu::Wiimote::PAD_UP);
GetButton<u16>(m_down_button, buttons, WiimoteEmu::Wiimote::PAD_DOWN);
GetButton<u16>(m_right_button, buttons, WiimoteEmu::Wiimote::PAD_RIGHT);
rpt.SetCoreData(core);
}
if (m_remote_orientation_box->isVisible() && accel_data && buttons_data)
if (m_remote_orientation_box->isVisible() && rpt.HasAccel())
{
wm_accel& accel = *reinterpret_cast<wm_accel*>(accel_data);
//wm_buttons& buttons = *reinterpret_cast<wm_buttons*>(buttons_data);
// FYI: Interleaved reports may behave funky as not all data is always available.
// TODO: lsb
u16 accel_x = (accel.x << 2); // & (buttons.acc_x_lsb & 0b11);
u16 accel_y = (accel.y << 2); // & ((buttons.acc_y_lsb & 0b1) << 1);
u16 accel_z = (accel.z << 2); // &((buttons.acc_z_lsb & 0b1) << 1);
DataReportBuilder::AccelData accel;
rpt.GetAccelData(&accel);
GetSpinBoxU16(m_remote_orientation_x_value, accel_x);
GetSpinBoxU16(m_remote_orientation_y_value, accel_y);
GetSpinBoxU16(m_remote_orientation_z_value, accel_z);
GetSpinBoxU16(m_remote_orientation_x_value, accel.x);
GetSpinBoxU16(m_remote_orientation_y_value, accel.y);
GetSpinBoxU16(m_remote_orientation_z_value, accel.z);
accel.x = accel_x >> 2;
accel.y = accel_y >> 2;
accel.z = accel_z >> 2;
// TODO: lsb
//buttons.acc_x_lsb = accel_x & 0b11;
//buttons.acc_y_lsb = (accel_y >> 1) & 0b1;
//buttons.acc_z_lsb = (accel_z >> 1) & 0b1;
rpt.SetAccelData(accel);
}
if (m_ir_box->isVisible() && ir_data && !m_use_controller->isChecked())
if (m_ir_box->isVisible() && rpt.HasIR() && !m_use_controller->isChecked())
{
u8* const ir_data = rpt.GetIRDataPtr();
u16 y = m_ir_y_value->value();
std::array<u16, 4> x;
x[0] = m_ir_x_value->value();
@ -380,18 +380,19 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
x[2] = x[0] - 10;
x[3] = x[1] + 10;
u8 mode;
// Mode 5 not supported in core anyway.
// TODO: Can just use ir_size to determine mode
if (rptf.ext_size)
mode = (rptf.GetExtOffset() - rptf.GetIROffset()) == 10 ? 1 : 3;
else
mode = (rptf.total_size - rptf.GetIROffset()) == 10 ? 1 : 3;
// FYI: This check is not entirely foolproof.
// TODO: IR "full" mode not implemented.
u8 mode = WiimoteEmu::CameraLogic::IR_MODE_BASIC;
if (mode == 1)
if (rpt.GetIRDataSize() == sizeof(WiimoteEmu::IRExtended) * 4)
mode = WiimoteEmu::CameraLogic::IR_MODE_EXTENDED;
else if (rpt.GetIRDataSize() == sizeof(WiimoteEmu::IRFull) * 2)
mode = WiimoteEmu::CameraLogic::IR_MODE_FULL;
if (mode == WiimoteEmu::CameraLogic::IR_MODE_BASIC)
{
memset(ir_data, 0xFF, sizeof(wm_ir_basic) * 2);
wm_ir_basic* const ir_basic = reinterpret_cast<wm_ir_basic*>(ir_data);
memset(ir_data, 0xFF, sizeof(WiimoteEmu::IRBasic) * 2);
auto* const ir_basic = reinterpret_cast<WiimoteEmu::IRBasic*>(ir_data);
for (int i = 0; i < 2; ++i)
{
if (x[i * 2] < 1024 && y < 768)
@ -416,8 +417,8 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
{
// TODO: this code doesnt work, resulting in no IR TAS inputs in e.g. wii sports menu when no
// remote extension is used
memset(ir_data, 0xFF, sizeof(wm_ir_extended) * 4);
wm_ir_extended* const ir_extended = reinterpret_cast<wm_ir_extended*>(ir_data);
memset(ir_data, 0xFF, sizeof(WiimoteEmu::IRExtended) * 4);
auto* const ir_extended = reinterpret_cast<WiimoteEmu::IRExtended*>(ir_data);
for (size_t i = 0; i < x.size(); ++i)
{
if (x[i] < 1024 && y < 768)
@ -434,9 +435,11 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
}
}
if (ext_data && m_nunchuk_stick_box->isVisible())
if (rpt.HasExt() && m_nunchuk_stick_box->isVisible())
{
wm_nc& nunchuk = *reinterpret_cast<wm_nc*>(ext_data);
u8* const ext_data = rpt.GetExtDataPtr();
auto& nunchuk = *reinterpret_cast<WiimoteEmu::Nunchuk::DataFormat*>(ext_data);
GetSpinBoxU8(m_nunchuk_stick_x_value, nunchuk.jx);
GetSpinBoxU8(m_nunchuk_stick_y_value, nunchuk.jy);
@ -462,13 +465,15 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
GetButton<u8>(m_z_button, nunchuk.bt.hex, WiimoteEmu::Nunchuk::BUTTON_Z);
nunchuk.bt.hex ^= 0b11;
WiimoteEncrypt(&key, reinterpret_cast<u8*>(&nunchuk), 0, sizeof(wm_nc));
key.Encrypt(reinterpret_cast<u8*>(&nunchuk), 0, sizeof(nunchuk));
}
if (m_classic_left_stick_box->isVisible())
{
wm_classic_extension& cc = *reinterpret_cast<wm_classic_extension*>(ext_data);
WiimoteDecrypt(&key, reinterpret_cast<u8*>(&cc), 0, sizeof(wm_classic_extension));
u8* const ext_data = rpt.GetExtDataPtr();
auto& cc = *reinterpret_cast<WiimoteEmu::Classic::DataFormat*>(ext_data);
key.Decrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
cc.bt.hex ^= 0xFFFF;
GetButton<u16>(m_classic_a_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_A);
@ -498,13 +503,13 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
GetSpinBoxU8(m_classic_right_stick_y_value, ry);
cc.ry = ry;
u8 lx = cc.regular_data.lx;
u8 lx = cc.lx;
GetSpinBoxU8(m_classic_left_stick_x_value, lx);
cc.regular_data.lx = lx;
cc.lx = lx;
u8 ly = cc.regular_data.ly;
u8 ly = cc.ly;
GetSpinBoxU8(m_classic_left_stick_y_value, ly);
cc.regular_data.ly = ly;
cc.ly = ly;
u8 rt = cc.rt;
GetSpinBoxU8(m_right_trigger_value, rt);
@ -515,6 +520,6 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp
cc.lt1 = lt & 0b111;
cc.lt2 = (lt >> 3) & 0b11;
WiimoteEncrypt(&key, reinterpret_cast<u8*>(&cc), 0, sizeof(wm_classic_extension));
key.Encrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
}
}

View File

@ -6,21 +6,27 @@
#include "DolphinQt/TAS/TASInputWindow.h"
namespace WiimoteCommon
{
class DataReportBuilder;
}
namespace WiimoteEmu
{
struct ReportFeatures;
class EncryptionKey;
}
class QCheckBox;
class QGroupBox;
class QSpinBox;
struct wiimote_key;
class WiiTASInputWindow : public TASInputWindow
{
Q_OBJECT
public:
explicit WiiTASInputWindow(QWidget* parent, int num);
void GetValues(u8* input_data, WiimoteEmu::ReportFeatures rptf, int ext, wiimote_key key);
void GetValues(WiimoteCommon::DataReportBuilder& rpt, int ext,
const WiimoteEmu::EncryptionKey& key);
private:
void UpdateExt(u8 ext);

View File

@ -7,10 +7,10 @@ add_library(inputcommon
ControllerEmu/Control/Input.cpp
ControllerEmu/Control/Output.cpp
ControllerEmu/ControlGroup/AnalogStick.cpp
ControllerEmu/ControlGroup/Attachments.cpp
ControllerEmu/ControlGroup/Buttons.cpp
ControllerEmu/ControlGroup/ControlGroup.cpp
ControllerEmu/ControlGroup/Cursor.cpp
ControllerEmu/ControlGroup/Extension.cpp
ControllerEmu/ControlGroup/Force.cpp
ControllerEmu/ControlGroup/MixedTriggers.cpp
ControllerEmu/ControlGroup/ModifySettingsButton.cpp

View File

@ -0,0 +1,33 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
namespace ControllerEmu
{
Attachments::Attachments(const std::string& name_) : ControlGroup(name_, GroupType::Attachments)
{
}
void Attachments::AddAttachment(std::unique_ptr<EmulatedController> att)
{
m_attachments.emplace_back(std::move(att));
}
u32 Attachments::GetSelectedAttachment() const
{
return m_selected_attachment;
}
void Attachments::SetSelectedAttachment(u32 val)
{
m_selected_attachment = val;
}
const std::vector<std::unique_ptr<EmulatedController>>& Attachments::GetAttachmentList() const
{
return m_attachments;
}
} // namespace ControllerEmu

View File

@ -0,0 +1,37 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
namespace ControllerEmu
{
// A container of the selected and available attachments
// for configuration saving/loading purposes
class Attachments : public ControlGroup
{
public:
explicit Attachments(const std::string& name);
void AddAttachment(std::unique_ptr<EmulatedController> att);
u32 GetSelectedAttachment() const;
void SetSelectedAttachment(u32 val);
const std::vector<std::unique_ptr<EmulatedController>>& GetAttachmentList() const;
private:
std::vector<std::unique_ptr<EmulatedController>> m_attachments;
std::atomic<u32> m_selected_attachment;
};
} // namespace ControllerEmu

View File

@ -9,7 +9,7 @@
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
@ -67,22 +67,22 @@ void ControlGroup::LoadConfig(IniFile::Section* sec, const std::string& defdev,
}
// extensions
if (type == GroupType::Extension)
if (type == GroupType::Attachments)
{
Extension* const ext = (Extension*)this;
auto* const ext = static_cast<Attachments*>(this);
ext->switch_extension = 0;
ext->SetSelectedAttachment(0);
u32 n = 0;
std::string extname;
sec->Get(base + name, &extname, "");
for (auto& ai : ext->attachments)
for (auto& ai : ext->GetAttachmentList())
{
ai->SetDefaultDevice(defdev);
ai->LoadConfig(sec, base + ai->GetName() + "/");
if (ai->GetName() == extname)
ext->switch_extension = n;
ext->SetSelectedAttachment(n);
n++;
}
@ -120,12 +120,13 @@ void ControlGroup::SaveConfig(IniFile::Section* sec, const std::string& defdev,
}
// extensions
if (type == GroupType::Extension)
if (type == GroupType::Attachments)
{
Extension* const ext = (Extension*)this;
sec->Set(base + name, ext->attachments[ext->switch_extension]->GetName(), "None");
auto* const ext = static_cast<Attachments*>(this);
sec->Set(base + name, ext->GetAttachmentList()[ext->GetSelectedAttachment()]->GetName(),
"None");
for (auto& ai : ext->attachments)
for (auto& ai : ext->GetAttachmentList())
ai->SaveConfig(sec, base + ai->GetName() + "/");
}
}

View File

@ -24,7 +24,7 @@ enum class GroupType
MixedTriggers,
Buttons,
Force,
Extension,
Attachments,
Tilt,
Cursor,
Triggers,

View File

@ -1,16 +0,0 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
#include <string>
#include "InputCommon/ControllerEmu/ControllerEmu.h"
namespace ControllerEmu
{
Extension::Extension(const std::string& name_) : ControlGroup(name_, GroupType::Extension)
{
}
} // namespace ControllerEmu

View File

@ -1,31 +0,0 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
namespace ControllerEmu
{
class EmulatedController;
class Extension : public ControlGroup
{
public:
explicit Extension(const std::string& name);
void GetState(u8* data);
bool IsButtonPressed() const;
std::vector<std::unique_ptr<EmulatedController>> attachments;
int switch_extension = 0;
int active_extension = 0;
};
} // namespace ControllerEmu

View File

@ -19,8 +19,5 @@ public:
explicit Force(const std::string& name);
StateData GetState();
private:
StateData m_swing{};
};
} // namespace ControllerEmu

View File

@ -12,8 +12,8 @@
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
namespace ControllerEmu
@ -41,10 +41,10 @@ void EmulatedController::UpdateReferences(const ControllerInterface& devi)
for (auto& control : ctrlGroup->controls)
control->control_ref.get()->UpdateReference(devi, GetDefaultDevice());
// extension
if (ctrlGroup->type == GroupType::Extension)
// Attachments:
if (ctrlGroup->type == GroupType::Attachments)
{
for (auto& attachment : ((Extension*)ctrlGroup.get())->attachments)
for (auto& attachment : static_cast<Attachments*>(ctrlGroup.get())->GetAttachmentList())
attachment->UpdateReferences(devi);
}
}
@ -73,10 +73,10 @@ void EmulatedController::SetDefaultDevice(ciface::Core::DeviceQualifier devq)
for (auto& ctrlGroup : groups)
{
// extension
if (ctrlGroup->type == GroupType::Extension)
// Attachments:
if (ctrlGroup->type == GroupType::Attachments)
{
for (auto& ai : ((Extension*)ctrlGroup.get())->attachments)
for (auto& ai : static_cast<Attachments*>(ctrlGroup.get())->GetAttachmentList())
{
ai->SetDefaultDevice(m_default_device);
}

View File

@ -45,7 +45,7 @@
<ClCompile Include="ControllerEmu\ControlGroup\Buttons.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\ControlGroup.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\Cursor.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\Extension.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\Attachments.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\Force.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\MixedTriggers.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\ModifySettingsButton.cpp" />
@ -84,7 +84,7 @@
<ClInclude Include="ControllerEmu\ControlGroup\Buttons.h" />
<ClInclude Include="ControllerEmu\ControlGroup\ControlGroup.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Cursor.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Extension.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Attachments.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Force.h" />
<ClInclude Include="ControllerEmu\ControlGroup\MixedTriggers.h" />
<ClInclude Include="ControllerEmu\ControlGroup\ModifySettingsButton.h" />

View File

@ -56,9 +56,6 @@
<ClCompile Include="ControllerEmu\ControlGroup\Cursor.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerEmu\ControlGroup\Extension.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
<ClCompile Include="ControllerEmu\ControlGroup\Force.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
@ -114,6 +111,9 @@
<Filter>ControllerInterface</Filter>
</ClCompile>
<ClCompile Include="InputProfile.cpp" />
<ClCompile Include="ControllerEmu\ControlGroup\Attachments.cpp">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GCAdapter.h" />
@ -146,9 +146,6 @@
<ClInclude Include="ControllerEmu\ControlGroup\Cursor.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerEmu\ControlGroup\Extension.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
<ClInclude Include="ControllerEmu\ControlGroup\Force.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
@ -210,6 +207,9 @@
<Filter>ControllerInterface</Filter>
</ClInclude>
<ClInclude Include="InputProfile.h" />
<ClInclude Include="ControllerEmu\ControlGroup\Attachments.h">
<Filter>ControllerEmu\ControlGroup</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />