@@ -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
@@ -0,0 +1,68 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

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

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

namespace WiimoteEmu
{
enum class UDrawTabletGroup
{
Buttons,
Stylus,
Touch,
};

class UDrawTablet : public Extension3rdParty
{
public:
UDrawTablet();

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

ControllerEmu::ControlGroup* GetGroup(UDrawTabletGroup group);

static constexpr u8 BUTTON_ROCKER_UP = 0x1;
static constexpr u8 BUTTON_ROCKER_DOWN = 0x2;

struct DataFormat
{
// Bytes 0-2 are 0xff when stylus is lifted
// X increases from left to right
// Y increases from bottom to top
u8 stylus_x1;
u8 stylus_y1;
u8 stylus_x2 : 4;
u8 stylus_y2 : 4;

// Valid even when stylus is lifted
u8 pressure;

// Always 0xff
u8 unk;

// Buttons are 0 when pressed
// 0x04 is always unset (neutral state is 0xfb)
u8 buttons;
};

static_assert(6 == sizeof(DataFormat), "Wrong size.");

private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::AnalogStick* m_stylus;
ControllerEmu::Triggers* m_touch;
};
} // namespace WiimoteEmu
@@ -19,6 +19,7 @@ enum ExtensionNumber : u8
GUITAR,
DRUMS,
TURNTABLE,
UDRAW_TABLET,
};

// FYI: An extension must be attached.
@@ -30,6 +30,7 @@
#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/Extension/UDrawTablet.h"

#include "InputCommon/ControllerEmu/Control/Input.h"
#include "InputCommon/ControllerEmu/Control/Output.h"
@@ -165,6 +166,7 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index)
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::Guitar>());
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::Drums>());
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::Turntable>());
m_attachments->AddAttachment(std::make_unique<WiimoteEmu::UDrawTablet>());

m_attachments->AddSetting(&m_motion_plus_setting, {_trans("Attach MotionPlus")}, true);

@@ -284,6 +286,13 @@ ControllerEmu::ControlGroup* Wiimote::GetTurntableGroup(TurntableGroup group)
->GetGroup(group);
}

ControllerEmu::ControlGroup* Wiimote::GetUDrawTabletGroup(UDrawTabletGroup group)
{
return static_cast<UDrawTablet*>(
m_attachments->GetAttachmentList()[ExtensionNumber::UDRAW_TABLET].get())
->GetGroup(group);
}

bool Wiimote::ProcessExtensionPortEvent()
{
// WiiBrew: Following a connection or disconnection event on the Extension Port,
@@ -55,6 +55,7 @@ enum class ClassicGroup;
enum class GuitarGroup;
enum class DrumsGroup;
enum class TurntableGroup;
enum class UDrawTabletGroup;

template <typename T>
void UpdateCalibrationDataChecksum(T& data, int cksum_bytes)
@@ -113,6 +114,7 @@ class Wiimote : public ControllerEmu::EmulatedController
ControllerEmu::ControlGroup* GetGuitarGroup(GuitarGroup group);
ControllerEmu::ControlGroup* GetDrumsGroup(DrumsGroup group);
ControllerEmu::ControlGroup* GetTurntableGroup(TurntableGroup group);
ControllerEmu::ControlGroup* GetUDrawTabletGroup(UDrawTabletGroup group);

void Update();
void StepDynamics();
@@ -15,6 +15,7 @@
#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/Extension/UDrawTablet.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"

#include "InputCommon/InputConfig.h"
@@ -27,6 +28,7 @@ WiimoteEmuExtension::WiimoteEmuExtension(MappingWindow* window) : MappingWidget(
CreateNoneLayout();
CreateNunchukLayout();
CreateTurntableLayout();
CreateUDrawTabletLayout();
CreateMainLayout();

ChangeExtensionType(WiimoteEmu::ExtensionNumber::NONE);
@@ -181,6 +183,24 @@ void WiimoteEmuExtension::CreateTurntableLayout()
m_turntable_box->setLayout(layout);
}

void WiimoteEmuExtension::CreateUDrawTabletLayout()
{
auto* hbox = new QHBoxLayout();
m_udraw_tablet_box = new QGroupBox(tr("uDraw GameTablet"), this);

hbox->addWidget(CreateGroupBox(
tr("Buttons"),
Wiimote::GetUDrawTabletGroup(GetPort(), WiimoteEmu::UDrawTabletGroup::Buttons)));

hbox->addWidget(CreateGroupBox(
tr("Stylus"), Wiimote::GetUDrawTabletGroup(GetPort(), WiimoteEmu::UDrawTabletGroup::Stylus)));

hbox->addWidget(CreateGroupBox(
tr("Touch"), Wiimote::GetUDrawTabletGroup(GetPort(), WiimoteEmu::UDrawTabletGroup::Touch)));

m_udraw_tablet_box->setLayout(hbox);
}

void WiimoteEmuExtension::CreateMainLayout()
{
m_main_layout = new QHBoxLayout();
@@ -191,6 +211,7 @@ void WiimoteEmuExtension::CreateMainLayout()
m_main_layout->addWidget(m_none_box);
m_main_layout->addWidget(m_nunchuk_box);
m_main_layout->addWidget(m_turntable_box);
m_main_layout->addWidget(m_udraw_tablet_box);

setLayout(m_main_layout);
}
@@ -220,4 +241,5 @@ void WiimoteEmuExtension::ChangeExtensionType(u32 type)
m_guitar_box->setHidden(type != ExtensionNumber::GUITAR);
m_drums_box->setHidden(type != ExtensionNumber::DRUMS);
m_turntable_box->setHidden(type != ExtensionNumber::TURNTABLE);
m_udraw_tablet_box->setHidden(type != ExtensionNumber::UDRAW_TABLET);
}
@@ -31,6 +31,7 @@ class WiimoteEmuExtension final : public MappingWidget
void CreateNoneLayout();
void CreateNunchukLayout();
void CreateTurntableLayout();
void CreateUDrawTabletLayout();
void CreateMainLayout();

// Main
@@ -41,4 +42,5 @@ class WiimoteEmuExtension final : public MappingWidget
QGroupBox* m_none_box;
QGroupBox* m_nunchuk_box;
QGroupBox* m_turntable_box;
QGroupBox* m_udraw_tablet_box;
};