Valid block/num checks, rename variables, remove translatable strings
Capitalize Skylander in tr strings Lint and validation method fixes Proper Attach and Change Interface method Re-jig code to exit early and read easier
This commit is contained in:
parent
18fd0d7dcd
commit
c76d2c16eb
|
@ -66,6 +66,12 @@ bool SkylanderUSB::Attach()
|
||||||
|
|
||||||
bool SkylanderUSB::AttachAndChangeInterface(const u8 interface)
|
bool SkylanderUSB::AttachAndChangeInterface(const u8 interface)
|
||||||
{
|
{
|
||||||
|
if (!Attach())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (interface != m_active_interface)
|
||||||
|
return ChangeInterface(interface) == 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,286 +115,25 @@ 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);
|
||||||
|
|
||||||
auto& system = Core::System::GetInstance();
|
// If not HID Host to Device type, return invalid
|
||||||
auto& memory = system.GetMemory();
|
if (cmd->request_type != 0x21)
|
||||||
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
|
||||||
if ((cmd->length == 0 || buf == nullptr) && cmd->request == 0x09)
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(IOS_USB, "Skylander command invalid");
|
|
||||||
return IPC_EINVAL;
|
return IPC_EINVAL;
|
||||||
}
|
|
||||||
std::array<u8, 64> result = {};
|
// Data to be sent back via the control transfer immediately
|
||||||
std::array<u8, 64> data = {};
|
std::array<u8, 64> control_response = {};
|
||||||
s32 expected_count = 0;
|
s32 expected_count = 0;
|
||||||
u64 expected_time_us = 100;
|
u64 expected_time_us = 100;
|
||||||
// Control transfers are instantaneous
|
|
||||||
u8 request_type = cmd->request_type;
|
// Non 0x09 Requests are handled here - no portal data is requested
|
||||||
if (request_type == 0x21)
|
if (cmd->request != 0x09)
|
||||||
{
|
{
|
||||||
// HID host to device type
|
|
||||||
switch (cmd->request)
|
switch (cmd->request)
|
||||||
{
|
{
|
||||||
case 0x09:
|
// Get Interface
|
||||||
switch (buf[0])
|
|
||||||
{
|
|
||||||
case 'A':
|
|
||||||
{
|
|
||||||
// Activation
|
|
||||||
// Command { 'A', (00 | 01), 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 }
|
|
||||||
// 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,
|
|
||||||
// 00, 00, 00, 00, 00, 00, 00, 00 }
|
|
||||||
// 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
|
|
||||||
// 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
|
|
||||||
// 3rd byte appears to count down from ff (possibly a battery power indication) and during
|
|
||||||
// deactivation ed and eb responses have been observed. The 4th byte appears to always be 00
|
|
||||||
// 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
|
|
||||||
if (cmd->length == 2)
|
|
||||||
{
|
|
||||||
data = {buf[0], buf[1]};
|
|
||||||
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};
|
|
||||||
m_queries.push(result);
|
|
||||||
expected_count = 10;
|
|
||||||
system.GetSkylanderPortal().Activate();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'C':
|
|
||||||
{
|
|
||||||
// Color
|
|
||||||
// Command { 'C', 12, 34, 56, 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 }
|
|
||||||
// Response { 'C', 12, 34, 56, 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 3 bytes {12, 34, 56} are RGB values.
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// is silently ignored and do not require a response.
|
|
||||||
if (cmd->length == 4)
|
|
||||||
{
|
|
||||||
system.GetSkylanderPortal().SetLEDs(0x01, buf[1], buf[2], buf[3]);
|
|
||||||
data = {0x43, buf[1], buf[2], buf[3]};
|
|
||||||
expected_count = 12;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'J':
|
|
||||||
{
|
|
||||||
// Sided color
|
|
||||||
// The 2nd byte is the side
|
|
||||||
// 0x00: right
|
|
||||||
// 0x01: left and right
|
|
||||||
// 0x02: left
|
|
||||||
|
|
||||||
// The 3rd, 4th and 5th bytes are red, green and blue
|
|
||||||
|
|
||||||
// The 6th byte is unknown. Observed values are 0x00, 0x0D and 0xF4
|
|
||||||
|
|
||||||
// The 7th byte is the fade duration. Exact value-time corrolation unknown. Observed values
|
|
||||||
// are 0x00, 0x01 and 0x07. Custom commands show that the higher this value the longer the
|
|
||||||
// duration.
|
|
||||||
|
|
||||||
// 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
|
|
||||||
if (cmd->length == 7)
|
|
||||||
{
|
|
||||||
data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]};
|
|
||||||
expected_count = 15;
|
|
||||||
result = {buf[0]};
|
|
||||||
m_queries.push(result);
|
|
||||||
system.GetSkylanderPortal().SetLEDs(buf[1], buf[2], buf[3], buf[4]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'L':
|
|
||||||
{
|
|
||||||
// Light
|
|
||||||
// This command is used while playing audio through the portal
|
|
||||||
|
|
||||||
// The 2nd bytes is the position
|
|
||||||
// 0x00: right
|
|
||||||
// 0x01: trap led
|
|
||||||
// 0x02: left
|
|
||||||
|
|
||||||
// The 3rd, 4th and 5th bytes are red, green and blue
|
|
||||||
// the trap led is white-only
|
|
||||||
// increasing or decreasing the values results in a brighter or dimmer light
|
|
||||||
if (cmd->length == 5)
|
|
||||||
{
|
|
||||||
data = {buf[0], buf[1], buf[2], buf[3], buf[4]};
|
|
||||||
expected_count = 13;
|
|
||||||
|
|
||||||
u8 side = buf[1];
|
|
||||||
if (side == 0x02)
|
|
||||||
{
|
|
||||||
side = 0x04;
|
|
||||||
}
|
|
||||||
system.GetSkylanderPortal().SetLEDs(side, buf[2], buf[3], buf[4]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'M':
|
|
||||||
{
|
|
||||||
// Audio Firmware version
|
|
||||||
// Respond with version obtained from Trap Team wired portal
|
|
||||||
if (cmd->length == 2)
|
|
||||||
{
|
|
||||||
data = {buf[0], buf[1]};
|
|
||||||
expected_count = 10;
|
|
||||||
result = {buf[0], buf[1], 0x00, 0x19};
|
|
||||||
m_queries.push(result);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Query
|
|
||||||
// Command { '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 }
|
|
||||||
// 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 }
|
|
||||||
// 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 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
|
|
||||||
// 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-19.
|
|
||||||
|
|
||||||
// A Skylander has 64 blocks of data indexed from 0x00 to 0x3f. SwapForce characters have 2
|
|
||||||
// character indexes, these may not be sequential.
|
|
||||||
case 'Q':
|
|
||||||
{
|
|
||||||
// Queries a block
|
|
||||||
if (cmd->length == 3)
|
|
||||||
{
|
|
||||||
const u8 sky_num = buf[1] & 0xF;
|
|
||||||
const u8 block = buf[2];
|
|
||||||
system.GetSkylanderPortal().QueryBlock(sky_num, block, result.data());
|
|
||||||
m_queries.push(result);
|
|
||||||
data = {buf[0], buf[1], buf[2]};
|
|
||||||
expected_count = 11;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'R':
|
|
||||||
{
|
|
||||||
// Ready
|
|
||||||
// Command { 'R', 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 }
|
|
||||||
// Response { 'R', 02, 0a, 03, 02, 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 4 byte sequence after the R (0x52) is unknown, but appears consistent based on device
|
|
||||||
// type.
|
|
||||||
if (cmd->length == 2)
|
|
||||||
{
|
|
||||||
data = {0x52, 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};
|
|
||||||
m_queries.push(result);
|
|
||||||
expected_count = 10;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Status
|
|
||||||
// Command { 'S', 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 }
|
|
||||||
// Response { 'S', 55, 00, 00, 55, 3e,
|
|
||||||
// (00|01), 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 }
|
|
||||||
// Status is the default command. If you open the HID device and
|
|
||||||
// activate the portal, you will get status outputs.
|
|
||||||
|
|
||||||
// The 4 bytes {55, 00, 00, 55} are the status of characters on the portal. The 4 bytes are
|
|
||||||
// treated as a 32-bit binary array. Each unique Skylander placed on a board is represented
|
|
||||||
// by 2 bits starting with the first Skylander in the least significant bit. This bit is
|
|
||||||
// present whenever the Skylandar is added or present on the portal. When the Skylander is
|
|
||||||
// added to the board, both bits are set in the next status message as a one-time signal.
|
|
||||||
// When a Skylander is removed from the board, only the most significant bit of the 2 bits
|
|
||||||
// is set.
|
|
||||||
|
|
||||||
// Different portals can track a different number of RFID tags. The Wii Wireless portal
|
|
||||||
// tracks 4, the Wired portal can track 8. The maximum number of unique Skylanders tracked
|
|
||||||
// at any time is 16, after which new Skylanders appear to cycle unused bits.
|
|
||||||
|
|
||||||
// Certain Skylanders, e.g. SwapForce Skylanders, are represented as 2 ID in the bit array.
|
|
||||||
// This may be due to the presence of 2 RFIDs, one for each half of the Skylander.
|
|
||||||
|
|
||||||
// The 6th byte {3e} is a counter and increments by one. It will roll over when reaching
|
|
||||||
// {ff}.
|
|
||||||
|
|
||||||
// The purpose of the (00\|01) byte at the 7th position appear to indicate if the portal has
|
|
||||||
// been activated: {01} when active and {00} when deactivated.
|
|
||||||
case 'S':
|
|
||||||
{
|
|
||||||
if (cmd->length == 1)
|
|
||||||
{
|
|
||||||
data = {buf[0]};
|
|
||||||
expected_count = 9;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'V':
|
|
||||||
{
|
|
||||||
if (cmd->length == 4)
|
|
||||||
{
|
|
||||||
data = {buf[0], buf[1], buf[2], buf[3]};
|
|
||||||
expected_count = 12;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Write
|
|
||||||
// Command { 'W', 10, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 00,
|
|
||||||
// 00, 00, 00, 00, 00, 00, 00, 00, 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 }
|
|
||||||
// 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
|
|
||||||
// indexed would be 0x20.
|
|
||||||
|
|
||||||
// 3rd byte is the block to write to.
|
|
||||||
|
|
||||||
// Bytes 4 - 19 ({ 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f }) are the
|
|
||||||
// data to write.
|
|
||||||
|
|
||||||
// The response does not appear to return the id of the Skylander being written, the 2nd
|
|
||||||
// byte is 0x00; however, the 3rd byte echos the block that was written (0x00 in example
|
|
||||||
// above.)
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
{
|
|
||||||
if (cmd->length == 19)
|
|
||||||
{
|
|
||||||
const u8 sky_num = buf[1] & 0xF;
|
|
||||||
const u8 block = buf[2];
|
|
||||||
system.GetSkylanderPortal().WriteBlock(sky_num, block, &buf[3], result.data());
|
|
||||||
m_queries.push(result);
|
|
||||||
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[14], buf[15], buf[16], buf[17], buf[18]};
|
|
||||||
expected_count = 27;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
ERROR_LOG_FMT(IOS_USB, "Unhandled Skylander Portal Query: {}", buf[0]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0A:
|
case 0x0A:
|
||||||
expected_count = 8;
|
expected_count = 8;
|
||||||
break;
|
break;
|
||||||
|
// Set Interface
|
||||||
case 0x0B:
|
case 0x0B:
|
||||||
expected_count = 8;
|
expected_count = 8;
|
||||||
break;
|
break;
|
||||||
|
@ -397,9 +142,282 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Skylander Portal Requests
|
||||||
|
auto& system = Core::System::GetInstance();
|
||||||
|
auto& memory = system.GetMemory();
|
||||||
|
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||||
|
if (cmd->length == 0 || buf == nullptr)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_USB, "Skylander command invalid");
|
||||||
|
return IPC_EINVAL;
|
||||||
|
}
|
||||||
|
// Data to be queued to be sent back via the Interrupt Transfer (if needed)
|
||||||
|
std::array<u8, 64> interrupt_response = {};
|
||||||
|
|
||||||
|
// The first byte of the Control Request is always a char for Skylanders
|
||||||
|
switch (buf[0])
|
||||||
|
{
|
||||||
|
case 'A':
|
||||||
|
{
|
||||||
|
// Activation
|
||||||
|
// Command { 'A', (00 | 01), 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 }
|
||||||
|
// 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,
|
||||||
|
// 00, 00, 00, 00, 00, 00, 00, 00 }
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
// 3rd byte appears to count down from ff (possibly a battery power indication) and during
|
||||||
|
// deactivation ed and eb responses have been observed. The 4th byte appears to always be 00
|
||||||
|
// 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
|
||||||
|
if (cmd->length == 2)
|
||||||
|
{
|
||||||
|
control_response = {buf[0], buf[1]};
|
||||||
|
interrupt_response = {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};
|
||||||
|
m_queries.push(interrupt_response);
|
||||||
|
expected_count = 10;
|
||||||
|
system.GetSkylanderPortal().Activate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'C':
|
||||||
|
{
|
||||||
|
// Color
|
||||||
|
// Command { 'C', 12, 34, 56, 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 }
|
||||||
|
// Response { 'C', 12, 34, 56, 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 3 bytes {12, 34, 56} are RGB values.
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// is silently ignored and do not require a response.
|
||||||
|
if (cmd->length == 4)
|
||||||
|
{
|
||||||
|
system.GetSkylanderPortal().SetLEDs(0x01, buf[1], buf[2], buf[3]);
|
||||||
|
control_response = {0x43, buf[1], buf[2], buf[3]};
|
||||||
|
expected_count = 12;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'J':
|
||||||
|
{
|
||||||
|
// Sided color
|
||||||
|
// The 2nd byte is the side
|
||||||
|
// 0x00: right
|
||||||
|
// 0x01: left and right
|
||||||
|
// 0x02: left
|
||||||
|
|
||||||
|
// The 3rd, 4th and 5th bytes are red, green and blue
|
||||||
|
|
||||||
|
// The 6th byte is unknown. Observed values are 0x00, 0x0D and 0xF4
|
||||||
|
|
||||||
|
// The 7th byte is the fade duration. Exact value-time corrolation unknown. Observed values
|
||||||
|
// are 0x00, 0x01 and 0x07. Custom commands show that the higher this value the longer the
|
||||||
|
// duration.
|
||||||
|
|
||||||
|
// Empty J response is sent after the fade is completed.
|
||||||
|
if (cmd->length == 7)
|
||||||
|
{
|
||||||
|
control_response = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]};
|
||||||
|
expected_count = 15;
|
||||||
|
interrupt_response = {buf[0]};
|
||||||
|
m_queries.push(interrupt_response);
|
||||||
|
system.GetSkylanderPortal().SetLEDs(buf[1], buf[2], buf[3], buf[4]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'L':
|
||||||
|
{
|
||||||
|
// Light
|
||||||
|
// This command is used while playing audio through the portal
|
||||||
|
|
||||||
|
// The 2nd bytes is the position
|
||||||
|
// 0x00: right
|
||||||
|
// 0x01: trap led
|
||||||
|
// 0x02: left
|
||||||
|
|
||||||
|
// The 3rd, 4th and 5th bytes are red, green and blue
|
||||||
|
// the trap led is white-only
|
||||||
|
// increasing or decreasing the values results in a brighter or dimmer light
|
||||||
|
if (cmd->length == 5)
|
||||||
|
{
|
||||||
|
control_response = {buf[0], buf[1], buf[2], buf[3], buf[4]};
|
||||||
|
expected_count = 13;
|
||||||
|
|
||||||
|
u8 side = buf[1];
|
||||||
|
if (side == 0x02)
|
||||||
|
{
|
||||||
|
side = 0x04;
|
||||||
|
}
|
||||||
|
system.GetSkylanderPortal().SetLEDs(side, buf[2], buf[3], buf[4]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'M':
|
||||||
|
{
|
||||||
|
// Audio Firmware version
|
||||||
|
// Respond with version obtained from Trap Team wired portal
|
||||||
|
if (cmd->length == 2)
|
||||||
|
{
|
||||||
|
control_response = {buf[0], buf[1]};
|
||||||
|
expected_count = 10;
|
||||||
|
interrupt_response = {buf[0], buf[1], 0x00, 0x19};
|
||||||
|
m_queries.push(interrupt_response);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Query
|
||||||
|
// Command { '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 }
|
||||||
|
// 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 }
|
||||||
|
// 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 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
|
||||||
|
// 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-19.
|
||||||
|
|
||||||
|
// A Skylander has 64 blocks of data indexed from 0x00 to 0x3f. SwapForce characters have 2
|
||||||
|
// character indexes, these may not be sequential.
|
||||||
|
case 'Q':
|
||||||
|
{
|
||||||
|
if (cmd->length == 3)
|
||||||
|
{
|
||||||
|
const u8 sky_num = buf[1] & 0xF;
|
||||||
|
const u8 block = buf[2];
|
||||||
|
system.GetSkylanderPortal().QueryBlock(sky_num, block, interrupt_response.data());
|
||||||
|
m_queries.push(interrupt_response);
|
||||||
|
control_response = {buf[0], buf[1], buf[2]};
|
||||||
|
expected_count = 11;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'R':
|
||||||
|
{
|
||||||
|
// Ready
|
||||||
|
// Command { 'R', 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 }
|
||||||
|
// Response { 'R', 02, 0a, 03, 02, 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 4 byte sequence after the R (0x52) is unknown, but appears consistent based on device
|
||||||
|
// type.
|
||||||
|
if (cmd->length == 2)
|
||||||
|
{
|
||||||
|
control_response = {0x52, 0x00};
|
||||||
|
interrupt_response = {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};
|
||||||
|
m_queries.push(interrupt_response);
|
||||||
|
expected_count = 10;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Status
|
||||||
|
// Command { 'S', 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 }
|
||||||
|
// Response { 'S', 55, 00, 00, 55, 3e,
|
||||||
|
// (00|01), 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 }
|
||||||
|
// Status is the default command. If you open the HID device and
|
||||||
|
// activate the portal, you will get status outputs.
|
||||||
|
|
||||||
|
// The 4 bytes {55, 00, 00, 55} are the status of characters on the portal. The 4 bytes are
|
||||||
|
// treated as a 32-bit binary array. Each unique Skylander placed on a board is represented
|
||||||
|
// by 2 bits starting with the first Skylander in the least significant bit. This bit is
|
||||||
|
// present whenever the Skylandar is added or present on the portal. When the Skylander is
|
||||||
|
// added to the board, both bits are set in the next status message as a one-time signal.
|
||||||
|
// When a Skylander is removed from the board, only the most significant bit of the 2 bits
|
||||||
|
// is set.
|
||||||
|
|
||||||
|
// Different portals can track a different number of RFID tags. The Wii Wireless portal
|
||||||
|
// tracks 4, the Wired portal can track 8. The maximum number of unique Skylanders tracked
|
||||||
|
// at any time is 16, after which new Skylanders appear to cycle unused bits.
|
||||||
|
|
||||||
|
// Certain Skylanders, e.g. SwapForce Skylanders, are represented as 2 ID in the bit array.
|
||||||
|
// This may be due to the presence of 2 RFIDs, one for each half of the Skylander.
|
||||||
|
|
||||||
|
// The 6th byte {3e} is a counter and increments by one. It will roll over when reaching
|
||||||
|
// {ff}.
|
||||||
|
|
||||||
|
// The purpose of the (00\|01) byte at the 7th position appear to indicate if the portal has
|
||||||
|
// been activated: {01} when active and {00} when deactivated.
|
||||||
|
case 'S':
|
||||||
|
{
|
||||||
|
if (cmd->length == 1)
|
||||||
|
{
|
||||||
|
// The Status interrupt responses are automatically handled via the GetStatus method
|
||||||
|
control_response = {buf[0]};
|
||||||
|
expected_count = 9;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'V':
|
||||||
|
{
|
||||||
|
if (cmd->length == 4)
|
||||||
|
{
|
||||||
|
control_response = {buf[0], buf[1], buf[2], buf[3]};
|
||||||
|
expected_count = 12;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Write
|
||||||
|
// Command { 'W', 10, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 00,
|
||||||
|
// 00, 00, 00, 00, 00, 00, 00, 00, 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 }
|
||||||
|
// 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
|
||||||
|
// indexed would be 0x20.
|
||||||
|
|
||||||
|
// 3rd byte is the block to write to.
|
||||||
|
|
||||||
|
// Bytes 4 - 19 ({ 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f }) are the
|
||||||
|
// data to write.
|
||||||
|
|
||||||
|
// The response does not appear to return the id of the Skylander being written, the 2nd
|
||||||
|
// byte is 0x00; however, the 3rd byte echos the block that was written (0x00 in example
|
||||||
|
// above.)
|
||||||
|
|
||||||
|
case 'W':
|
||||||
|
{
|
||||||
|
if (cmd->length == 19)
|
||||||
|
{
|
||||||
|
const u8 sky_num = buf[1] & 0xF;
|
||||||
|
const u8 block = buf[2];
|
||||||
|
system.GetSkylanderPortal().WriteBlock(sky_num, block, &buf[3], interrupt_response.data());
|
||||||
|
m_queries.push(interrupt_response);
|
||||||
|
control_response = {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[14], buf[15], buf[16], buf[17], buf[18]};
|
||||||
|
expected_count = 27;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ERROR_LOG_FMT(IOS_USB, "Unhandled Skylander Portal Query: {}", buf[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (expected_count == 0)
|
if (expected_count == 0)
|
||||||
return IPC_EINVAL;
|
return IPC_EINVAL;
|
||||||
ScheduleTransfer(std::move(cmd), data, expected_count, expected_time_us);
|
|
||||||
|
ScheduleTransfer(std::move(cmd), control_response, expected_count, expected_time_us);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,26 +445,26 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||||
ERROR_LOG_FMT(IOS_USB, "Skylander command invalid");
|
ERROR_LOG_FMT(IOS_USB, "Skylander command invalid");
|
||||||
return IPC_EINVAL;
|
return IPC_EINVAL;
|
||||||
}
|
}
|
||||||
std::array<u8, 64> result = {};
|
std::array<u8, 64> interrupt_response = {};
|
||||||
s32 expected_count;
|
s32 expected_count;
|
||||||
u64 expected_time_us;
|
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 && cmd->length <= 64)
|
if (cmd->length > 32 && cmd->length <= 64)
|
||||||
{
|
{
|
||||||
std::array<u8, 64> audio_result = {};
|
std::array<u8, 64> audio_interrupt_response = {};
|
||||||
u8* audio_buf = audio_result.data();
|
u8* audio_buf = audio_interrupt_response.data();
|
||||||
memcpy(audio_buf, buf, cmd->length);
|
memcpy(audio_buf, buf, cmd->length);
|
||||||
expected_time_us = 1000;
|
expected_time_us = 1000;
|
||||||
expected_count = cmd->length;
|
expected_count = cmd->length;
|
||||||
ScheduleTransfer(std::move(cmd), audio_result, expected_count, expected_time_us);
|
ScheduleTransfer(std::move(cmd), audio_interrupt_response, 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 (!m_queries.empty())
|
if (!m_queries.empty())
|
||||||
{
|
{
|
||||||
result = m_queries.front();
|
interrupt_response = m_queries.front();
|
||||||
m_queries.pop();
|
m_queries.pop();
|
||||||
// This needs to happen after ~22 milliseconds
|
// This needs to happen after ~22 milliseconds
|
||||||
expected_time_us = 22000;
|
expected_time_us = 22000;
|
||||||
|
@ -454,11 +472,11 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||||
// 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
|
||||||
{
|
{
|
||||||
result = system.GetSkylanderPortal().GetStatus();
|
interrupt_response = system.GetSkylanderPortal().GetStatus();
|
||||||
expected_time_us = 2000;
|
expected_time_us = 2000;
|
||||||
}
|
}
|
||||||
expected_count = 32;
|
expected_count = 32;
|
||||||
ScheduleTransfer(std::move(cmd), result, expected_count, expected_time_us);
|
ScheduleTransfer(std::move(cmd), interrupt_response, expected_count, expected_time_us);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,18 +634,21 @@ std::array<u8, 64> SkylanderPortal::GetStatus()
|
||||||
status |= s.status;
|
status |= s.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<u8, 64> result = {0x53, 0x00, 0x00, 0x00, 0x00, m_interrupt_counter++,
|
std::array<u8, 64> interrupt_response = {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(&result[1], &status, sizeof(status));
|
memcpy(&interrupt_response[1], &status, sizeof(status));
|
||||||
return result;
|
return interrupt_response;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf)
|
void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf)
|
||||||
{
|
{
|
||||||
|
if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block))
|
||||||
|
return;
|
||||||
|
|
||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
|
|
||||||
const auto& skylander = skylanders[sky_num];
|
const auto& skylander = skylanders[sky_num];
|
||||||
|
@ -647,6 +668,9 @@ void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf)
|
||||||
|
|
||||||
void SkylanderPortal::WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf)
|
void SkylanderPortal::WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf)
|
||||||
{
|
{
|
||||||
|
if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block))
|
||||||
|
return;
|
||||||
|
|
||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
|
|
||||||
auto& skylander = skylanders[sky_num];
|
auto& skylander = skylanders[sky_num];
|
||||||
|
@ -738,6 +762,9 @@ bool SkylanderPortal::CreateSkylander(const std::string& file_path, u16 sky_id,
|
||||||
|
|
||||||
bool SkylanderPortal::RemoveSkylander(u8 sky_num)
|
bool SkylanderPortal::RemoveSkylander(u8 sky_num)
|
||||||
{
|
{
|
||||||
|
if (!IsSkylanderNumberValid(sky_num))
|
||||||
|
return false;
|
||||||
|
|
||||||
DEBUG_LOG_FMT(IOS_USB, "Cleared Skylander from slot {}", sky_num);
|
DEBUG_LOG_FMT(IOS_USB, "Cleared Skylander from slot {}", sky_num);
|
||||||
std::lock_guard lock(sky_mutex);
|
std::lock_guard lock(sky_mutex);
|
||||||
auto& skylander = skylanders[sky_num];
|
auto& skylander = skylanders[sky_num];
|
||||||
|
@ -801,4 +828,14 @@ u8 SkylanderPortal::LoadSkylander(u8* buf, File::IOFile in_file)
|
||||||
return found_slot;
|
return found_slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SkylanderPortal::IsSkylanderNumberValid(u8 sky_num)
|
||||||
|
{
|
||||||
|
return sky_num < MAX_SKYLANDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkylanderPortal::IsBlockNumberValid(u8 block)
|
||||||
|
{
|
||||||
|
return block < 64;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IOS::HLE::USB
|
} // namespace IOS::HLE::USB
|
||||||
|
|
|
@ -107,6 +107,10 @@ protected:
|
||||||
SkylanderLEDColor m_color_trap = {};
|
SkylanderLEDColor m_color_trap = {};
|
||||||
|
|
||||||
std::array<Skylander, MAX_SKYLANDERS> skylanders;
|
std::array<Skylander, MAX_SKYLANDERS> skylanders;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool IsSkylanderNumberValid(u8 sky_num);
|
||||||
|
static bool IsBlockNumberValid(u8 block);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace IOS::HLE::USB
|
} // namespace IOS::HLE::USB
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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
|
||||||
|
|
||||||
#include "DolphinQt/SkylanderPortal/SkylanderPortalWindow.h"
|
#include "DolphinQt/SkylanderPortal/SkylanderPortalWindow.h"
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
|
@ -522,7 +521,7 @@ const std::map<const std::pair<const u16, const u16>, const char*> list_skylande
|
||||||
SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent)
|
SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Skylanders Manager"));
|
setWindowTitle(tr("Skylanders Manager"));
|
||||||
setObjectName(tr("skylanders_manager"));
|
setObjectName(QString::fromStdString("skylanders_manager"));
|
||||||
setMinimumSize(QSize(700, 200));
|
setMinimumSize(QSize(700, 200));
|
||||||
|
|
||||||
CreateMainWindow();
|
CreateMainWindow();
|
||||||
|
@ -611,7 +610,7 @@ void SkylanderPortalWindow::OnEmulationStateChanged(Core::State state)
|
||||||
CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Skylander Creator"));
|
setWindowTitle(tr("Skylander Creator"));
|
||||||
setObjectName(tr("skylanders_creator"));
|
setObjectName(QString::fromStdString("skylanders_creator"));
|
||||||
setMinimumSize(QSize(500, 150));
|
setMinimumSize(QSize(500, 150));
|
||||||
auto* layout = new QVBoxLayout;
|
auto* layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
@ -620,8 +619,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
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(tr(entry.second), QVariant(qvar));
|
combo_skylist->addItem(QString::fromStdString(entry.second), QVariant(qvar));
|
||||||
filterlist << tr(entry.second);
|
filterlist << QString::fromStdString(entry.second);
|
||||||
}
|
}
|
||||||
combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF));
|
combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF));
|
||||||
combo_skylist->setEditable(true);
|
combo_skylist->setEditable(true);
|
||||||
|
@ -645,7 +644,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
auto* label_var = new QLabel(tr("Variant:"));
|
auto* label_var = new QLabel(tr("Variant:"));
|
||||||
auto* edit_id = new QLineEdit(tr("0"));
|
auto* edit_id = new QLineEdit(tr("0"));
|
||||||
auto* edit_var = new QLineEdit(tr("0"));
|
auto* edit_var = new QLineEdit(tr("0"));
|
||||||
auto* rxv = new QRegularExpressionValidator(QRegularExpression(tr("\\d*")), this);
|
auto* rxv =
|
||||||
|
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);
|
||||||
|
@ -693,8 +693,7 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
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())
|
||||||
{
|
{
|
||||||
std::string name = std::string(found_sky->second) + ".sky";
|
predef_name += QString::fromStdString(std::string(found_sky->second) + ".sky");
|
||||||
predef_name += tr(name.c_str());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -702,8 +701,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
predef_name += str.arg(sky_id, sky_var);
|
predef_name += str.arg(sky_id, sky_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name,
|
m_file_path = DolphinFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name,
|
||||||
tr("Skylander Object (*.sky);;"));
|
tr("Skylander Object (*.sky);;"));
|
||||||
if (m_file_path.isEmpty())
|
if (m_file_path.isEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -713,12 +712,12 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent)
|
||||||
|
|
||||||
if (!system.GetSkylanderPortal().CreateSkylander(m_file_path.toStdString(), sky_id, sky_var))
|
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(m_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("/");
|
s_last_skylander_path = QFileInfo(m_file_path).absolutePath() + QString::fromStdString("/");
|
||||||
accept();
|
accept();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -753,13 +752,14 @@ 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 =
|
||||||
this, tr("Select Skylander File"), s_last_skylander_path, tr("Skylander (*.sky);;"));
|
DolphinFileDialog::getOpenFileName(this, tr("Select Skylander File"), s_last_skylander_path,
|
||||||
|
QString::fromStdString("Skylander (*.sky);;"));
|
||||||
if (file_path.isEmpty())
|
if (file_path.isEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s_last_skylander_path = QFileInfo(file_path).absolutePath() + tr("/");
|
s_last_skylander_path = QFileInfo(file_path).absolutePath() + QString::fromStdString("/");
|
||||||
|
|
||||||
LoadSkylanderPath(slot, file_path);
|
LoadSkylanderPath(slot, file_path);
|
||||||
}
|
}
|
||||||
|
@ -770,8 +770,8 @@ void SkylanderPortalWindow::LoadSkylanderPath(u8 slot, const QString& path)
|
||||||
if (!sky_file)
|
if (!sky_file)
|
||||||
{
|
{
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(
|
||||||
this, tr("Failed to open the skylander file!"),
|
this, tr("Failed to open the Skylander file!"),
|
||||||
tr("Failed to open the skylander file(%1)!\nFile may already be in use on the portal.")
|
tr("Failed to open the Skylander file(%1)!\nFile may already be in use on the portal.")
|
||||||
.arg(path),
|
.arg(path),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
return;
|
return;
|
||||||
|
@ -780,8 +780,8 @@ void SkylanderPortalWindow::LoadSkylanderPath(u8 slot, const QString& path)
|
||||||
if (!sky_file.ReadBytes(file_data.data(), file_data.size()))
|
if (!sky_file.ReadBytes(file_data.data(), file_data.size()))
|
||||||
{
|
{
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(
|
||||||
this, tr("Failed to read the skylander file!"),
|
this, tr("Failed to read the Skylander file!"),
|
||||||
tr("Failed to read the skylander file(%1)!\nFile was too small.").arg(path),
|
tr("Failed to read the Skylander file(%1)!\nFile was too small.").arg(path),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -804,8 +804,8 @@ void SkylanderPortalWindow::LoadSkylanderPath(u8 slot, const QString& path)
|
||||||
u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(file_data.data(), std::move(sky_file));
|
u8 portal_slot = system.GetSkylanderPortal().LoadSkylander(file_data.data(), std::move(sky_file));
|
||||||
if (portal_slot == 0xFF)
|
if (portal_slot == 0xFF)
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, tr("Failed to load the skylander file!"),
|
QMessageBox::warning(this, tr("Failed to load the Skylander file!"),
|
||||||
tr("Failed to load the skylander file(%1)!\n").arg(path), QMessageBox::Ok);
|
tr("Failed to load the Skylander file(%1)!\n").arg(path), QMessageBox::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_sky_slots[slot] = {portal_slot, sky_id, sky_var};
|
m_sky_slots[slot] = {portal_slot, sky_id, sky_var};
|
||||||
|
@ -817,6 +817,13 @@ void SkylanderPortalWindow::ClearSkylander(u8 slot)
|
||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
if (auto slot_infos = m_sky_slots[slot])
|
if (auto slot_infos = m_sky_slots[slot])
|
||||||
{
|
{
|
||||||
|
if (!system.GetSkylanderPortal().RemoveSkylander(slot_infos->portal_slot))
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Failed to clear Skylander!"),
|
||||||
|
tr("Failed to clear the Skylander from slot(%1)!\n").arg(slot),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
system.GetSkylanderPortal().RemoveSkylander(slot_infos->portal_slot);
|
system.GetSkylanderPortal().RemoveSkylander(slot_infos->portal_slot);
|
||||||
m_sky_slots[slot].reset();
|
m_sky_slots[slot].reset();
|
||||||
UpdateEdits();
|
UpdateEdits();
|
||||||
|
@ -833,7 +840,7 @@ void SkylanderPortalWindow::UpdateEdits()
|
||||||
auto found_sky = list_skylanders.find(std::make_pair(sd->sky_id, sd->sky_var));
|
auto found_sky = list_skylanders.find(std::make_pair(sd->sky_id, sd->sky_var));
|
||||||
if (found_sky != list_skylanders.end())
|
if (found_sky != list_skylanders.end())
|
||||||
{
|
{
|
||||||
display_string = tr(found_sky->second);
|
display_string = QString::fromStdString(found_sky->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue