nsyshid: Add infrastructure and support for emulating Skylander Portal (#971)

This commit is contained in:
Joshua de Reeper 2024-06-27 23:55:20 +01:00 committed by GitHub
parent f3d20832c1
commit 93b58ae6f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1658 additions and 58 deletions

View File

@ -457,10 +457,14 @@ add_library(CemuCafe
OS/libs/nsyshid/AttachDefaultBackends.cpp OS/libs/nsyshid/AttachDefaultBackends.cpp
OS/libs/nsyshid/Whitelist.cpp OS/libs/nsyshid/Whitelist.cpp
OS/libs/nsyshid/Whitelist.h OS/libs/nsyshid/Whitelist.h
OS/libs/nsyshid/BackendEmulated.cpp
OS/libs/nsyshid/BackendEmulated.h
OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.cpp
OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendLibusb.h
OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.cpp
OS/libs/nsyshid/BackendWindowsHID.h OS/libs/nsyshid/BackendWindowsHID.h
OS/libs/nsyshid/Skylander.cpp
OS/libs/nsyshid/Skylander.h
OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.cpp
OS/libs/nsyskbd/nsyskbd.h OS/libs/nsyskbd/nsyskbd.h
OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.cpp

View File

@ -1,5 +1,6 @@
#include "nsyshid.h" #include "nsyshid.h"
#include "Backend.h" #include "Backend.h"
#include "BackendEmulated.h"
#if NSYSHID_ENABLE_BACKEND_LIBUSB #if NSYSHID_ENABLE_BACKEND_LIBUSB
@ -37,5 +38,13 @@ namespace nsyshid::backend
} }
} }
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID #endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
// add emulated backend
{
auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>();
if (backendEmulated->IsInitialisedOk())
{
AttachBackend(backendEmulated);
}
}
} }
} // namespace nsyshid::backend } // namespace nsyshid::backend

View File

@ -23,6 +23,55 @@ namespace nsyshid
/* +0x12 */ uint16be maxPacketSizeTX; /* +0x12 */ uint16be maxPacketSizeTX;
} HID_t; } HID_t;
struct TransferCommand
{
uint8* data;
sint32 length;
TransferCommand(uint8* data, sint32 length)
: data(data), length(length)
{
}
virtual ~TransferCommand() = default;
};
struct ReadMessage final : TransferCommand
{
sint32 bytesRead;
ReadMessage(uint8* data, sint32 length, sint32 bytesRead)
: bytesRead(bytesRead), TransferCommand(data, length)
{
}
using TransferCommand::TransferCommand;
};
struct WriteMessage final : TransferCommand
{
sint32 bytesWritten;
WriteMessage(uint8* data, sint32 length, sint32 bytesWritten)
: bytesWritten(bytesWritten), TransferCommand(data, length)
{
}
using TransferCommand::TransferCommand;
};
struct ReportMessage final : TransferCommand
{
uint8* reportData;
sint32 length;
uint8* originalData;
sint32 originalLength;
ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength)
: reportData(reportData), length(length), originalData(originalData),
originalLength(originalLength), TransferCommand(reportData, length)
{
}
using TransferCommand::TransferCommand;
};
static_assert(offsetof(HID_t, vendorId) == 0x8, ""); static_assert(offsetof(HID_t, vendorId) == 0x8, "");
static_assert(offsetof(HID_t, productId) == 0xA, ""); static_assert(offsetof(HID_t, productId) == 0xA, "");
static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
@ -69,7 +118,7 @@ namespace nsyshid
ErrorTimeout, ErrorTimeout,
}; };
virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0; virtual ReadResult Read(ReadMessage* message) = 0;
enum class WriteResult enum class WriteResult
{ {
@ -78,7 +127,7 @@ namespace nsyshid
ErrorTimeout, ErrorTimeout,
}; };
virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0; virtual WriteResult Write(WriteMessage* message) = 0;
virtual bool GetDescriptor(uint8 descType, virtual bool GetDescriptor(uint8 descType,
uint8 descIndex, uint8 descIndex,
@ -88,7 +137,7 @@ namespace nsyshid
virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0;
virtual bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) = 0; virtual bool SetReport(ReportMessage* message) = 0;
}; };
class Backend { class Backend {
@ -121,6 +170,8 @@ namespace nsyshid
std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice); std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice);
bool FindDeviceById(uint16 vendorId, uint16 productId);
bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId);
// called from OnAttach() - attach devices that your backend can see here // called from OnAttach() - attach devices that your backend can see here

View File

@ -0,0 +1,29 @@
#include "BackendEmulated.h"
#include "Skylander.h"
#include "config/CemuConfig.h"
namespace nsyshid::backend::emulated
{
BackendEmulated::BackendEmulated()
{
cemuLog_logDebug(LogType::Force, "nsyshid::BackendEmulated: emulated backend initialised");
}
BackendEmulated::~BackendEmulated() = default;
bool BackendEmulated::IsInitialisedOk()
{
return true;
}
void BackendEmulated::AttachVisibleDevices()
{
if (GetConfig().emulated_usb_devices.emulate_skylander_portal && !FindDeviceById(0x1430, 0x0150))
{
cemuLog_logDebug(LogType::Force, "Attaching Emulated Portal");
// Add Skylander Portal
auto device = std::make_shared<SkylanderPortalDevice>();
AttachDevice(device);
}
}
} // namespace nsyshid::backend::emulated

View File

@ -0,0 +1,16 @@
#include "nsyshid.h"
#include "Backend.h"
namespace nsyshid::backend::emulated
{
class BackendEmulated : public nsyshid::Backend {
public:
BackendEmulated();
~BackendEmulated();
bool IsInitialisedOk() override;
protected:
void AttachVisibleDevices() override;
};
} // namespace nsyshid::backend::emulated

View File

@ -241,11 +241,6 @@ namespace nsyshid::backend::libusb
ret); ret);
return nullptr; return nullptr;
} }
if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241)
{
cemuLog_logDebug(LogType::Force,
"nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected");
}
auto device = std::make_shared<DeviceLibusb>(m_ctx, auto device = std::make_shared<DeviceLibusb>(m_ctx,
desc.idVendor, desc.idVendor,
desc.idProduct, desc.idProduct,
@ -471,7 +466,7 @@ namespace nsyshid::backend::libusb
return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; return m_libusbHandle != nullptr && m_handleInUseCounter >= 0;
} }
Device::ReadResult DeviceLibusb::Read(uint8* data, sint32 length, sint32& bytesRead) Device::ReadResult DeviceLibusb::Read(ReadMessage* message)
{ {
auto handleLock = AquireHandleLock(); auto handleLock = AquireHandleLock();
if (!handleLock->IsValid()) if (!handleLock->IsValid())
@ -488,8 +483,8 @@ namespace nsyshid::backend::libusb
{ {
ret = libusb_bulk_transfer(handleLock->GetHandle(), ret = libusb_bulk_transfer(handleLock->GetHandle(),
this->m_libusbEndpointIn, this->m_libusbEndpointIn,
data, message->data,
length, message->length,
&actualLength, &actualLength,
timeout); timeout);
} }
@ -500,8 +495,8 @@ namespace nsyshid::backend::libusb
// success // success
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes",
actualLength, actualLength,
length); message->length);
bytesRead = actualLength; message->bytesRead = actualLength;
return ReadResult::Success; return ReadResult::Success;
} }
cemuLog_logDebug(LogType::Force, cemuLog_logDebug(LogType::Force,
@ -510,7 +505,7 @@ namespace nsyshid::backend::libusb
return ReadResult::Error; return ReadResult::Error;
} }
Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten) Device::WriteResult DeviceLibusb::Write(WriteMessage* message)
{ {
auto handleLock = AquireHandleLock(); auto handleLock = AquireHandleLock();
if (!handleLock->IsValid()) if (!handleLock->IsValid())
@ -520,23 +515,23 @@ namespace nsyshid::backend::libusb
return WriteResult::Error; return WriteResult::Error;
} }
bytesWritten = 0; message->bytesWritten = 0;
int actualLength = 0; int actualLength = 0;
int ret = libusb_bulk_transfer(handleLock->GetHandle(), int ret = libusb_bulk_transfer(handleLock->GetHandle(),
this->m_libusbEndpointOut, this->m_libusbEndpointOut,
data, message->data,
length, message->length,
&actualLength, &actualLength,
0); 0);
if (ret == 0) if (ret == 0)
{ {
// success // success
bytesWritten = actualLength; message->bytesWritten = actualLength;
cemuLog_logDebug(LogType::Force, cemuLog_logDebug(LogType::Force,
"nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes",
bytesWritten, message->bytesWritten,
length); message->length);
return WriteResult::Success; return WriteResult::Success;
} }
cemuLog_logDebug(LogType::Force, cemuLog_logDebug(LogType::Force,
@ -713,8 +708,7 @@ namespace nsyshid::backend::libusb
return true; return true;
} }
bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData, bool DeviceLibusb::SetReport(ReportMessage* message)
sint32 originalLength)
{ {
auto handleLock = AquireHandleLock(); auto handleLock = AquireHandleLock();
if (!handleLock->IsValid()) if (!handleLock->IsValid())
@ -731,8 +725,8 @@ namespace nsyshid::backend::libusb
bRequest, bRequest,
wValue, wValue,
wIndex, wIndex,
reportData, message->reportData,
length, message->length,
timeout); timeout);
#endif #endif

