diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp b/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp index 60c7302563..c799c3477e 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp @@ -66,6 +66,12 @@ bool SkylanderUSB::Attach() bool SkylanderUSB::AttachAndChangeInterface(const u8 interface) { + if (!Attach()) + return false; + + if (interface != m_active_interface) + return ChangeInterface(interface) == 0; + return true; } @@ -109,286 +115,25 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length); - auto& system = Core::System::GetInstance(); - auto& memory = system.GetMemory(); - 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"); + // If not HID Host to Device type, return invalid + if (cmd->request_type != 0x21) return IPC_EINVAL; - } - std::array result = {}; - std::array data = {}; + + // Data to be sent back via the control transfer immediately + std::array control_response = {}; s32 expected_count = 0; u64 expected_time_us = 100; - // Control transfers are instantaneous - u8 request_type = cmd->request_type; - if (request_type == 0x21) + + // Non 0x09 Requests are handled here - no portal data is requested + if (cmd->request != 0x09) { - // HID host to device type switch (cmd->request) { - case 0x09: - 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; + // Get Interface case 0x0A: expected_count = 8; break; + // Set Interface case 0x0B: expected_count = 8; break; @@ -397,9 +142,282 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) 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 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) 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; } @@ -427,26 +445,26 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) ERROR_LOG_FMT(IOS_USB, "Skylander command invalid"); return IPC_EINVAL; } - std::array result = {}; + std::array interrupt_response = {}; s32 expected_count; u64 expected_time_us; // 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 if (cmd->length > 32 && cmd->length <= 64) { - std::array audio_result = {}; - u8* audio_buf = audio_result.data(); + std::array audio_interrupt_response = {}; + u8* audio_buf = audio_interrupt_response.data(); memcpy(audio_buf, buf, cmd->length); expected_time_us = 1000; 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; } // 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 if (!m_queries.empty()) { - result = m_queries.front(); + interrupt_response = m_queries.front(); m_queries.pop(); // This needs to happen after ~22 milliseconds expected_time_us = 22000; @@ -454,11 +472,11 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // If there is no relevant data to respond with, respond with the currentstatus of the Portal else { - result = system.GetSkylanderPortal().GetStatus(); + interrupt_response = system.GetSkylanderPortal().GetStatus(); expected_time_us = 2000; } 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; } @@ -616,18 +634,21 @@ std::array SkylanderPortal::GetStatus() status |= s.status; } - std::array result = {0x53, 0x00, 0x00, 0x00, 0x00, m_interrupt_counter++, - active, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00}; - memcpy(&result[1], &status, sizeof(status)); - return result; + std::array interrupt_response = {0x53, 0x00, 0x00, 0x00, 0x00, m_interrupt_counter++, + active, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; + memcpy(&interrupt_response[1], &status, sizeof(status)); + return interrupt_response; } void SkylanderPortal::QueryBlock(u8 sky_num, u8 block, u8* reply_buf) { + if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block)) + return; + std::lock_guard lock(sky_mutex); 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) { + if (!IsSkylanderNumberValid(sky_num) || !IsBlockNumberValid(block)) + return; + std::lock_guard lock(sky_mutex); 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) { + if (!IsSkylanderNumberValid(sky_num)) + return false; + DEBUG_LOG_FMT(IOS_USB, "Cleared Skylander from slot {}", sky_num); std::lock_guard lock(sky_mutex); auto& skylander = skylanders[sky_num]; @@ -801,4 +828,14 @@ u8 SkylanderPortal::LoadSkylander(u8* buf, File::IOFile in_file) 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 diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylander.h b/Source/Core/Core/IOS/USB/Emulated/Skylander.h index cfd862f881..5c7b308a42 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylander.h +++ b/Source/Core/Core/IOS/USB/Emulated/Skylander.h @@ -107,6 +107,10 @@ protected: SkylanderLEDColor m_color_trap = {}; std::array skylanders; + +private: + static bool IsSkylanderNumberValid(u8 sky_num); + static bool IsBlockNumberValid(u8 block); }; } // namespace IOS::HLE::USB diff --git a/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp b/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp index 346612fa3b..7f4f9994fd 100644 --- a/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp +++ b/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // 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" @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -522,7 +521,7 @@ const std::map, const char*> list_skylande SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent) { setWindowTitle(tr("Skylanders Manager")); - setObjectName(tr("skylanders_manager")); + setObjectName(QString::fromStdString("skylanders_manager")); setMinimumSize(QSize(700, 200)); CreateMainWindow(); @@ -611,7 +610,7 @@ void SkylanderPortalWindow::OnEmulationStateChanged(Core::State state) CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Skylander Creator")); - setObjectName(tr("skylanders_creator")); + setObjectName(QString::fromStdString("skylanders_creator")); setMinimumSize(QSize(500, 150)); auto* layout = new QVBoxLayout; @@ -620,8 +619,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) for (const auto& entry : list_skylanders) { const uint qvar = (entry.first.first << 16) | entry.first.second; - combo_skylist->addItem(tr(entry.second), QVariant(qvar)); - filterlist << tr(entry.second); + combo_skylist->addItem(QString::fromStdString(entry.second), QVariant(qvar)); + filterlist << QString::fromStdString(entry.second); } combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF)); combo_skylist->setEditable(true); @@ -645,7 +644,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) auto* label_var = new QLabel(tr("Variant:")); auto* edit_id = 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_var->setValidator(rxv); 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)); if (found_sky != list_skylanders.end()) { - std::string name = std::string(found_sky->second) + ".sky"; - predef_name += tr(name.c_str()); + predef_name += QString::fromStdString(std::string(found_sky->second) + ".sky"); } else { @@ -702,8 +701,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) predef_name += str.arg(sky_id, sky_var); } - m_file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name, - tr("Skylander Object (*.sky);;")); + m_file_path = DolphinFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name, + tr("Skylander Object (*.sky);;")); if (m_file_path.isEmpty()) { return; @@ -713,12 +712,12 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) if (!system.GetSkylanderPortal().CreateSkylander(m_file_path.toStdString(), sky_id, sky_var)) { - QMessageBox::warning(this, tr("Failed to create skylander file!"), - tr("Failed to create skylander file:\n%1").arg(m_file_path), + QMessageBox::warning(this, tr("Failed to create Skylander file!"), + tr("Failed to create Skylander file:\n%1").arg(m_file_path), QMessageBox::Ok); return; } - s_last_skylander_path = QFileInfo(m_file_path).absolutePath() + tr("/"); + s_last_skylander_path = QFileInfo(m_file_path).absolutePath() + QString::fromStdString("/"); accept(); }); @@ -753,13 +752,14 @@ void SkylanderPortalWindow::CreateSkylander(u8 slot) void SkylanderPortalWindow::LoadSkylander(u8 slot) { - const QString file_path = DolphinFileDialog::getOpenFileName( - this, tr("Select Skylander File"), s_last_skylander_path, tr("Skylander (*.sky);;")); + const QString file_path = + DolphinFileDialog::getOpenFileName(this, tr("Select Skylander File"), s_last_skylander_path, + QString::fromStdString("Skylander (*.sky);;")); if (file_path.isEmpty()) { return; } - s_last_skylander_path = QFileInfo(file_path).absolutePath() + tr("/"); + s_last_skylander_path = QFileInfo(file_path).absolutePath() + QString::fromStdString("/"); LoadSkylanderPath(slot, file_path); } @@ -770,8 +770,8 @@ void SkylanderPortalWindow::LoadSkylanderPath(u8 slot, const QString& path) if (!sky_file) { QMessageBox::warning( - 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.") + 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.") .arg(path), QMessageBox::Ok); return; @@ -780,8 +780,8 @@ void SkylanderPortalWindow::LoadSkylanderPath(u8 slot, const QString& path) if (!sky_file.ReadBytes(file_data.data(), file_data.size())) { QMessageBox::warning( - this, tr("Failed to read the skylander file!"), - tr("Failed to read the skylander file(%1)!\nFile was too small.").arg(path), + this, tr("Failed to read the Skylander file!"), + tr("Failed to read the Skylander file(%1)!\nFile was too small.").arg(path), QMessageBox::Ok); 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)); 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); + 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}; @@ -817,6 +817,13 @@ void SkylanderPortalWindow::ClearSkylander(u8 slot) auto& system = Core::System::GetInstance(); 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); m_sky_slots[slot].reset(); UpdateEdits(); @@ -833,7 +840,7 @@ void SkylanderPortalWindow::UpdateEdits() auto found_sky = list_skylanders.find(std::make_pair(sd->sky_id, sd->sky_var)); if (found_sky != list_skylanders.end()) { - display_string = tr(found_sky->second); + display_string = QString::fromStdString(found_sky->second); } else {