mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-04 20:06:41 +01:00
Use Core timing instead of thread
Skylander code tidy ups Convert c array to std::array and fix comments Formatting fixes/review changes Variable comment Migrate portal to System Impl and code tidy ups Use struct Restore review changes Minor fix to schedule transfer method Change descriptors to hex and fix comments
This commit is contained in:
parent
f76a6789a0
commit
18fd0d7dcd
@ -399,8 +399,6 @@ add_library(core
|
|||||||
IOS/USB/Bluetooth/WiimoteHIDAttr.h
|
IOS/USB/Bluetooth/WiimoteHIDAttr.h
|
||||||
IOS/USB/Common.cpp
|
IOS/USB/Common.cpp
|
||||||
IOS/USB/Common.h
|
IOS/USB/Common.h
|
||||||
IOS/USB/EmulatedUSBDevice.cpp
|
|
||||||
IOS/USB/EmulatedUSBDevice.h
|
|
||||||
IOS/USB/Emulated/Skylander.cpp
|
IOS/USB/Emulated/Skylander.cpp
|
||||||
IOS/USB/Emulated/Skylander.h
|
IOS/USB/Emulated/Skylander.h
|
||||||
IOS/USB/Host.cpp
|
IOS/USB/Host.cpp
|
||||||
|
@ -556,11 +556,6 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
|
|||||||
const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
|
const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
|
||||||
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
|
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
|
||||||
|
|
||||||
bool EmulateSkylanderPortal()
|
|
||||||
{
|
|
||||||
return Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The reason we need this function is because some memory card code
|
// The reason we need this function is because some memory card code
|
||||||
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
|
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
|
||||||
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
|
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
|
||||||
|
@ -345,7 +345,6 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
|
|||||||
// Main.EmulatedUSBDevices
|
// Main.EmulatedUSBDevices
|
||||||
|
|
||||||
extern const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL;
|
extern const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL;
|
||||||
bool EmulateSkylanderPortal();
|
|
||||||
|
|
||||||
// GameCube path utility functions
|
// GameCube path utility functions
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ bool IsSettingSaveable(const Config::Location& config_location)
|
|||||||
for (const std::string_view section :
|
for (const std::string_view section :
|
||||||
{"NetPlay", "General", "GBA", "Display", "Network", "Analytics", "AndroidOverlayButtons",
|
{"NetPlay", "General", "GBA", "Display", "Network", "Analytics", "AndroidOverlayButtons",
|
||||||
"DSP", "GameList", "FifoPlayer", "AutoUpdate", "Movie", "Input", "Debug",
|
"DSP", "GameList", "FifoPlayer", "AutoUpdate", "Movie", "Input", "Debug",
|
||||||
"BluetoothPassthrough", "USBPassthrough", "Interface"})
|
"BluetoothPassthrough", "USBPassthrough", "Interface", "EmulatedUSBDevices"})
|
||||||
{
|
{
|
||||||
if (config_location.section == section)
|
if (config_location.section == section)
|
||||||
return true;
|
return true;
|
||||||
|
@ -38,6 +38,13 @@ void TransferCommand::OnTransferComplete(s32 return_value) const
|
|||||||
m_ios.EnqueueIPCReply(ios_request, return_value, 0, CoreTiming::FromThread::NON_CPU);
|
m_ios.EnqueueIPCReply(ios_request, return_value, 0, CoreTiming::FromThread::NON_CPU);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransferCommand::ScheduleTransferCompletion(s32 return_value, u32 expected_time_us) const
|
||||||
|
{
|
||||||
|
auto ticks = SystemTimers::GetTicksPerSecond();
|
||||||
|
s64 cycles_in_future = static_cast<s64>((static_cast<u64>(ticks) * expected_time_us) / 1'000'000);
|
||||||
|
m_ios.EnqueueIPCReply(ios_request, return_value, cycles_in_future, CoreTiming::FromThread::ANY);
|
||||||
|
}
|
||||||
|
|
||||||
void IsoMessage::SetPacketReturnValue(const size_t packet_num, const u16 return_value) const
|
void IsoMessage::SetPacketReturnValue(const size_t packet_num, const u16 return_value) const
|
||||||
{
|
{
|
||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
|
@ -109,13 +109,10 @@ struct TransferCommand
|
|||||||
// Called after a transfer has completed to reply to the IPC request.
|
// Called after a transfer has completed to reply to the IPC request.
|
||||||
// This can be overridden for additional processing before replying.
|
// This can be overridden for additional processing before replying.
|
||||||
virtual void OnTransferComplete(s32 return_value) const;
|
virtual void OnTransferComplete(s32 return_value) const;
|
||||||
|
void ScheduleTransferCompletion(s32 return_value, u32 expected_time_us) const;
|
||||||
std::unique_ptr<u8[]> MakeBuffer(size_t size) const;
|
std::unique_ptr<u8[]> MakeBuffer(size_t size) const;
|
||||||
void FillBuffer(const u8* src, size_t size) const;
|
void FillBuffer(const u8* src, size_t size) const;
|
||||||
|
|
||||||
// Fake Transfers
|
|
||||||
u64 expected_time;
|
|
||||||
u32 expected_count;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Kernel& m_ios;
|
Kernel& m_ios;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2017 Dolphin Emulator Project
|
// Copyright 2022 Dolphin Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "Core/IOS/USB/Emulated/Skylander.h"
|
#include "Core/IOS/USB/Emulated/Skylander.h"
|
||||||
@ -16,44 +16,41 @@
|
|||||||
|
|
||||||
namespace IOS::HLE::USB
|
namespace IOS::HLE::USB
|
||||||
{
|
{
|
||||||
SkylanderPortal g_skyportal;
|
SkylanderUSB::SkylanderUSB(Kernel& ios, const std::string& device_name) : m_ios(ios)
|
||||||
|
|
||||||
SkylanderUSB::SkylanderUSB(Kernel& ios, const std::string& device_name)
|
|
||||||
: EmulatedUSBDevice(ios, device_name)
|
|
||||||
{
|
{
|
||||||
m_vid = 0x1430;
|
m_vid = 0x1430;
|
||||||
m_pid = 0x150;
|
m_pid = 0x150;
|
||||||
m_id = (static_cast<u64>(m_vid) << 32 | static_cast<u64>(m_pid) << 16 | static_cast<u64>(9) << 8 |
|
m_id = (static_cast<u64>(m_vid) << 32 | static_cast<u64>(m_pid) << 16 | static_cast<u64>(9) << 8 |
|
||||||
static_cast<u64>(1));
|
static_cast<u64>(1));
|
||||||
deviceDesc = DeviceDescriptor{18, 1, 512, 0, 0, 0, 64, 5168, 336, 256, 1, 2, 0, 1};
|
m_device_descriptor = DeviceDescriptor{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x40,
|
||||||
configDesc.emplace_back(ConfigDescriptor{9, 2, 41, 1, 1, 0, 128, 250});
|
0x1430, 0x150, 0x100, 0x1, 0x2, 0x0, 0x1};
|
||||||
interfaceDesc.emplace_back(InterfaceDescriptor{9, 4, 0, 0, 2, 3, 0, 0, 0});
|
m_config_descriptor.emplace_back(ConfigDescriptor{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA});
|
||||||
endpointDesc.emplace_back(EndpointDescriptor{7, 5, 129, 3, 64, 1});
|
m_interface_descriptor.emplace_back(
|
||||||
endpointDesc.emplace_back(EndpointDescriptor{7, 5, 2, 3, 64, 1});
|
InterfaceDescriptor{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0});
|
||||||
|
m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x81, 0x3, 0x40, 0x1});
|
||||||
|
m_endpoint_descriptor.emplace_back(EndpointDescriptor{0x7, 0x5, 0x2, 0x3, 0x40, 0x1});
|
||||||
}
|
}
|
||||||
|
|
||||||
SkylanderUSB::~SkylanderUSB()
|
SkylanderUSB::~SkylanderUSB() = default;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceDescriptor SkylanderUSB::GetDeviceDescriptor() const
|
DeviceDescriptor SkylanderUSB::GetDeviceDescriptor() const
|
||||||
{
|
{
|
||||||
return deviceDesc;
|
return m_device_descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ConfigDescriptor> SkylanderUSB::GetConfigurations() const
|
std::vector<ConfigDescriptor> SkylanderUSB::GetConfigurations() const
|
||||||
{
|
{
|
||||||
return configDesc;
|
return m_config_descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<InterfaceDescriptor> SkylanderUSB::GetInterfaces(u8 config) const
|
std::vector<InterfaceDescriptor> SkylanderUSB::GetInterfaces(u8 config) const
|
||||||
{
|
{
|
||||||
return interfaceDesc;
|
return m_interface_descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<EndpointDescriptor> SkylanderUSB::GetEndpoints(u8 config, u8 interface, u8 alt) const
|
std::vector<EndpointDescriptor> SkylanderUSB::GetEndpoints(u8 config, u8 interface, u8 alt) const
|
||||||
{
|
{
|
||||||
return endpointDesc;
|
return m_endpoint_descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkylanderUSB::Attach()
|
bool SkylanderUSB::Attach()
|
||||||
@ -64,11 +61,6 @@ bool SkylanderUSB::Attach()
|
|||||||
}
|
}
|
||||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid);
|
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid);
|
||||||
m_device_attached = true;
|
m_device_attached = true;
|
||||||
if (!m_has_initialised && !Core::WantsDeterminism())
|
|
||||||
{
|
|
||||||
GetTransferThread().Start();
|
|
||||||
m_has_initialised = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,11 +73,7 @@ int SkylanderUSB::CancelTransfer(const u8 endpoint)
|
|||||||
{
|
{
|
||||||
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid,
|
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid,
|
||||||
m_active_interface, endpoint);
|
m_active_interface, endpoint);
|
||||||
if (GetTransferThread().GetTransfers())
|
|
||||||
{
|
|
||||||
return IPC_ENOENT;
|
|
||||||
}
|
|
||||||
GetTransferThread().ClearTransfers();
|
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,17 +109,23 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value,
|
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value,
|
||||||
cmd->index, cmd->length);
|
cmd->index, cmd->length);
|
||||||
|
|
||||||
cmd->expected_time = Common::Timer::NowUs() + 100;
|
|
||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||||
std::array<u8, 64> q_result = {};
|
if ((cmd->length == 0 || buf == nullptr) && cmd->request == 0x09)
|
||||||
std::array<u8, 64> q_data = {};
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_USB, "Skylander command invalid");
|
||||||
|
return IPC_EINVAL;
|
||||||
|
}
|
||||||
|
std::array<u8, 64> result = {};
|
||||||
|
std::array<u8, 64> data = {};
|
||||||
|
s32 expected_count = 0;
|
||||||
|
u64 expected_time_us = 100;
|
||||||
// Control transfers are instantaneous
|
// Control transfers are instantaneous
|
||||||
switch (cmd->request_type)
|
u8 request_type = cmd->request_type;
|
||||||
|
if (request_type == 0x21)
|
||||||
{
|
{
|
||||||
// HID host to device type
|
// HID host to device type
|
||||||
case 0x21:
|
|
||||||
switch (cmd->request)
|
switch (cmd->request)
|
||||||
{
|
{
|
||||||
case 0x09:
|
case 0x09:
|
||||||
@ -145,7 +139,7 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// Response { 'A', (00 | 01),
|
// Response { 'A', (00 | 01),
|
||||||
// ff, 77, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
// ff, 77, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||||
// 00, 00, 00, 00, 00, 00, 00, 00 }
|
// 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||||
// The 3rd byte of the command is whether to activate (0x01) or deactivate (0x00) the
|
// The 2nd byte of the command is whether to activate (0x01) or deactivate (0x00) the
|
||||||
// portal. The response echos back the activation byte as the 2nd byte of the response. The
|
// portal. The response echos back the activation byte as the 2nd byte of the response. The
|
||||||
// 3rd and 4th bytes of the response appear to vary from wired to wireless. On wired
|
// 3rd and 4th bytes of the response appear to vary from wired to wireless. On wired
|
||||||
// portals, the bytes appear to always be ff 77. On wireless portals, during activation the
|
// portals, the bytes appear to always be ff 77. On wireless portals, during activation the
|
||||||
@ -154,15 +148,15 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// for wireless portals.
|
// for wireless portals.
|
||||||
|
|
||||||
// Wii U Wireless: 41 01 f4 00 41 00 ed 00 41 01 f4 00 41 00 eb 00 41 01 f3 00 41 00 ed 00
|
// Wii U Wireless: 41 01 f4 00 41 00 ed 00 41 01 f4 00 41 00 eb 00 41 01 f3 00 41 00 ed 00
|
||||||
if (cmd->length == 2 || cmd->length == 32)
|
if (cmd->length == 2)
|
||||||
{
|
{
|
||||||
q_data = {buf[0], buf[1]};
|
data = {buf[0], buf[1]};
|
||||||
q_result = {0x41, buf[1], 0xFF, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
result = {0x41, buf[1], 0xFF, 0x77, 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, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
q_queries.push(q_result);
|
m_queries.push(result);
|
||||||
cmd->expected_count = 10;
|
expected_count = 10;
|
||||||
g_skyportal.Activate();
|
system.GetSkylanderPortal().Activate();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -179,39 +173,39 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// This command should set the color of the LED in the portal, however this appears
|
// This command should set the color of the LED in the portal, however this appears
|
||||||
// deprecated in most of the recent portals. On portals that do not have LEDs, this command
|
// deprecated in most of the recent portals. On portals that do not have LEDs, this command
|
||||||
// is silently ignored and do not require a response.
|
// is silently ignored and do not require a response.
|
||||||
if (cmd->length == 4 || cmd->length == 32)
|
if (cmd->length == 4)
|
||||||
{
|
{
|
||||||
g_skyportal.SetLEDs(0x01, buf[1], buf[2], buf[3]);
|
system.GetSkylanderPortal().SetLEDs(0x01, buf[1], buf[2], buf[3]);
|
||||||
q_data = {0x43, buf[1], buf[2], buf[3]};
|
data = {0x43, buf[1], buf[2], buf[3]};
|
||||||
cmd->expected_count = 12;
|
expected_count = 12;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'J':
|
case 'J':
|
||||||
{
|
{
|
||||||
// Sided color
|
// Sided color
|
||||||
// buf[1] is the side
|
// The 2nd byte is the side
|
||||||
// 0x00: right
|
// 0x00: right
|
||||||
// 0x01: left and right
|
// 0x01: left and right
|
||||||
// 0x02: left
|
// 0x02: left
|
||||||
|
|
||||||
// buf[2], buf[3] and buf[4] are red, green and blue
|
// The 3rd, 4th and 5th bytes are red, green and blue
|
||||||
|
|
||||||
// buf[5] is unknown. Observed values are 0x00, 0x0D and 0xF4
|
// The 6th byte is unknown. Observed values are 0x00, 0x0D and 0xF4
|
||||||
|
|
||||||
// buf[6] is the fade duration. Exact value-time corrolation unknown. Observed values are
|
// The 7th byte is the fade duration. Exact value-time corrolation unknown. Observed values
|
||||||
// 0x00, 0x01 and 0x07. Custom commands show that the higher this value the longer the
|
// are 0x00, 0x01 and 0x07. Custom commands show that the higher this value the longer the
|
||||||
// duration.
|
// duration.
|
||||||
|
|
||||||
// Empty J response is send after the fade is completed. Immeditately sending it is fine
|
// Empty J response is send after the fade is completed. Immeditately sending it is fine
|
||||||
// as long as we don't show the fade happening
|
// as long as we don't show the fade happening
|
||||||
if (cmd->length == 7)
|
if (cmd->length == 7)
|
||||||
{
|
{
|
||||||
q_data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]};
|
data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]};
|
||||||
cmd->expected_count = 15;
|
expected_count = 15;
|
||||||
q_result = {buf[0]};
|
result = {buf[0]};
|
||||||
q_queries.push(q_result);
|
m_queries.push(result);
|
||||||
g_skyportal.SetLEDs(buf[1], buf[2], buf[3], buf[4]);
|
system.GetSkylanderPortal().SetLEDs(buf[1], buf[2], buf[3], buf[4]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -220,40 +214,38 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// Light
|
// Light
|
||||||
// This command is used while playing audio through the portal
|
// This command is used while playing audio through the portal
|
||||||
|
|
||||||
// buf[1] is the position
|
// The 2nd bytes is the position
|
||||||
// 0x00: right
|
// 0x00: right
|
||||||
// 0x01: trap led
|
// 0x01: trap led
|
||||||
// 0x02: left
|
// 0x02: left
|
||||||
|
|
||||||
// buf[2], buf[3] and buf[4] are red, green and blue
|
// The 3rd, 4th and 5th bytes are red, green and blue
|
||||||
// the trap led is white-only
|
// the trap led is white-only
|
||||||
// increasing or decreasing the values results in a birghter or dimmer light
|
// increasing or decreasing the values results in a brighter or dimmer light
|
||||||
|
|
||||||
// buf[5] is unknown.
|
|
||||||
// A range of values have been observed
|
|
||||||
if (cmd->length == 5)
|
if (cmd->length == 5)
|
||||||
{
|
{
|
||||||
q_data = {buf[0], buf[1], buf[2], buf[3], buf[4]};
|
data = {buf[0], buf[1], buf[2], buf[3], buf[4]};
|
||||||
cmd->expected_count = 13;
|
expected_count = 13;
|
||||||
|
|
||||||
u8 side = buf[1];
|
u8 side = buf[1];
|
||||||
if (side == 0x02)
|
if (side == 0x02)
|
||||||
{
|
{
|
||||||
side = 0x04;
|
side = 0x04;
|
||||||
}
|
}
|
||||||
g_skyportal.SetLEDs(side, buf[2], buf[3], buf[4]);
|
system.GetSkylanderPortal().SetLEDs(side, buf[2], buf[3], buf[4]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'M':
|
case 'M':
|
||||||
{
|
{
|
||||||
// Audio Firmware version
|
// Audio Firmware version
|
||||||
|
// Respond with version obtained from Trap Team wired portal
|
||||||
if (cmd->length == 2)
|
if (cmd->length == 2)
|
||||||
{
|
{
|
||||||
q_data = {buf[0], buf[1]};
|
data = {buf[0], buf[1]};
|
||||||
cmd->expected_count = 10;
|
expected_count = 10;
|
||||||
q_result = {buf[0], buf[1], 0x00, 0x19};
|
result = {buf[0], buf[1], 0x00, 0x19};
|
||||||
q_queries.push(q_result);
|
m_queries.push(result);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -263,25 +255,28 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// Response { 'Q', 10, 00, 00, 00, 00,
|
// Response { 'Q', 10, 00, 00, 00, 00,
|
||||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||||
// 00, 00, 00, 00 }
|
// 00, 00, 00, 00 }
|
||||||
// In the command the 3rd byte indicates which Skylander to query data
|
// In the command the 2nd byte indicates which Skylander to query data
|
||||||
// from. Index starts at 0x10 for the 1st Skylander (as reported in the Status command.) The
|
// from. Index starts at 0x10 for the 1st Skylander (as reported in the Status command.) The
|
||||||
// 16th Skylander indexed would be 0x20.
|
// 16th Skylander indexed would be 0x20. The 3rd byte indicate which block to read from.
|
||||||
|
|
||||||
// A response with the 2nd byte of 0x01 indicates an error in the read. Otherwise, the
|
// A response with the 2nd byte of 0x01 indicates an error in the read. Otherwise, the
|
||||||
// response indicates the Skylander's index in the 2nd byte, the block read in the 3rd byte,
|
// response indicates the Skylander's index in the 2nd byte, the block read in the 3rd byte,
|
||||||
// data (16 bytes) is contained in bytes 4-20.
|
// data (16 bytes) is contained in bytes 4-19.
|
||||||
|
|
||||||
// A Skylander has 64 blocks of data indexed from 0x00 to 0x3f. SwapForce characters have 2
|
// A Skylander has 64 blocks of data indexed from 0x00 to 0x3f. SwapForce characters have 2
|
||||||
// character indexes, these may not be sequential.
|
// character indexes, these may not be sequential.
|
||||||
case 'Q':
|
case 'Q':
|
||||||
{
|
{
|
||||||
// Queries a block
|
// Queries a block
|
||||||
|
if (cmd->length == 3)
|
||||||
|
{
|
||||||
const u8 sky_num = buf[1] & 0xF;
|
const u8 sky_num = buf[1] & 0xF;
|
||||||
const u8 block = buf[2];
|
const u8 block = buf[2];
|
||||||
g_skyportal.QueryBlock(sky_num, block, q_result.data());
|
system.GetSkylanderPortal().QueryBlock(sky_num, block, result.data());
|
||||||
q_queries.push(q_result);
|
m_queries.push(result);
|
||||||
q_data = {buf[0], buf[1], buf[2]};
|
data = {buf[0], buf[1], buf[2]};
|
||||||
cmd->expected_count = 11;
|
expected_count = 11;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'R':
|
case 'R':
|
||||||
@ -294,14 +289,14 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// 00, 00, 00, 00 }
|
// 00, 00, 00, 00 }
|
||||||
// The 4 byte sequence after the R (0x52) is unknown, but appears consistent based on device
|
// The 4 byte sequence after the R (0x52) is unknown, but appears consistent based on device
|
||||||
// type.
|
// type.
|
||||||
if (cmd->length == 2 || cmd->length == 32)
|
if (cmd->length == 2)
|
||||||
{
|
{
|
||||||
q_data = {0x52, 0x00};
|
data = {0x52, 0x00};
|
||||||
q_result = {0x52, 0x02, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
result = {0x52, 0x02, 0x1b, 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, 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};
|
||||||
q_queries.push(q_result);
|
m_queries.push(result);
|
||||||
cmd->expected_count = 10;
|
expected_count = 10;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -336,14 +331,20 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// been activated: {01} when active and {00} when deactivated.
|
// been activated: {01} when active and {00} when deactivated.
|
||||||
case 'S':
|
case 'S':
|
||||||
{
|
{
|
||||||
q_data = {buf[0]};
|
if (cmd->length == 1)
|
||||||
cmd->expected_count = 9;
|
{
|
||||||
|
data = {buf[0]};
|
||||||
|
expected_count = 9;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'V':
|
case 'V':
|
||||||
{
|
{
|
||||||
q_data = {buf[0], buf[1], buf[2], buf[3]};
|
if (cmd->length == 4)
|
||||||
cmd->expected_count = 12;
|
{
|
||||||
|
data = {buf[0], buf[1], buf[2], buf[3]};
|
||||||
|
expected_count = 12;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Write
|
// Write
|
||||||
@ -352,13 +353,13 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// Response { 'W', 00, 00, 00, 00, 00,
|
// Response { 'W', 00, 00, 00, 00, 00,
|
||||||
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
// 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
|
||||||
// 00, 00, 00, 00 }
|
// 00, 00, 00, 00 }
|
||||||
// In the command the 3rd byte indicates which Skylander to query data from. Index starts at
|
// In the command the 2nd byte indicates which Skylander to query data from. Index starts at
|
||||||
// 0x10 for the 1st Skylander (as reported in the Status command.) The 16th Skylander
|
// 0x10 for the 1st Skylander (as reported in the Status command.) The 16th Skylander
|
||||||
// indexed would be 0x20.
|
// indexed would be 0x20.
|
||||||
|
|
||||||
// 4th byte is the block to write to.
|
// 3rd byte is the block to write to.
|
||||||
|
|
||||||
// Bytes 5 - 20 ({ 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f }) are the
|
// Bytes 4 - 19 ({ 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f }) are the
|
||||||
// data to write.
|
// data to write.
|
||||||
|
|
||||||
// The response does not appear to return the id of the Skylander being written, the 2nd
|
// The response does not appear to return the id of the Skylander being written, the 2nd
|
||||||
@ -366,15 +367,18 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
// above.)
|
// above.)
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
|
{
|
||||||
|
if (cmd->length == 19)
|
||||||
{
|
{
|
||||||
const u8 sky_num = buf[1] & 0xF;
|
const u8 sky_num = buf[1] & 0xF;
|
||||||
const u8 block = buf[2];
|
const u8 block = buf[2];
|
||||||
g_skyportal.WriteBlock(sky_num, block, &buf[3], q_result.data());
|
system.GetSkylanderPortal().WriteBlock(sky_num, block, &buf[3], result.data());
|
||||||
q_queries.push(q_result);
|
m_queries.push(result);
|
||||||
q_data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6],
|
data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6],
|
||||||
buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13],
|
buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13],
|
||||||
buf[14], buf[15], buf[16], buf[17], buf[18]};
|
buf[14], buf[15], buf[16], buf[17], buf[18]};
|
||||||
cmd->expected_count = 19;
|
expected_count = 27;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -383,24 +387,22 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x0A:
|
case 0x0A:
|
||||||
cmd->expected_count = 8;
|
expected_count = 8;
|
||||||
break;
|
break;
|
||||||
case 0x0B:
|
case 0x0B:
|
||||||
cmd->expected_count = 8;
|
expected_count = 8;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ERROR_LOG_FMT(IOS_USB, "Unhandled Request {}", cmd->request);
|
ERROR_LOG_FMT(IOS_USB, "Unhandled Request {}", cmd->request);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
cmd->expected_time = Common::Timer::NowUs() + 100;
|
if (expected_count == 0)
|
||||||
GetTransferThread().AddTransfer(std::move(cmd), q_data);
|
return IPC_EINVAL;
|
||||||
|
ScheduleTransfer(std::move(cmd), data, expected_count, expected_time_us);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SkylanderUSB::SubmitTransfer(std::unique_ptr<BulkMessage> cmd)
|
int SkylanderUSB::SubmitTransfer(std::unique_ptr<BulkMessage> cmd)
|
||||||
{
|
{
|
||||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={} endpoint={}", m_vid, m_pid,
|
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={} endpoint={}", m_vid, m_pid,
|
||||||
@ -420,38 +422,46 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
|||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||||
std::array<u8, 64> q_result = {};
|
if (cmd->length == 0 || buf == nullptr)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_USB, "Skylander command invalid");
|
||||||
|
return IPC_EINVAL;
|
||||||
|
}
|
||||||
|
std::array<u8, 64> result = {};
|
||||||
|
s32 expected_count;
|
||||||
|
u64 expected_time_us;
|
||||||
// Audio requests are 64 bytes long, are the only Interrupt requests longer than 32 bytes,
|
// Audio requests are 64 bytes long, are the only Interrupt requests longer than 32 bytes,
|
||||||
// echo the request as the response and respond after 1ms
|
// echo the request as the response and respond after 1ms
|
||||||
if (cmd->length > 32)
|
if (cmd->length > 32 && cmd->length <= 64)
|
||||||
{
|
{
|
||||||
std::array<u8, 64> q_audio_result = {};
|
std::array<u8, 64> audio_result = {};
|
||||||
u8* audio_buf = q_audio_result.data();
|
u8* audio_buf = audio_result.data();
|
||||||
memcpy(audio_buf, buf, cmd->length);
|
memcpy(audio_buf, buf, cmd->length);
|
||||||
cmd->expected_time = Common::Timer::NowUs() + 1000;
|
expected_time_us = 1000;
|
||||||
cmd->expected_count = cmd->length;
|
expected_count = cmd->length;
|
||||||
GetTransferThread().AddTransfer(std::move(cmd), q_audio_result);
|
ScheduleTransfer(std::move(cmd), audio_result, expected_count, expected_time_us);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// If some data was requested from the Control Message, then the Interrupt message needs to
|
// If some data was requested from the Control Message, then the Interrupt message needs to
|
||||||
// respond with that data. Check if the queries queue is empty
|
// respond with that data. Check if the queries queue is empty
|
||||||
if (!q_queries.empty())
|
if (!m_queries.empty())
|
||||||
{
|
{
|
||||||
q_result = q_queries.front();
|
result = m_queries.front();
|
||||||
q_queries.pop();
|
m_queries.pop();
|
||||||
// This needs to happen after ~22 milliseconds
|
// This needs to happen after ~22 milliseconds
|
||||||
cmd->expected_time = Common::Timer::NowUs() + 22000;
|
expected_time_us = 22000;
|
||||||
}
|
}
|
||||||
// If there is no relevant data to respond with, respond with the currentstatus of the Portal
|
// If there is no relevant data to respond with, respond with the currentstatus of the Portal
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
q_result = g_skyportal.GetStatus();
|
result = system.GetSkylanderPortal().GetStatus();
|
||||||
cmd->expected_time = Common::Timer::NowUs() + 2000;
|
expected_time_us = 2000;
|
||||||
}
|
}
|
||||||
cmd->expected_count = 32;
|
expected_count = 32;
|
||||||
GetTransferThread().AddTransfer(std::move(cmd), q_result);
|
ScheduleTransfer(std::move(cmd), result, expected_count, expected_time_us);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SkylanderUSB::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
int SkylanderUSB::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
||||||
{
|
{
|
||||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Isochronous: length={} endpoint={} num_packets={}",
|
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Isochronous: length={} endpoint={} num_packets={}",
|
||||||
@ -459,23 +469,27 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skylander::save()
|
void SkylanderUSB::ScheduleTransfer(std::unique_ptr<TransferCommand> command,
|
||||||
|
const std::array<u8, 64>& data, s32 expected_count,
|
||||||
|
u64 expected_time_us)
|
||||||
|
{
|
||||||
|
command->FillBuffer(data.data(), expected_count);
|
||||||
|
command->ScheduleTransferCompletion(expected_count, expected_time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Skylander::Save()
|
||||||
{
|
{
|
||||||
if (!sky_file)
|
if (!sky_file)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
sky_file.Seek(0, File::SeekOrigin::Begin);
|
sky_file.Seek(0, File::SeekOrigin::Begin);
|
||||||
sky_file.WriteBytes(data.data(), 0x40 * 0x10);
|
sky_file.WriteBytes(data.data(), 0x40 * 0x10);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortal::Activate()
|
void SkylanderPortal::Activate()
|
||||||
{
|
{
|
||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
if (activated)
|
if (m_activated)
|
||||||
{
|
{
|
||||||
// If the portal was already active no change is needed
|
// If the portal was already active no change is needed
|
||||||
return;
|
return;
|
||||||
@ -486,12 +500,12 @@ void SkylanderPortal::Activate()
|
|||||||
{
|
{
|
||||||
if (s.status & 1)
|
if (s.status & 1)
|
||||||
{
|
{
|
||||||
s.queued_status.push(3);
|
s.queued_status.push(Skylander::ADDED);
|
||||||
s.queued_status.push(1);
|
s.queued_status.push(Skylander::READY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activated = true;
|
m_activated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortal::Deactivate()
|
void SkylanderPortal::Deactivate()
|
||||||
@ -510,32 +524,32 @@ void SkylanderPortal::Deactivate()
|
|||||||
s.status &= 1;
|
s.status &= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
activated = false;
|
m_activated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkylanderPortal::IsActivated()
|
bool SkylanderPortal::IsActivated()
|
||||||
{
|
{
|
||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
|
|
||||||
return activated;
|
return m_activated;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortal::UpdateStatus()
|
void SkylanderPortal::UpdateStatus()
|
||||||
{
|
{
|
||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
|
|
||||||
if (!status_updated)
|
if (!m_status_updated)
|
||||||
{
|
{
|
||||||
for (auto& s : skylanders)
|
for (auto& s : skylanders)
|
||||||
{
|
{
|
||||||
if (s.status & 1)
|
if (s.status & 1)
|
||||||
{
|
{
|
||||||
s.queued_status.push(0);
|
s.queued_status.push(Skylander::REMOVED);
|
||||||
s.queued_status.push(3);
|
s.queued_status.push(Skylander::ADDED);
|
||||||
s.queued_status.push(1);
|
s.queued_status.push(Skylander::READY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status_updated = true;
|
m_status_updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,31 +563,31 @@ void SkylanderPortal::SetLEDs(u8 side, u8 red, u8 green, u8 blue)
|
|||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
if (side == 0x00)
|
if (side == 0x00)
|
||||||
{
|
{
|
||||||
this->color_right.r = red;
|
m_color_right.red = red;
|
||||||
this->color_right.g = green;
|
m_color_right.green = green;
|
||||||
this->color_right.b = blue;
|
m_color_right.blue = blue;
|
||||||
}
|
}
|
||||||
else if (side == 0x01)
|
else if (side == 0x01)
|
||||||
{
|
{
|
||||||
this->color_right.r = red;
|
m_color_right.red = red;
|
||||||
this->color_right.g = green;
|
m_color_right.green = green;
|
||||||
this->color_right.b = blue;
|
m_color_right.blue = blue;
|
||||||
|
|
||||||
this->color_left.r = red;
|
m_color_left.red = red;
|
||||||
this->color_left.g = green;
|
m_color_left.green = green;
|
||||||
this->color_left.b = blue;
|
m_color_left.blue = blue;
|
||||||
}
|
}
|
||||||
else if (side == 0x02)
|
else if (side == 0x02)
|
||||||
{
|
{
|
||||||
this->color_left.r = red;
|
m_color_left.red = red;
|
||||||
this->color_left.g = green;
|
m_color_left.green = green;
|
||||||
this->color_left.b = blue;
|
m_color_left.blue = blue;
|
||||||
}
|
}
|
||||||
else if (side == 0x03)
|
else if (side == 0x03)
|
||||||
{
|
{
|
||||||
this->color_trap.r = red;
|
m_color_trap.red = red;
|
||||||
this->color_trap.g = green;
|
m_color_trap.green = green;
|
||||||
this->color_trap.b = blue;
|
m_color_trap.blue = blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,7 +598,7 @@ std::array<u8, 64> SkylanderPortal::GetStatus()
|
|||||||
u32 status = 0;
|
u32 status = 0;
|
||||||
u8 active = 0x00;
|
u8 active = 0x00;
|
||||||
|
|
||||||
if (activated)
|
if (m_activated)
|
||||||
{
|
{
|
||||||
active = 0x01;
|
active = 0x01;
|
||||||
}
|
}
|
||||||
@ -602,14 +616,14 @@ std::array<u8, 64> SkylanderPortal::GetStatus()
|
|||||||
status |= s.status;
|
status |= s.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<u8, 64> q_result = {0x53, 0x00, 0x00, 0x00, 0x00, interrupt_counter++,
|
std::array<u8, 64> result = {0x53, 0x00, 0x00, 0x00, 0x00, m_interrupt_counter++,
|
||||||
active, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00};
|
0x00, 0x00};
|
||||||
memcpy(&q_result.data()[1], &status, sizeof(status));
|
memcpy(&result[1], &status, sizeof(status));
|
||||||
return q_result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf)
|
void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf)
|
||||||
@ -644,7 +658,7 @@ void SkylanderPortal::WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u
|
|||||||
{
|
{
|
||||||
reply_buf[1] = (0x10 | sky_num);
|
reply_buf[1] = (0x10 | sky_num);
|
||||||
memcpy(skylander.data.data() + (block * 16), to_write_buf, 16);
|
memcpy(skylander.data.data() + (block * 16), to_write_buf, 16);
|
||||||
skylander.save();
|
skylander.Save();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -652,6 +666,76 @@ void SkylanderPortal::WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u16 SkylanderCRC16(u16 init_value, const u8* buffer, u32 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};
|
||||||
|
|
||||||
|
u16 crc = init_value;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
const u16 tmp = (crc >> 8) ^ buffer[i];
|
||||||
|
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkylanderPortal::CreateSkylander(const std::string& file_path, u16 sky_id, u16 sky_var)
|
||||||
|
{
|
||||||
|
File::IOFile sky_file(file_path, "w+b");
|
||||||
|
if (!sky_file)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<u8, 0x40 * 0x10> buf{};
|
||||||
|
const auto file_data = buf.data();
|
||||||
|
// Set the block permissions
|
||||||
|
u32 first_block = 0x690F0F0F;
|
||||||
|
u32 other_blocks = 0x69080F7F;
|
||||||
|
memcpy(&file_data[0x36], &first_block, sizeof(first_block));
|
||||||
|
for (u32 index = 1; index < 0x10; index++)
|
||||||
|
{
|
||||||
|
memcpy(&file_data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks));
|
||||||
|
}
|
||||||
|
// Set the skylander info
|
||||||
|
u16 sky_info = (sky_id | sky_var) + 1;
|
||||||
|
memcpy(&file_data[0], &sky_info, sizeof(sky_info));
|
||||||
|
memcpy(&file_data[0x10], &sky_id, sizeof(sky_id));
|
||||||
|
memcpy(&file_data[0x1C], &sky_var, sizeof(sky_var));
|
||||||
|
// Set checksum
|
||||||
|
u16 checksum = SkylanderCRC16(0xFFFF, file_data, 0x1E);
|
||||||
|
memcpy(&file_data[0x1E], &checksum, sizeof(checksum));
|
||||||
|
|
||||||
|
sky_file.WriteBytes(buf.data(), buf.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool SkylanderPortal::RemoveSkylander(u8 sky_num)
|
bool SkylanderPortal::RemoveSkylander(u8 sky_num)
|
||||||
{
|
{
|
||||||
DEBUG_LOG_FMT(IOS_USB, "Cleared Skylander from slot {}", sky_num);
|
DEBUG_LOG_FMT(IOS_USB, "Cleared Skylander from slot {}", sky_num);
|
||||||
@ -660,10 +744,9 @@ bool SkylanderPortal::RemoveSkylander(u8 sky_num)
|
|||||||
|
|
||||||
if (skylander.status & 1)
|
if (skylander.status & 1)
|
||||||
{
|
{
|
||||||
skylander.status = 2;
|
skylander.status = Skylander::REMOVING;
|
||||||
skylander.queued_status.push(2);
|
skylander.queued_status.push(Skylander::REMOVING);
|
||||||
skylander.queued_status.push(0);
|
skylander.queued_status.push(Skylander::REMOVED);
|
||||||
skylander.sky_file.Close();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,9 +793,9 @@ u8 SkylanderPortal::LoadSkylander(u8* buf, File::IOFile in_file)
|
|||||||
DEBUG_LOG_FMT(IOS_USB, "Skylander Data: \n{}",
|
DEBUG_LOG_FMT(IOS_USB, "Skylander Data: \n{}",
|
||||||
HexDump(skylander.data.data(), skylander.data.size()));
|
HexDump(skylander.data.data(), skylander.data.size()));
|
||||||
skylander.sky_file = std::move(in_file);
|
skylander.sky_file = std::move(in_file);
|
||||||
skylander.status = 3;
|
skylander.status = Skylander::ADDED;
|
||||||
skylander.queued_status.push(3);
|
skylander.queued_status.push(Skylander::ADDED);
|
||||||
skylander.queued_status.push(1);
|
skylander.queued_status.push(Skylander::READY);
|
||||||
skylander.last_id = sky_serial;
|
skylander.last_id = sky_serial;
|
||||||
}
|
}
|
||||||
return found_slot;
|
return found_slot;
|
||||||
|
@ -8,8 +8,9 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/IOFile.h"
|
#include "Common/IOFile.h"
|
||||||
#include "Core/IOS/USB/EmulatedUSBDevice.h"
|
#include "Core/IOS/USB/Common.h"
|
||||||
|
|
||||||
// The maximum possible characters the portal can handle.
|
// The maximum possible characters the portal can handle.
|
||||||
// The status array is 32 bits and every character takes 2 bits.
|
// The status array is 32 bits and every character takes 2 bits.
|
||||||
@ -18,7 +19,7 @@ constexpr u8 MAX_SKYLANDERS = 16;
|
|||||||
|
|
||||||
namespace IOS::HLE::USB
|
namespace IOS::HLE::USB
|
||||||
{
|
{
|
||||||
class SkylanderUSB final : public EmulatedUSBDevice
|
class SkylanderUSB final : public Device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SkylanderUSB(Kernel& ios, const std::string& device_name);
|
SkylanderUSB(Kernel& ios, const std::string& device_name);
|
||||||
@ -37,20 +38,20 @@ public:
|
|||||||
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
|
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
|
||||||
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
|
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
|
||||||
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
||||||
|
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, const std::array<u8, 64>& data,
|
||||||
protected:
|
s32 expected_count, u64 expected_time_us);
|
||||||
std::queue<std::array<u8, 64>> q_queries;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Kernel& m_ios;
|
||||||
u16 m_vid = 0;
|
u16 m_vid = 0;
|
||||||
u16 m_pid = 0;
|
u16 m_pid = 0;
|
||||||
u8 m_active_interface = 0;
|
u8 m_active_interface = 0;
|
||||||
bool m_device_attached = false;
|
bool m_device_attached = false;
|
||||||
DeviceDescriptor deviceDesc;
|
DeviceDescriptor m_device_descriptor;
|
||||||
std::vector<ConfigDescriptor> configDesc;
|
std::vector<ConfigDescriptor> m_config_descriptor;
|
||||||
std::vector<InterfaceDescriptor> interfaceDesc;
|
std::vector<InterfaceDescriptor> m_interface_descriptor;
|
||||||
std::vector<EndpointDescriptor> endpointDesc;
|
std::vector<EndpointDescriptor> m_endpoint_descriptor;
|
||||||
bool m_has_initialised = false;
|
std::queue<std::array<u8, 64>> m_queries;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Skylander final
|
struct Skylander final
|
||||||
@ -60,12 +61,22 @@ struct Skylander final
|
|||||||
std::queue<u8> queued_status;
|
std::queue<u8> queued_status;
|
||||||
std::array<u8, 0x40 * 0x10> data{};
|
std::array<u8, 0x40 * 0x10> data{};
|
||||||
u32 last_id = 0;
|
u32 last_id = 0;
|
||||||
void save();
|
void Save();
|
||||||
|
|
||||||
|
enum : u8
|
||||||
|
{
|
||||||
|
REMOVED = 0,
|
||||||
|
READY = 1,
|
||||||
|
REMOVING = 2,
|
||||||
|
ADDED = 3
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LedColor final
|
struct SkylanderLEDColor final
|
||||||
{
|
{
|
||||||
u8 r = 0, g = 0, b = 0;
|
u8 red = 0;
|
||||||
|
u8 green = 0;
|
||||||
|
u8 blue = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SkylanderPortal final
|
class SkylanderPortal final
|
||||||
@ -81,22 +92,21 @@ public:
|
|||||||
void QueryBlock(u8 sky_num, u8 block, u8* reply_buf);
|
void QueryBlock(u8 sky_num, u8 block, u8* reply_buf);
|
||||||
void WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
|
void WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
|
||||||
|
|
||||||
|
bool CreateSkylander(const std::string& file_path, u16 sky_id, u16 sky_var);
|
||||||
bool RemoveSkylander(u8 sky_num);
|
bool RemoveSkylander(u8 sky_num);
|
||||||
u8 LoadSkylander(u8* buf, File::IOFile in_file);
|
u8 LoadSkylander(u8* buf, File::IOFile in_file);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::mutex sky_mutex;
|
std::mutex sky_mutex;
|
||||||
|
|
||||||
bool activated = true;
|
bool m_activated = true;
|
||||||
bool status_updated = false;
|
bool m_status_updated = false;
|
||||||
u8 interrupt_counter = 0;
|
u8 m_interrupt_counter = 0;
|
||||||
LedColor color_right = {};
|
SkylanderLEDColor m_color_right = {};
|
||||||
LedColor color_left = {};
|
SkylanderLEDColor m_color_left = {};
|
||||||
LedColor color_trap = {};
|
SkylanderLEDColor m_color_trap = {};
|
||||||
|
|
||||||
Skylander skylanders[MAX_SKYLANDERS];
|
std::array<Skylander, MAX_SKYLANDERS> skylanders;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern SkylanderPortal g_skyportal;
|
|
||||||
|
|
||||||
} // namespace IOS::HLE::USB
|
} // namespace IOS::HLE::USB
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
#include "Core/IOS/USB/EmulatedUSBDevice.h"
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "Common/Thread.h"
|
|
||||||
#include "Common/Timer.h"
|
|
||||||
#include "Core/Core.h"
|
|
||||||
|
|
||||||
namespace IOS::HLE::USB
|
|
||||||
{
|
|
||||||
EmulatedUSBDevice::EmulatedUSBDevice(Kernel& ios, const std::string& device_name) : m_ios(ios)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
EmulatedUSBDevice::~EmulatedUSBDevice()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
EmulatedUSBDevice::FakeTransferThread::~FakeTransferThread()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmulatedUSBDevice::FakeTransferThread::Start()
|
|
||||||
{
|
|
||||||
if (Core::WantsDeterminism())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_thread_running.TestAndSet())
|
|
||||||
{
|
|
||||||
m_thread = std::thread([this] {
|
|
||||||
Common::SetCurrentThreadName("Fake Transfer Thread");
|
|
||||||
while (m_thread_running.IsSet())
|
|
||||||
{
|
|
||||||
if (!m_transfers.empty())
|
|
||||||
{
|
|
||||||
std::lock_guard lk{m_transfers_mutex};
|
|
||||||
u64 timestamp = Common::Timer::NowUs();
|
|
||||||
for (auto iterator = m_transfers.begin(); iterator != m_transfers.end();)
|
|
||||||
{
|
|
||||||
auto* command = iterator->second.get();
|
|
||||||
if (command->expected_time > timestamp)
|
|
||||||
{
|
|
||||||
++iterator;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
command->FillBuffer(iterator->first.data(), command->expected_count);
|
|
||||||
command->OnTransferComplete(command->expected_count);
|
|
||||||
iterator = m_transfers.erase(iterator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmulatedUSBDevice::FakeTransferThread::Stop()
|
|
||||||
{
|
|
||||||
if (m_thread_running.TestAndClear())
|
|
||||||
m_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmulatedUSBDevice::FakeTransferThread::AddTransfer(std::unique_ptr<TransferCommand> command,
|
|
||||||
std::array<u8, 64> data)
|
|
||||||
{
|
|
||||||
std::lock_guard lk{m_transfers_mutex};
|
|
||||||
m_transfers.emplace(data, std::move(command));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmulatedUSBDevice::FakeTransferThread::ClearTransfers()
|
|
||||||
{
|
|
||||||
std::lock_guard lk{m_transfers_mutex};
|
|
||||||
m_transfers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EmulatedUSBDevice::FakeTransferThread::GetTransfers()
|
|
||||||
{
|
|
||||||
std::lock_guard lk{m_transfers_mutex};
|
|
||||||
return m_transfers.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace IOS::HLE::USB
|
|
@ -1,43 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
|
||||||
#include "Core/IOS/USB/Common.h"
|
|
||||||
|
|
||||||
namespace IOS::HLE::USB
|
|
||||||
{
|
|
||||||
class EmulatedUSBDevice : public Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
EmulatedUSBDevice(Kernel& ios, const std::string& device_name);
|
|
||||||
virtual ~EmulatedUSBDevice();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
class FakeTransferThread final
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit FakeTransferThread(EmulatedUSBDevice* device) : m_device(device) {}
|
|
||||||
~FakeTransferThread();
|
|
||||||
void Start();
|
|
||||||
void Stop();
|
|
||||||
void AddTransfer(std::unique_ptr<TransferCommand> command, std::array<u8, 64> data);
|
|
||||||
void ClearTransfers();
|
|
||||||
bool GetTransfers();
|
|
||||||
|
|
||||||
private:
|
|
||||||
EmulatedUSBDevice* m_device = nullptr;
|
|
||||||
Common::Flag m_thread_running;
|
|
||||||
std::thread m_thread;
|
|
||||||
Common::Flag m_is_initialized;
|
|
||||||
std::map<std::array<u8, 64>, std::unique_ptr<TransferCommand>> m_transfers;
|
|
||||||
std::mutex m_transfers_mutex;
|
|
||||||
};
|
|
||||||
FakeTransferThread m_transfer_thread{this};
|
|
||||||
FakeTransferThread& GetTransferThread() { return m_transfer_thread; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Kernel& m_ios;
|
|
||||||
};
|
|
||||||
} // namespace IOS::HLE::USB
|
|
@ -24,6 +24,8 @@
|
|||||||
#include "Core/IOS/USB/Common.h"
|
#include "Core/IOS/USB/Common.h"
|
||||||
#include "Core/IOS/USB/Emulated/Skylander.h"
|
#include "Core/IOS/USB/Emulated/Skylander.h"
|
||||||
#include "Core/IOS/USB/LibusbDevice.h"
|
#include "Core/IOS/USB/LibusbDevice.h"
|
||||||
|
#include "Core/NetPlayProto.h"
|
||||||
|
#include "Core/System.h"
|
||||||
|
|
||||||
namespace IOS::HLE
|
namespace IOS::HLE
|
||||||
{
|
{
|
||||||
@ -35,7 +37,7 @@ USBHost::~USBHost() = default;
|
|||||||
|
|
||||||
std::optional<IPCReply> USBHost::Open(const OpenRequest& request)
|
std::optional<IPCReply> USBHost::Open(const OpenRequest& request)
|
||||||
{
|
{
|
||||||
if (!m_has_initialised && !Core::WantsDeterminism())
|
if (!m_has_initialised)
|
||||||
{
|
{
|
||||||
GetScanThread().Start();
|
GetScanThread().Start();
|
||||||
// Force a device scan to complete, because some games (including Your Shape) only care
|
// Force a device scan to complete, because some games (including Your Shape) only care
|
||||||
@ -97,12 +99,15 @@ bool USBHost::ShouldAddDevice(const USB::Device& device) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USBHost::Update()
|
||||||
|
{
|
||||||
|
if (Core::WantsDeterminism())
|
||||||
|
UpdateDevices();
|
||||||
|
}
|
||||||
|
|
||||||
// This is called from the scan thread. Returns false if we failed to update the device list.
|
// This is called from the scan thread. Returns false if we failed to update the device list.
|
||||||
bool USBHost::UpdateDevices(const bool always_add_hooks)
|
bool USBHost::UpdateDevices(const bool always_add_hooks)
|
||||||
{
|
{
|
||||||
if (Core::WantsDeterminism())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
DeviceChangeHooks hooks;
|
DeviceChangeHooks hooks;
|
||||||
std::set<u64> plugged_devices;
|
std::set<u64> plugged_devices;
|
||||||
// If we failed to get a new, up-to-date list of devices, we cannot detect device removals.
|
// If we failed to get a new, up-to-date list of devices, we cannot detect device removals.
|
||||||
@ -116,19 +121,10 @@ bool USBHost::UpdateDevices(const bool always_add_hooks)
|
|||||||
bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
||||||
const bool always_add_hooks)
|
const bool always_add_hooks)
|
||||||
{
|
{
|
||||||
if (Config::EmulateSkylanderPortal())
|
AddEmulatedDevices(new_devices, hooks, always_add_hooks);
|
||||||
{
|
|
||||||
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(m_ios, "Skylander Portal");
|
|
||||||
if (!ShouldAddDevice(*skylanderportal))
|
|
||||||
return true;
|
|
||||||
const u64 skyid = skylanderportal->GetId();
|
|
||||||
new_devices.insert(skyid);
|
|
||||||
if (AddDevice(std::move(skylanderportal)))
|
|
||||||
{
|
|
||||||
hooks.emplace(GetDeviceById(skyid), ChangeEvent::Inserted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef __LIBUSB__
|
#ifdef __LIBUSB__
|
||||||
|
if (!Core::WantsDeterminism())
|
||||||
|
{
|
||||||
auto whitelist = Config::GetUSBDeviceWhitelist();
|
auto whitelist = Config::GetUSBDeviceWhitelist();
|
||||||
if (whitelist.empty())
|
if (whitelist.empty())
|
||||||
return true;
|
return true;
|
||||||
@ -154,6 +150,7 @@ bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks
|
|||||||
if (ret != LIBUSB_SUCCESS)
|
if (ret != LIBUSB_SUCCESS)
|
||||||
WARN_LOG_FMT(IOS_USB, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret));
|
WARN_LOG_FMT(IOS_USB, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -188,6 +185,24 @@ void USBHost::DispatchHooks(const DeviceChangeHooks& hooks)
|
|||||||
OnDeviceChangeEnd();
|
OnDeviceChangeEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USBHost::AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
||||||
|
bool always_add_hooks)
|
||||||
|
{
|
||||||
|
if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning())
|
||||||
|
{
|
||||||
|
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(m_ios, "Skylander Portal");
|
||||||
|
if (ShouldAddDevice(*skylanderportal))
|
||||||
|
{
|
||||||
|
const u64 skyid = skylanderportal->GetId();
|
||||||
|
new_devices.insert(skyid);
|
||||||
|
if (AddDevice(std::move(skylanderportal)) || always_add_hooks)
|
||||||
|
{
|
||||||
|
hooks.emplace(GetDeviceById(skyid), ChangeEvent::Inserted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
USBHost::ScanThread::~ScanThread()
|
USBHost::ScanThread::~ScanThread()
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
@ -195,14 +210,19 @@ USBHost::ScanThread::~ScanThread()
|
|||||||
|
|
||||||
void USBHost::ScanThread::WaitForFirstScan()
|
void USBHost::ScanThread::WaitForFirstScan()
|
||||||
{
|
{
|
||||||
|
if (m_thread_running.IsSet())
|
||||||
|
{
|
||||||
m_first_scan_complete_event.Wait();
|
m_first_scan_complete_event.Wait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void USBHost::ScanThread::Start()
|
void USBHost::ScanThread::Start()
|
||||||
{
|
{
|
||||||
if (Core::WantsDeterminism())
|
if (Core::WantsDeterminism())
|
||||||
|
{
|
||||||
|
m_host->UpdateDevices();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
if (m_thread_running.TestAndSet())
|
if (m_thread_running.TestAndSet())
|
||||||
{
|
{
|
||||||
m_thread = std::thread([this] {
|
m_thread = std::thread([this] {
|
||||||
|
@ -76,10 +76,13 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool AddDevice(std::unique_ptr<USB::Device> device);
|
bool AddDevice(std::unique_ptr<USB::Device> device);
|
||||||
|
void Update() override;
|
||||||
bool UpdateDevices(bool always_add_hooks = false);
|
bool UpdateDevices(bool always_add_hooks = false);
|
||||||
bool AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks);
|
bool AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks);
|
||||||
void DetectRemovedDevices(const std::set<u64>& plugged_devices, DeviceChangeHooks& hooks);
|
void DetectRemovedDevices(const std::set<u64>& plugged_devices, DeviceChangeHooks& hooks);
|
||||||
void DispatchHooks(const DeviceChangeHooks& hooks);
|
void DispatchHooks(const DeviceChangeHooks& hooks);
|
||||||
|
void AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
||||||
|
bool always_add_hooks);
|
||||||
|
|
||||||
bool m_has_initialised = false;
|
bool m_has_initialised = false;
|
||||||
LibusbUtils::Context m_context;
|
LibusbUtils::Context m_context;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "Core/HW/SI/SI.h"
|
#include "Core/HW/SI/SI.h"
|
||||||
#include "Core/HW/Sram.h"
|
#include "Core/HW/Sram.h"
|
||||||
#include "Core/HW/VideoInterface.h"
|
#include "Core/HW/VideoInterface.h"
|
||||||
|
#include "IOS/USB/Emulated/Skylander.h"
|
||||||
#include "VideoCommon/CommandProcessor.h"
|
#include "VideoCommon/CommandProcessor.h"
|
||||||
#include "VideoCommon/Fifo.h"
|
#include "VideoCommon/Fifo.h"
|
||||||
#include "VideoCommon/GeometryShaderManager.h"
|
#include "VideoCommon/GeometryShaderManager.h"
|
||||||
@ -47,6 +48,7 @@ struct System::Impl
|
|||||||
Fifo::FifoManager m_fifo;
|
Fifo::FifoManager m_fifo;
|
||||||
GeometryShaderManager m_geometry_shader_manager;
|
GeometryShaderManager m_geometry_shader_manager;
|
||||||
GPFifo::GPFifoManager m_gp_fifo;
|
GPFifo::GPFifoManager m_gp_fifo;
|
||||||
|
IOS::HLE::USB::SkylanderPortal m_skylander_portal;
|
||||||
Memory::MemoryManager m_memory;
|
Memory::MemoryManager m_memory;
|
||||||
MemoryInterface::MemoryInterfaceState m_memory_interface_state;
|
MemoryInterface::MemoryInterfaceState m_memory_interface_state;
|
||||||
PixelEngine::PixelEngineManager m_pixel_engine;
|
PixelEngine::PixelEngineManager m_pixel_engine;
|
||||||
@ -151,6 +153,11 @@ GPFifo::GPFifoManager& System::GetGPFifo() const
|
|||||||
return m_impl->m_gp_fifo;
|
return m_impl->m_gp_fifo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IOS::HLE::USB::SkylanderPortal& System::GetSkylanderPortal() const
|
||||||
|
{
|
||||||
|
return m_impl->m_skylander_portal;
|
||||||
|
}
|
||||||
|
|
||||||
Memory::MemoryManager& System::GetMemory() const
|
Memory::MemoryManager& System::GetMemory() const
|
||||||
{
|
{
|
||||||
return m_impl->m_memory;
|
return m_impl->m_memory;
|
||||||
|
@ -47,6 +47,10 @@ namespace GPFifo
|
|||||||
{
|
{
|
||||||
class GPFifoManager;
|
class GPFifoManager;
|
||||||
}
|
}
|
||||||
|
namespace IOS::HLE::USB
|
||||||
|
{
|
||||||
|
class SkylanderPortal;
|
||||||
|
};
|
||||||
namespace Memory
|
namespace Memory
|
||||||
{
|
{
|
||||||
class MemoryManager;
|
class MemoryManager;
|
||||||
@ -116,6 +120,7 @@ public:
|
|||||||
Fifo::FifoManager& GetFifo() const;
|
Fifo::FifoManager& GetFifo() const;
|
||||||
GeometryShaderManager& GetGeometryShaderManager() const;
|
GeometryShaderManager& GetGeometryShaderManager() const;
|
||||||
GPFifo::GPFifoManager& GetGPFifo() const;
|
GPFifo::GPFifoManager& GetGPFifo() const;
|
||||||
|
IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const;
|
||||||
Memory::MemoryManager& GetMemory() const;
|
Memory::MemoryManager& GetMemory() const;
|
||||||
MemoryInterface::MemoryInterfaceState& GetMemoryInterfaceState() const;
|
MemoryInterface::MemoryInterfaceState& GetMemoryInterfaceState() const;
|
||||||
PixelEngine::PixelEngineManager& GetPixelEngine() const;
|
PixelEngine::PixelEngineManager& GetPixelEngine() const;
|
||||||
|
@ -373,7 +373,6 @@
|
|||||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Common.h" />
|
<ClInclude Include="Core\IOS\USB\Common.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\EmulatedUSBDevice.h" />
|
|
||||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylander.h" />
|
<ClInclude Include="Core\IOS\USB\Emulated\Skylander.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Host.h" />
|
<ClInclude Include="Core\IOS\USB\Host.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\LibusbDevice.h" />
|
<ClInclude Include="Core\IOS\USB\LibusbDevice.h" />
|
||||||
@ -987,7 +986,6 @@
|
|||||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\EmulatedUSBDevice.cpp" />
|
|
||||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylander.cpp" />
|
<ClCompile Include="Core\IOS\USB\Emulated\Skylander.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Host.cpp" />
|
<ClCompile Include="Core\IOS\USB\Host.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\LibusbDevice.cpp" />
|
<ClCompile Include="Core\IOS\USB\LibusbDevice.cpp" />
|
||||||
|
@ -221,7 +221,7 @@ void MenuBar::AddToolsMenu()
|
|||||||
|
|
||||||
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
||||||
|
|
||||||
tools_menu->addAction(tr("&Skylanders Portal"), this, [this] { emit ShowSkylanderPortal(); });
|
tools_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
|
||||||
|
|
||||||
tools_menu->addSeparator();
|
tools_menu->addSeparator();
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2022 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// DolphinQt code copied and modified for Dolphin from the RPCS3 Qt utility for Creating, Loading
|
// DolphinQt code copied and modified for Dolphin from the RPCS3 Qt utility for Creating, Loading
|
||||||
// and Clearing skylanders
|
// and Clearing skylanders
|
||||||
|
|
||||||
@ -6,12 +9,14 @@
|
|||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
@ -22,15 +27,16 @@
|
|||||||
#include "Common/IOFile.h"
|
#include "Common/IOFile.h"
|
||||||
|
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
|
#include "Core/System.h"
|
||||||
|
|
||||||
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
SkylanderPortalWindow* SkylanderPortalWindow::inst = nullptr;
|
// Qt is not guaranteed to keep track of file paths using native file pickers, so we use this
|
||||||
std::optional<std::tuple<u8, u16, u16>> SkylanderPortalWindow::sky_slots[MAX_SKYLANDERS];
|
// static variable to ensure we open at the most recent Skylander file location
|
||||||
QString last_skylander_path;
|
static QString s_last_skylander_path;
|
||||||
|
|
||||||
const std::map<const std::pair<const u16, const u16>, const std::string> list_skylanders = {
|
const std::map<const std::pair<const u16, const u16>, const char*> list_skylanders = {
|
||||||
{{0, 0x0000}, "Whirlwind"},
|
{{0, 0x0000}, "Whirlwind"},
|
||||||
{{0, 0x1801}, "Series 2 Whirlwind"},
|
{{0, 0x1801}, "Series 2 Whirlwind"},
|
||||||
{{0, 0x1C02}, "Polar Whirlwind"},
|
{{0, 0x1C02}, "Polar Whirlwind"},
|
||||||
@ -513,49 +519,10 @@ const std::map<const std::pair<const u16, const u16>, const std::string> list_sk
|
|||||||
{{3503, 0x0000}, "Kaos Trophy"},
|
{{3503, 0x0000}, "Kaos Trophy"},
|
||||||
};
|
};
|
||||||
|
|
||||||
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 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};
|
|
||||||
|
|
||||||
u16 crc = init_value;
|
|
||||||
|
|
||||||
for (u32 i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
const u16 tmp = (crc >> 8) ^ buffer[i];
|
|
||||||
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent)
|
SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Skylanders Manager"));
|
setWindowTitle(tr("Skylanders Manager"));
|
||||||
setObjectName(QString::fromStdString("skylanders_manager"));
|
setObjectName(tr("skylanders_manager"));
|
||||||
setMinimumSize(QSize(700, 200));
|
setMinimumSize(QSize(700, 200));
|
||||||
|
|
||||||
CreateMainWindow();
|
CreateMainWindow();
|
||||||
@ -568,33 +535,32 @@ SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent)
|
|||||||
OnEmulationStateChanged(Core::GetState());
|
OnEmulationStateChanged(Core::GetState());
|
||||||
};
|
};
|
||||||
|
|
||||||
SkylanderPortalWindow::~SkylanderPortalWindow()
|
SkylanderPortalWindow::~SkylanderPortalWindow() = default;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkylanderPortalWindow::CreateMainWindow()
|
void SkylanderPortalWindow::CreateMainWindow()
|
||||||
{
|
{
|
||||||
QVBoxLayout* mainLayout = new QVBoxLayout();
|
auto* main_layout = new QVBoxLayout();
|
||||||
|
|
||||||
QGroupBox* checkbox_group = new QGroupBox();
|
auto* checkbox_group = new QGroupBox();
|
||||||
QHBoxLayout* checkboxLayout = new QHBoxLayout();
|
auto* checkbox_layout = new QHBoxLayout();
|
||||||
checkboxLayout->setAlignment(Qt::AlignHCenter);
|
checkbox_layout->setAlignment(Qt::AlignHCenter);
|
||||||
checkbox = new QCheckBox(QString::fromStdString("Emulate Skylander Portal"), this);
|
m_checkbox = new QCheckBox(tr("Emulate Skylander Portal"), this);
|
||||||
connect(checkbox, &QCheckBox::toggled, [=](bool checked) { EmulatePortal(checked); });
|
m_checkbox->setChecked(Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL));
|
||||||
checkboxLayout->addWidget(checkbox);
|
connect(m_checkbox, &QCheckBox::toggled, [=](bool checked) { EmulatePortal(checked); });
|
||||||
checkbox_group->setLayout(checkboxLayout);
|
checkbox_layout->addWidget(m_checkbox);
|
||||||
mainLayout->addWidget(checkbox_group);
|
checkbox_group->setLayout(checkbox_layout);
|
||||||
|
main_layout->addWidget(checkbox_group);
|
||||||
|
|
||||||
auto add_line = [](QVBoxLayout* vbox) {
|
auto add_line = [](QVBoxLayout* vbox) {
|
||||||
QFrame* line = new QFrame();
|
auto* line = new QFrame();
|
||||||
line->setFrameShape(QFrame::HLine);
|
line->setFrameShape(QFrame::HLine);
|
||||||
line->setFrameShadow(QFrame::Sunken);
|
line->setFrameShadow(QFrame::Sunken);
|
||||||
vbox->addWidget(line);
|
vbox->addWidget(line);
|
||||||
};
|
};
|
||||||
|
|
||||||
group_skylanders = new QGroupBox(tr("Active Portal Skylanders:"));
|
m_group_skylanders = new QGroupBox(tr("Active Portal Skylanders:"));
|
||||||
QVBoxLayout* vbox_group = new QVBoxLayout();
|
auto* vbox_group = new QVBoxLayout();
|
||||||
QScrollArea* scroll_area = new QScrollArea();
|
auto* scroll_area = new QScrollArea();
|
||||||
|
|
||||||
for (auto i = 0; i < MAX_SKYLANDERS; i++)
|
for (auto i = 0; i < MAX_SKYLANDERS; i++)
|
||||||
{
|
{
|
||||||
@ -603,21 +569,21 @@ void SkylanderPortalWindow::CreateMainWindow()
|
|||||||
add_line(vbox_group);
|
add_line(vbox_group);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHBoxLayout* hbox_skylander = new QHBoxLayout();
|
auto* hbox_skylander = new QHBoxLayout();
|
||||||
QLabel* label_skyname = new QLabel(QString(tr("Skylander %1")).arg(i + 1));
|
auto* label_skyname = new QLabel(QString(tr("Skylander %1")).arg(i + 1));
|
||||||
edit_skylanders[i] = new QLineEdit();
|
m_edit_skylanders[i] = new QLineEdit();
|
||||||
edit_skylanders[i]->setEnabled(false);
|
m_edit_skylanders[i]->setEnabled(false);
|
||||||
|
|
||||||
QPushButton* clear_btn = new QPushButton(tr("Clear"));
|
auto* clear_btn = new QPushButton(tr("Clear"));
|
||||||
QPushButton* create_btn = new QPushButton(tr("Create"));
|
auto* create_btn = new QPushButton(tr("Create"));
|
||||||
QPushButton* load_btn = new QPushButton(tr("Load"));
|
auto* load_btn = new QPushButton(tr("Load"));
|
||||||
|
|
||||||
connect(clear_btn, &QAbstractButton::clicked, this, [this, i]() { ClearSkylander(i); });
|
connect(clear_btn, &QAbstractButton::clicked, this, [this, i]() { ClearSkylander(i); });
|
||||||
connect(create_btn, &QAbstractButton::clicked, this, [this, i]() { CreateSkylander(i); });
|
connect(create_btn, &QAbstractButton::clicked, this, [this, i]() { CreateSkylander(i); });
|
||||||
connect(load_btn, &QAbstractButton::clicked, this, [this, i]() { LoadSkylander(i); });
|
connect(load_btn, &QAbstractButton::clicked, this, [this, i]() { LoadSkylander(i); });
|
||||||
|
|
||||||
hbox_skylander->addWidget(label_skyname);
|
hbox_skylander->addWidget(label_skyname);
|
||||||
hbox_skylander->addWidget(edit_skylanders[i]);
|
hbox_skylander->addWidget(m_edit_skylanders[i]);
|
||||||
hbox_skylander->addWidget(clear_btn);
|
hbox_skylander->addWidget(clear_btn);
|
||||||
hbox_skylander->addWidget(create_btn);
|
hbox_skylander->addWidget(create_btn);
|
||||||
hbox_skylander->addWidget(load_btn);
|
hbox_skylander->addWidget(load_btn);
|
||||||
@ -625,12 +591,12 @@ void SkylanderPortalWindow::CreateMainWindow()
|
|||||||
vbox_group->addLayout(hbox_skylander);
|
vbox_group->addLayout(hbox_skylander);
|
||||||
}
|
}
|
||||||
|
|
||||||
group_skylanders->setLayout(vbox_group);
|
m_group_skylanders->setLayout(vbox_group);
|
||||||
scroll_area->setWidget(group_skylanders);
|
scroll_area->setWidget(m_group_skylanders);
|
||||||
scroll_area->setWidgetResizable(true);
|
scroll_area->setWidgetResizable(true);
|
||||||
group_skylanders->setVisible(false);
|
m_group_skylanders->setVisible(Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL));
|
||||||
mainLayout->addWidget(scroll_area);
|
main_layout->addWidget(scroll_area);
|
||||||
setLayout(mainLayout);
|
setLayout(main_layout);
|
||||||
|
|
||||||
UpdateEdits();
|
UpdateEdits();
|
||||||
}
|
}
|
||||||
@ -639,29 +605,29 @@ void SkylanderPortalWindow::OnEmulationStateChanged(Core::State state)
|
|||||||
{
|
{
|
||||||
const bool running = state != Core::State::Uninitialized;
|
const bool running = state != Core::State::Uninitialized;
|
||||||
|
|
||||||
checkbox->setEnabled(!running);
|
m_checkbox->setEnabled(!running);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Skylander Creator"));
|
setWindowTitle(tr("Skylander Creator"));
|
||||||
setObjectName(QString::fromStdString("skylanders_creator"));
|
setObjectName(tr("skylanders_creator"));
|
||||||
setMinimumSize(QSize(500, 150));
|
setMinimumSize(QSize(500, 150));
|
||||||
QVBoxLayout* layout = new QVBoxLayout;
|
auto* layout = new QVBoxLayout;
|
||||||
|
|
||||||
QComboBox* combo_skylist = new QComboBox();
|
auto* combo_skylist = new QComboBox();
|
||||||
QStringList filterlist;
|
QStringList filterlist;
|
||||||
for (const auto& entry : list_skylanders)
|
for (const auto& entry : list_skylanders)
|
||||||
{
|
{
|
||||||
const uint qvar = (entry.first.first << 16) | entry.first.second;
|
const uint qvar = (entry.first.first << 16) | entry.first.second;
|
||||||
combo_skylist->addItem(QString::fromStdString(entry.second), QVariant(qvar));
|
combo_skylist->addItem(tr(entry.second), QVariant(qvar));
|
||||||
filterlist << QString::fromStdString(entry.second.c_str());
|
filterlist << tr(entry.second);
|
||||||
}
|
}
|
||||||
combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF));
|
combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF));
|
||||||
combo_skylist->setEditable(true);
|
combo_skylist->setEditable(true);
|
||||||
combo_skylist->setInsertPolicy(QComboBox::NoInsert);
|
combo_skylist->setInsertPolicy(QComboBox::NoInsert);
|
||||||
|
|
||||||
QCompleter* co_compl = new QCompleter(filterlist, this);
|
auto* co_compl = new QCompleter(filterlist, this);
|
||||||
co_compl->setCaseSensitivity(Qt::CaseInsensitive);
|
co_compl->setCaseSensitivity(Qt::CaseInsensitive);
|
||||||
co_compl->setCompletionMode(QCompleter::PopupCompletion);
|
co_compl->setCompletionMode(QCompleter::PopupCompletion);
|
||||||
co_compl->setFilterMode(Qt::MatchContains);
|
co_compl->setFilterMode(Qt::MatchContains);
|
||||||
@ -669,18 +635,17 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
|||||||
|
|
||||||
layout->addWidget(combo_skylist);
|
layout->addWidget(combo_skylist);
|
||||||
|
|
||||||
QFrame* line = new QFrame();
|
auto* line = new QFrame();
|
||||||
line->setFrameShape(QFrame::HLine);
|
line->setFrameShape(QFrame::HLine);
|
||||||
line->setFrameShadow(QFrame::Sunken);
|
line->setFrameShadow(QFrame::Sunken);
|
||||||
layout->addWidget(line);
|
layout->addWidget(line);
|
||||||
|
|
||||||
QHBoxLayout* hbox_idvar = new QHBoxLayout();
|
auto* hbox_idvar = new QHBoxLayout();
|
||||||
QLabel* label_id = new QLabel(tr("ID:"));
|
auto* label_id = new QLabel(tr("ID:"));
|
||||||
QLabel* label_var = new QLabel(tr("Variant:"));
|
auto* label_var = new QLabel(tr("Variant:"));
|
||||||
QLineEdit* edit_id = new QLineEdit(QString::fromStdString("0"));
|
auto* edit_id = new QLineEdit(tr("0"));
|
||||||
QLineEdit* edit_var = new QLineEdit(QString::fromStdString("0"));
|
auto* edit_var = new QLineEdit(tr("0"));
|
||||||
QRegularExpressionValidator* rxv =
|
auto* rxv = new QRegularExpressionValidator(QRegularExpression(tr("\\d*")), this);
|
||||||
new QRegularExpressionValidator(QRegularExpression(QString::fromStdString("\\d*")), this);
|
|
||||||
edit_id->setValidator(rxv);
|
edit_id->setValidator(rxv);
|
||||||
edit_var->setValidator(rxv);
|
edit_var->setValidator(rxv);
|
||||||
hbox_idvar->addWidget(label_id);
|
hbox_idvar->addWidget(label_id);
|
||||||
@ -689,13 +654,9 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
|||||||
hbox_idvar->addWidget(edit_var);
|
hbox_idvar->addWidget(edit_var);
|
||||||
layout->addLayout(hbox_idvar);
|
layout->addLayout(hbox_idvar);
|
||||||
|
|
||||||
QHBoxLayout* hbox_buttons = new QHBoxLayout();
|
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
QPushButton* btn_create = new QPushButton(tr("Create"), this);
|
buttons->button(QDialogButtonBox::Ok)->setText(tr("Create"));
|
||||||
QPushButton* btn_cancel = new QPushButton(tr("Cancel"), this);
|
layout->addWidget(buttons);
|
||||||
hbox_buttons->addStretch();
|
|
||||||
hbox_buttons->addWidget(btn_create);
|
|
||||||
hbox_buttons->addWidget(btn_cancel);
|
|
||||||
layout->addLayout(hbox_buttons);
|
|
||||||
|
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
@ -711,7 +672,7 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(btn_create, &QAbstractButton::clicked, this, [=, this]() {
|
connect(buttons, &QDialogButtonBox::accepted, this, [=, this]() {
|
||||||
bool ok_id = false, ok_var = false;
|
bool ok_id = false, ok_var = false;
|
||||||
const u16 sky_id = edit_id->text().toUShort(&ok_id);
|
const u16 sky_id = edit_id->text().toUShort(&ok_id);
|
||||||
if (!ok_id)
|
if (!ok_id)
|
||||||
@ -728,61 +689,40 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString predef_name = last_skylander_path;
|
QString predef_name = s_last_skylander_path;
|
||||||
const auto found_sky = list_skylanders.find(std::make_pair(sky_id, sky_var));
|
const auto found_sky = list_skylanders.find(std::make_pair(sky_id, sky_var));
|
||||||
if (found_sky != list_skylanders.end())
|
if (found_sky != list_skylanders.end())
|
||||||
{
|
{
|
||||||
predef_name += QString::fromStdString(found_sky->second + ".sky");
|
std::string name = std::string(found_sky->second) + ".sky";
|
||||||
|
predef_name += tr(name.c_str());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QString str = QString::fromStdString("Unknown(%1 %2).sky");
|
QString str = tr("Unknown(%1 %2).sky");
|
||||||
predef_name += str.arg(sky_id, sky_var);
|
predef_name += str.arg(sky_id, sky_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name,
|
m_file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name,
|
||||||
tr("Skylander Object (*.sky);;"));
|
tr("Skylander Object (*.sky);;"));
|
||||||
if (file_path.isEmpty())
|
if (m_file_path.isEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File::IOFile sky_file(file_path.toStdString(), "w+b");
|
auto& system = Core::System::GetInstance();
|
||||||
if (!sky_file)
|
|
||||||
|
if (!system.GetSkylanderPortal().CreateSkylander(m_file_path.toStdString(), sky_id, sky_var))
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, tr("Failed to create skylander file!"),
|
QMessageBox::warning(this, tr("Failed to create skylander file!"),
|
||||||
tr("Failed to create skylander file:\n%1").arg(file_path),
|
tr("Failed to create skylander file:\n%1").arg(m_file_path),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
s_last_skylander_path = QFileInfo(m_file_path).absolutePath() + tr("/");
|
||||||
std::array<u8, 0x40 * 0x10> buf{};
|
|
||||||
const auto file_data = buf.data();
|
|
||||||
// Set the block permissions
|
|
||||||
u32 first_block = 0x690F0F0F;
|
|
||||||
u32 other_blocks = 0x69080F7F;
|
|
||||||
memcpy(&file_data[0x36], &first_block, sizeof(first_block));
|
|
||||||
for (u32 index = 1; index < 0x10; index++)
|
|
||||||
{
|
|
||||||
memcpy(&file_data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks));
|
|
||||||
}
|
|
||||||
// Set the skylander info
|
|
||||||
u16 sky_info = (sky_id | sky_var) + 1;
|
|
||||||
memcpy(&file_data[0], &sky_info, sizeof(sky_info));
|
|
||||||
memcpy(&file_data[0x10], &sky_id, sizeof(sky_id));
|
|
||||||
memcpy(&file_data[0x1C], &sky_var, sizeof(sky_var));
|
|
||||||
// Set checksum
|
|
||||||
u16 checksum = skylander_crc16(0xFFFF, file_data, 0x1E);
|
|
||||||
memcpy(&file_data[0x1E], &checksum, sizeof(checksum));
|
|
||||||
|
|
||||||
sky_file.WriteBytes(buf.data(), buf.size());
|
|
||||||
sky_file.Close();
|
|
||||||
|
|
||||||
last_skylander_path = QFileInfo(file_path).absolutePath() + QString::fromStdString("/");
|
|
||||||
accept();
|
accept();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated),
|
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated),
|
||||||
[=](const QString& text) {
|
[=](const QString& text) {
|
||||||
@ -793,13 +733,13 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
|||||||
|
|
||||||
QString CreateSkylanderDialog::GetFilePath() const
|
QString CreateSkylanderDialog::GetFilePath() const
|
||||||
{
|
{
|
||||||
return file_path;
|
return m_file_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortalWindow::EmulatePortal(bool emulate)
|
void SkylanderPortalWindow::EmulatePortal(bool emulate)
|
||||||
{
|
{
|
||||||
Config::SetBaseOrCurrent(Config::MAIN_EMULATE_SKYLANDER_PORTAL, emulate);
|
Config::SetBaseOrCurrent(Config::MAIN_EMULATE_SKYLANDER_PORTAL, emulate);
|
||||||
group_skylanders->setVisible(emulate);
|
m_group_skylanders->setVisible(emulate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortalWindow::CreateSkylander(u8 slot)
|
void SkylanderPortalWindow::CreateSkylander(u8 slot)
|
||||||
@ -814,12 +754,12 @@ void SkylanderPortalWindow::CreateSkylander(u8 slot)
|
|||||||
void SkylanderPortalWindow::LoadSkylander(u8 slot)
|
void SkylanderPortalWindow::LoadSkylander(u8 slot)
|
||||||
{
|
{
|
||||||
const QString file_path = DolphinFileDialog::getOpenFileName(
|
const QString file_path = DolphinFileDialog::getOpenFileName(
|
||||||
this, tr("Select Skylander File"), last_skylander_path, tr("Skylander (*.sky);;"));
|
this, tr("Select Skylander File"), s_last_skylander_path, tr("Skylander (*.sky);;"));
|
||||||
if (file_path.isEmpty())
|
if (file_path.isEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
last_skylander_path = QFileInfo(file_path).absolutePath() + QString::fromStdString("/");
|
s_last_skylander_path = QFileInfo(file_path).absolutePath() + tr("/");
|
||||||
|
|
||||||
LoadSkylanderPath(slot, file_path);
|
LoadSkylanderPath(slot, file_path);
|
||||||
}
|
}
|
||||||
@ -859,19 +799,26 @@ void SkylanderPortalWindow::LoadSkylanderPath(u8 slot, const QString& path)
|
|||||||
DEBUG_LOG_FMT(IOS_USB, "Sky Var: {}, 0x1D: {} 0x1C: {}", sky_var, file_data[0x1D],
|
DEBUG_LOG_FMT(IOS_USB, "Sky Var: {}, 0x1D: {} 0x1C: {}", sky_var, file_data[0x1D],
|
||||||
file_data[0x1C]);
|
file_data[0x1C]);
|
||||||
|
|
||||||
u8 portal_slot = IOS::HLE::USB::g_skyportal.LoadSkylander(file_data.data(), std::move(sky_file));
|
auto& system = Core::System::GetInstance();
|
||||||
sky_slots[slot] = std::tuple(portal_slot, sky_id, sky_var);
|
|
||||||
|
|
||||||
|
u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(file_data.data(), std::move(sky_file));
|
||||||
|
if (portal_slot == 0xFF)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Failed to load the skylander file!"),
|
||||||
|
tr("Failed to load the skylander file(%1)!\n").arg(path), QMessageBox::Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_sky_slots[slot] = {portal_slot, sky_id, sky_var};
|
||||||
UpdateEdits();
|
UpdateEdits();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortalWindow::ClearSkylander(u8 slot)
|
void SkylanderPortalWindow::ClearSkylander(u8 slot)
|
||||||
{
|
{
|
||||||
if (auto slot_infos = sky_slots[slot])
|
auto& system = Core::System::GetInstance();
|
||||||
|
if (auto slot_infos = m_sky_slots[slot])
|
||||||
{
|
{
|
||||||
auto [cur_slot, id, var] = slot_infos.value();
|
system.GetSkylanderPortal().RemoveSkylander(slot_infos->portal_slot);
|
||||||
IOS::HLE::USB::g_skyportal.RemoveSkylander(cur_slot);
|
m_sky_slots[slot].reset();
|
||||||
sky_slots[slot] = {};
|
|
||||||
UpdateEdits();
|
UpdateEdits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -881,26 +828,24 @@ void SkylanderPortalWindow::UpdateEdits()
|
|||||||
for (auto i = 0; i < MAX_SKYLANDERS; i++)
|
for (auto i = 0; i < MAX_SKYLANDERS; i++)
|
||||||
{
|
{
|
||||||
QString display_string;
|
QString display_string;
|
||||||
if (auto sd = sky_slots[i])
|
if (auto sd = m_sky_slots[i])
|
||||||
{
|
{
|
||||||
auto [portal_slot, sky_id, sky_var] = sd.value();
|
auto found_sky = list_skylanders.find(std::make_pair(sd->sky_id, sd->sky_var));
|
||||||
auto found_sky = list_skylanders.find(std::make_pair(sky_id, sky_var));
|
|
||||||
if (found_sky != list_skylanders.end())
|
if (found_sky != list_skylanders.end())
|
||||||
{
|
{
|
||||||
display_string = QString::fromStdString(found_sky->second);
|
display_string = tr(found_sky->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
display_string =
|
display_string = QString(tr("Unknown (Id:%1 Var:%2)")).arg(sd->sky_id).arg(sd->sky_var);
|
||||||
QString(QString::fromStdString("Unknown (Id:%1 Var:%2)")).arg(sky_id).arg(sky_var);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
display_string = QString::fromStdString("None");
|
display_string = tr("None");
|
||||||
}
|
}
|
||||||
|
|
||||||
edit_skylanders[i]->setText(display_string);
|
m_edit_skylanders[i]->setText(display_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,32 +1,39 @@
|
|||||||
|
// Copyright 2022 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <QCheckBox>
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QGroupBox>
|
#include <QString>
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/IOS/USB/Emulated/Skylander.h"
|
#include "Core/IOS/USB/Emulated/Skylander.h"
|
||||||
|
|
||||||
class QDialogButtonBox;
|
class QCheckBox;
|
||||||
class QLabel;
|
class QGroupBox;
|
||||||
class QPushButton;
|
class QLineEdit;
|
||||||
class QSpinBox;
|
|
||||||
class QTabWidget;
|
struct Skylander
|
||||||
|
{
|
||||||
|
u8 portal_slot;
|
||||||
|
u16 sky_id;
|
||||||
|
u16 sky_var;
|
||||||
|
};
|
||||||
|
|
||||||
class SkylanderPortalWindow : public QWidget
|
class SkylanderPortalWindow : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SkylanderPortalWindow(QWidget* parent = nullptr);
|
explicit SkylanderPortalWindow(QWidget* parent = nullptr);
|
||||||
~SkylanderPortalWindow();
|
~SkylanderPortalWindow() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QLineEdit* edit_skylanders[MAX_SKYLANDERS]{};
|
std::array<QLineEdit*, MAX_SKYLANDERS> m_edit_skylanders;
|
||||||
static std::optional<std::tuple<u8, u16, u16>> sky_slots[MAX_SKYLANDERS];
|
std::array<std::optional<Skylander>, MAX_SKYLANDERS> m_sky_slots;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateMainWindow();
|
void CreateMainWindow();
|
||||||
@ -38,13 +45,10 @@ private:
|
|||||||
void LoadSkylanderPath(u8 slot, const QString& path);
|
void LoadSkylanderPath(u8 slot, const QString& path);
|
||||||
void UpdateEdits();
|
void UpdateEdits();
|
||||||
void closeEvent(QCloseEvent* bar) override;
|
void closeEvent(QCloseEvent* bar) override;
|
||||||
|
|
||||||
static SkylanderPortalWindow* inst;
|
|
||||||
|
|
||||||
QCheckBox* checkbox;
|
|
||||||
QGroupBox* group_skylanders;
|
|
||||||
|
|
||||||
bool eventFilter(QObject* object, QEvent* event) final override;
|
bool eventFilter(QObject* object, QEvent* event) final override;
|
||||||
|
|
||||||
|
QCheckBox* m_checkbox;
|
||||||
|
QGroupBox* m_group_skylanders;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreateSkylanderDialog : public QDialog
|
class CreateSkylanderDialog : public QDialog
|
||||||
@ -56,5 +60,5 @@ public:
|
|||||||
QString GetFilePath() const;
|
QString GetFilePath() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString file_path;
|
QString m_file_path;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user