View File

@ -63,9 +63,9 @@ namespace nsyshid::backend::libusb
bool IsOpened() override; bool IsOpened() override;
ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; ReadResult Read(ReadMessage* message) override;
WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; WriteResult Write(WriteMessage* message) override;
bool GetDescriptor(uint8 descType, bool GetDescriptor(uint8 descType,
uint8 descIndex, uint8 descIndex,
@ -75,7 +75,7 @@ namespace nsyshid::backend::libusb
bool SetProtocol(uint32 ifIndex, uint32 protocol) override; bool SetProtocol(uint32 ifIndex, uint32 protocol) override;
bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; bool SetReport(ReportMessage* message) override;
uint8 m_libusbBusNumber; uint8 m_libusbBusNumber;
uint8 m_libusbDeviceAddress; uint8 m_libusbDeviceAddress;

View File

@ -196,20 +196,20 @@ namespace nsyshid::backend::windows
return m_hFile != INVALID_HANDLE_VALUE; return m_hFile != INVALID_HANDLE_VALUE;
} }
Device::ReadResult DeviceWindowsHID::Read(uint8* data, sint32 length, sint32& bytesRead) Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message)
{ {
bytesRead = 0; message->bytesRead = 0;
DWORD bt; DWORD bt;
OVERLAPPED ovlp = {0}; OVERLAPPED ovlp = {0};
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
uint8* tempBuffer = (uint8*)malloc(length + 1); uint8* tempBuffer = (uint8*)malloc(message->length + 1);
sint32 transferLength = 0; // minus report byte sint32 transferLength = 0; // minus report byte
_debugPrintHex("HID_READ_BEFORE", data, length); _debugPrintHex("HID_READ_BEFORE", message->data, message->length);
cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length); cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length);
BOOL readResult = ReadFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
if (readResult != FALSE) if (readResult != FALSE)
{ {
// sometimes we get the result immediately // sometimes we get the result immediately
@ -247,7 +247,7 @@ namespace nsyshid::backend::windows
ReadResult result = ReadResult::Success; ReadResult result = ReadResult::Success;
if (bt != 0) if (bt != 0)
{ {
memcpy(data, tempBuffer + 1, transferLength); memcpy(message->data, tempBuffer + 1, transferLength);
sint32 hidReadLength = transferLength; sint32 hidReadLength = transferLength;
char debugOutput[1024] = {0}; char debugOutput[1024] = {0};
@ -257,7 +257,7 @@ namespace nsyshid::backend::windows
} }
cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput);
bytesRead = transferLength; message->bytesRead = transferLength;
result = ReadResult::Success; result = ReadResult::Success;
} }
else else
@ -270,19 +270,19 @@ namespace nsyshid::backend::windows
return result; return result;
} }
Device::WriteResult DeviceWindowsHID::Write(uint8* data, sint32 length, sint32& bytesWritten) Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message)
{ {
bytesWritten = 0; message->bytesWritten = 0;
DWORD bt; DWORD bt;
OVERLAPPED ovlp = {0}; OVERLAPPED ovlp = {0};
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
uint8* tempBuffer = (uint8*)malloc(length + 1); uint8* tempBuffer = (uint8*)malloc(message->length + 1);
memcpy(tempBuffer + 1, data, length); memcpy(tempBuffer + 1, message->data, message->length);
tempBuffer[0] = 0; // report byte? tempBuffer[0] = 0; // report byte?
cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", length); cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length);
BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
if (writeResult != FALSE) if (writeResult != FALSE)
{ {
// sometimes we get the result immediately // sometimes we get the result immediately
@ -314,7 +314,7 @@ namespace nsyshid::backend::windows
if (bt != 0) if (bt != 0)
{ {
bytesWritten = length; message->bytesWritten = message->length;
return WriteResult::Success; return WriteResult::Success;
} }
return WriteResult::Error; return WriteResult::Error;
@ -407,12 +407,12 @@ namespace nsyshid::backend::windows
return true; return true;
} }
bool DeviceWindowsHID::SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) bool DeviceWindowsHID::SetReport(ReportMessage* message)
{ {
sint32 retryCount = 0; sint32 retryCount = 0;
while (true) while (true)
{ {
BOOL r = HidD_SetOutputReport(this->m_hFile, reportData, length); BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length);
if (r != FALSE) if (r != FALSE)
break; break;
Sleep(20); // retry Sleep(20); // retry

View File

@ -41,15 +41,15 @@ namespace nsyshid::backend::windows
bool IsOpened() override; bool IsOpened() override;
ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; ReadResult Read(ReadMessage* message) override;
WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; WriteResult Write(WriteMessage* message) override;
bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override;
bool SetProtocol(uint32 ifIndef, uint32 protocol) override; bool SetProtocol(uint32 ifIndef, uint32 protocol) override;
bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; bool SetReport(ReportMessage* message) override;
private: private:
wchar_t* m_devicePath; wchar_t* m_devicePath;

View File

@ -0,0 +1,939 @@
#include "Skylander.h"
#include "nsyshid.h"
#include "Backend.h"
#include "Common/FileStream.h"
namespace nsyshid
{
SkylanderUSB g_skyportal;
const std::map<const std::pair<const uint16, const uint16>, const std::string>
listSkylanders = {
{{0, 0x0000}, "Whirlwind"},
{{0, 0x1801}, "Series 2 Whirlwind"},
{{0, 0x1C02}, "Polar Whirlwind"},
{{0, 0x2805}, "Horn Blast Whirlwind"},
{{0, 0x3810}, "Eon's Elite Whirlwind"},
{{1, 0x0000}, "Sonic Boom"},
{{1, 0x1801}, "Series 2 Sonic Boom"},
{{2, 0x0000}, "Warnado"},
{{2, 0x2206}, "LightCore Warnado"},
{{3, 0x0000}, "Lightning Rod"},
{{3, 0x1801}, "Series 2 Lightning Rod"},
{{4, 0x0000}, "Bash"},
{{4, 0x1801}, "Series 2 Bash"},
{{5, 0x0000}, "Terrafin"},
{{5, 0x1801}, "Series 2 Terrafin"},
{{5, 0x2805}, "Knockout Terrafin"},
{{5, 0x3810}, "Eon's Elite Terrafin"},
{{6, 0x0000}, "Dino Rang"},
{{6, 0x4810}, "Eon's Elite Dino Rang"},
{{7, 0x0000}, "Prism Break"},
{{7, 0x1801}, "Series 2 Prism Break"},
{{7, 0x2805}, "Hyper Beam Prism Break"},
{{7, 0x1206}, "LightCore Prism Break"},
{{8, 0x0000}, "Sunburn"},
{{9, 0x0000}, "Eruptor"},
{{9, 0x1801}, "Series 2 Eruptor"},
{{9, 0x2C02}, "Volcanic Eruptor"},
{{9, 0x2805}, "Lava Barf Eruptor"},
{{9, 0x1206}, "LightCore Eruptor"},
{{9, 0x3810}, "Eon's Elite Eruptor"},
{{10, 0x0000}, "Ignitor"},
{{10, 0x1801}, "Series 2 Ignitor"},
{{10, 0x1C03}, "Legendary Ignitor"},
{{11, 0x0000}, "Flameslinger"},
{{11, 0x1801}, "Series 2 Flameslinger"},
{{12, 0x0000}, "Zap"},
{{12, 0x1801}, "Series 2 Zap"},
{{13, 0x0000}, "Wham Shell"},
{{13, 0x2206}, "LightCore Wham Shell"},
{{14, 0x0000}, "Gill Grunt"},
{{14, 0x1801}, "Series 2 Gill Grunt"},
{{14, 0x2805}, "Anchors Away Gill Grunt"},
{{14, 0x3805}, "Tidal Wave Gill Grunt"},
{{14, 0x3810}, "Eon's Elite Gill Grunt"},
{{15, 0x0000}, "Slam Bam"},
{{15, 0x1801}, "Series 2 Slam Bam"},
{{15, 0x1C03}, "Legendary Slam Bam"},
{{15, 0x4810}, "Eon's Elite Slam Bam"},
{{16, 0x0000}, "Spyro"},
{{16, 0x1801}, "Series 2 Spyro"},
{{16, 0x2C02}, "Dark Mega Ram Spyro"},
{{16, 0x2805}, "Mega Ram Spyro"},
{{16, 0x3810}, "Eon's Elite Spyro"},
{{17, 0x0000}, "Voodood"},
{{17, 0x4810}, "Eon's Elite Voodood"},
{{18, 0x0000}, "Double Trouble"},
{{18, 0x1801}, "Series 2 Double Trouble"},
{{18, 0x1C02}, "Royal Double Trouble"},
{{19, 0x0000}, "Trigger Happy"},
{{19, 0x1801}, "Series 2 Trigger Happy"},
{{19, 0x2C02}, "Springtime Trigger Happy"},
{{19, 0x2805}, "Big Bang Trigger Happy"},
{{19, 0x3810}, "Eon's Elite Trigger Happy"},
{{20, 0x0000}, "Drobot"},
{{20, 0x1801}, "Series 2 Drobot"},
{{20, 0x1206}, "LightCore Drobot"},
{{21, 0x0000}, "Drill Seargeant"},
{{21, 0x1801}, "Series 2 Drill Seargeant"},
{{22, 0x0000}, "Boomer"},
{{22, 0x4810}, "Eon's Elite Boomer"},
{{23, 0x0000}, "Wrecking Ball"},
{{23, 0x1801}, "Series 2 Wrecking Ball"},
{{24, 0x0000}, "Camo"},
{{24, 0x2805}, "Thorn Horn Camo"},
{{25, 0x0000}, "Zook"},
{{25, 0x1801}, "Series 2 Zook"},
{{25, 0x4810}, "Eon's Elite Zook"},
{{26, 0x0000}, "Stealth Elf"},
{{26, 0x1801}, "Series 2 Stealth Elf"},
{{26, 0x2C02}, "Dark Stealth Elf"},
{{26, 0x1C03}, "Legendary Stealth Elf"},
{{26, 0x2805}, "Ninja Stealth Elf"},
{{26, 0x3810}, "Eon's Elite Stealth Elf"},
{{27, 0x0000}, "Stump Smash"},
{{27, 0x1801}, "Series 2 Stump Smash"},
{{28, 0x0000}, "Dark Spyro"},
{{29, 0x0000}, "Hex"},
{{29, 0x1801}, "Series 2 Hex"},
{{29, 0x1206}, "LightCore Hex"},
{{30, 0x0000}, "Chop Chop"},
{{30, 0x1801}, "Series 2 Chop Chop"},
{{30, 0x2805}, "Twin Blade Chop Chop"},
{{30, 0x3810}, "Eon's Elite Chop Chop"},
{{31, 0x0000}, "Ghost Roaster"},
{{31, 0x4810}, "Eon's Elite Ghost Roaster"},
{{32, 0x0000}, "Cynder"},
{{32, 0x1801}, "Series 2 Cynder"},
{{32, 0x2805}, "Phantom Cynder"},
{{100, 0x0000}, "Jet Vac"},
{{100, 0x1403}, "Legendary Jet Vac"},
{{100, 0x2805}, "Turbo Jet Vac"},
{{100, 0x3805}, "Full Blast Jet Vac"},
{{100, 0x1206}, "LightCore Jet Vac"},
{{101, 0x0000}, "Swarm"},
{{102, 0x0000}, "Crusher"},
{{102, 0x1602}, "Granite Crusher"},
{{103, 0x0000}, "Flashwing"},
{{103, 0x1402}, "Jade Flash Wing"},
{{103, 0x2206}, "LightCore Flashwing"},
{{104, 0x0000}, "Hot Head"},
{{105, 0x0000}, "Hot Dog"},
{{105, 0x1402}, "Molten Hot Dog"},
{{105, 0x2805}, "Fire Bone Hot Dog"},
{{106, 0x0000}, "Chill"},
{{106, 0x1603}, "Legendary Chill"},
{{106, 0x2805}, "Blizzard Chill"},
{{106, 0x1206}, "LightCore Chill"},
{{107, 0x0000}, "Thumpback"},
{{108, 0x0000}, "Pop Fizz"},
{{108, 0x1402}, "Punch Pop Fizz"},
{{108, 0x3C02}, "Love Potion Pop Fizz"},
{{108, 0x2805}, "Super Gulp Pop Fizz"},
{{108, 0x3805}, "Fizzy Frenzy Pop Fizz"},
{{108, 0x1206}, "LightCore Pop Fizz"},
{{109, 0x0000}, "Ninjini"},
{{109, 0x1602}, "Scarlet Ninjini"},
{{110, 0x0000}, "Bouncer"},
{{110, 0x1603}, "Legendary Bouncer"},
{{111, 0x0000}, "Sprocket"},
{{111, 0x2805}, "Heavy Duty Sprocket"},
{{112, 0x0000}, "Tree Rex"},
{{112, 0x1602}, "Gnarly Tree Rex"},
{{113, 0x0000}, "Shroomboom"},
{{113, 0x3805}, "Sure Shot Shroomboom"},
{{113, 0x1206}, "LightCore Shroomboom"},
{{114, 0x0000}, "Eye Brawl"},
{{115, 0x0000}, "Fright Rider"},
{{200, 0x0000}, "Anvil Rain"},
{{201, 0x0000}, "Hidden Treasure"},
{{201, 0x2000}, "Platinum Hidden Treasure"},
{{202, 0x0000}, "Healing Elixir"},
{{203, 0x0000}, "Ghost Pirate Swords"},
{{204, 0x0000}, "Time Twist Hourglass"},
{{205, 0x0000}, "Sky Iron Shield"},
{{206, 0x0000}, "Winged Boots"},
{{207, 0x0000}, "Sparx the Dragonfly"},
{{208, 0x0000}, "Dragonfire Cannon"},
{{208, 0x1602}, "Golden Dragonfire Cannon"},
{{209, 0x0000}, "Scorpion Striker"},
{{210, 0x3002}, "Biter's Bane"},
{{210, 0x3008}, "Sorcerous Skull"},
{{210, 0x300B}, "Axe of Illusion"},
{{210, 0x300E}, "Arcane Hourglass"},
{{210, 0x3012}, "Spell Slapper"},
{{210, 0x3014}, "Rune Rocket"},
{{211, 0x3001}, "Tidal Tiki"},
{{211, 0x3002}, "Wet Walter"},
{{211, 0x3006}, "Flood Flask"},
{{211, 0x3406}, "Legendary Flood Flask"},
{{211, 0x3007}, "Soaking Staff"},
{{211, 0x300B}, "Aqua Axe"},
{{211, 0x3016}, "Frost Helm"},
{{212, 0x3003}, "Breezy Bird"},
{{212, 0x3006}, "Drafty Decanter"},
{{212, 0x300D}, "Tempest Timer"},
{{212, 0x3010}, "Cloudy Cobra"},
{{212, 0x3011}, "Storm Warning"},
{{212, 0x3018}, "Cyclone Saber"},
{{213, 0x3004}, "Spirit Sphere"},
{{213, 0x3404}, "Legendary Spirit Sphere"},
{{213, 0x3008}, "Spectral Skull"},
{{213, 0x3408}, "Legendary Spectral Skull"},
{{213, 0x300B}, "Haunted Hatchet"},
{{213, 0x300C}, "Grim Gripper"},
{{213, 0x3010}, "Spooky Snake"},
{{213, 0x3017}, "Dream Piercer"},
{{214, 0x3000}, "Tech Totem"},
{{214, 0x3007}, "Automatic Angel"},
{{214, 0x3009}, "Factory Flower"},
{{214, 0x300C}, "Grabbing Gadget"},
{{214, 0x3016}, "Makers Mana"},
{{214, 0x301A}, "Topsy Techy"},
{{215, 0x3005}, "Eternal Flame"},
{{215, 0x3009}, "Fire Flower"},
{{215, 0x3011}, "Scorching Stopper"},
{{215, 0x3012}, "Searing Spinner"},
{{215, 0x3017}, "Spark Spear"},
{{215, 0x301B}, "Blazing Belch"},
{{216, 0x3000}, "Banded Boulder"},
{{216, 0x3003}, "Rock Hawk"},
{{216, 0x300A}, "Slag Hammer"},
{{216, 0x300E}, "Dust Of Time"},
{{216, 0x3013}, "Spinning Sandstorm"},
{{216, 0x301A}, "Rubble Trouble"},
{{217, 0x3003}, "Oak Eagle"},
{{217, 0x3005}, "Emerald Energy"},
{{217, 0x300A}, "Weed Whacker"},
{{217, 0x3010}, "Seed Serpent"},
{{217, 0x3018}, "Jade Blade"},
{{217, 0x301B}, "Shrub Shrieker"},
{{218, 0x3000}, "Dark Dagger"},
{{218, 0x3014}, "Shadow Spider"},
{{218, 0x301A}, "Ghastly Grimace"},
{{219, 0x3000}, "Shining Ship"},
{{219, 0x300F}, "Heavenly Hawk"},
{{219, 0x301B}, "Beam Scream"},
{{220, 0x301E}, "Kaos Trap"},
{{220, 0x351F}, "Ultimate Kaos Trap"},
{{230, 0x0000}, "Hand of Fate"},
{{230, 0x3403}, "Legendary Hand of Fate"},
{{231, 0x0000}, "Piggy Bank"},
{{232, 0x0000}, "Rocket Ram"},
{{233, 0x0000}, "Tiki Speaky"},
{{300, 0x0000}, "Dragons Peak"},
{{301, 0x0000}, "Empire of Ice"},
{{302, 0x0000}, "Pirate Seas"},
{{303, 0x0000}, "Darklight Crypt"},
{{304, 0x0000}, "Volcanic Vault"},
{{305, 0x0000}, "Mirror of Mystery"},
{{306, 0x0000}, "Nightmare Express"},
{{307, 0x0000}, "Sunscraper Spire"},
{{308, 0x0000}, "Midnight Museum"},
{{404, 0x0000}, "Legendary Bash"},
{{416, 0x0000}, "Legendary Spyro"},
{{419, 0x0000}, "Legendary Trigger Happy"},
{{430, 0x0000}, "Legendary Chop Chop"},
{{450, 0x0000}, "Gusto"},
{{451, 0x0000}, "Thunderbolt"},
{{452, 0x0000}, "Fling Kong"},
{{453, 0x0000}, "Blades"},
{{453, 0x3403}, "Legendary Blades"},
{{454, 0x0000}, "Wallop"},
{{455, 0x0000}, "Head Rush"},
{{455, 0x3402}, "Nitro Head Rush"},
{{456, 0x0000}, "Fist Bump"},
{{457, 0x0000}, "Rocky Roll"},
{{458, 0x0000}, "Wildfire"},
{{458, 0x3402}, "Dark Wildfire"},
{{459, 0x0000}, "Ka Boom"},
{{460, 0x0000}, "Trail Blazer"},
{{461, 0x0000}, "Torch"},
{{462, 0x3000}, "Snap Shot"},
{{462, 0x3402}, "Dark Snap Shot"},
{{463, 0x0000}, "Lob Star"},
{{463, 0x3402}, "Winterfest Lob-Star"},
{{464, 0x0000}, "Flip Wreck"},
{{465, 0x0000}, "Echo"},
{{466, 0x0000}, "Blastermind"},
{{467, 0x0000}, "Enigma"},
{{468, 0x0000}, "Deja Vu"},
{{468, 0x3403}, "Legendary Deja Vu"},
{{469, 0x0000}, "Cobra Candabra"},
{{469, 0x3402}, "King Cobra Cadabra"},
{{470, 0x0000}, "Jawbreaker"},
{{470, 0x3403}, "Legendary Jawbreaker"},
{{471, 0x0000}, "Gearshift"},
{{472, 0x0000}, "Chopper"},
{{473, 0x0000}, "Tread Head"},
{{474, 0x0000}, "Bushwack"},
{{474, 0x3403}, "Legendary Bushwack"},
{{475, 0x0000}, "Tuff Luck"},
{{476, 0x0000}, "Food Fight"},
{{476, 0x3402}, "Dark Food Fight"},
{{477, 0x0000}, "High Five"},
{{478, 0x0000}, "Krypt King"},
{{478, 0x3402}, "Nitro Krypt King"},
{{479, 0x0000}, "Short Cut"},
{{480, 0x0000}, "Bat Spin"},
{{481, 0x0000}, "Funny Bone"},
{{482, 0x0000}, "Knight Light"},
{{483, 0x0000}, "Spotlight"},
{{484, 0x0000}, "Knight Mare"},
{{485, 0x0000}, "Blackout"},
{{502, 0x0000}, "Bop"},
{{505, 0x0000}, "Terrabite"},
{{506, 0x0000}, "Breeze"},
{{508, 0x0000}, "Pet Vac"},
{{508, 0x3402}, "Power Punch Pet Vac"},
{{507, 0x0000}, "Weeruptor"},
{{507, 0x3402}, "Eggcellent Weeruptor"},
{{509, 0x0000}, "Small Fry"},
{{510, 0x0000}, "Drobit"},
{{519, 0x0000}, "Trigger Snappy"},
{{526, 0x0000}, "Whisper Elf"},
{{540, 0x0000}, "Barkley"},
{{540, 0x3402}, "Gnarly Barkley"},
{{541, 0x0000}, "Thumpling"},
{{514, 0x0000}, "Gill Runt"},
{{542, 0x0000}, "Mini-Jini"},
{{503, 0x0000}, "Spry"},
{{504, 0x0000}, "Hijinx"},
{{543, 0x0000}, "Eye Small"},
{{601, 0x0000}, "King Pen"},
{{602, 0x0000}, "Tri-Tip"},
{{603, 0x0000}, "Chopscotch"},
{{604, 0x0000}, "Boom Bloom"},
{{605, 0x0000}, "Pit Boss"},
{{606, 0x0000}, "Barbella"},
{{607, 0x0000}, "Air Strike"},
{{608, 0x0000}, "Ember"},
{{609, 0x0000}, "Ambush"},
{{610, 0x0000}, "Dr. Krankcase"},
{{611, 0x0000}, "Hood Sickle"},
{{612, 0x0000}, "Tae Kwon Crow"},
{{613, 0x0000}, "Golden Queen"},
{{614, 0x0000}, "Wolfgang"},
{{615, 0x0000}, "Pain-Yatta"},
{{616, 0x0000}, "Mysticat"},
{{617, 0x0000}, "Starcast"},
{{618, 0x0000}, "Buckshot"},
{{619, 0x0000}, "Aurora"},
{{620, 0x0000}, "Flare Wolf"},
{{621, 0x0000}, "Chompy Mage"},
{{622, 0x0000}, "Bad Juju"},
{{623, 0x0000}, "Grave Clobber"},
{{624, 0x0000}, "Blaster-Tron"},
{{625, 0x0000}, "Ro-Bow"},
{{626, 0x0000}, "Chain Reaction"},
{{627, 0x0000}, "Kaos"},
{{628, 0x0000}, "Wild Storm"},
{{629, 0x0000}, "Tidepool"},
{{630, 0x0000}, "Crash Bandicoot"},
{{631, 0x0000}, "Dr. Neo Cortex"},
{{1000, 0x0000}, "Boom Jet (Bottom)"},
{{1001, 0x0000}, "Free Ranger (Bottom)"},
{{1001, 0x2403}, "Legendary Free Ranger (Bottom)"},
{{1002, 0x0000}, "Rubble Rouser (Bottom)"},
{{1003, 0x0000}, "Doom Stone (Bottom)"},
{{1004, 0x0000}, "Blast Zone (Bottom)"},
{{1004, 0x2402}, "Dark Blast Zone (Bottom)"},
{{1005, 0x0000}, "Fire Kraken (Bottom)"},
{{1005, 0x2402}, "Jade Fire Kraken (Bottom)"},
{{1006, 0x0000}, "Stink Bomb (Bottom)"},
{{1007, 0x0000}, "Grilla Drilla (Bottom)"},
{{1008, 0x0000}, "Hoot Loop (Bottom)"},
{{1008, 0x2402}, "Enchanted Hoot Loop (Bottom)"},
{{1009, 0x0000}, "Trap Shadow (Bottom)"},
{{1010, 0x0000}, "Magna Charge (Bottom)"},
{{1010, 0x2402}, "Nitro Magna Charge (Bottom)"},
{{1011, 0x0000}, "Spy Rise (Bottom)"},
{{1012, 0x0000}, "Night Shift (Bottom)"},
{{1012, 0x2403}, "Legendary Night Shift (Bottom)"},
{{1013, 0x0000}, "Rattle Shake (Bottom)"},
{{1013, 0x2402}, "Quick Draw Rattle Shake (Bottom)"},
{{1014, 0x0000}, "Freeze Blade (Bottom)"},
{{1014, 0x2402}, "Nitro Freeze Blade (Bottom)"},
{{1015, 0x0000}, "Wash Buckler (Bottom)"},
{{1015, 0x2402}, "Dark Wash Buckler (Bottom)"},
{{2000, 0x0000}, "Boom Jet (Top)"},
{{2001, 0x0000}, "Free Ranger (Top)"},
{{2001, 0x2403}, "Legendary Free Ranger (Top)"},
{{2002, 0x0000}, "Rubble Rouser (Top)"},
{{2003, 0x0000}, "Doom Stone (Top)"},
{{2004, 0x0000}, "Blast Zone (Top)"},
{{2004, 0x2402}, "Dark Blast Zone (Top)"},
{{2005, 0x0000}, "Fire Kraken (Top)"},
{{2005, 0x2402}, "Jade Fire Kraken (Top)"},
{{2006, 0x0000}, "Stink Bomb (Top)"},
{{2007, 0x0000}, "Grilla Drilla (Top)"},
{{2008, 0x0000}, "Hoot Loop (Top)"},
{{2008, 0x2402}, "Enchanted Hoot Loop (Top)"},
{{2009, 0x0000}, "Trap Shadow (Top)"},
{{2010, 0x0000}, "Magna Charge (Top)"},
{{2010, 0x2402}, "Nitro Magna Charge (Top)"},
{{2011, 0x0000}, "Spy Rise (Top)"},
{{2012, 0x0000}, "Night Shift (Top)"},
{{2012, 0x2403}, "Legendary Night Shift (Top)"},
{{2013, 0x0000}, "Rattle Shake (Top)"},
{{2013, 0x2402}, "Quick Draw Rattle Shake (Top)"},
{{2014, 0x0000}, "Freeze Blade (Top)"},
{{2014, 0x2402}, "Nitro Freeze Blade (Top)"},
{{2015, 0x0000}, "Wash Buckler (Top)"},
{{2015, 0x2402}, "Dark Wash Buckler (Top)"},
{{3000, 0x0000}, "Scratch"},
{{3001, 0x0000}, "Pop Thorn"},
{{3002, 0x0000}, "Slobber Tooth"},
{{3002, 0x2402}, "Dark Slobber Tooth"},
{{3003, 0x0000}, "Scorp"},
{{3004, 0x0000}, "Fryno"},
{{3004, 0x3805}, "Hog Wild Fryno"},
{{3005, 0x0000}, "Smolderdash"},
{{3005, 0x2206}, "LightCore Smolderdash"},
{{3006, 0x0000}, "Bumble Blast"},
{{3006, 0x2402}, "Jolly Bumble Blast"},
{{3006, 0x2206}, "LightCore Bumble Blast"},
{{3007, 0x0000}, "Zoo Lou"},
{{3007, 0x2403}, "Legendary Zoo Lou"},
{{3008, 0x0000}, "Dune Bug"},
{{3009, 0x0000}, "Star Strike"},
{{3009, 0x2602}, "Enchanted Star Strike"},
{{3009, 0x2206}, "LightCore Star Strike"},
{{3010, 0x0000}, "Countdown"},
{{3010, 0x2402}, "Kickoff Countdown"},
{{3010, 0x2206}, "LightCore Countdown"},
{{3011, 0x0000}, "Wind Up"},
{{3011, 0x2404}, "Gear Head VVind Up"},
{{3012, 0x0000}, "Roller Brawl"},
{{3013, 0x0000}, "Grim Creeper"},
{{3013, 0x2603}, "Legendary Grim Creeper"},
{{3013, 0x2206}, "LightCore Grim Creeper"},
{{3014, 0x0000}, "Rip Tide"},
{{3015, 0x0000}, "Punk Shock"},
{{3200, 0x0000}, "Battle Hammer"},
{{3201, 0x0000}, "Sky Diamond"},
{{3202, 0x0000}, "Platinum Sheep"},
{{3203, 0x0000}, "Groove Machine"},
{{3204, 0x0000}, "UFO Hat"},
{{3300, 0x0000}, "Sheep Wreck Island"},
{{3301, 0x0000}, "Tower of Time"},
{{3302, 0x0000}, "Fiery Forge"},
{{3303, 0x0000}, "Arkeyan Crossbow"},
{{3220, 0x0000}, "Jet Stream"},
{{3221, 0x0000}, "Tomb Buggy"},
{{3222, 0x0000}, "Reef Ripper"},
{{3223, 0x0000}, "Burn Cycle"},
{{3224, 0x0000}, "Hot Streak"},
{{3224, 0x4402}, "Dark Hot Streak"},
{{3224, 0x4004}, "E3 Hot Streak"},
{{3224, 0x441E}, "Golden Hot Streak"},
{{3225, 0x0000}, "Shark Tank"},
{{3226, 0x0000}, "Thump Truck"},
{{3227, 0x0000}, "Crypt Crusher"},
{{3228, 0x0000}, "Stealth Stinger"},
{{3228, 0x4402}, "Nitro Stealth Stinger"},
{{3231, 0x0000}, "Dive Bomber"},
{{3231, 0x4402}, "Spring Ahead Dive Bomber"},
{{3232, 0x0000}, "Sky Slicer"},
{{3233, 0x0000}, "Clown Cruiser (Nintendo Only)"},
{{3233, 0x4402}, "Dark Clown Cruiser (Nintendo Only)"},
{{3234, 0x0000}, "Gold Rusher"},
{{3234, 0x4402}, "Power Blue Gold Rusher"},
{{3235, 0x0000}, "Shield Striker"},
{{3236, 0x0000}, "Sun Runner"},
{{3236, 0x4403}, "Legendary Sun Runner"},
{{3237, 0x0000}, "Sea Shadow"},
{{3237, 0x4402}, "Dark Sea Shadow"},
{{3238, 0x0000}, "Splatter Splasher"},
{{3238, 0x4402}, "Power Blue Splatter Splasher"},
{{3239, 0x0000}, "Soda Skimmer"},
{{3239, 0x4402}, "Nitro Soda Skimmer"},
{{3240, 0x0000}, "Barrel Blaster (Nintendo Only)"},
{{3240, 0x4402}, "Dark Barrel Blaster (Nintendo Only)"},
{{3241, 0x0000}, "Buzz Wing"},
{{3400, 0x0000}, "Fiesta"},
{{3400, 0x4515}, "Frightful Fiesta"},
{{3401, 0x0000}, "High Volt"},
{{3402, 0x0000}, "Splat"},
{{3402, 0x4502}, "Power Blue Splat"},
{{3406, 0x0000}, "Stormblade"},
{{3411, 0x0000}, "Smash Hit"},
{{3411, 0x4502}, "Steel Plated Smash Hit"},
{{3412, 0x0000}, "Spitfire"},
{{3412, 0x4502}, "Dark Spitfire"},
{{3413, 0x0000}, "Hurricane Jet Vac"},
{{3413, 0x4503}, "Legendary Hurricane Jet Vac"},
{{3414, 0x0000}, "Double Dare Trigger Happy"},
{{3414, 0x4502}, "Power Blue Double Dare Trigger Happy"},
{{3415, 0x0000}, "Super Shot Stealth Elf"},
{{3415, 0x4502}, "Dark Super Shot Stealth Elf"},
{{3416, 0x0000}, "Shark Shooter Terrafin"},
{{3417, 0x0000}, "Bone Bash Roller Brawl"},
{{3417, 0x4503}, "Legendary Bone Bash Roller Brawl"},
{{3420, 0x0000}, "Big Bubble Pop Fizz"},
{{3420, 0x450E}, "Birthday Bash Big Bubble Pop Fizz"},
{{3421, 0x0000}, "Lava Lance Eruptor"},
{{3422, 0x0000}, "Deep Dive Gill Grunt"},
{{3423, 0x0000}, "Turbo Charge Donkey Kong (Nintendo Only)"},
{{3423, 0x4502}, "Dark Turbo Charge Donkey Kong (Nintendo Only)"},
{{3424, 0x0000}, "Hammer Slam Bowser (Nintendo Only)"},
{{3424, 0x4502}, "Dark Hammer Slam Bowser (Nintendo Only)"},
{{3425, 0x0000}, "Dive-Clops"},
{{3425, 0x450E}, "Missile-Tow Dive-Clops"},
{{3426, 0x0000}, "Astroblast"},
{{3426, 0x4503}, "Legendary Astroblast"},
{{3427, 0x0000}, "Nightfall"},
{{3428, 0x0000}, "Thrillipede"},
{{3428, 0x450D}, "Eggcited Thrillipede"},
{{3500, 0x0000}, "Sky Trophy"},
{{3501, 0x0000}, "Land Trophy"},
{{3502, 0x0000}, "Sea Trophy"},
{{3503, 0x0000}, "Kaos Trophy"},
};
uint16 SkylanderUSB::SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size)
{
const unsigned short CRC_CCITT_TABLE[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,
0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,
0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886,
0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,
0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5,
0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2,
0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,
0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691,
0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F,
0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64,
0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
uint16 crc = initValue;
for (uint32 i = 0; i < size; i++)
{
const uint16 tmp = (crc >> 8) ^ buffer[i];
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
}
return crc;
}
SkylanderPortalDevice::SkylanderPortalDevice()
: Device(0x1430, 0x0150, 1, 2, 0)
{
m_IsOpened = false;
}
bool SkylanderPortalDevice::Open()
{
if (!IsOpened())
{
m_IsOpened = true;
}
return true;
}
void SkylanderPortalDevice::Close()
{
if (IsOpened())
{
m_IsOpened = false;
}
}
bool SkylanderPortalDevice::IsOpened()
{
return m_IsOpened;
}
Device::ReadResult SkylanderPortalDevice::Read(ReadMessage* message)
{
memcpy(message->data, g_skyportal.GetStatus().data(), message->length);
message->bytesRead = message->length;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return Device::ReadResult::Success;
}
Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message)
{
message->bytesWritten = message->length;
return Device::WriteResult::Success;
}
bool SkylanderPortalDevice::GetDescriptor(uint8 descType,
uint8 descIndex,
uint8 lang,
uint8* output,
uint32 outputMaxLength)
{
uint8 configurationDescriptor[0x29];
uint8* currentWritePtr;
// configuration descriptor
currentWritePtr = configurationDescriptor + 0;
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType
*(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength
*(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces
*(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue
*(uint8*)(currentWritePtr + 6) = 0; // iConfiguration
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
currentWritePtr = currentWritePtr + 9;
// configuration descriptor
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
*(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting
*(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints
*(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass
*(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
currentWritePtr = currentWritePtr + 9;
// configuration descriptor
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
*(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode
*(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors
*(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType
*(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength
currentWritePtr = currentWritePtr + 9;
// endpoint descriptor 1
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
// endpoint descriptor 2
*(uint8*)(currentWritePtr + 0) = 7; // bLength
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29);
memcpy(output, configurationDescriptor,
std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor)));
return true;
}
bool SkylanderPortalDevice::SetProtocol(uint32 ifIndex, uint32 protocol)
{
return true;
}
bool SkylanderPortalDevice::SetReport(ReportMessage* message)
{
g_skyportal.ControlTransfer(message->originalData, message->originalLength);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return true;
}
void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength)
{
std::array<uint8, 64> interruptResponse = {};
switch (buf[0])
{
case 'A':
{
interruptResponse = {buf[0], buf[1], 0xFF, 0x77};
g_skyportal.Activate();
break;
}
case 'C':
{
g_skyportal.SetLeds(0x01, buf[1], buf[2], buf[3]);
break;
}
case 'J':
{
g_skyportal.SetLeds(buf[1], buf[2], buf[3], buf[4]);
interruptResponse = {buf[0]};
break;
}
case 'L':
{
uint8 side = buf[1];
if (side == 0x02)
{
side = 0x04;
}
g_skyportal.SetLeds(side, buf[2], buf[3], buf[4]);
break;
}
case 'M':
{
interruptResponse = {buf[0], buf[1], 0x00, 0x19};
break;
}
case 'Q':
{
const uint8 skyNum = buf[1] & 0xF;
const uint8 block = buf[2];
g_skyportal.QueryBlock(skyNum, block, interruptResponse.data());
break;
}
case 'R':
{
interruptResponse = {buf[0], 0x02, 0x1b};
break;
}
case 'S':
case 'V':
{
// No response needed
break;
}
case 'W':
{
const uint8 skyNum = buf[1] & 0xF;
const uint8 block = buf[2];
g_skyportal.WriteBlock(skyNum, block, &buf[3], interruptResponse.data());
break;
}
default:
cemu_assert_error();
break;
}
if (interruptResponse[0] != 0)
{
std::lock_guard lock(m_queryMutex);
m_queries.push(interruptResponse);
}
}
void SkylanderUSB::Activate()
{
std::lock_guard lock(m_skyMutex);
if (m_activated)
{
// If the portal was already active no change is needed
return;
}
// If not we need to advertise change to all the figures present on the portal
for (auto& s : m_skylanders)
{
if (s.status & 1)
{
s.queuedStatus.push(3);
s.queuedStatus.push(1);
}
}
m_activated = true;
}
void SkylanderUSB::Deactivate()
{
std::lock_guard lock(m_skyMutex);
for (auto& s : m_skylanders)
{
// check if at the end of the updates there would be a figure on the portal
if (!s.queuedStatus.empty())
{
s.status = s.queuedStatus.back();
s.queuedStatus = std::queue<uint8>();
}
s.status &= 1;
}
m_activated = false;
}
void SkylanderUSB::SetLeds(uint8 side, uint8 r, uint8 g, uint8 b)
{
std::lock_guard lock(m_skyMutex);
if (side == 0x00)
{
m_colorRight.red = r;
m_colorRight.green = g;
m_colorRight.blue = b;
}
else if (side == 0x01)
{
m_colorRight.red = r;
m_colorRight.green = g;
m_colorRight.blue = b;
m_colorLeft.red = r;
m_colorLeft.green = g;
m_colorLeft.blue = b;
}
else if (side == 0x02)
{
m_colorLeft.red = r;
m_colorLeft.green = g;
m_colorLeft.blue = b;
}
else if (side == 0x03)
{
m_colorTrap.red = r;
m_colorTrap.green = g;
m_colorTrap.blue = b;
}
}
uint8 SkylanderUSB::LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file)
{
std::lock_guard lock(m_skyMutex);
uint32 skySerial = 0;
for (int i = 3; i > -1; i--)
{
skySerial <<= 8;
skySerial |= buf[i];
}
uint8 foundSlot = 0xFF;
// mimics spot retaining on the portal
for (auto i = 0; i < 16; i++)
{
if ((m_skylanders[i].status & 1) == 0)
{
if (m_skylanders[i].lastId == skySerial)
{
foundSlot = i;
break;
}
if (i < foundSlot)
{
foundSlot = i;
}
}
}
if (foundSlot != 0xFF)
{
auto& skylander = m_skylanders[foundSlot];
memcpy(skylander.data.data(), buf, skylander.data.size());
skylander.skyFile = std::move(file);
skylander.status = Skylander::ADDED;
skylander.queuedStatus.push(Skylander::ADDED);
skylander.queuedStatus.push(Skylander::READY);
skylander.lastId = skySerial;
}
return foundSlot;
}
bool SkylanderUSB::RemoveSkylander(uint8 skyNum)
{
std::lock_guard lock(m_skyMutex);
auto& thesky = m_skylanders[skyNum];
if (thesky.status & 1)
{
thesky.status = 2;
thesky.queuedStatus.push(2);
thesky.queuedStatus.push(0);
thesky.Save();
thesky.skyFile.reset();
return true;
}
return false;
}
void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf)
{
std::lock_guard lock(m_skyMutex);
const auto& skylander = m_skylanders[skyNum];
replyBuf[0] = 'Q';
replyBuf[2] = block;
if (skylander.status & 1)
{
replyBuf[1] = (0x10 | skyNum);
memcpy(replyBuf + 3, skylander.data.data() + (16 * block), 16);
}
else
{
replyBuf[1] = skyNum;
}
}
void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block,
const uint8* toWriteBuf, uint8* replyBuf)
{
std::lock_guard lock(m_skyMutex);
auto& skylander = m_skylanders[skyNum];
replyBuf[0] = 'W';
replyBuf[2] = block;
if (skylander.status & 1)
{
replyBuf[1] = (0x10 | skyNum);
memcpy(skylander.data.data() + (block * 16), toWriteBuf, 16);
skylander.Save();
}
else
{
replyBuf[1] = skyNum;
}
}
std::array<uint8, 64> SkylanderUSB::GetStatus()
{
std::lock_guard lock(m_queryMutex);
std::array<uint8, 64> interruptResponse = {};
if (!m_queries.empty())
{
interruptResponse = m_queries.front();
m_queries.pop();
// This needs to happen after ~22 milliseconds
}
else
{
uint32 status = 0;
uint8 active = 0x00;
if (m_activated)
{
active = 0x01;
}
for (int i = 16 - 1; i >= 0; i--)
{
auto& s = m_skylanders[i];
if (!s.queuedStatus.empty())
{
s.status = s.queuedStatus.front();
s.queuedStatus.pop();
}
status <<= 2;
status |= s.status;
}
interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++,
active, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00};
memcpy(&interruptResponse[1], &status, sizeof(status));
}
return interruptResponse;
}
void SkylanderUSB::Skylander::Save()
{
if (!skyFile)
return;
skyFile->writeData(data.data(), data.size());
}
} // namespace nsyshid

View File

@ -0,0 +1,98 @@
#include <mutex>
#include "nsyshid.h"
#include "Backend.h"
#include "Common/FileStream.h"
namespace nsyshid
{
class SkylanderPortalDevice final : public Device {
public:
SkylanderPortalDevice();
~SkylanderPortalDevice() = default;
bool Open() override;
void Close() override;
bool IsOpened() override;
ReadResult Read(ReadMessage* message) override;
WriteResult Write(WriteMessage* message) override;
bool GetDescriptor(uint8 descType,
uint8 descIndex,
uint8 lang,
uint8* output,
uint32 outputMaxLength) override;
bool SetProtocol(uint32 ifIndex, uint32 protocol) override;
bool SetReport(ReportMessage* message) override;
private:
bool m_IsOpened;
};
extern const std::map<const std::pair<const uint16, const uint16>, const std::string> listSkylanders;
class SkylanderUSB {
public:
struct Skylander final
{
std::unique_ptr<FileStream> skyFile;
uint8 status = 0;
std::queue<uint8> queuedStatus;
std::array<uint8, 0x40 * 0x10> data{};
uint32 lastId = 0;
void Save();
enum : uint8
{
REMOVED = 0,
READY = 1,
REMOVING = 2,
ADDED = 3
};
};
struct SkylanderLEDColor final
{
uint8 red = 0;
uint8 green = 0;
uint8 blue = 0;
};
void ControlTransfer(uint8* buf, sint32 originalLength);
void Activate();
void Deactivate();
void SetLeds(uint8 side, uint8 r, uint8 g, uint8 b);
std::array<uint8, 64> GetStatus();
void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf);
void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf,
uint8* replyBuf);
uint8 LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file);
bool RemoveSkylander(uint8 skyNum);
uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size);
protected:
std::mutex m_skyMutex;
std::mutex m_queryMutex;
std::array<Skylander, 16> m_skylanders;
private:
std::queue<std::array<uint8, 64>> m_queries;
bool m_activated = true;
uint8 m_interruptCounter = 0;
SkylanderLEDColor m_colorRight = {};
SkylanderLEDColor m_colorLeft = {};
SkylanderLEDColor m_colorTrap = {};
};
extern SkylanderUSB g_skyportal;
} // namespace nsyshid

View File

@ -256,6 +256,19 @@ namespace nsyshid
device->m_productId); device->m_productId);
} }
bool FindDeviceById(uint16 vendorId, uint16 productId)
{
std::lock_guard<std::recursive_mutex> lock(hidMutex);
for (const auto& device : deviceList)
{
if (device->m_vendorId == vendorId && device->m_productId == productId)
{
return true;
}
}
return false;
}
void export_HIDAddClient(PPCInterpreter_t* hCPU) void export_HIDAddClient(PPCInterpreter_t* hCPU)
{ {
ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); ppcDefineParamTypePtr(hidClient, HIDClient_t, 0);
@ -406,7 +419,8 @@ namespace nsyshid
sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
{ {
cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin");
if (device->SetReport(reportData, length, originalData, originalLength)) ReportMessage message(reportData, length, originalData, originalLength);
if (device->SetReport(&message))
{ {
DoHIDTransferCallback(callbackFuncMPTR, DoHIDTransferCallback(callbackFuncMPTR,
callbackParamMPTR, callbackParamMPTR,
@ -433,7 +447,8 @@ namespace nsyshid
{ {
_debugPrintHex("_hidSetReportSync Begin", reportData, length); _debugPrintHex("_hidSetReportSync Begin", reportData, length);
sint32 returnCode = 0; sint32 returnCode = 0;
if (device->SetReport(reportData, length, originalData, originalLength)) ReportMessage message(reportData, length, originalData, originalLength);
if (device->SetReport(&message))
{ {
returnCode = originalLength; returnCode = originalLength;
} }
@ -511,17 +526,16 @@ namespace nsyshid
return -1; return -1;
} }
memset(data, 0, maxLength); memset(data, 0, maxLength);
ReadMessage message(data, maxLength, 0);
sint32 bytesRead = 0; Device::ReadResult readResult = device->Read(&message);
Device::ReadResult readResult = device->Read(data, maxLength, bytesRead);
switch (readResult) switch (readResult)
{ {
case Device::ReadResult::Success: case Device::ReadResult::Success:
{ {
cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes",
bytesRead, message.bytesRead,
maxLength); maxLength);
return bytesRead; return message.bytesRead;
} }
break; break;
case Device::ReadResult::Error: case Device::ReadResult::Error:
@ -609,15 +623,15 @@ namespace nsyshid
cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device");
return -1; return -1;
} }
sint32 bytesWritten = 0; WriteMessage message(data, maxLength, 0);
Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten); Device::WriteResult writeResult = device->Write(&message);
switch (writeResult) switch (writeResult)
{ {
case Device::WriteResult::Success: case Device::WriteResult::Success:
{ {
cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", bytesWritten, cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", message.bytesWritten,
maxLength); maxLength);
return bytesWritten; return message.bytesWritten;
} }
break; break;
case Device::WriteResult::Error: case Device::WriteResult::Error:
@ -758,6 +772,11 @@ namespace nsyshid
return nullptr; return nullptr;
} }
bool Backend::FindDeviceById(uint16 vendorId, uint16 productId)
{
return nsyshid::FindDeviceById(vendorId, productId);
}
bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId)
{ {
return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId);

View File

@ -358,6 +358,10 @@ void CemuConfig::Load(XMLConfigParser& parser)
auto dsuc = input.get("DSUC"); auto dsuc = input.get("DSUC");
dsu_client.host = dsuc.get_attribute("host", dsu_client.host); dsu_client.host = dsuc.get_attribute("host", dsu_client.host);
dsu_client.port = dsuc.get_attribute("port", dsu_client.port); dsu_client.port = dsuc.get_attribute("port", dsu_client.port);
// emulatedusbdevices
auto usbdevices = parser.get("EmulatedUsbDevices");
emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal);
} }
void CemuConfig::Save(XMLConfigParser& parser) void CemuConfig::Save(XMLConfigParser& parser)
@ -551,6 +555,10 @@ void CemuConfig::Save(XMLConfigParser& parser)
auto dsuc = input.set("DSUC"); auto dsuc = input.set("DSUC");
dsuc.set_attribute("host", dsu_client.host); dsuc.set_attribute("host", dsu_client.host);
dsuc.set_attribute("port", dsu_client.port); dsuc.set_attribute("port", dsu_client.port);
// emulated usb devices
auto usbdevices = config.set("EmulatedUsbDevices");
usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue());
} }
GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId)

