diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 16bc0b1b0b..682c686c7d 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -399,8 +399,6 @@ add_library(core IOS/USB/Bluetooth/WiimoteHIDAttr.h IOS/USB/Common.cpp IOS/USB/Common.h - IOS/USB/EmulatedUSBDevice.cpp - IOS/USB/EmulatedUSBDevice.h IOS/USB/Emulated/Skylander.cpp IOS/USB/Emulated/Skylander.h IOS/USB/Host.cpp diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 09cfcabb55..552763c8d1 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -556,11 +556,6 @@ void SetUSBDeviceWhitelist(const std::set>& devices) const Info MAIN_EMULATE_SKYLANDER_PORTAL{ {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 // expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii. DiscIO::Region ToGameCubeRegion(DiscIO::Region region) diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index e2a4549e49..e47cb910ce 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -345,7 +345,6 @@ void SetUSBDeviceWhitelist(const std::set>& devices); // Main.EmulatedUSBDevices extern const Info MAIN_EMULATE_SKYLANDER_PORTAL; -bool EmulateSkylanderPortal(); // GameCube path utility functions diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 86c0ffc5fb..69684786a7 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -29,7 +29,7 @@ bool IsSettingSaveable(const Config::Location& config_location) for (const std::string_view section : {"NetPlay", "General", "GBA", "Display", "Network", "Analytics", "AndroidOverlayButtons", "DSP", "GameList", "FifoPlayer", "AutoUpdate", "Movie", "Input", "Debug", - "BluetoothPassthrough", "USBPassthrough", "Interface"}) + "BluetoothPassthrough", "USBPassthrough", "Interface", "EmulatedUSBDevices"}) { if (config_location.section == section) return true; diff --git a/Source/Core/Core/IOS/USB/Common.cpp b/Source/Core/Core/IOS/USB/Common.cpp index 2c77371590..fda8e37129 100644 --- a/Source/Core/Core/IOS/USB/Common.cpp +++ b/Source/Core/Core/IOS/USB/Common.cpp @@ -38,6 +38,13 @@ void TransferCommand::OnTransferComplete(s32 return_value) const 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((static_cast(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 { auto& system = Core::System::GetInstance(); diff --git a/Source/Core/Core/IOS/USB/Common.h b/Source/Core/Core/IOS/USB/Common.h index eb506dd64f..edf9a724e1 100644 --- a/Source/Core/Core/IOS/USB/Common.h +++ b/Source/Core/Core/IOS/USB/Common.h @@ -109,13 +109,10 @@ struct TransferCommand // Called after a transfer has completed to reply to the IPC request. // This can be overridden for additional processing before replying. virtual void OnTransferComplete(s32 return_value) const; + void ScheduleTransferCompletion(s32 return_value, u32 expected_time_us) const; std::unique_ptr MakeBuffer(size_t size) const; void FillBuffer(const u8* src, size_t size) const; - // Fake Transfers - u64 expected_time; - u32 expected_count; - private: Kernel& m_ios; }; diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp b/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp index 9a09299607..60c7302563 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Skylander.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Dolphin Emulator Project +// Copyright 2022 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/IOS/USB/Emulated/Skylander.h" @@ -16,44 +16,41 @@ namespace IOS::HLE::USB { -SkylanderPortal g_skyportal; - -SkylanderUSB::SkylanderUSB(Kernel& ios, const std::string& device_name) - : EmulatedUSBDevice(ios, device_name) +SkylanderUSB::SkylanderUSB(Kernel& ios, const std::string& device_name) : m_ios(ios) { m_vid = 0x1430; m_pid = 0x150; m_id = (static_cast(m_vid) << 32 | static_cast(m_pid) << 16 | static_cast(9) << 8 | static_cast(1)); - deviceDesc = DeviceDescriptor{18, 1, 512, 0, 0, 0, 64, 5168, 336, 256, 1, 2, 0, 1}; - configDesc.emplace_back(ConfigDescriptor{9, 2, 41, 1, 1, 0, 128, 250}); - interfaceDesc.emplace_back(InterfaceDescriptor{9, 4, 0, 0, 2, 3, 0, 0, 0}); - endpointDesc.emplace_back(EndpointDescriptor{7, 5, 129, 3, 64, 1}); - endpointDesc.emplace_back(EndpointDescriptor{7, 5, 2, 3, 64, 1}); + m_device_descriptor = DeviceDescriptor{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x40, + 0x1430, 0x150, 0x100, 0x1, 0x2, 0x0, 0x1}; + m_config_descriptor.emplace_back(ConfigDescriptor{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}); + m_interface_descriptor.emplace_back( + 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 { - return deviceDesc; + return m_device_descriptor; } std::vector SkylanderUSB::GetConfigurations() const { - return configDesc; + return m_config_descriptor; } std::vector SkylanderUSB::GetInterfaces(u8 config) const { - return interfaceDesc; + return m_interface_descriptor; } std::vector SkylanderUSB::GetEndpoints(u8 config, u8 interface, u8 alt) const { - return endpointDesc; + return m_endpoint_descriptor; } bool SkylanderUSB::Attach() @@ -64,11 +61,6 @@ bool SkylanderUSB::Attach() } DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid); m_device_attached = true; - if (!m_has_initialised && !Core::WantsDeterminism()) - { - GetTransferThread().Start(); - m_has_initialised = 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, m_active_interface, endpoint); - if (GetTransferThread().GetTransfers()) - { - return IPC_ENOENT; - } - GetTransferThread().ClearTransfers(); + return IPC_SUCCESS; } @@ -121,17 +109,23 @@ 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); - cmd->expected_time = Common::Timer::NowUs() + 100; auto& system = Core::System::GetInstance(); auto& memory = system.GetMemory(); u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length); - std::array q_result = {}; - std::array q_data = {}; - // Control transfers are instantaneous - switch (cmd->request_type) + if ((cmd->length == 0 || buf == nullptr) && cmd->request == 0x09) { - // HID host to device type - case 0x21: + ERROR_LOG_FMT(IOS_USB, "Skylander command invalid"); + return IPC_EINVAL; + } + std::array result = {}; + std::array data = {}; + s32 expected_count = 0; + u64 expected_time_us = 100; + // Control transfers are instantaneous + u8 request_type = cmd->request_type; + if (request_type == 0x21) + { + // HID host to device type switch (cmd->request) { case 0x09: @@ -145,7 +139,7 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // 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 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 // 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 @@ -154,15 +148,15 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // 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 || cmd->length == 32) + if (cmd->length == 2) { - q_data = {buf[0], buf[1]}; - q_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}; - q_queries.push(q_result); - cmd->expected_count = 10; - g_skyportal.Activate(); + 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; } @@ -179,39 +173,39 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // 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 || cmd->length == 32) + if (cmd->length == 4) { - g_skyportal.SetLEDs(0x01, buf[1], buf[2], buf[3]); - q_data = {0x43, buf[1], buf[2], buf[3]}; - cmd->expected_count = 12; + 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 - // buf[1] is the side + // The 2nd byte is the side // 0x00: right // 0x01: left and right // 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 - // 0x00, 0x01 and 0x07. Custom commands show that the higher this value the longer the + // 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) { - q_data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]}; - cmd->expected_count = 15; - q_result = {buf[0]}; - q_queries.push(q_result); - g_skyportal.SetLEDs(buf[1], buf[2], buf[3], buf[4]); + 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; } @@ -220,40 +214,38 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // Light // This command is used while playing audio through the portal - // buf[1] is the position + // The 2nd bytes is the position // 0x00: right // 0x01: trap led // 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 - // increasing or decreasing the values results in a birghter or dimmer light - - // buf[5] is unknown. - // A range of values have been observed + // increasing or decreasing the values results in a brighter or dimmer light if (cmd->length == 5) { - q_data = {buf[0], buf[1], buf[2], buf[3], buf[4]}; - cmd->expected_count = 13; + data = {buf[0], buf[1], buf[2], buf[3], buf[4]}; + expected_count = 13; u8 side = buf[1]; if (side == 0x02) { side = 0x04; } - g_skyportal.SetLEDs(side, buf[2], buf[3], buf[4]); + 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) { - q_data = {buf[0], buf[1]}; - cmd->expected_count = 10; - q_result = {buf[0], buf[1], 0x00, 0x19}; - q_queries.push(q_result); + data = {buf[0], buf[1]}; + expected_count = 10; + result = {buf[0], buf[1], 0x00, 0x19}; + m_queries.push(result); } break; } @@ -263,25 +255,28 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // 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 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 - // 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 // 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 // character indexes, these may not be sequential. case 'Q': { // Queries a block - const u8 sky_num = buf[1] & 0xF; - const u8 block = buf[2]; - g_skyportal.QueryBlock(sky_num, block, q_result.data()); - q_queries.push(q_result); - q_data = {buf[0], buf[1], buf[2]}; - cmd->expected_count = 11; + 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': @@ -294,14 +289,14 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // 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 || cmd->length == 32) + if (cmd->length == 2) { - q_data = {0x52, 0x00}; - q_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}; - q_queries.push(q_result); - cmd->expected_count = 10; + 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; } @@ -336,14 +331,20 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // been activated: {01} when active and {00} when deactivated. case 'S': { - q_data = {buf[0]}; - cmd->expected_count = 9; + if (cmd->length == 1) + { + data = {buf[0]}; + expected_count = 9; + } break; } case 'V': { - q_data = {buf[0], buf[1], buf[2], buf[3]}; - cmd->expected_count = 12; + if (cmd->length == 4) + { + data = {buf[0], buf[1], buf[2], buf[3]}; + expected_count = 12; + } break; } // Write @@ -352,13 +353,13 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) // 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 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 // 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. // The response does not appear to return the id of the Skylander being written, the 2nd @@ -367,14 +368,17 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) case 'W': { - const u8 sky_num = buf[1] & 0xF; - const u8 block = buf[2]; - g_skyportal.WriteBlock(sky_num, block, &buf[3], q_result.data()); - q_queries.push(q_result); - q_data = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], + 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]}; - cmd->expected_count = 19; + expected_count = 27; + } break; } default: @@ -383,24 +387,22 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) } break; case 0x0A: - cmd->expected_count = 8; + expected_count = 8; break; case 0x0B: - cmd->expected_count = 8; + expected_count = 8; break; default: ERROR_LOG_FMT(IOS_USB, "Unhandled Request {}", cmd->request); break; } - break; - - default: - break; } - cmd->expected_time = Common::Timer::NowUs() + 100; - GetTransferThread().AddTransfer(std::move(cmd), q_data); + if (expected_count == 0) + return IPC_EINVAL; + ScheduleTransfer(std::move(cmd), data, expected_count, expected_time_us); return 0; } + int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) { DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={} endpoint={}", m_vid, m_pid, @@ -420,38 +422,46 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) auto& system = Core::System::GetInstance(); auto& memory = system.GetMemory(); u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length); - std::array q_result = {}; + if (cmd->length == 0 || buf == nullptr) + { + ERROR_LOG_FMT(IOS_USB, "Skylander command invalid"); + return IPC_EINVAL; + } + std::array result = {}; + 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) + if (cmd->length > 32 && cmd->length <= 64) { - std::array q_audio_result = {}; - u8* audio_buf = q_audio_result.data(); + std::array audio_result = {}; + u8* audio_buf = audio_result.data(); memcpy(audio_buf, buf, cmd->length); - cmd->expected_time = Common::Timer::NowUs() + 1000; - cmd->expected_count = cmd->length; - GetTransferThread().AddTransfer(std::move(cmd), q_audio_result); + expected_time_us = 1000; + expected_count = cmd->length; + ScheduleTransfer(std::move(cmd), audio_result, 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 (!q_queries.empty()) + if (!m_queries.empty()) { - q_result = q_queries.front(); - q_queries.pop(); + result = m_queries.front(); + m_queries.pop(); // 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 else { - q_result = g_skyportal.GetStatus(); - cmd->expected_time = Common::Timer::NowUs() + 2000; + result = system.GetSkylanderPortal().GetStatus(); + expected_time_us = 2000; } - cmd->expected_count = 32; - GetTransferThread().AddTransfer(std::move(cmd), q_result); + expected_count = 32; + ScheduleTransfer(std::move(cmd), result, expected_count, expected_time_us); return 0; } + int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) { DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Isochronous: length={} endpoint={} num_packets={}", @@ -459,23 +469,27 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) return 0; } -void Skylander::save() +void SkylanderUSB::ScheduleTransfer(std::unique_ptr command, + const std::array& 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) - { return; - } - { - sky_file.Seek(0, File::SeekOrigin::Begin); - sky_file.WriteBytes(data.data(), 0x40 * 0x10); - } + sky_file.Seek(0, File::SeekOrigin::Begin); + sky_file.WriteBytes(data.data(), 0x40 * 0x10); } void SkylanderPortal::Activate() { std::lock_guard lock(sky_mutex); - if (activated) + if (m_activated) { // If the portal was already active no change is needed return; @@ -486,12 +500,12 @@ void SkylanderPortal::Activate() { if (s.status & 1) { - s.queued_status.push(3); - s.queued_status.push(1); + s.queued_status.push(Skylander::ADDED); + s.queued_status.push(Skylander::READY); } } - activated = true; + m_activated = true; } void SkylanderPortal::Deactivate() @@ -510,32 +524,32 @@ void SkylanderPortal::Deactivate() s.status &= 1; } - activated = false; + m_activated = false; } bool SkylanderPortal::IsActivated() { std::lock_guard lock(sky_mutex); - return activated; + return m_activated; } void SkylanderPortal::UpdateStatus() { std::lock_guard lock(sky_mutex); - if (!status_updated) + if (!m_status_updated) { for (auto& s : skylanders) { if (s.status & 1) { - s.queued_status.push(0); - s.queued_status.push(3); - s.queued_status.push(1); + s.queued_status.push(Skylander::REMOVED); + s.queued_status.push(Skylander::ADDED); + 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); if (side == 0x00) { - this->color_right.r = red; - this->color_right.g = green; - this->color_right.b = blue; + m_color_right.red = red; + m_color_right.green = green; + m_color_right.blue = blue; } else if (side == 0x01) { - this->color_right.r = red; - this->color_right.g = green; - this->color_right.b = blue; + m_color_right.red = red; + m_color_right.green = green; + m_color_right.blue = blue; - this->color_left.r = red; - this->color_left.g = green; - this->color_left.b = blue; + m_color_left.red = red; + m_color_left.green = green; + m_color_left.blue = blue; } else if (side == 0x02) { - this->color_left.r = red; - this->color_left.g = green; - this->color_left.b = blue; + m_color_left.red = red; + m_color_left.green = green; + m_color_left.blue = blue; } else if (side == 0x03) { - this->color_trap.r = red; - this->color_trap.g = green; - this->color_trap.b = blue; + m_color_trap.red = red; + m_color_trap.green = green; + m_color_trap.blue = blue; } } @@ -584,7 +598,7 @@ std::array SkylanderPortal::GetStatus() u32 status = 0; u8 active = 0x00; - if (activated) + if (m_activated) { active = 0x01; } @@ -602,14 +616,14 @@ std::array SkylanderPortal::GetStatus() status |= s.status; } - std::array q_result = {0x53, 0x00, 0x00, 0x00, 0x00, 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(&q_result.data()[1], &status, sizeof(status)); - return q_result; + 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; } 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); memcpy(skylander.data.data() + (block * 16), to_write_buf, 16); - skylander.save(); + skylander.Save(); } 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 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) { 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) { - skylander.status = 2; - skylander.queued_status.push(2); - skylander.queued_status.push(0); - skylander.sky_file.Close(); + skylander.status = Skylander::REMOVING; + skylander.queued_status.push(Skylander::REMOVING); + skylander.queued_status.push(Skylander::REMOVED); return true; } @@ -710,9 +793,9 @@ u8 SkylanderPortal::LoadSkylander(u8* buf, File::IOFile in_file) DEBUG_LOG_FMT(IOS_USB, "Skylander Data: \n{}", HexDump(skylander.data.data(), skylander.data.size())); skylander.sky_file = std::move(in_file); - skylander.status = 3; - skylander.queued_status.push(3); - skylander.queued_status.push(1); + skylander.status = Skylander::ADDED; + skylander.queued_status.push(Skylander::ADDED); + skylander.queued_status.push(Skylander::READY); skylander.last_id = sky_serial; } return found_slot; diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylander.h b/Source/Core/Core/IOS/USB/Emulated/Skylander.h index 3e16d25a95..cfd862f881 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylander.h +++ b/Source/Core/Core/IOS/USB/Emulated/Skylander.h @@ -8,8 +8,9 @@ #include #include +#include "Common/CommonTypes.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 status array is 32 bits and every character takes 2 bits. @@ -18,7 +19,7 @@ constexpr u8 MAX_SKYLANDERS = 16; namespace IOS::HLE::USB { -class SkylanderUSB final : public EmulatedUSBDevice +class SkylanderUSB final : public Device { public: SkylanderUSB(Kernel& ios, const std::string& device_name); @@ -37,20 +38,20 @@ public: int SubmitTransfer(std::unique_ptr message) override; int SubmitTransfer(std::unique_ptr message) override; int SubmitTransfer(std::unique_ptr message) override; - -protected: - std::queue> q_queries; + void ScheduleTransfer(std::unique_ptr command, const std::array& data, + s32 expected_count, u64 expected_time_us); private: + Kernel& m_ios; u16 m_vid = 0; u16 m_pid = 0; u8 m_active_interface = 0; bool m_device_attached = false; - DeviceDescriptor deviceDesc; - std::vector configDesc; - std::vector interfaceDesc; - std::vector endpointDesc; - bool m_has_initialised = false; + DeviceDescriptor m_device_descriptor; + std::vector m_config_descriptor; + std::vector m_interface_descriptor; + std::vector m_endpoint_descriptor; + std::queue> m_queries; }; struct Skylander final @@ -60,12 +61,22 @@ struct Skylander final std::queue queued_status; std::array data{}; 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 @@ -81,22 +92,21 @@ public: 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); + bool CreateSkylander(const std::string& file_path, u16 sky_id, u16 sky_var); bool RemoveSkylander(u8 sky_num); u8 LoadSkylander(u8* buf, File::IOFile in_file); protected: std::mutex sky_mutex; - bool activated = true; - bool status_updated = false; - u8 interrupt_counter = 0; - LedColor color_right = {}; - LedColor color_left = {}; - LedColor color_trap = {}; + bool m_activated = true; + bool m_status_updated = false; + u8 m_interrupt_counter = 0; + SkylanderLEDColor m_color_right = {}; + SkylanderLEDColor m_color_left = {}; + SkylanderLEDColor m_color_trap = {}; - Skylander skylanders[MAX_SKYLANDERS]; + std::array skylanders; }; -extern SkylanderPortal g_skyportal; - } // namespace IOS::HLE::USB diff --git a/Source/Core/Core/IOS/USB/EmulatedUSBDevice.cpp b/Source/Core/Core/IOS/USB/EmulatedUSBDevice.cpp deleted file mode 100644 index 8f271745d0..0000000000 --- a/Source/Core/Core/IOS/USB/EmulatedUSBDevice.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "Core/IOS/USB/EmulatedUSBDevice.h" - -#include - -#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 command, - std::array 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 diff --git a/Source/Core/Core/IOS/USB/EmulatedUSBDevice.h b/Source/Core/Core/IOS/USB/EmulatedUSBDevice.h deleted file mode 100644 index 60e469f7ce..0000000000 --- a/Source/Core/Core/IOS/USB/EmulatedUSBDevice.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -#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 command, std::array 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::unique_ptr> 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 diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index a7378efcc2..8870f730b0 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -24,6 +24,8 @@ #include "Core/IOS/USB/Common.h" #include "Core/IOS/USB/Emulated/Skylander.h" #include "Core/IOS/USB/LibusbDevice.h" +#include "Core/NetPlayProto.h" +#include "Core/System.h" namespace IOS::HLE { @@ -35,7 +37,7 @@ USBHost::~USBHost() = default; std::optional USBHost::Open(const OpenRequest& request) { - if (!m_has_initialised && !Core::WantsDeterminism()) + if (!m_has_initialised) { GetScanThread().Start(); // 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; } +void USBHost::Update() +{ + if (Core::WantsDeterminism()) + UpdateDevices(); +} + // 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) { - if (Core::WantsDeterminism()) - return true; - DeviceChangeHooks hooks; std::set plugged_devices; // If we failed to get a new, up-to-date list of devices, we cannot detect device removals. @@ -116,43 +121,35 @@ bool USBHost::UpdateDevices(const bool always_add_hooks) bool USBHost::AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, const bool always_add_hooks) { - if (Config::EmulateSkylanderPortal()) - { - auto skylanderportal = std::make_unique(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); - } - } + AddEmulatedDevices(new_devices, hooks, always_add_hooks); #ifdef __LIBUSB__ - auto whitelist = Config::GetUSBDeviceWhitelist(); - if (whitelist.empty()) - return true; - - if (m_context.IsValid()) + if (!Core::WantsDeterminism()) { - const int ret = m_context.GetDeviceList([&](libusb_device* device) { - libusb_device_descriptor descriptor; - libusb_get_device_descriptor(device, &descriptor); - if (whitelist.count({descriptor.idVendor, descriptor.idProduct}) == 0) - return true; - - auto usb_device = std::make_unique(m_ios, device, descriptor); - if (!ShouldAddDevice(*usb_device)) - return true; - - const u64 id = usb_device->GetId(); - new_devices.insert(id); - if (AddDevice(std::move(usb_device)) || always_add_hooks) - hooks.emplace(GetDeviceById(id), ChangeEvent::Inserted); + auto whitelist = Config::GetUSBDeviceWhitelist(); + if (whitelist.empty()) return true; - }); - if (ret != LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_USB, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret)); + + if (m_context.IsValid()) + { + const int ret = m_context.GetDeviceList([&](libusb_device* device) { + libusb_device_descriptor descriptor; + libusb_get_device_descriptor(device, &descriptor); + if (whitelist.count({descriptor.idVendor, descriptor.idProduct}) == 0) + return true; + + auto usb_device = std::make_unique(m_ios, device, descriptor); + if (!ShouldAddDevice(*usb_device)) + return true; + + const u64 id = usb_device->GetId(); + new_devices.insert(id); + if (AddDevice(std::move(usb_device)) || always_add_hooks) + hooks.emplace(GetDeviceById(id), ChangeEvent::Inserted); + return true; + }); + if (ret != LIBUSB_SUCCESS) + WARN_LOG_FMT(IOS_USB, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret)); + } } #endif return true; @@ -188,6 +185,24 @@ void USBHost::DispatchHooks(const DeviceChangeHooks& hooks) OnDeviceChangeEnd(); } +void USBHost::AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& hooks, + bool always_add_hooks) +{ + if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning()) + { + auto skylanderportal = std::make_unique(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() { Stop(); @@ -195,14 +210,19 @@ USBHost::ScanThread::~ScanThread() void USBHost::ScanThread::WaitForFirstScan() { - m_first_scan_complete_event.Wait(); + if (m_thread_running.IsSet()) + { + m_first_scan_complete_event.Wait(); + } } void USBHost::ScanThread::Start() { if (Core::WantsDeterminism()) + { + m_host->UpdateDevices(); return; - + } if (m_thread_running.TestAndSet()) { m_thread = std::thread([this] { diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h index 341cbaa628..8e146df9d1 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -76,10 +76,13 @@ protected: private: bool AddDevice(std::unique_ptr device); + void Update() override; bool UpdateDevices(bool always_add_hooks = false); bool AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks); void DetectRemovedDevices(const std::set& plugged_devices, DeviceChangeHooks& hooks); void DispatchHooks(const DeviceChangeHooks& hooks); + void AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& hooks, + bool always_add_hooks); bool m_has_initialised = false; LibusbUtils::Context m_context; diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp index a03d3958be..fc4272ed12 100644 --- a/Source/Core/Core/System.cpp +++ b/Source/Core/Core/System.cpp @@ -20,6 +20,7 @@ #include "Core/HW/SI/SI.h" #include "Core/HW/Sram.h" #include "Core/HW/VideoInterface.h" +#include "IOS/USB/Emulated/Skylander.h" #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/GeometryShaderManager.h" @@ -47,6 +48,7 @@ struct System::Impl Fifo::FifoManager m_fifo; GeometryShaderManager m_geometry_shader_manager; GPFifo::GPFifoManager m_gp_fifo; + IOS::HLE::USB::SkylanderPortal m_skylander_portal; Memory::MemoryManager m_memory; MemoryInterface::MemoryInterfaceState m_memory_interface_state; PixelEngine::PixelEngineManager m_pixel_engine; @@ -151,6 +153,11 @@ GPFifo::GPFifoManager& System::GetGPFifo() const return m_impl->m_gp_fifo; } +IOS::HLE::USB::SkylanderPortal& System::GetSkylanderPortal() const +{ + return m_impl->m_skylander_portal; +} + Memory::MemoryManager& System::GetMemory() const { return m_impl->m_memory; diff --git a/Source/Core/Core/System.h b/Source/Core/Core/System.h index 410da6d697..159a73bb99 100644 --- a/Source/Core/Core/System.h +++ b/Source/Core/Core/System.h @@ -47,6 +47,10 @@ namespace GPFifo { class GPFifoManager; } +namespace IOS::HLE::USB +{ +class SkylanderPortal; +}; namespace Memory { class MemoryManager; @@ -116,6 +120,7 @@ public: Fifo::FifoManager& GetFifo() const; GeometryShaderManager& GetGeometryShaderManager() const; GPFifo::GPFifoManager& GetGPFifo() const; + IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const; Memory::MemoryManager& GetMemory() const; MemoryInterface::MemoryInterfaceState& GetMemoryInterfaceState() const; PixelEngine::PixelEngineManager& GetPixelEngine() const; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 729db0a46d..b720e736b7 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -373,7 +373,6 @@ - @@ -987,7 +986,6 @@ - diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index ae72f12158..0acc8a36b9 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -221,7 +221,7 @@ void MenuBar::AddToolsMenu() 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(); diff --git a/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp b/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp index 1e91c273ae..346612fa3b 100644 --- a/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp +++ b/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.cpp @@ -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 // and Clearing skylanders @@ -6,12 +9,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -22,15 +27,16 @@ #include "Common/IOFile.h" #include "Core/Config/MainSettings.h" +#include "Core/System.h" #include "DolphinQt/QtUtils/DolphinFileDialog.h" #include "DolphinQt/Settings.h" -SkylanderPortalWindow* SkylanderPortalWindow::inst = nullptr; -std::optional> SkylanderPortalWindow::sky_slots[MAX_SKYLANDERS]; -QString last_skylander_path; +// Qt is not guaranteed to keep track of file paths using native file pickers, so we use this +// static variable to ensure we open at the most recent Skylander file location +static QString s_last_skylander_path; -const std::map, const std::string> list_skylanders = { +const std::map, const char*> list_skylanders = { {{0, 0x0000}, "Whirlwind"}, {{0, 0x1801}, "Series 2 Whirlwind"}, {{0, 0x1C02}, "Polar Whirlwind"}, @@ -513,49 +519,10 @@ const std::map, const std::string> list_sk {{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) { setWindowTitle(tr("Skylanders Manager")); - setObjectName(QString::fromStdString("skylanders_manager")); + setObjectName(tr("skylanders_manager")); setMinimumSize(QSize(700, 200)); CreateMainWindow(); @@ -568,33 +535,32 @@ SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent) OnEmulationStateChanged(Core::GetState()); }; -SkylanderPortalWindow::~SkylanderPortalWindow() -{ -} +SkylanderPortalWindow::~SkylanderPortalWindow() = default; void SkylanderPortalWindow::CreateMainWindow() { - QVBoxLayout* mainLayout = new QVBoxLayout(); + auto* main_layout = new QVBoxLayout(); - QGroupBox* checkbox_group = new QGroupBox(); - QHBoxLayout* checkboxLayout = new QHBoxLayout(); - checkboxLayout->setAlignment(Qt::AlignHCenter); - checkbox = new QCheckBox(QString::fromStdString("Emulate Skylander Portal"), this); - connect(checkbox, &QCheckBox::toggled, [=](bool checked) { EmulatePortal(checked); }); - checkboxLayout->addWidget(checkbox); - checkbox_group->setLayout(checkboxLayout); - mainLayout->addWidget(checkbox_group); + auto* checkbox_group = new QGroupBox(); + auto* checkbox_layout = new QHBoxLayout(); + checkbox_layout->setAlignment(Qt::AlignHCenter); + m_checkbox = new QCheckBox(tr("Emulate Skylander Portal"), this); + m_checkbox->setChecked(Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL)); + connect(m_checkbox, &QCheckBox::toggled, [=](bool checked) { EmulatePortal(checked); }); + checkbox_layout->addWidget(m_checkbox); + checkbox_group->setLayout(checkbox_layout); + main_layout->addWidget(checkbox_group); auto add_line = [](QVBoxLayout* vbox) { - QFrame* line = new QFrame(); + auto* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); vbox->addWidget(line); }; - group_skylanders = new QGroupBox(tr("Active Portal Skylanders:")); - QVBoxLayout* vbox_group = new QVBoxLayout(); - QScrollArea* scroll_area = new QScrollArea(); + m_group_skylanders = new QGroupBox(tr("Active Portal Skylanders:")); + auto* vbox_group = new QVBoxLayout(); + auto* scroll_area = new QScrollArea(); for (auto i = 0; i < MAX_SKYLANDERS; i++) { @@ -603,21 +569,21 @@ void SkylanderPortalWindow::CreateMainWindow() add_line(vbox_group); } - QHBoxLayout* hbox_skylander = new QHBoxLayout(); - QLabel* label_skyname = new QLabel(QString(tr("Skylander %1")).arg(i + 1)); - edit_skylanders[i] = new QLineEdit(); - edit_skylanders[i]->setEnabled(false); + auto* hbox_skylander = new QHBoxLayout(); + auto* label_skyname = new QLabel(QString(tr("Skylander %1")).arg(i + 1)); + m_edit_skylanders[i] = new QLineEdit(); + m_edit_skylanders[i]->setEnabled(false); - QPushButton* clear_btn = new QPushButton(tr("Clear")); - QPushButton* create_btn = new QPushButton(tr("Create")); - QPushButton* load_btn = new QPushButton(tr("Load")); + auto* clear_btn = new QPushButton(tr("Clear")); + auto* create_btn = new QPushButton(tr("Create")); + auto* load_btn = new QPushButton(tr("Load")); connect(clear_btn, &QAbstractButton::clicked, this, [this, i]() { ClearSkylander(i); }); connect(create_btn, &QAbstractButton::clicked, this, [this, i]() { CreateSkylander(i); }); connect(load_btn, &QAbstractButton::clicked, this, [this, i]() { LoadSkylander(i); }); 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(create_btn); hbox_skylander->addWidget(load_btn); @@ -625,12 +591,12 @@ void SkylanderPortalWindow::CreateMainWindow() vbox_group->addLayout(hbox_skylander); } - group_skylanders->setLayout(vbox_group); - scroll_area->setWidget(group_skylanders); + m_group_skylanders->setLayout(vbox_group); + scroll_area->setWidget(m_group_skylanders); scroll_area->setWidgetResizable(true); - group_skylanders->setVisible(false); - mainLayout->addWidget(scroll_area); - setLayout(mainLayout); + m_group_skylanders->setVisible(Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL)); + main_layout->addWidget(scroll_area); + setLayout(main_layout); UpdateEdits(); } @@ -639,29 +605,29 @@ void SkylanderPortalWindow::OnEmulationStateChanged(Core::State state) { const bool running = state != Core::State::Uninitialized; - checkbox->setEnabled(!running); + m_checkbox->setEnabled(!running); } CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Skylander Creator")); - setObjectName(QString::fromStdString("skylanders_creator")); + setObjectName(tr("skylanders_creator")); setMinimumSize(QSize(500, 150)); - QVBoxLayout* layout = new QVBoxLayout; + auto* layout = new QVBoxLayout; - QComboBox* combo_skylist = new QComboBox(); + auto* combo_skylist = new QComboBox(); QStringList filterlist; for (const auto& entry : list_skylanders) { const uint qvar = (entry.first.first << 16) | entry.first.second; - combo_skylist->addItem(QString::fromStdString(entry.second), QVariant(qvar)); - filterlist << QString::fromStdString(entry.second.c_str()); + combo_skylist->addItem(tr(entry.second), QVariant(qvar)); + filterlist << tr(entry.second); } combo_skylist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF)); combo_skylist->setEditable(true); 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->setCompletionMode(QCompleter::PopupCompletion); co_compl->setFilterMode(Qt::MatchContains); @@ -669,18 +635,17 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) layout->addWidget(combo_skylist); - QFrame* line = new QFrame(); + auto* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); layout->addWidget(line); - QHBoxLayout* hbox_idvar = new QHBoxLayout(); - QLabel* label_id = new QLabel(tr("ID:")); - QLabel* label_var = new QLabel(tr("Variant:")); - QLineEdit* edit_id = new QLineEdit(QString::fromStdString("0")); - QLineEdit* edit_var = new QLineEdit(QString::fromStdString("0")); - QRegularExpressionValidator* rxv = - new QRegularExpressionValidator(QRegularExpression(QString::fromStdString("\\d*")), this); + auto* hbox_idvar = new QHBoxLayout(); + auto* label_id = new QLabel(tr("ID:")); + 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); edit_id->setValidator(rxv); edit_var->setValidator(rxv); hbox_idvar->addWidget(label_id); @@ -689,13 +654,9 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) hbox_idvar->addWidget(edit_var); layout->addLayout(hbox_idvar); - QHBoxLayout* hbox_buttons = new QHBoxLayout(); - QPushButton* btn_create = new QPushButton(tr("Create"), this); - QPushButton* btn_cancel = new QPushButton(tr("Cancel"), this); - hbox_buttons->addStretch(); - hbox_buttons->addWidget(btn_create); - hbox_buttons->addWidget(btn_cancel); - layout->addLayout(hbox_buttons); + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->button(QDialogButtonBox::Ok)->setText(tr("Create")); + layout->addWidget(buttons); 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; const u16 sky_id = edit_id->text().toUShort(&ok_id); if (!ok_id) @@ -728,61 +689,40 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) 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)); 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 { - QString str = QString::fromStdString("Unknown(%1 %2).sky"); + QString str = tr("Unknown(%1 %2).sky"); predef_name += str.arg(sky_id, sky_var); } - file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name, - tr("Skylander Object (*.sky);;")); - if (file_path.isEmpty()) + m_file_path = QFileDialog::getSaveFileName(this, tr("Create Skylander File"), predef_name, + tr("Skylander Object (*.sky);;")); + if (m_file_path.isEmpty()) { return; } - File::IOFile sky_file(file_path.toStdString(), "w+b"); - if (!sky_file) + auto& system = Core::System::GetInstance(); + + 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(file_path), + tr("Failed to create skylander file:\n%1").arg(m_file_path), QMessageBox::Ok); return; } - - std::array 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("/"); + s_last_skylander_path = QFileInfo(m_file_path).absolutePath() + tr("/"); accept(); }); - connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) { @@ -793,13 +733,13 @@ CreateSkylanderDialog::CreateSkylanderDialog(QWidget* parent) : QDialog(parent) QString CreateSkylanderDialog::GetFilePath() const { - return file_path; + return m_file_path; } void SkylanderPortalWindow::EmulatePortal(bool emulate) { Config::SetBaseOrCurrent(Config::MAIN_EMULATE_SKYLANDER_PORTAL, emulate); - group_skylanders->setVisible(emulate); + m_group_skylanders->setVisible(emulate); } void SkylanderPortalWindow::CreateSkylander(u8 slot) @@ -814,12 +754,12 @@ void SkylanderPortalWindow::CreateSkylander(u8 slot) void SkylanderPortalWindow::LoadSkylander(u8 slot) { 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()) { return; } - last_skylander_path = QFileInfo(file_path).absolutePath() + QString::fromStdString("/"); + s_last_skylander_path = QFileInfo(file_path).absolutePath() + tr("/"); 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], file_data[0x1C]); - u8 portal_slot = IOS::HLE::USB::g_skyportal.LoadSkylander(file_data.data(), std::move(sky_file)); - sky_slots[slot] = std::tuple(portal_slot, sky_id, sky_var); + auto& system = Core::System::GetInstance(); + 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(); } 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(); - IOS::HLE::USB::g_skyportal.RemoveSkylander(cur_slot); - sky_slots[slot] = {}; + system.GetSkylanderPortal().RemoveSkylander(slot_infos->portal_slot); + m_sky_slots[slot].reset(); UpdateEdits(); } } @@ -881,26 +828,24 @@ void SkylanderPortalWindow::UpdateEdits() for (auto i = 0; i < MAX_SKYLANDERS; i++) { 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(sky_id, sky_var)); + auto found_sky = list_skylanders.find(std::make_pair(sd->sky_id, sd->sky_var)); if (found_sky != list_skylanders.end()) { - display_string = QString::fromStdString(found_sky->second); + display_string = tr(found_sky->second); } else { - display_string = - QString(QString::fromStdString("Unknown (Id:%1 Var:%2)")).arg(sky_id).arg(sky_var); + display_string = QString(tr("Unknown (Id:%1 Var:%2)")).arg(sd->sky_id).arg(sd->sky_var); } } else { - display_string = QString::fromStdString("None"); + display_string = tr("None"); } - edit_skylanders[i]->setText(display_string); + m_edit_skylanders[i]->setText(display_string); } } diff --git a/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.h b/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.h index 1a3fda52a8..320d524710 100644 --- a/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.h +++ b/Source/Core/DolphinQt/SkylanderPortal/SkylanderPortalWindow.h @@ -1,32 +1,39 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #pragma once +#include #include -#include #include -#include -#include +#include #include #include "Core/Core.h" #include "Core/IOS/USB/Emulated/Skylander.h" -class QDialogButtonBox; -class QLabel; -class QPushButton; -class QSpinBox; -class QTabWidget; +class QCheckBox; +class QGroupBox; +class QLineEdit; + +struct Skylander +{ + u8 portal_slot; + u16 sky_id; + u16 sky_var; +}; class SkylanderPortalWindow : public QWidget { Q_OBJECT public: explicit SkylanderPortalWindow(QWidget* parent = nullptr); - ~SkylanderPortalWindow(); + ~SkylanderPortalWindow() override; protected: - QLineEdit* edit_skylanders[MAX_SKYLANDERS]{}; - static std::optional> sky_slots[MAX_SKYLANDERS]; + std::array m_edit_skylanders; + std::array, MAX_SKYLANDERS> m_sky_slots; private: void CreateMainWindow(); @@ -38,13 +45,10 @@ private: void LoadSkylanderPath(u8 slot, const QString& path); void UpdateEdits(); void closeEvent(QCloseEvent* bar) override; - - static SkylanderPortalWindow* inst; - - QCheckBox* checkbox; - QGroupBox* group_skylanders; - bool eventFilter(QObject* object, QEvent* event) final override; + + QCheckBox* m_checkbox; + QGroupBox* m_group_skylanders; }; class CreateSkylanderDialog : public QDialog @@ -56,5 +60,5 @@ public: QString GetFilePath() const; protected: - QString file_path; + QString m_file_path; };