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

This commit is contained in:
Joshua de Reeper
2024-06-28 00:55:20 +02:00
committed by GitHub
parent f3d20832c1
commit 93b58ae6f7
19 changed files with 1658 additions and 58 deletions
+4
View File
@@ -457,10 +457,14 @@ add_library(CemuCafe
OS/libs/nsyshid/AttachDefaultBackends.cpp
OS/libs/nsyshid/Whitelist.cpp
OS/libs/nsyshid/Whitelist.h
OS/libs/nsyshid/BackendEmulated.cpp
OS/libs/nsyshid/BackendEmulated.h
OS/libs/nsyshid/BackendLibusb.cpp
OS/libs/nsyshid/BackendLibusb.h
OS/libs/nsyshid/BackendWindowsHID.cpp
OS/libs/nsyshid/BackendWindowsHID.h
OS/libs/nsyshid/Skylander.cpp
OS/libs/nsyshid/Skylander.h
OS/libs/nsyskbd/nsyskbd.cpp
OS/libs/nsyskbd/nsyskbd.h
OS/libs/nsysnet/nsysnet.cpp
@@ -1,5 +1,6 @@
#include "nsyshid.h"
#include "Backend.h"
#include "BackendEmulated.h"
#if NSYSHID_ENABLE_BACKEND_LIBUSB
@@ -37,5 +38,13 @@ namespace nsyshid::backend
}
}
#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
+54 -3
View File
@@ -23,6 +23,55 @@ namespace nsyshid
/* +0x12 */ uint16be maxPacketSizeTX;
} 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, productId) == 0xA, "");
static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
@@ -69,7 +118,7 @@ namespace nsyshid
ErrorTimeout,
};
virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0;
virtual ReadResult Read(ReadMessage* message) = 0;
enum class WriteResult
{
@@ -78,7 +127,7 @@ namespace nsyshid
ErrorTimeout,
};
virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0;
virtual WriteResult Write(WriteMessage* message) = 0;
virtual bool GetDescriptor(uint8 descType,
uint8 descIndex,
@@ -88,7 +137,7 @@ namespace nsyshid
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 {
@@ -121,6 +170,8 @@ namespace nsyshid
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);
// called from OnAttach() - attach devices that your backend can see here
@@ -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
@@ -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
+15 -21
View File
@@ -241,11 +241,6 @@ namespace nsyshid::backend::libusb
ret);
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,
desc.idVendor,
desc.idProduct,
@@ -471,7 +466,7 @@ namespace nsyshid::backend::libusb
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();
if (!handleLock->IsValid())
@@ -488,8 +483,8 @@ namespace nsyshid::backend::libusb
{
ret = libusb_bulk_transfer(handleLock->GetHandle(),
this->m_libusbEndpointIn,
data,
length,
message->data,
message->length,
&actualLength,
timeout);
}
@@ -500,8 +495,8 @@ namespace nsyshid::backend::libusb
// success
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes",
actualLength,
length);
bytesRead = actualLength;
message->length);
message->bytesRead = actualLength;
return ReadResult::Success;
}
cemuLog_logDebug(LogType::Force,
@@ -510,7 +505,7 @@ namespace nsyshid::backend::libusb
return ReadResult::Error;
}
Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten)
Device::WriteResult DeviceLibusb::Write(WriteMessage* message)
{
auto handleLock = AquireHandleLock();
if (!handleLock->IsValid())
@@ -520,23 +515,23 @@ namespace nsyshid::backend::libusb
return WriteResult::Error;
}
bytesWritten = 0;
message->bytesWritten = 0;
int actualLength = 0;
int ret = libusb_bulk_transfer(handleLock->GetHandle(),
this->m_libusbEndpointOut,
data,
length,
message->data,
message->length,
&actualLength,
0);
if (ret == 0)
{
// success
bytesWritten = actualLength;
message->bytesWritten = actualLength;
cemuLog_logDebug(LogType::Force,
"nsyshid::DeviceLibusb::write(): wrote {} of {} bytes",
bytesWritten,
length);
message->bytesWritten,
message->length);
return WriteResult::Success;
}
cemuLog_logDebug(LogType::Force,
@@ -713,8 +708,7 @@ namespace nsyshid::backend::libusb
return true;
}
bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData,
sint32 originalLength)
bool DeviceLibusb::SetReport(ReportMessage* message)
{
auto handleLock = AquireHandleLock();
if (!handleLock->IsValid())
@@ -731,8 +725,8 @@ namespace nsyshid::backend::libusb
bRequest,
wValue,
wIndex,
reportData,
length,
message->reportData,
message->length,
timeout);
#endif
+3 -3
View File
@@ -63,9 +63,9 @@ namespace nsyshid::backend::libusb
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,
@@ -75,7 +75,7 @@ namespace nsyshid::backend::libusb
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_libusbDeviceAddress;
+17 -17
View File
@@ -196,20 +196,20 @@ namespace nsyshid::backend::windows
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;
OVERLAPPED ovlp = {0};
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
_debugPrintHex("HID_READ_BEFORE", data, length);
_debugPrintHex("HID_READ_BEFORE", message->data, message->length);
cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length);
BOOL readResult = ReadFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp);
cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length);
BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
if (readResult != FALSE)
{
// sometimes we get the result immediately
@@ -247,7 +247,7 @@ namespace nsyshid::backend::windows
ReadResult result = ReadResult::Success;
if (bt != 0)
{
memcpy(data, tempBuffer + 1, transferLength);
memcpy(message->data, tempBuffer + 1, transferLength);
sint32 hidReadLength = transferLength;
char debugOutput[1024] = {0};
@@ -257,7 +257,7 @@ namespace nsyshid::backend::windows
}
cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput);
bytesRead = transferLength;
message->bytesRead = transferLength;
result = ReadResult::Success;
}
else
@@ -270,19 +270,19 @@ namespace nsyshid::backend::windows
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;
OVERLAPPED ovlp = {0};
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
uint8* tempBuffer = (uint8*)malloc(length + 1);
memcpy(tempBuffer + 1, data, length);
uint8* tempBuffer = (uint8*)malloc(message->length + 1);
memcpy(tempBuffer + 1, message->data, message->length);
tempBuffer[0] = 0; // report byte?
cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", length);
BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp);
cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length);
BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
if (writeResult != FALSE)
{
// sometimes we get the result immediately
@@ -314,7 +314,7 @@ namespace nsyshid::backend::windows
if (bt != 0)
{
bytesWritten = length;
message->bytesWritten = message->length;
return WriteResult::Success;
}
return WriteResult::Error;
@@ -407,12 +407,12 @@ namespace nsyshid::backend::windows
return true;
}
bool DeviceWindowsHID::SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength)
bool DeviceWindowsHID::SetReport(ReportMessage* message)
{
sint32 retryCount = 0;
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)
break;
Sleep(20); // retry
+3 -3
View File
@@ -41,15 +41,15 @@ namespace nsyshid::backend::windows
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 SetProtocol(uint32 ifIndef, uint32 protocol) override;
bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override;
bool SetReport(ReportMessage* message) override;
private:
wchar_t* m_devicePath;
File diff suppressed because it is too large Load Diff
+98
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
+30 -11
View File
@@ -256,6 +256,19 @@ namespace nsyshid
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)
{
ppcDefineParamTypePtr(hidClient, HIDClient_t, 0);
@@ -406,7 +419,8 @@ namespace nsyshid
sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
{
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,
callbackParamMPTR,
@@ -433,7 +447,8 @@ namespace nsyshid
{
_debugPrintHex("_hidSetReportSync Begin", reportData, length);
sint32 returnCode = 0;
if (device->SetReport(reportData, length, originalData, originalLength))
ReportMessage message(reportData, length, originalData, originalLength);
if (device->SetReport(&message))
{
returnCode = originalLength;
}
@@ -511,17 +526,16 @@ namespace nsyshid
return -1;
}
memset(data, 0, maxLength);
sint32 bytesRead = 0;
Device::ReadResult readResult = device->Read(data, maxLength, bytesRead);
ReadMessage message(data, maxLength, 0);
Device::ReadResult readResult = device->Read(&message);
switch (readResult)
{
case Device::ReadResult::Success:
{
cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes",
bytesRead,
message.bytesRead,
maxLength);
return bytesRead;
return message.bytesRead;
}
break;
case Device::ReadResult::Error:
@@ -609,15 +623,15 @@ namespace nsyshid
cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device");
return -1;
}
sint32 bytesWritten = 0;
Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten);
WriteMessage message(data, maxLength, 0);
Device::WriteResult writeResult = device->Write(&message);
switch (writeResult)
{
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);
return bytesWritten;
return message.bytesWritten;
}
break;
case Device::WriteResult::Error:
@@ -758,6 +772,11 @@ namespace nsyshid
return nullptr;
}
bool Backend::FindDeviceById(uint16 vendorId, uint16 productId)
{
return nsyshid::FindDeviceById(vendorId, productId);
}
bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId)
{
return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId);
+8
View File
@@ -358,6 +358,10 @@ void CemuConfig::Load(XMLConfigParser& parser)
auto dsuc = input.get("DSUC");
dsu_client.host = dsuc.get_attribute("host", dsu_client.host);
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)
@@ -551,6 +555,10 @@ void CemuConfig::Save(XMLConfigParser& parser)
auto dsuc = input.set("DSUC");
dsuc.set_attribute("host", dsu_client.host);
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)
+6
View File
@@ -514,6 +514,12 @@ struct CemuConfig
NetworkService GetAccountNetworkService(uint32 persistentId);
void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex);
// emulated usb devices
struct
{
ConfigValue<bool> emulate_skylander_portal{false};
}emulated_usb_devices{};
private:
GameEntry* GetGameEntryByTitleId(uint64 titleId);
+2
View File
@@ -101,6 +101,8 @@ add_library(CemuGui
PairingDialog.h
TitleManager.cpp
TitleManager.h
EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp
EmulatedUSBDevices/EmulatedUSBDeviceFrame.h
windows/PPCThreadsViewer
windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
windows/PPCThreadsViewer/DebugPPCThreadsWindow.h
@@ -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);
}
}
@@ -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;
};
+27
View File
@@ -30,6 +30,7 @@
#include "Cafe/Filesystem/FST/FST.h"
#include "gui/TitleManager.h"
#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h"
#include "Cafe/CafeSystem.h"
@@ -110,6 +111,7 @@ enum
MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600,
MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER,
MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER,
MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES,
// cpu
// cpu->timer speed
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_TITLE_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
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, 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();
}
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;
}
@@ -2166,6 +2192,7 @@ void MainWindow::RecreateMenu()
m_memorySearcherMenuItem->Enable(false);
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_EMULATED_USB_DEVICES, _("&Emulated USB Devices"));
m_menuBar->Append(toolsMenu, _("&Tools"));
+2
View File
@@ -22,6 +22,7 @@ struct GameEntry;
class DiscordPresence;
class TitleManager;
class GraphicPacksWindow2;
class EmulatedUSBDeviceFrame;
class wxLaunchGameEvent;
wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
@@ -164,6 +165,7 @@ private:
MemorySearcherTool* m_toolWindow = nullptr;
TitleManager* m_title_manager = nullptr;
EmulatedUSBDeviceFrame* m_usb_devices = nullptr;
PadViewFrame* m_padView = nullptr;
GraphicPacksWindow2* m_graphic_pack_window = nullptr;