View File

@ -514,6 +514,12 @@ struct CemuConfig
NetworkService GetAccountNetworkService(uint32 persistentId); NetworkService GetAccountNetworkService(uint32 persistentId);
void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex);
// emulated usb devices
struct
{
ConfigValue<bool> emulate_skylander_portal{false};
}emulated_usb_devices{};
private: private:
GameEntry* GetGameEntryByTitleId(uint64 titleId); GameEntry* GetGameEntryByTitleId(uint64 titleId);

View File

@ -101,6 +101,8 @@ add_library(CemuGui
PairingDialog.h PairingDialog.h
TitleManager.cpp TitleManager.cpp
TitleManager.h TitleManager.h
EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp
EmulatedUSBDevices/EmulatedUSBDeviceFrame.h
windows/PPCThreadsViewer windows/PPCThreadsViewer
windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
windows/PPCThreadsViewer/DebugPPCThreadsWindow.h windows/PPCThreadsViewer/DebugPPCThreadsWindow.h

View File

@ -0,0 +1,354 @@
#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"
#include <algorithm>
#include <random>
#include "config/CemuConfig.h"
#include "gui/helpers/wxHelpers.h"
#include "gui/wxHelper.h"
#include "util/helpers/helpers.h"
#include "Cafe/OS/libs/nsyshid/nsyshid.h"
#include "Cafe/OS/libs/nsyshid/Skylander.h"
#include "Common/FileStream.h"
#include <wx/arrstr.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/filedlg.h>
#include <wx/msgdlg.h>
#include <wx/notebook.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/stream.h>
#include <wx/textctrl.h>
#include <wx/textentry.h>
#include <wx/valnum.h>
#include <wx/wfstream.h>
#include "resource/embedded/resources.h"
#include "EmulatedUSBDeviceFrame.h"
EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent)
: wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
{
SetIcon(wxICON(X_BOX));
auto& config = GetConfig();
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* notebook = new wxNotebook(this, wxID_ANY);
notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal"));
sizer->Add(notebook, 1, wxEXPAND | wxALL, 2);
SetSizerAndFit(sizer);
Layout();
Centre(wxBOTH);
}
EmulatedUSBDeviceFrame::~EmulatedUSBDeviceFrame() {}
wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook)
{
auto* panel = new wxPanel(notebook);
auto* panelSizer = new wxBoxSizer(wxVERTICAL);
auto* box = new wxStaticBox(panel, wxID_ANY, _("Skylanders Manager"));
auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL);
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_emulatePortal =
new wxCheckBox(box, wxID_ANY, _("Emulate Skylander Portal"));
m_emulatePortal->SetValue(
GetConfig().emulated_usb_devices.emulate_skylander_portal);
m_emulatePortal->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
GetConfig().emulated_usb_devices.emulate_skylander_portal =
m_emulatePortal->IsChecked();
g_config.Save();
});
row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2);
boxSizer->Add(row, 1, wxEXPAND | wxALL, 2);
for (int i = 0; i < 16; i++)
{
boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2);
}
panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2);
panel->SetSizerAndFit(panelSizer);
return panel;
}
wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 row_number,
wxStaticBox* box)
{
auto* row = new wxBoxSizer(wxHORIZONTAL);
row->Add(new wxStaticText(box, wxID_ANY,
fmt::format("{} {}", _("Skylander").ToStdString(),
(row_number + 1))),
1, wxEXPAND | wxALL, 2);
m_skylanderSlots[row_number] =
new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize,
wxTE_READONLY);
m_skylanderSlots[row_number]->SetMinSize(wxSize(150, -1));
m_skylanderSlots[row_number]->Disable();
row->Add(m_skylanderSlots[row_number], 1, wxEXPAND | wxALL, 2);
auto* loadButton = new wxButton(box, wxID_ANY, _("Load"));
loadButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) {
LoadSkylander(row_number);
});
auto* createButton = new wxButton(box, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) {
CreateSkylander(row_number);
});
auto* clearButton = new wxButton(box, wxID_ANY, _("Clear"));
clearButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) {
ClearSkylander(row_number);
});
row->Add(loadButton, 1, wxEXPAND | wxALL, 2);
row->Add(createButton, 1, wxEXPAND | wxALL, 2);
row->Add(clearButton, 1, wxEXPAND | wxALL, 2);
return row;
}
void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot)
{
wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "",
"Skylander files (*.sky;*.bin;*.dump;*.dmp)|*.sky;*.bin;*.dump;*.dmp",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty())
return;
LoadSkylanderPath(slot, openFileDialog.GetPath());
}
void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path)
{
std::unique_ptr<FileStream> skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true));
if (!skyFile)
{
wxMessageDialog open_error(this, "Error Opening File: " + path.c_str());
open_error.ShowModal();
return;
}
std::array<uint8, 0x40 * 0x10> fileData;
if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size())
{
wxMessageDialog open_error(this, "Failed to read file! File was too small");
open_error.ShowModal();
return;
}
ClearSkylander(slot);
uint16 skyId = uint16(fileData[0x11]) << 8 | uint16(fileData[0x10]);
uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]);
uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(),
std::move(skyFile));
m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar);
UpdateSkylanderEdits();
}
void EmulatedUSBDeviceFrame::CreateSkylander(uint8 slot)
{
CreateSkylanderDialog create_dlg(this, slot);
create_dlg.ShowModal();
if (create_dlg.GetReturnCode() == 1)
{
LoadSkylanderPath(slot, create_dlg.GetFilePath());
}
}
void EmulatedUSBDeviceFrame::ClearSkylander(uint8 slot)
{
if (auto slotInfos = m_skySlots[slot])
{
auto [curSlot, id, var] = slotInfos.value();
nsyshid::g_skyportal.RemoveSkylander(curSlot);
m_skySlots[slot] = {};
UpdateSkylanderEdits();
}
}
CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot)
: wxDialog(parent, wxID_ANY, _("Skylander Figure Creator"), wxDefaultPosition, wxSize(500, 150))
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
auto* comboRow = new wxBoxSizer(wxHORIZONTAL);
auto* comboBox = new wxComboBox(this, wxID_ANY);
comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF));
wxArrayString filterlist;
for (auto it = nsyshid::listSkylanders.begin(); it != nsyshid::listSkylanders.end(); it++)
{
const uint32 variant = uint32(uint32(it->first.first) << 16) | uint32(it->first.second);
comboBox->Append(it->second, reinterpret_cast<void*>(variant));
filterlist.Add(it->second);
}
comboBox->SetSelection(0);
bool enabled = comboBox->AutoComplete(filterlist);
comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2);
auto* idVarRow = new wxBoxSizer(wxHORIZONTAL);
wxIntegerValidator<uint32> validator;
auto* labelId = new wxStaticText(this, wxID_ANY, "ID:");
auto* labelVar = new wxStaticText(this, wxID_ANY, "Variant:");
auto* editId = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator);
auto* editVar = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator);
idVarRow->Add(labelId, 1, wxALL, 5);
idVarRow->Add(editId, 1, wxALL, 5);
idVarRow->Add(labelVar, 1, wxALL, 5);
idVarRow->Add(editVar, 1, wxALL, 5);
auto* buttonRow = new wxBoxSizer(wxHORIZONTAL);
auto* createButton = new wxButton(this, wxID_ANY, _("Create"));
createButton->Bind(wxEVT_BUTTON, [editId, editVar, this](wxCommandEvent&) {
long longSkyId;
if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF)
{
wxMessageDialog id_error(this, "Error Converting ID!", "ID Entered is Invalid");
id_error.ShowModal();
return;
}
long longSkyVar;
if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF)
{
wxMessageDialog id_error(this, "Error Converting Variant!", "Variant Entered is Invalid");
id_error.ShowModal();
return;
}
uint16 skyId = longSkyId & 0xFFFF;
uint16 skyVar = longSkyVar & 0xFFFF;
const auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar));
wxString predefName;
if (foundSky != nsyshid::listSkylanders.end())
{
predefName = foundSky->second + ".sky";
}
else
{
predefName = wxString::Format(_("Unknown(%i %i).sky"), skyId, skyVar);
}
wxFileDialog
saveFileDialog(this, _("Create Skylander file"), "", predefName,
"SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (saveFileDialog.ShowModal() == wxID_CANCEL)
return;
m_filePath = saveFileDialog.GetPath();
wxFileOutputStream output_stream(saveFileDialog.GetPath());
if (!output_stream.IsOk())
{
wxMessageDialog saveError(this, "Error Creating Skylander File");
return;
}
std::array<uint8, 0x40 * 0x10> data{};
uint32 first_block = 0x690F0F0F;
uint32 other_blocks = 0x69080F7F;
memcpy(&data[0x36], &first_block, sizeof(first_block));
for (size_t index = 1; index < 0x10; index++)
{
memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks));
}
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist(0, 255);
data[0] = dist(mt);
data[1] = dist(mt);
data[2] = dist(mt);
data[3] = dist(mt);
data[4] = data[0] ^ data[1] ^ data[2] ^ data[3];
data[5] = 0x81;
data[6] = 0x01;
data[7] = 0x0F;
memcpy(&data[0x10], &skyId, sizeof(skyId));
memcpy(&data[0x1C], &skyVar, sizeof(skyVar));
uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E);
memcpy(&data[0x1E], &crc, sizeof(crc));
output_stream.SeekO(0);
output_stream.WriteAll(data.data(), data.size());
output_stream.Close();
this->EndModal(1);
});
auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
this->EndModal(0);
});
comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editId, editVar, this](wxCommandEvent&) {
const uint64 sky_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection()));
if (sky_info != 0xFFFFFFFF)
{
const uint16 skyId = sky_info >> 16;
const uint16 skyVar = sky_info & 0xFFFF;
editId->SetValue(wxString::Format(wxT("%i"), skyId));
editVar->SetValue(wxString::Format(wxT("%i"), skyVar));
}
});
buttonRow->Add(createButton, 1, wxALL, 5);
buttonRow->Add(cancelButton, 1, wxALL, 5);
sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(idVarRow, 1, wxEXPAND | wxALL, 2);
sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2);
this->SetSizer(sizer);
this->Centre(wxBOTH);
}
wxString CreateSkylanderDialog::GetFilePath() const
{
return m_filePath;
}
void EmulatedUSBDeviceFrame::UpdateSkylanderEdits()
{
for (auto i = 0; i < 16; i++)
{
std::string displayString;
if (auto sd = m_skySlots[i])
{
auto [portalSlot, skyId, skyVar] = sd.value();
auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar));
if (foundSky != nsyshid::listSkylanders.end())
{
displayString = foundSky->second;
}
else
{
displayString = fmt::format("Unknown (Id:{} Var:{})", skyId, skyVar);
}
}
else
{
displayString = "None";
}
m_skylanderSlots[i]->ChangeValue(displayString);
}
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <array>
#include <wx/dialog.h>
#include <wx/frame.h>
class wxBoxSizer;
class wxCheckBox;
class wxFlexGridSizer;
class wxNotebook;
class wxPanel;
class wxStaticBox;
class wxString;
class wxTextCtrl;
class EmulatedUSBDeviceFrame : public wxFrame {
public:
EmulatedUSBDeviceFrame(wxWindow* parent);
~EmulatedUSBDeviceFrame();
private:
wxCheckBox* m_emulatePortal;
std::array<wxTextCtrl*, 16> m_skylanderSlots;
std::array<std::optional<std::tuple<uint8, uint16, uint16>>, 16> m_skySlots;
wxPanel* AddSkylanderPage(wxNotebook* notebook);
wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box);
void LoadSkylander(uint8 slot);
void LoadSkylanderPath(uint8 slot, wxString path);
void CreateSkylander(uint8 slot);
void ClearSkylander(uint8 slot);
void UpdateSkylanderEdits();
};
class CreateSkylanderDialog : public wxDialog {
public:
explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot);
wxString GetFilePath() const;
protected:
wxString m_filePath;
};

