Merge pull request #7880 from jordan-woyak/udraw-tablet-emu

WiimoteEmu: Implement 3rd-Party extension encryption and uDraw GameTablet.
This commit is contained in:
JMC47
2019-04-30 19:45:33 -04:00
committed by GitHub
26 changed files with 875 additions and 282 deletions
+1
View File
@@ -148,6 +148,7 @@ add_library(core
HW/WiimoteEmu/Extension/Drums.cpp
HW/WiimoteEmu/Extension/Guitar.cpp
HW/WiimoteEmu/Extension/Turntable.cpp
HW/WiimoteEmu/Extension/UDrawTablet.cpp
HW/WiimoteReal/WiimoteReal.cpp
HW/WiiSave.cpp
IOS/Device.cpp
+2
View File
@@ -182,6 +182,7 @@
<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\Extension\UDrawTablet.cpp" />
<ClCompile Include="HW\WiimoteEmu\I2CBus.cpp" />
<ClCompile Include="HW\WiimoteEmu\MotionPlus.cpp" />
<ClCompile Include="HW\WiimoteEmu\Speaker.cpp" />
@@ -449,6 +450,7 @@
<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\Extension\UDrawTablet.h" />
<ClInclude Include="HW\WiimoteEmu\I2CBus.h" />
<ClInclude Include="HW\WiimoteEmu\MotionPlus.h" />
<ClInclude Include="HW\WiimoteEmu\Speaker.h" />
+7
View File
@@ -907,6 +907,9 @@
<ClCompile Include="HW\WiimoteEmu\Extension\Turntable.cpp">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClCompile>
<ClCompile Include="HW\WiimoteEmu\Extension\UDrawTablet.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>
@@ -1619,6 +1622,9 @@
<ClInclude Include="HW\WiimoteEmu\Extension\Turntable.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu\Extension</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\Extension\UDrawTablet.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>
@@ -1628,6 +1634,7 @@
<ClInclude Include="HW\WiimoteCommon\DataReport.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Common</Filter>
</ClInclude>
<ClInclude Include="PowerPC\ConditionRegister.h" />
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
+6
View File
@@ -66,6 +66,12 @@ ControllerEmu::ControlGroup* GetTurntableGroup(int number, WiimoteEmu::Turntable
->GetTurntableGroup(group);
}
ControllerEmu::ControlGroup* GetUDrawTabletGroup(int number, WiimoteEmu::UDrawTabletGroup group)
{
return static_cast<WiimoteEmu::Wiimote*>(s_config.GetController(number))
->GetUDrawTabletGroup(group);
}
void Shutdown()
{
s_config.UnregisterHotplugCallback();
+2
View File
@@ -26,6 +26,7 @@ enum class ClassicGroup;
enum class GuitarGroup;
enum class DrumsGroup;
enum class TurntableGroup;
enum class UDrawTabletGroup;
} // namespace WiimoteEmu
enum
@@ -78,6 +79,7 @@ ControllerEmu::ControlGroup* GetClassicGroup(int number, WiimoteEmu::ClassicGrou
ControllerEmu::ControlGroup* GetGuitarGroup(int number, WiimoteEmu::GuitarGroup group);
ControllerEmu::ControlGroup* GetDrumsGroup(int number, WiimoteEmu::DrumsGroup group);
ControllerEmu::ControlGroup* GetTurntableGroup(int number, WiimoteEmu::TurntableGroup group);
ControllerEmu::ControlGroup* GetUDrawTabletGroup(int number, WiimoteEmu::UDrawTabletGroup group);
void ControlChannel(int number, u16 channel_id, const void* data, u32 size);
void InterruptChannel(int number, u16 channel_id, const void* data, u32 size);
File diff suppressed because it is too large Load Diff
+43 -5
View File
@@ -6,6 +6,8 @@
#pragma once
#include <array>
#include "Common/CommonTypes.h"
namespace WiimoteEmu
@@ -13,14 +15,50 @@ namespace WiimoteEmu
class EncryptionKey
{
public:
void Generate(const u8* keydata);
void Encrypt(u8* data, u32 addr, u32 len) const;
void Decrypt(u8* data, u32 addr, u32 len) const;
void Encrypt(u8* data, int addr, u8 len) const;
void Decrypt(u8* data, int addr, u8 len) const;
using RandData = std::array<u8, 10>;
using KeyData = std::array<u8, 6>;
void GenerateTables(const RandData& rand, const KeyData& key, const u32 idx, const u8 offset);
std::array<u8, 8> ft = {};
std::array<u8, 8> sb = {};
};
class KeyGen
{
public:
virtual ~KeyGen() = default;
using ExtKeyData = std::array<u8, 16>;
EncryptionKey GenerateFromExtensionKeyData(const ExtKeyData& key_data) const;
protected:
virtual EncryptionKey::KeyData GenerateKeyData(const EncryptionKey::RandData& rand,
u32 idx) const = 0;
virtual EncryptionKey GenerateTables(const EncryptionKey::RandData& rand,
const EncryptionKey::KeyData& key, u32 idx) const = 0;
};
class KeyGen1stParty final : public KeyGen
{
private:
u8 ft[8] = {};
u8 sb[8] = {};
EncryptionKey::KeyData GenerateKeyData(const EncryptionKey::RandData& rand,
u32 idx) const final override;
EncryptionKey GenerateTables(const EncryptionKey::RandData& rand,
const EncryptionKey::KeyData& key, u32 idx) const final override;
};
class KeyGen3rdParty final : public KeyGen
{
private:
EncryptionKey::KeyData GenerateKeyData(const EncryptionKey::RandData& rand,
u32 idx) const final override;
EncryptionKey GenerateTables(const EncryptionKey::RandData& rand,
const EncryptionKey::KeyData& key, u32 idx) const final override;
};
} // namespace WiimoteEmu
@@ -72,7 +72,7 @@ constexpr std::array<u16, 4> classic_dpad_bitmasks{{
Classic::PAD_RIGHT,
}};
Classic::Classic() : EncryptedExtension(_trans("Classic"))
Classic::Classic() : Extension1stParty(_trans("Classic"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -172,7 +172,8 @@ bool Classic::IsButtonPressed() const
void Classic::Reset()
{
m_reg = {};
EncryptedExtension::Reset();
m_reg.identifier = classic_id;
// Build calibration data:
@@ -26,7 +26,7 @@ enum class ClassicGroup
RightStick
};
class Classic : public EncryptedExtension
class Classic : public Extension1stParty
{
public:
union ButtonFormat
@@ -44,7 +44,7 @@ constexpr std::array<u16, 2> drum_button_bitmasks{{
Drums::BUTTON_PLUS,
}};
Drums::Drums() : EncryptedExtension(_trans("Drums"))
Drums::Drums() : Extension1stParty(_trans("Drums"))
{
// pads
groups.emplace_back(m_pads = new ControllerEmu::Buttons(_trans("Pads")));
@@ -106,7 +106,8 @@ bool Drums::IsButtonPressed() const
void Drums::Reset()
{
m_reg = {};
EncryptedExtension::Reset();
m_reg.identifier = drums_id;
// TODO: Is there calibration data?
@@ -22,8 +22,8 @@ enum class DrumsGroup
Stick
};
// TODO: Do the drums ever use encryption?
class Drums : public EncryptedExtension
// The Drums use the "1st-party" extension encryption scheme.
class Drums : public Extension1stParty
{
public:
struct DataFormat
@@ -12,6 +12,8 @@
#include "Common/Compiler.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Common/Logging/Log.h"
namespace WiimoteEmu
{
Extension::Extension(const char* name) : m_name(name)
@@ -85,15 +87,16 @@ int EncryptedExtension::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
auto const result = RawRead(&m_reg, addr, count, data_out);
// Encrypt data read from extension register
// 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.");
if (m_is_key_dirty)
{
UpdateEncryptionKey();
m_is_key_dirty = false;
}
ext_key.Encrypt(data_out, addr, count);
}
return result;
@@ -106,24 +109,48 @@ int EncryptedExtension::BusWrite(u8 slave_addr, u8 addr, int count, const u8* da
auto const result = RawWrite(&m_reg, addr, count, data_in);
// TODO: make this check less ugly:
if (addr + count > 0x40 && addr < 0x50)
constexpr u8 ENCRYPTION_KEY_DATA_BEGIN = offsetof(Register, encryption_key_data);
constexpr u8 ENCRYPTION_KEY_DATA_END = ENCRYPTION_KEY_DATA_BEGIN + 0x10;
if (addr + count > ENCRYPTION_KEY_DATA_BEGIN && addr < ENCRYPTION_KEY_DATA_END)
{
// 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);
// FYI: Real extensions seem to require the key data written in specifically sized chunks.
// We just run the key generation on all writes to the key area.
m_is_key_dirty = true;
}
return result;
}
void EncryptedExtension::Reset()
{
// Clear register state.
m_reg = {};
// Clear encryption key state.
ext_key = {};
m_is_key_dirty = true;
}
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);
{
// No need to sync the key when we can just regenerate it.
m_is_key_dirty = true;
}
}
void Extension1stParty::UpdateEncryptionKey()
{
ext_key = KeyGen1stParty().GenerateFromExtensionKeyData(m_reg.encryption_key_data);
}
void Extension3rdParty::UpdateEncryptionKey()
{
ext_key = KeyGen3rdParty().GenerateFromExtensionKeyData(m_reg.encryption_key_data);
}
} // namespace WiimoteEmu
@@ -64,7 +64,7 @@ public:
// TODO: This is public for TAS reasons.
// TODO: TAS handles encryption poorly.
WiimoteEmu::EncryptionKey ext_key = {};
EncryptionKey ext_key;
protected:
static constexpr int CALIBRATION_CHECKSUM_BYTES = 2;
@@ -82,7 +82,7 @@ protected:
u8 unknown3[0x10];
// address 0x40
u8 encryption_key_data[0x10];
std::array<u8, 0x10> encryption_key_data;
u8 unknown4[0xA0];
// address 0xF0
@@ -98,9 +98,14 @@ protected:
Register m_reg = {};
void Reset() override;
void DoState(PointerWrap& p) override;
virtual void UpdateEncryptionKey() = 0;
private:
bool m_is_key_dirty = true;
static constexpr u8 ENCRYPTION_ENABLED = 0xaa;
bool ReadDeviceDetectPin() const override;
@@ -109,4 +114,22 @@ private:
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override;
};
class Extension1stParty : public EncryptedExtension
{
protected:
using EncryptedExtension::EncryptedExtension;
private:
void UpdateEncryptionKey() final override;
};
class Extension3rdParty : public EncryptedExtension
{
protected:
using EncryptedExtension::EncryptedExtension;
private:
void UpdateEncryptionKey() final override;
};
} // namespace WiimoteEmu
@@ -62,7 +62,7 @@ constexpr std::array<u16, 2> guitar_strum_bitmasks{{
Guitar::BAR_DOWN,
}};
Guitar::Guitar() : EncryptedExtension(_trans("Guitar"))
Guitar::Guitar() : Extension1stParty(_trans("Guitar"))
{
// frets
groups.emplace_back(m_frets = new ControllerEmu::Buttons(_trans("Frets")));
@@ -152,7 +152,8 @@ bool Guitar::IsButtonPressed() const
void Guitar::Reset()
{
m_reg = {};
EncryptedExtension::Reset();
m_reg.identifier = guitar_id;
// TODO: Is there calibration data?
@@ -27,8 +27,8 @@ enum class GuitarGroup
SliderBar
};
// TODO: Does the guitar ever use encryption?
class Guitar : public EncryptedExtension
// The Guitar uses the "1st-party" extension encryption scheme.
class Guitar : public Extension1stParty
{
public:
struct DataFormat
@@ -31,7 +31,7 @@ constexpr std::array<u8, 2> nunchuk_button_bitmasks{{
Nunchuk::BUTTON_Z,
}};
Nunchuk::Nunchuk() : EncryptedExtension(_trans("Nunchuk"))
Nunchuk::Nunchuk() : Extension1stParty(_trans("Nunchuk"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -121,7 +121,8 @@ bool Nunchuk::IsButtonPressed() const
void Nunchuk::Reset()
{
m_reg = {};
EncryptedExtension::Reset();
m_reg.identifier = nunchuk_id;
m_swing_state = {};
@@ -30,7 +30,7 @@ enum class NunchukGroup
Shake
};
class Nunchuk : public EncryptedExtension
class Nunchuk : public Extension1stParty
{
public:
union ButtonFormat
@@ -45,7 +45,7 @@ constexpr std::array<const char*, 6> turntable_button_names{{
_trans("Blue Right"),
}};
Turntable::Turntable() : EncryptedExtension(_trans("Turntable"))
Turntable::Turntable() : Extension1stParty(_trans("Turntable"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
@@ -148,7 +148,8 @@ bool Turntable::IsButtonPressed() const
void Turntable::Reset()
{
m_reg = {};
EncryptedExtension::Reset();
m_reg.identifier = turntable_id;
// TODO: Is there calibration data?
@@ -27,8 +27,8 @@ enum class TurntableGroup
Crossfade
};
// TODO: Does the turntable ever use encryption?
class Turntable : public EncryptedExtension
// The DJ Hero Turntable uses the "1st-party" extension encryption scheme.
class Turntable : public Extension1stParty
{
public:
struct DataFormat
@@ -0,0 +1,147 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h"
#include <array>
#include <cassert>
#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/Triggers.h"
namespace WiimoteEmu
{
constexpr std::array<u8, 6> udraw_tablet_id{{0xff, 0x00, 0xa4, 0x20, 0x01, 0x12}};
constexpr std::array<u8, 2> udraw_tablet_button_bitmasks{{
UDrawTablet::BUTTON_ROCKER_UP,
UDrawTablet::BUTTON_ROCKER_DOWN,
}};
constexpr std::array<const char*, 2> udraw_tablet_button_names{{
_trans("Rocker Up"),
_trans("Rocker Down"),
}};
UDrawTablet::UDrawTablet() : Extension3rdParty(_trans("uDraw"))
{
// Buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
for (auto& button_name : udraw_tablet_button_names)
{
m_buttons->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::Translate, button_name));
}
// Stylus
groups.emplace_back(m_stylus = new ControllerEmu::AnalogStick(
_trans("Stylus"), std::make_unique<ControllerEmu::SquareStickGate>(1.0)));
// Touch
groups.emplace_back(m_touch = new ControllerEmu::Triggers(_trans("Touch")));
m_touch->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::Translate, _trans("Pressure")));
}
void UDrawTablet::Update()
{
DataFormat tablet_data = {};
// Pressure:
// Min/Max values produced on my device: 0x08, 0xf2
// We're just gonna assume it's an old sensor and use the full byte range:
// Note: Pressure values are valid even when stylus is lifted.
constexpr u8 max_pressure = 0xff;
const auto touch_state = m_touch->GetState();
tablet_data.pressure = static_cast<u8>(touch_state.data[0] * max_pressure);
// Stylus X/Y:
// Min/Max X values (when touched) produced on my device: 0x4f, 0x7B3
// Drawing area edge (approx) X values on my device: 0x56, 0x7a5
// Min/Max Y values (when touched) produced on my device: 0x53, 0x5b4
// Drawing area edge (approx) Y values on my device: 0x5e, 0x5a5
// Calibrated for "uDraw Studio: Instant Artist".
constexpr u16 min_x = 0x56;
constexpr u16 max_x = 0x780;
constexpr u16 min_y = 0x65;
constexpr u16 max_y = 0x5a5;
constexpr double center_x = (max_x + min_x) / 2.0;
constexpr double center_y = (max_y + min_y) / 2.0;
// Neutral (lifted) stylus state:
u16 stylus_x = 0x7ff;
u16 stylus_y = 0x7ff;
// TODO: Expose the lifted stylus state in the UI.
bool is_stylus_lifted = false;
const auto stylus_state = m_stylus->GetState();
if (!is_stylus_lifted)
{
stylus_x = u16(center_x + stylus_state.x * (max_x - center_x));
stylus_y = u16(center_y + stylus_state.y * (max_y - center_y));
}
tablet_data.stylus_x1 = stylus_x & 0xff;
tablet_data.stylus_x2 = stylus_x >> 8;
tablet_data.stylus_y1 = stylus_y & 0xff;
tablet_data.stylus_y2 = stylus_y >> 8;
// Buttons:
m_buttons->GetState(&tablet_data.buttons, udraw_tablet_button_bitmasks.data());
// Flip button bits
constexpr u8 buttons_neutral_state = 0xfb;
tablet_data.buttons ^= buttons_neutral_state;
// Always 0xff
tablet_data.unk = 0xff;
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data;
}
void UDrawTablet::Reset()
{
EncryptedExtension::Reset();
m_reg.identifier = udraw_tablet_id;
// Both 0x20 and 0x30 calibration sections are just filled with 0xff on the real tablet:
m_reg.calibration.fill(0xff);
}
bool UDrawTablet::IsButtonPressed() const
{
u8 buttons = 0;
m_buttons->GetState(&buttons, udraw_tablet_button_bitmasks.data());
return buttons != 0;
}
ControllerEmu::ControlGroup* UDrawTablet::GetGroup(UDrawTabletGroup group)
{
switch (group)
{
case UDrawTabletGroup::Buttons:
return m_buttons;
case UDrawTabletGroup::Stylus:
return m_stylus;
case UDrawTabletGroup::Touch:
return m_touch;
default:
assert(false);
return nullptr;
}
}
} // namespace WiimoteEmu

Some files were not shown because too many files have changed in this diff Show More