View File

@ -30,6 +30,7 @@
#include "Cafe/Filesystem/FST/FST.h" #include "Cafe/Filesystem/FST/FST.h"
#include "gui/TitleManager.h" #include "gui/TitleManager.h"
#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"
#include "Cafe/CafeSystem.h" #include "Cafe/CafeSystem.h"
@ -110,6 +111,7 @@ enum
MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600, MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600,
MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER,
MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER,
MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES,
// cpu // cpu
// cpu->timer speed // cpu->timer speed
MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700, MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700,
@ -188,6 +190,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, MainWindow::OnToolsInput)
// cpu menu // cpu menu
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting)
@ -1515,6 +1518,29 @@ void MainWindow::OnToolsInput(wxCommandEvent& event)
}); });
m_title_manager->Show(); m_title_manager->Show();
} }
break;
}
case MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES:
{
if (m_usb_devices)
{
m_usb_devices->Show(true);
m_usb_devices->Raise();
m_usb_devices->SetFocus();
}
else
{
m_usb_devices = new EmulatedUSBDeviceFrame(this);
m_usb_devices->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event)
{
if (event.CanVeto()) {
m_usb_devices->Show(false);
event.Veto();
}
});
m_usb_devices->Show(true);
}
break;
} }
break; break;
} }
@ -2166,6 +2192,7 @@ void MainWindow::RecreateMenu()
m_memorySearcherMenuItem->Enable(false); m_memorySearcherMenuItem->Enable(false);
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager"));
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager"));
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, _("&Emulated USB Devices"));
m_menuBar->Append(toolsMenu, _("&Tools")); m_menuBar->Append(toolsMenu, _("&Tools"));

View File

@ -22,6 +22,7 @@ struct GameEntry;
class DiscordPresence; class DiscordPresence;
class TitleManager; class TitleManager;
class GraphicPacksWindow2; class GraphicPacksWindow2;
class EmulatedUSBDeviceFrame;
class wxLaunchGameEvent; class wxLaunchGameEvent;
wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
@ -164,6 +165,7 @@ private:
MemorySearcherTool* m_toolWindow = nullptr; MemorySearcherTool* m_toolWindow = nullptr;
TitleManager* m_title_manager = nullptr; TitleManager* m_title_manager = nullptr;
EmulatedUSBDeviceFrame* m_usb_devices = nullptr;
PadViewFrame* m_padView = nullptr; PadViewFrame* m_padView = nullptr;
GraphicPacksWindow2* m_graphic_pack_window = nullptr; GraphicPacksWindow2* m_graphic_pack_window = nullptr;