From 011f7789e040f581e3fe2d42ba7262b23132559b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 25 Feb 2021 16:18:58 +0100 Subject: [PATCH 1/9] IOS: Clear 0-0x3fff when setting up low MEM1 constants Low MEM1 is cleared by IOS before all the other constants are written. This will overwrite the Gecko code handler but it should be fine because HLE::Reload (which will set up the code handler hook again) will be called after a title change is detected. --- Source/Core/Core/IOS/IOS.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 50b586e343..b8301b3f66 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -137,6 +137,12 @@ static bool SetupMemory(u64 ios_title_id, MemorySetupType setup_type) return true; } + // This region is typically used to store constants (e.g. game ID, console type, ...) + // and system information (see below). + constexpr u32 LOW_MEM1_REGION_START = 0; + constexpr u32 LOW_MEM1_REGION_SIZE = 0x3fff; + Memory::Memset(LOW_MEM1_REGION_START, 0, LOW_MEM1_REGION_SIZE); + Memory::Write_U32(target_imv->mem1_physical_size, ADDR_MEM1_SIZE); Memory::Write_U32(target_imv->mem1_simulated_size, ADDR_MEM1_SIM_SIZE); Memory::Write_U32(target_imv->mem1_end, ADDR_MEM1_END); From 688bd6141a9471eebb65c3e2f3a17d34876df970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Wed, 24 Feb 2021 17:56:19 +0100 Subject: [PATCH 2/9] IOS: Emulate BootstrapPPC syscall delays Reading the boot content from the NAND takes a non-negligible amount of time and the PPC should be held in reset while the DOL is being read. --- Source/Core/Core/IOS/IOS.cpp | 61 ++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index b8301b3f66..b927b0947b 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -67,6 +67,7 @@ constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL; constexpr u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL; static CoreTiming::EventType* s_event_enqueue; static CoreTiming::EventType* s_event_sdio_notify; +static CoreTiming::EventType* s_event_finish_ppc_bootstrap; constexpr u32 ADDR_MEM1_SIZE = 0x3100; constexpr u32 ADDR_MEM1_SIM_SIZE = 0x3104; @@ -176,6 +177,28 @@ static bool SetupMemory(u64 ios_title_id, MemorySetupType setup_type) return true; } +// On a real console, the Starlet resets the PPC and holds it in reset limbo +// by asserting the PPC's HRESET signal (via HW_RESETS). +// We will simulate that by resetting MSR and putting the PPC into an infinite loop. +// The memory write will not be observable since the PPC is not running any code... +static void ResetAndPausePPC() +{ + // This should be cleared when the PPC is released so that the write is not observable. + Memory::Write_U32(0x48000000, 0x00000000); // b 0x0 + PowerPC::Reset(); + PC = 0; +} + +static void ReleasePPC() +{ + Memory::Write_U32(0, 0); + // HLE the bootstub that jumps to 0x3400. + // NAND titles start with address translation off at 0x3400 (via the PPC bootstub) + // The state of other CPU registers (like the BAT registers) doesn't matter much + // because the realmode code at 0x3400 initializes everything itself anyway. + PC = 0x3400; +} + void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type) { // Don't touch anything if the feature isn't enabled. @@ -323,18 +346,19 @@ u16 Kernel::GetGidForPPC() const return m_ppc_gid; } -static std::vector ReadBootContent(FS::FileSystem* fs, const std::string& path, size_t max_size) +static std::vector ReadBootContent(FSDevice* fs, const std::string& path, size_t max_size, + Ticks ticks = {}) { - const auto file = fs->OpenFile(0, 0, path, FS::Mode::Read); - if (!file) + const s64 fd = fs->Open(0, 0, path, FS::Mode::Read, {}, ticks); + if (fd < 0) return {}; - const size_t file_size = file->GetStatus()->size; + const size_t file_size = fs->GetFileStatus(fd, ticks)->size; if (max_size != 0 && file_size > max_size) return {}; std::vector buffer(file_size); - if (!file->Read(buffer.data(), buffer.size())) + if (!fs->Read(fd, buffer.data(), buffer.size(), ticks)) return {}; return buffer; } @@ -343,7 +367,10 @@ static std::vector ReadBootContent(FS::FileSystem* fs, const std::string& pa // Unlike 0x42, IOS will set up some constants in memory before booting the PPC. bool Kernel::BootstrapPPC(const std::string& boot_content_path) { - const DolReader dol{ReadBootContent(m_fs.get(), boot_content_path, 0)}; + // Seeking and processing overhead is ignored as most time is spent reading from the NAND. + u64 ticks = 0; + + const DolReader dol{ReadBootContent(GetFSDevice().get(), boot_content_path, 0, &ticks)}; if (!dol.IsValid()) return false; @@ -351,15 +378,14 @@ bool Kernel::BootstrapPPC(const std::string& boot_content_path) if (!SetupMemory(m_title_id, MemorySetupType::Full)) return false; + // Reset the PPC and pause its execution until we're ready. + ResetAndPausePPC(); + if (!dol.LoadIntoMemory()) return false; - // NAND titles start with address translation off at 0x3400 (via the PPC bootstub) - // The state of other CPU registers (like the BAT registers) doesn't matter much - // because the realmode code at 0x3400 initializes everything itself anyway. - MSR.Hex = 0; - PC = 0x3400; - + INFO_LOG_FMT(IOS, "BootstrapPPC: {}", boot_content_path); + CoreTiming::ScheduleEvent(ticks, s_event_finish_ppc_bootstrap); return true; } @@ -402,7 +428,7 @@ bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_pat // Load the ARM binary to memory (if possible). // Because we do not actually emulate the Starlet, only load the sections that are in MEM1. - ARMBinary binary{ReadBootContent(m_fs.get(), boot_content_path, 0xB00000)}; + ARMBinary binary{ReadBootContent(GetFSDevice().get(), boot_content_path, 0xB00000)}; if (!binary.IsValid()) return false; @@ -815,6 +841,12 @@ IOSC& Kernel::GetIOSC() return m_iosc; } +static void FinishPPCBootstrap(u64 userdata, s64 cycles_late) +{ + ReleasePPC(); + INFO_LOG_FMT(IOS, "Bootstrapping done."); +} + void Init() { s_event_enqueue = CoreTiming::RegisterEvent("IPCEvent", [](u64 userdata, s64) { @@ -832,6 +864,9 @@ void Init() device->EventNotify(); }); + s_event_finish_ppc_bootstrap = + CoreTiming::RegisterEvent("IOSFinishPPCBootstrap", FinishPPCBootstrap); + DIDevice::s_finish_executing_di_command = CoreTiming::RegisterEvent("FinishDICommand", DIDevice::FinishDICommandCallback); From 0da5ea86a334d24608a07b6a85da179a3172af13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 26 Feb 2021 13:59:11 +0100 Subject: [PATCH 3/9] IOS: Emulate ES boot timings --- Source/Core/Core/IOS/ES/ES.cpp | 45 ++++++++++++++++++++++++++++++++++ Source/Core/Core/IOS/ES/ES.h | 5 ++++ Source/Core/Core/IOS/IOS.cpp | 3 +++ 3 files changed, 53 insertions(+) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 5ce32591c8..dce4799956 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -18,6 +18,8 @@ #include "Common/StringUtil.h" #include "Core/CommonTitles.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/FS/FileSystem.h" @@ -54,6 +56,25 @@ constexpr std::array s_directories_to_create = {{ {"/meta", 0, public_modes, SYSMENU_UID, SYSMENU_GID}, {"/wfs", 0, {FS::Mode::ReadWrite, FS::Mode::None, FS::Mode::None}, PID_UNKNOWN, PID_UNKNOWN}, }}; + +CoreTiming::EventType* s_finish_init_event; + +constexpr SystemTimers::TimeBaseTick GetESBootTicks(u32 ios_version) +{ + if (ios_version < 28) + return 22'000'000_tbticks; + + // Starting from IOS28, ES needs to load additional modules when it starts + // since the main ELF only contains the kernel and core modules. + if (ios_version < 57) + return 33'000'000_tbticks; + + // These versions have extra modules that make them noticeably slower to load. + if (ios_version == 57 || ios_version == 58 || ios_version == 59) + return 39'000'000_tbticks; + + return 37'000'000_tbticks; +} } // namespace ESDevice::ESDevice(Kernel& ios, const std::string& device_name) : Device(ios, device_name) @@ -77,6 +98,30 @@ ESDevice::ESDevice(Kernel& ios, const std::string& device_name) : Device(ios, de FinishAllStaleImports(); + if (Core::IsRunningAndStarted()) + { + CoreTiming::RemoveEvent(s_finish_init_event); + CoreTiming::ScheduleEvent(GetESBootTicks(m_ios.GetVersion()), s_finish_init_event); + } + else + { + FinishInit(); + } +} + +void ESDevice::InitializeEmulationState() +{ + s_finish_init_event = CoreTiming::RegisterEvent( + "IOS-ESFinishInit", [](u64, s64) { GetIOS()->GetES()->FinishInit(); }); +} + +void ESDevice::FinalizeEmulationState() +{ + s_finish_init_event = nullptr; +} + +void ESDevice::FinishInit() +{ if (s_title_to_launch != 0) { NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload."); diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 989474749d..3f79211efa 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -43,6 +43,9 @@ class ESDevice final : public Device public: ESDevice(Kernel& ios, const std::string& device_name); + static void InitializeEmulationState(); + static void FinalizeEmulationState(); + ReturnCode DIVerify(const ES::TMDReader& tmd, const ES::TicketReader& ticket); bool LaunchTitle(u64 title_id, bool skip_reload = false); @@ -364,6 +367,8 @@ private: void FinishStaleImport(u64 title_id); void FinishAllStaleImports(); + void FinishInit(); + std::string GetContentPath(u64 title_id, const ES::Content& content, Ticks ticks = {}) const; struct OpenedContent diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index b927b0947b..82d02328fd 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -864,6 +864,8 @@ void Init() device->EventNotify(); }); + ESDevice::InitializeEmulationState(); + s_event_finish_ppc_bootstrap = CoreTiming::RegisterEvent("IOSFinishPPCBootstrap", FinishPPCBootstrap); @@ -883,6 +885,7 @@ void Init() void Shutdown() { s_ios.reset(); + ESDevice::FinalizeEmulationState(); } EmulationKernel* GetIOS() From 820c4836d7ec5fb623bd08762c8c3d81de701f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 25 Feb 2021 17:41:14 +0100 Subject: [PATCH 4/9] IOS: Simplify IPC initialisation The extra IPC ack is triggered by a syscall that is invoked in ES's main function; the syscall literally just sets Y2, IX1 and IX2 in HW_IPC_ARMCTRL -- there is no complicated ack queue or anything. --- Source/Core/Core/IOS/ES/ES.cpp | 2 ++ Source/Core/Core/IOS/IOS.cpp | 31 ++++++++++--------------------- Source/Core/Core/IOS/IOS.h | 3 +-- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index dce4799956..da09d5e036 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -122,6 +122,8 @@ void ESDevice::FinalizeEmulationState() void ESDevice::FinishInit() { + m_ios.InitIPC(); + if (s_title_to_launch != 0) { NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload."); diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 82d02328fd..b3e6c3a863 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -64,7 +64,6 @@ namespace IOS::HLE static std::unique_ptr s_ios; constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL; -constexpr u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL; static CoreTiming::EventType* s_event_enqueue; static CoreTiming::EventType* s_event_sdio_notify; static CoreTiming::EventType* s_event_finish_ppc_bootstrap; @@ -289,9 +288,6 @@ EmulationKernel::EmulationKernel(u64 title_id) : Kernel(title_id) return; } - // IOS re-inits IPC and sends a dummy ack during its boot process. - EnqueueIPCAcknowledgement(0); - AddCoreDevices(); AddStaticDevices(); } @@ -443,6 +439,15 @@ bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_pat return true; } +void Kernel::InitIPC() +{ + if (s_ios == nullptr) + return; + + INFO_LOG_FMT(IOS, "IPC initialised."); + GenerateAck(0); +} + void Kernel::AddDevice(std::unique_ptr device) { ASSERT(device->GetDeviceType() == Device::DeviceType::Static); @@ -690,17 +695,9 @@ void Kernel::EnqueueIPCReply(const Request& request, const s32 return_value, s64 CoreTiming::ScheduleEvent(cycles_in_future, s_event_enqueue, request.address, from); } -void Kernel::EnqueueIPCAcknowledgement(u32 address, int cycles_in_future) -{ - CoreTiming::ScheduleEvent(cycles_in_future, s_event_enqueue, - address | ENQUEUE_ACKNOWLEDGEMENT_FLAG); -} - void Kernel::HandleIPCEvent(u64 userdata) { - if (userdata & ENQUEUE_ACKNOWLEDGEMENT_FLAG) - m_ack_queue.push_back(static_cast(userdata)); - else if (userdata & ENQUEUE_REQUEST_FLAG) + if (userdata & ENQUEUE_REQUEST_FLAG) m_request_queue.push_back(static_cast(userdata)); else m_reply_queue.push_back(static_cast(userdata)); @@ -730,14 +727,6 @@ void Kernel::UpdateIPC() m_reply_queue.pop_front(); return; } - - if (!m_ack_queue.empty()) - { - GenerateAck(m_ack_queue.front()); - WARN_LOG_FMT(IOS, "<<-- Double-ack to IPC Request @ {:#010x}", m_ack_queue.front()); - m_ack_queue.pop_front(); - return; - } } void Kernel::UpdateDevices() diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index a3a82bdb49..87d8d33b9a 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -133,6 +133,7 @@ public: bool BootstrapPPC(const std::string& boot_content_path); bool BootIOS(u64 ios_title_id, const std::string& boot_content_path = ""); + void InitIPC(); u32 GetVersion() const; IOSC& GetIOSC(); @@ -142,7 +143,6 @@ protected: void ExecuteIPCCommand(u32 address); std::optional HandleIPCCommand(const Request& request); - void EnqueueIPCAcknowledgement(u32 address, int cycles_in_future = 0); void AddDevice(std::unique_ptr device); void AddCoreDevices(); @@ -165,7 +165,6 @@ protected: using IPCMsgQueue = std::deque; IPCMsgQueue m_request_queue; // ppc -> arm IPCMsgQueue m_reply_queue; // arm -> ppc - IPCMsgQueue m_ack_queue; // arm -> ppc u64 m_last_reply_time = 0; IOSC m_iosc; From a658cbce167d39a30005deb6208a3dcf798a1691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 26 Feb 2021 15:21:36 +0100 Subject: [PATCH 5/9] IOS: Emulate IOS boot timings --- Source/Core/Core/IOS/IOS.cpp | 34 ++++++++++++++++++++++++++++++---- Source/Core/Core/IOS/IOS.h | 1 + Source/Core/Core/State.cpp | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index b3e6c3a863..d5a26a06e1 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -67,6 +67,7 @@ constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL; static CoreTiming::EventType* s_event_enqueue; static CoreTiming::EventType* s_event_sdio_notify; static CoreTiming::EventType* s_event_finish_ppc_bootstrap; +static CoreTiming::EventType* s_event_finish_ios_boot; constexpr u32 ADDR_MEM1_SIZE = 0x3100; constexpr u32 ADDR_MEM1_SIM_SIZE = 0x3104; @@ -410,6 +411,21 @@ private: std::vector m_bytes; }; +static void FinishIOSBoot(u64 ios_title_id) +{ + // Shut down the active IOS first before switching to the new one. + s_ios.reset(); + s_ios = std::make_unique(ios_title_id); +} + +static constexpr SystemTimers::TimeBaseTick GetIOSBootTicks(u32 version) +{ + // Older IOS versions are monolithic so the main ELF is much larger and takes longer to load. + if (version < 28) + return 16'000'000_tbticks; + return 2'600'000_tbticks; +} + // Similar to syscall 0x42 (ios_boot); this is used to change the current active IOS. // IOS writes the new version to 0x3140 before restarting, but it does *not* poke any // of the other constants to the memory. Warning: this resets the kernel instance. @@ -419,6 +435,10 @@ private: // on the NAND, or the call will fail like on a Wii. bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_path) { + // IOS suspends regular PPC<->ARM IPC before loading a new IOS. + // IPC is not resumed if the boot fails for any reason. + m_ipc_paused = true; + if (!boot_content_path.empty()) { // Load the ARM binary to memory (if possible). @@ -433,9 +453,11 @@ bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_pat return false; } - // Shut down the active IOS first before switching to the new one. - s_ios.reset(); - s_ios = std::make_unique(ios_title_id); + if (Core::IsRunningAndStarted()) + CoreTiming::ScheduleEvent(GetIOSBootTicks(GetVersion()), s_event_finish_ios_boot, ios_title_id); + else + FinishIOSBoot(ios_title_id); + return true; } @@ -707,7 +729,7 @@ void Kernel::HandleIPCEvent(u64 userdata) void Kernel::UpdateIPC() { - if (!IsReady()) + if (m_ipc_paused || !IsReady()) return; if (!m_request_queue.empty()) @@ -761,6 +783,7 @@ void Kernel::DoState(PointerWrap& p) p.Do(m_request_queue); p.Do(m_reply_queue); p.Do(m_last_reply_time); + p.Do(m_ipc_paused); p.Do(m_title_id); p.Do(m_ppc_uid); p.Do(m_ppc_gid); @@ -858,6 +881,9 @@ void Init() s_event_finish_ppc_bootstrap = CoreTiming::RegisterEvent("IOSFinishPPCBootstrap", FinishPPCBootstrap); + s_event_finish_ios_boot = CoreTiming::RegisterEvent( + "IOSFinishIOSBoot", [](u64 ios_title_id, s64) { FinishIOSBoot(ios_title_id); }); + DIDevice::s_finish_executing_di_command = CoreTiming::RegisterEvent("FinishDICommand", DIDevice::FinishDICommandCallback); diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index 87d8d33b9a..917f4ffa6e 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -166,6 +166,7 @@ protected: IPCMsgQueue m_request_queue; // ppc -> arm IPCMsgQueue m_reply_queue; // arm -> ppc u64 m_last_reply_time = 0; + bool m_ipc_paused = false; IOSC m_iosc; std::shared_ptr m_fs; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 3d20c0fb05..8e832d8e28 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 129; // Last changed in PR 9511 +constexpr u32 STATE_VERSION = 130; // Last changed in PR 9545 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, From 19667cb801f032723095100b6c97fa1d43d279fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 26 Feb 2021 16:20:24 +0100 Subject: [PATCH 6/9] Fix symbol map being loaded too early during title changes We should only try to load a symbol map for the new title *after* it has been loaded into memory, not before. Likewise for applying HLE patches and loading new custom textures. In practice, loading/repatching too early was only a problem for titles that are launched via ES_Launch. This commit fixes that. --- Source/Core/Core/Boot/Boot.cpp | 28 +++++++++++++++------------- Source/Core/Core/ConfigManager.cpp | 26 +++++++++++++++----------- Source/Core/Core/ConfigManager.h | 4 ++++ Source/Core/Core/IOS/IOS.cpp | 1 + 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index b56d7981cf..8dc65b1090 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -436,11 +436,7 @@ bool CBoot::BootUp(std::unique_ptr boot) if (!EmulatedBS2(config.bWii, *volume)) return false; - // Try to load the symbol map if there is one, and then scan it for - // and eventually replace code - if (LoadMapFromFilename()) - HLE::PatchFunctions(); - + SConfig::OnNewTitleLoad(); return true; } @@ -482,9 +478,11 @@ bool CBoot::BootUp(std::unique_ptr boot) SetupGCMemory(); } + SConfig::OnNewTitleLoad(); + PC = executable.reader->GetEntryPoint(); - if (executable.reader->LoadSymbols() || LoadMapFromFilename()) + if (executable.reader->LoadSymbols()) { UpdateDebugger_MapLoaded(); HLE::PatchFunctions(); @@ -495,13 +493,21 @@ bool CBoot::BootUp(std::unique_ptr boot) bool operator()(const DiscIO::VolumeWAD& wad) const { SetDefaultDisc(); - return Boot_WiiWAD(wad); + if (!Boot_WiiWAD(wad)) + return false; + + SConfig::OnNewTitleLoad(); + return true; } bool operator()(const BootParameters::NANDTitle& nand_title) const { SetDefaultDisc(); - return BootNANDTitle(nand_title.id); + if (!BootNANDTitle(nand_title.id)) + return false; + + SConfig::OnNewTitleLoad(); + return true; } bool operator()(const BootParameters::IPL& ipl) const @@ -525,9 +531,7 @@ bool CBoot::BootUp(std::unique_ptr boot) SetDisc(DiscIO::CreateDisc(ipl.disc->path), ipl.disc->auto_disc_change_paths); } - if (LoadMapFromFilename()) - HLE::PatchFunctions(); - + SConfig::OnNewTitleLoad(); return true; } @@ -544,8 +548,6 @@ bool CBoot::BootUp(std::unique_ptr boot) if (!std::visit(BootTitle(), boot->parameters)) return false; - PatchEngine::LoadPatches(); - HLE::PatchFixedFunctions(); return true; } diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index accc958615..d4ec804b3a 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -706,19 +706,23 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision)); if (Core::IsRunning()) - { - // TODO: have a callback mechanism for title changes? - if (!g_symbolDB.IsEmpty()) - { - g_symbolDB.Clear(); - Host_NotifyMapLoaded(); - } - CBoot::LoadMapFromFilename(); - HLE::Reload(); - PatchEngine::Reload(); - HiresTexture::Update(); DolphinAnalytics::Instance().ReportGameStart(); +} + +void SConfig::OnNewTitleLoad() +{ + if (!Core::IsRunning()) + return; + + if (!g_symbolDB.IsEmpty()) + { + g_symbolDB.Clear(); + Host_NotifyMapLoaded(); } + CBoot::LoadMapFromFilename(); + HLE::Reload(); + PatchEngine::Reload(); + HiresTexture::Update(); } void SConfig::LoadDefaults() diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index d1de769706..8d67d35ffd 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -197,6 +197,10 @@ struct SConfig void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition); void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform); void SetRunningGameMetadata(const std::string& game_id); + // Reloads title-specific map files, patches, custom textures, etc. + // This should only be called after the new title has been loaded into memory. + static void OnNewTitleLoad(); + void LoadDefaults(); static std::string MakeGameID(std::string_view file_name); // Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index d5a26a06e1..b7b5a34e15 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -856,6 +856,7 @@ IOSC& Kernel::GetIOSC() static void FinishPPCBootstrap(u64 userdata, s64 cycles_late) { ReleasePPC(); + SConfig::OnNewTitleLoad(); INFO_LOG_FMT(IOS, "Bootstrapping done."); } From 93f0d122c0d424e0f34af3681fff3667268ad191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 26 Feb 2021 17:11:47 +0100 Subject: [PATCH 7/9] IOS: Hang PPC when reloading IOS for a PPC title launch The PPC is supposed to be held in reset when another version of IOS is in the process of being launched for a PPC title launch. Probably doesn't matter in practice, though the inaccuracy was definitely observable from the PPC. --- Source/Core/Core/IOS/ES/ES.cpp | 18 +++++++++--------- Source/Core/Core/IOS/ES/ES.h | 4 ++-- Source/Core/Core/IOS/IOS.cpp | 5 ++++- Source/Core/Core/IOS/IOS.h | 9 ++++++++- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index da09d5e036..2f2b9f0c23 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -127,7 +127,7 @@ void ESDevice::FinishInit() if (s_title_to_launch != 0) { NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload."); - LaunchTitle(s_title_to_launch, true); + LaunchTitle(s_title_to_launch, HangPPC::No, true); s_title_to_launch = 0; } } @@ -270,7 +270,7 @@ IPCReply ESDevice::SetUID(u32 uid, const IOCtlVRequest& request) return IPCReply(IPC_SUCCESS); } -bool ESDevice::LaunchTitle(u64 title_id, bool skip_reload) +bool ESDevice::LaunchTitle(u64 title_id, HangPPC hang_ppc, bool skip_reload) { m_title_context.Clear(); INFO_LOG_FMT(IOS_ES, "ES_Launch: Title context changed: (none)"); @@ -290,15 +290,15 @@ bool ESDevice::LaunchTitle(u64 title_id, bool skip_reload) // likely make the system menu crash. Doing this is okay as anyone who has the shop // also has the system menu installed, and this behaviour is consistent with what // ES does when its DRM system refuses the use of a particular title. - return LaunchTitle(Titles::SYSTEM_MENU); + return LaunchTitle(Titles::SYSTEM_MENU, hang_ppc); } if (IsTitleType(title_id, ES::TitleType::System) && title_id != Titles::SYSTEM_MENU) - return LaunchIOS(title_id); + return LaunchIOS(title_id, hang_ppc); return LaunchPPCTitle(title_id, skip_reload); } -bool ESDevice::LaunchIOS(u64 ios_title_id) +bool ESDevice::LaunchIOS(u64 ios_title_id, HangPPC hang_ppc) { // A real Wii goes through several steps before getting to MIOS. // @@ -311,7 +311,7 @@ bool ESDevice::LaunchIOS(u64 ios_title_id) if (ios_title_id == Titles::BC) { NOTICE_LOG_FMT(IOS, "BC: Launching MIOS..."); - return LaunchIOS(Titles::MIOS); + return LaunchIOS(Titles::MIOS, hang_ppc); } // IOS checks whether the system title is installed and returns an error if it isn't. @@ -323,7 +323,7 @@ bool ESDevice::LaunchIOS(u64 ios_title_id) const ES::TicketReader ticket = FindSignedTicket(ios_title_id); ES::Content content; if (!tmd.IsValid() || !ticket.IsValid() || !tmd.GetContent(tmd.GetBootIndex(), &content) || - !m_ios.BootIOS(ios_title_id, GetContentPath(ios_title_id, content))) + !m_ios.BootIOS(ios_title_id, hang_ppc, GetContentPath(ios_title_id, content))) { PanicAlertFmtT("Could not launch IOS {0:016x} because it is missing from the NAND.\n" "The emulated software will likely hang now.", @@ -333,7 +333,7 @@ bool ESDevice::LaunchIOS(u64 ios_title_id) return true; } - return m_ios.BootIOS(ios_title_id); + return m_ios.BootIOS(ios_title_id, hang_ppc); } bool ESDevice::LaunchPPCTitle(u64 title_id, bool skip_reload) @@ -364,7 +364,7 @@ bool ESDevice::LaunchPPCTitle(u64 title_id, bool skip_reload) { s_title_to_launch = title_id; const u64 required_ios = tmd.GetIOSId(); - return LaunchTitle(required_ios); + return LaunchTitle(required_ios, HangPPC::Yes); } m_title_context.Update(tmd, ticket, DiscIO::Platform::WiiWAD); diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 3f79211efa..6d074b390a 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -47,7 +47,7 @@ public: static void FinalizeEmulationState(); ReturnCode DIVerify(const ES::TMDReader& tmd, const ES::TicketReader& ticket); - bool LaunchTitle(u64 title_id, bool skip_reload = false); + bool LaunchTitle(u64 title_id, HangPPC hang_ppc = HangPPC::No, bool skip_reload = false); void DoState(PointerWrap& p) override; @@ -346,7 +346,7 @@ private: ContextArray::iterator FindActiveContext(s32 fd); ContextArray::iterator FindInactiveContext(); - bool LaunchIOS(u64 ios_title_id); + bool LaunchIOS(u64 ios_title_id, HangPPC hang_ppc); bool LaunchPPCTitle(u64 title_id, bool skip_reload); bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const; diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index b7b5a34e15..8cb05aa08a 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -433,7 +433,7 @@ static constexpr SystemTimers::TimeBaseTick GetIOSBootTicks(u32 version) // Passing a boot content path is optional because we do not require IOSes // to be installed at the moment. If one is passed, the boot binary must exist // on the NAND, or the call will fail like on a Wii. -bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_path) +bool Kernel::BootIOS(const u64 ios_title_id, HangPPC hang_ppc, const std::string& boot_content_path) { // IOS suspends regular PPC<->ARM IPC before loading a new IOS. // IPC is not resumed if the boot fails for any reason. @@ -453,6 +453,9 @@ bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_pat return false; } + if (hang_ppc == HangPPC::Yes) + ResetAndPausePPC(); + if (Core::IsRunningAndStarted()) CoreTiming::ScheduleEvent(GetIOSBootTicks(GetVersion()), s_event_finish_ios_boot, ios_title_id); else diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index 917f4ffa6e..8da7f497e9 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -97,6 +97,12 @@ enum class MemorySetupType Full, }; +enum class HangPPC : bool +{ + No = false, + Yes = true, +}; + void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type); void WriteReturnValue(s32 value, u32 address); @@ -132,7 +138,8 @@ public: u16 GetGidForPPC() const; bool BootstrapPPC(const std::string& boot_content_path); - bool BootIOS(u64 ios_title_id, const std::string& boot_content_path = ""); + bool BootIOS(u64 ios_title_id, HangPPC hang_ppc = HangPPC::No, + const std::string& boot_content_path = {}); void InitIPC(); u32 GetVersion() const; From bdaac718acf553da4b5f32d77886e212b4bc8967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 27 Feb 2021 21:13:29 +0100 Subject: [PATCH 8/9] IOS/FS: Expose some more ioctls for internal Dolphin use --- Source/Core/Core/IOS/FS/FileSystemProxy.cpp | 52 ++++++++++++++++----- Source/Core/Core/IOS/FS/FileSystemProxy.h | 6 +++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp index bc6c400288..1f8b017abb 100644 --- a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp +++ b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp @@ -610,15 +610,35 @@ IPCReply FSDevice::GetAttribute(const Handle& handle, const IOCtlRequest& reques return GetFSReply(IPC_SUCCESS, ticks); } +FS::ResultCode FSDevice::DeleteFile(FS::Uid uid, FS::Gid gid, const std::string& path, Ticks ticks) +{ + ticks.Add(IPC_OVERHEAD_TICKS); + + const ResultCode result = m_ios.GetFS()->Delete(uid, gid, path); + ticks.Add(GetSuperblockWriteTbTicks(m_ios.GetVersion())); + LogResult(result, "Delete({})", path); + return result; +} + IPCReply FSDevice::DeleteFile(const Handle& handle, const IOCtlRequest& request) { if (request.buffer_in_size < 64) return GetFSReply(ConvertResult(ResultCode::Invalid)); const std::string path = Memory::GetString(request.buffer_in, 64); - const ResultCode result = m_ios.GetFS()->Delete(handle.uid, handle.gid, path); - LogResult(result, "Delete({})", path); - return GetReplyForSuperblockOperation(m_ios.GetVersion(), result); + return MakeIPCReply( + [&](Ticks ticks) { return ConvertResult(DeleteFile(handle.uid, handle.gid, path, ticks)); }); +} + +FS::ResultCode FSDevice::RenameFile(FS::Uid uid, FS::Gid gid, const std::string& old_path, + const std::string& new_path, Ticks ticks) +{ + ticks.Add(IPC_OVERHEAD_TICKS); + + const ResultCode result = m_ios.GetFS()->Rename(uid, gid, old_path, new_path); + ticks.Add(GetSuperblockWriteTbTicks(m_ios.GetVersion())); + LogResult(result, "Rename({}, {})", old_path, new_path); + return result; } IPCReply FSDevice::RenameFile(const Handle& handle, const IOCtlRequest& request) @@ -628,9 +648,20 @@ IPCReply FSDevice::RenameFile(const Handle& handle, const IOCtlRequest& request) const std::string old_path = Memory::GetString(request.buffer_in, 64); const std::string new_path = Memory::GetString(request.buffer_in + 64, 64); - const ResultCode result = m_ios.GetFS()->Rename(handle.uid, handle.gid, old_path, new_path); - LogResult(result, "Rename({}, {})", old_path, new_path); - return GetReplyForSuperblockOperation(m_ios.GetVersion(), result); + return MakeIPCReply([&](Ticks ticks) { + return ConvertResult(RenameFile(handle.uid, handle.gid, old_path, new_path, ticks)); + }); +} + +FS::ResultCode FSDevice::CreateFile(FS::Uid uid, FS::Gid gid, const std::string& path, + FS::FileAttribute attribute, FS::Modes modes, Ticks ticks) +{ + ticks.Add(IPC_OVERHEAD_TICKS); + + const ResultCode result = m_ios.GetFS()->CreateFile(uid, gid, path, attribute, modes); + ticks.Add(GetSuperblockWriteTbTicks(m_ios.GetVersion())); + LogResult(result, "CreateFile({})", path); + return result; } IPCReply FSDevice::CreateFile(const Handle& handle, const IOCtlRequest& request) @@ -638,11 +669,10 @@ IPCReply FSDevice::CreateFile(const Handle& handle, const IOCtlRequest& request) const auto params = GetParams(request); if (!params) return GetFSReply(ConvertResult(params.Error())); - - const ResultCode result = m_ios.GetFS()->CreateFile(handle.uid, handle.gid, params->path, - params->attribute, params->modes); - LogResult(result, "CreateFile({})", params->path); - return GetReplyForSuperblockOperation(m_ios.GetVersion(), result); + return MakeIPCReply([&](Ticks ticks) { + return ConvertResult( + CreateFile(handle.uid, handle.gid, params->path, params->attribute, params->modes)); + }); } IPCReply FSDevice::SetFileVersionControl(const Handle& handle, const IOCtlRequest& request) diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.h b/Source/Core/Core/IOS/FS/FileSystemProxy.h index 101dbdffc2..450422d1b1 100644 --- a/Source/Core/Core/IOS/FS/FileSystemProxy.h +++ b/Source/Core/Core/IOS/FS/FileSystemProxy.h @@ -35,7 +35,13 @@ public: s32 Write(u64 fd, const u8* data, u32 size, std::optional ipc_buffer_addr = {}, Ticks ticks = {}); s32 Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks = {}); + FS::Result GetFileStatus(u64 fd, Ticks ticks = {}); + FS::ResultCode RenameFile(FS::Uid uid, FS::Gid gid, const std::string& old_path, + const std::string& new_path, Ticks ticks = {}); + FS::ResultCode DeleteFile(FS::Uid uid, FS::Gid gid, const std::string& path, Ticks ticks = {}); + FS::ResultCode CreateFile(FS::Uid uid, FS::Gid gid, const std::string& path, + FS::FileAttribute attribute, FS::Modes modes, Ticks ticks = {}); template s32 Read(u64 fd, T* data, size_t count, Ticks ticks = {}) From aef0760efe7e52cd2087e99efa9cb3e9a493353b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 27 Feb 2021 22:04:07 +0100 Subject: [PATCH 9/9] IOS/ES: Emulate /sys/launch.sys for more accurate timings Also gets rid of one static variable --- Source/Core/Core/IOS/ES/ES.cpp | 106 ++++++++++++++++++++++---- Source/Core/Core/IOS/ES/ES.h | 9 ++- Source/Core/Core/IOS/ES/Formats.h | 2 + Source/Core/Core/IOS/ES/NandUtils.cpp | 43 +++++++++++ Source/Core/DiscIO/DirectoryBlob.cpp | 3 +- 5 files changed, 143 insertions(+), 20 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 2f2b9f0c23..2953adbc1d 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -23,6 +23,7 @@ #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/FS/FileSystem.h" +#include "Core/IOS/FS/FileSystemProxy.h" #include "Core/IOS/IOSC.h" #include "Core/IOS/Uids.h" #include "Core/IOS/VersionInfo.h" @@ -32,9 +33,6 @@ namespace IOS::HLE { namespace { -// Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys). -u64 s_title_to_launch; - struct DirectoryToCreate { const char* path; @@ -57,7 +55,13 @@ constexpr std::array s_directories_to_create = {{ {"/wfs", 0, {FS::Mode::ReadWrite, FS::Mode::None, FS::Mode::None}, PID_UNKNOWN, PID_UNKNOWN}, }}; +constexpr const char LAUNCH_FILE_PATH[] = "/sys/launch.sys"; +constexpr const char SPACE_FILE_PATH[] = "/sys/space.sys"; +constexpr size_t SPACE_FILE_SIZE = sizeof(u64) + sizeof(ES::TicketView) + ES::MAX_TMD_SIZE; + CoreTiming::EventType* s_finish_init_event; +CoreTiming::EventType* s_reload_ios_for_ppc_launch_event; +CoreTiming::EventType* s_bootstrap_ppc_for_launch_event; constexpr SystemTimers::TimeBaseTick GetESBootTicks(u32 ios_version) { @@ -113,22 +117,42 @@ void ESDevice::InitializeEmulationState() { s_finish_init_event = CoreTiming::RegisterEvent( "IOS-ESFinishInit", [](u64, s64) { GetIOS()->GetES()->FinishInit(); }); + s_reload_ios_for_ppc_launch_event = + CoreTiming::RegisterEvent("IOS-ESReloadIOSForPPCLaunch", [](u64 ios_id, s64) { + GetIOS()->GetES()->LaunchTitle(ios_id, HangPPC::Yes); + }); + s_bootstrap_ppc_for_launch_event = CoreTiming::RegisterEvent( + "IOS-ESBootstrapPPCForLaunch", [](u64, s64) { GetIOS()->GetES()->BootstrapPPC(); }); } void ESDevice::FinalizeEmulationState() { s_finish_init_event = nullptr; + s_reload_ios_for_ppc_launch_event = nullptr; + s_bootstrap_ppc_for_launch_event = nullptr; } void ESDevice::FinishInit() { m_ios.InitIPC(); - if (s_title_to_launch != 0) + std::optional pending_launch_title_id; + { - NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload."); - LaunchTitle(s_title_to_launch, HangPPC::No, true); - s_title_to_launch = 0; + const auto launch_file = + m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, LAUNCH_FILE_PATH, FS::Mode::Read); + if (launch_file) + { + u64 id; + if (launch_file->Read(&id, 1).Succeeded()) + pending_launch_title_id = id; + } + } + + if (pending_launch_title_id.has_value()) + { + NOTICE_LOG_FMT(IOS, "Re-launching title {:016x} after IOS reload.", *pending_launch_title_id); + LaunchTitle(*pending_launch_title_id, HangPPC::No); } } @@ -270,7 +294,7 @@ IPCReply ESDevice::SetUID(u32 uid, const IOCtlVRequest& request) return IPCReply(IPC_SUCCESS); } -bool ESDevice::LaunchTitle(u64 title_id, HangPPC hang_ppc, bool skip_reload) +bool ESDevice::LaunchTitle(u64 title_id, HangPPC hang_ppc) { m_title_context.Clear(); INFO_LOG_FMT(IOS_ES, "ES_Launch: Title context changed: (none)"); @@ -295,7 +319,7 @@ bool ESDevice::LaunchTitle(u64 title_id, HangPPC hang_ppc, bool skip_reload) if (IsTitleType(title_id, ES::TitleType::System) && title_id != Titles::SYSTEM_MENU) return LaunchIOS(title_id, hang_ppc); - return LaunchPPCTitle(title_id, skip_reload); + return LaunchPPCTitle(title_id); } bool ESDevice::LaunchIOS(u64 ios_title_id, HangPPC hang_ppc) @@ -336,9 +360,24 @@ bool ESDevice::LaunchIOS(u64 ios_title_id, HangPPC hang_ppc) return m_ios.BootIOS(ios_title_id, hang_ppc); } -bool ESDevice::LaunchPPCTitle(u64 title_id, bool skip_reload) +s32 ESDevice::WriteLaunchFile(const ES::TMDReader& tmd, Ticks ticks) { - const ES::TMDReader tmd = FindInstalledTMD(title_id); + m_ios.GetFSDevice()->DeleteFile(PID_KERNEL, PID_KERNEL, SPACE_FILE_PATH, ticks); + + std::vector launch_data(sizeof(u64) + sizeof(ES::TicketView)); + const u64 title_id = tmd.GetTitleId(); + std::memcpy(launch_data.data(), &title_id, sizeof(title_id)); + // We're supposed to write a ticket view here, but we don't use it for anything (other than + // to take up space in the NAND and slow down launches) so don't bother. + launch_data.insert(launch_data.end(), tmd.GetBytes().begin(), tmd.GetBytes().end()); + return WriteSystemFile(LAUNCH_FILE_PATH, launch_data, ticks); +} + +bool ESDevice::LaunchPPCTitle(u64 title_id) +{ + u64 ticks = 0; + + const ES::TMDReader tmd = FindInstalledTMD(title_id, &ticks); const ES::TicketReader ticket = FindSignedTicket(title_id); if (!tmd.IsValid() || !ticket.IsValid()) @@ -359,14 +398,35 @@ bool ESDevice::LaunchPPCTitle(u64 title_id, bool skip_reload) // Before launching a title, IOS first reads the TMD and reloads into the specified IOS version, // even when that version is already running. After it has reloaded, ES_Launch will be called - // again with the reload skipped, and the PPC will be bootstrapped then. - if (!skip_reload) + // again and the PPC will be bootstrapped then. + // + // To keep track of the PPC title launch, a temporary launch file (LAUNCH_FILE_PATH) is used + // to store the title ID of the title to launch and its TMD. + // The launch file not existing means an IOS reload is required. + const auto launch_file_fd = m_ios.GetFSDevice()->Open(PID_KERNEL, PID_KERNEL, LAUNCH_FILE_PATH, + FS::Mode::Read, {}, &ticks); + if (launch_file_fd < 0) { - s_title_to_launch = title_id; + if (WriteLaunchFile(tmd, &ticks) != IPC_SUCCESS) + { + PanicAlertFmt("LaunchPPCTitle: Failed to write launch file"); + return false; + } + const u64 required_ios = tmd.GetIOSId(); - return LaunchTitle(required_ios, HangPPC::Yes); + if (!Core::IsRunningAndStarted()) + return LaunchTitle(required_ios, HangPPC::Yes); + CoreTiming::RemoveEvent(s_reload_ios_for_ppc_launch_event); + CoreTiming::ScheduleEvent(ticks, s_reload_ios_for_ppc_launch_event, required_ios); + return true; } + // Otherwise, assume that the PPC title can now be launched directly. + // Unlike IOS, we won't bother checking the title ID in the launch file. (It's not useful.) + m_ios.GetFSDevice()->Close(launch_file_fd, &ticks); + m_ios.GetFSDevice()->DeleteFile(PID_KERNEL, PID_KERNEL, LAUNCH_FILE_PATH, &ticks); + WriteSystemFile(SPACE_FILE_PATH, std::vector(SPACE_FILE_SIZE), &ticks); + m_title_context.Update(tmd, ticket, DiscIO::Platform::WiiWAD); INFO_LOG_FMT(IOS_ES, "LaunchPPCTitle: Title context changed: {:016x}", tmd.GetTitleId()); @@ -383,7 +443,19 @@ bool ESDevice::LaunchPPCTitle(u64 title_id, bool skip_reload) if (!tmd.GetContent(tmd.GetBootIndex(), &content)) return false; - return m_ios.BootstrapPPC(GetContentPath(tmd.GetTitleId(), content)); + m_pending_ppc_boot_content_path = GetContentPath(tmd.GetTitleId(), content); + if (!Core::IsRunningAndStarted()) + return BootstrapPPC(); + CoreTiming::RemoveEvent(s_bootstrap_ppc_for_launch_event); + CoreTiming::ScheduleEvent(ticks, s_bootstrap_ppc_for_launch_event); + return true; +} + +bool ESDevice::BootstrapPPC() +{ + const bool result = m_ios.BootstrapPPC(m_pending_ppc_boot_content_path); + m_pending_ppc_boot_content_path = {}; + return result; } void ESDevice::Context::DoState(PointerWrap& p) @@ -413,6 +485,8 @@ void ESDevice::DoState(PointerWrap& p) for (auto& context : m_contexts) context.DoState(p); + + p.Do(m_pending_ppc_boot_content_path); } ESDevice::ContextArray::iterator ESDevice::FindActiveContext(s32 fd) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 6d074b390a..b711fd93b9 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -47,7 +47,7 @@ public: static void FinalizeEmulationState(); ReturnCode DIVerify(const ES::TMDReader& tmd, const ES::TicketReader& ticket); - bool LaunchTitle(u64 title_id, HangPPC hang_ppc = HangPPC::No, bool skip_reload = false); + bool LaunchTitle(u64 title_id, HangPPC hang_ppc = HangPPC::No); void DoState(PointerWrap& p) override; @@ -347,7 +347,7 @@ private: ContextArray::iterator FindInactiveContext(); bool LaunchIOS(u64 ios_title_id, HangPPC hang_ppc); - bool LaunchPPCTitle(u64 title_id, bool skip_reload); + bool LaunchPPCTitle(u64 title_id); bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const; ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view, @@ -371,6 +371,10 @@ private: std::string GetContentPath(u64 title_id, const ES::Content& content, Ticks ticks = {}) const; + s32 WriteSystemFile(const std::string& path, const std::vector& data, Ticks ticks = {}); + s32 WriteLaunchFile(const ES::TMDReader& tmd, Ticks ticks = {}); + bool BootstrapPPC(); + struct OpenedContent { bool m_opened = false; @@ -385,5 +389,6 @@ private: ContextArray m_contexts; TitleContext m_title_context{}; + std::string m_pending_ppc_boot_content_path; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 1b1fe6996e..75c77f47b4 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -151,6 +151,8 @@ struct Ticket static_assert(sizeof(Ticket) == 0x2A4, "Ticket has the wrong size"); #pragma pack(pop) +constexpr u32 MAX_TMD_SIZE = 0x49e4; + class SignedBlobReader { public: diff --git a/Source/Core/Core/IOS/ES/NandUtils.cpp b/Source/Core/Core/IOS/ES/NandUtils.cpp index 88cc040552..a96d85c3e2 100644 --- a/Source/Core/Core/IOS/ES/NandUtils.cpp +++ b/Source/Core/Core/IOS/ES/NandUtils.cpp @@ -393,4 +393,47 @@ std::string ESDevice::GetContentPath(const u64 title_id, const ES::Content& cont } return fmt::format("{}/{:08x}.app", Common::GetTitleContentPath(title_id), content.id); } + +s32 ESDevice::WriteSystemFile(const std::string& path, const std::vector& data, Ticks ticks) +{ + auto& fs = *m_ios.GetFSDevice(); + const std::string tmp_path = "/tmp/" + PathToFileName(path); + + auto result = fs.CreateFile(PID_KERNEL, PID_KERNEL, tmp_path, {}, + {FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None}, ticks); + if (result != FS::ResultCode::Success) + { + ERROR_LOG_FMT(IOS_ES, "Failed to create temporary file {}: {}", tmp_path, result); + return FS::ConvertResult(result); + } + + const auto fd = fs.Open(PID_KERNEL, PID_KERNEL, tmp_path, FS::Mode::ReadWrite, {}, ticks); + if (fd < 0) + { + ERROR_LOG_FMT(IOS_ES, "Failed to open temporary file {}: {}", tmp_path, fd); + return fd; + } + + if (fs.Write(fd, data.data(), u32(data.size()), {}, ticks) != s32(data.size())) + { + ERROR_LOG_FMT(IOS_ES, "Failed to write to temporary file {}", tmp_path); + return ES_EIO; + } + + if (const auto ret = fs.Close(fd, ticks); ret != IPC_SUCCESS) + { + ERROR_LOG_FMT(IOS_ES, "Failed to close temporary file {}", tmp_path); + return ret; + } + + result = fs.RenameFile(PID_KERNEL, PID_KERNEL, tmp_path, path, ticks); + if (result != FS::ResultCode::Success) + { + ERROR_LOG_FMT(IOS_ES, "Failed to move launch file to final destination ({}): {}", path, result); + return FS::ConvertResult(result); + } + + return IPC_SUCCESS; +} + } // namespace IOS::HLE diff --git a/Source/Core/DiscIO/DirectoryBlob.cpp b/Source/Core/DiscIO/DirectoryBlob.cpp index 8840283777..5210bf1032 100644 --- a/Source/Core/DiscIO/DirectoryBlob.cpp +++ b/Source/Core/DiscIO/DirectoryBlob.cpp @@ -584,7 +584,6 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition, constexpr u32 TICKET_OFFSET = 0x0; constexpr u32 TICKET_SIZE = 0x2a4; constexpr u32 TMD_OFFSET = 0x2c0; - constexpr u32 MAX_TMD_SIZE = 0x49e4; constexpr u32 H3_OFFSET = 0x4000; constexpr u32 H3_SIZE = 0x18000; @@ -594,7 +593,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition, partition_address + TICKET_OFFSET, TICKET_SIZE, partition_root + "ticket.bin"); const u64 tmd_size = m_nonpartition_contents.CheckSizeAndAdd( - partition_address + TMD_OFFSET, MAX_TMD_SIZE, partition_root + "tmd.bin"); + partition_address + TMD_OFFSET, IOS::ES::MAX_TMD_SIZE, partition_root + "tmd.bin"); const u64 cert_offset = Common::AlignUp(TMD_OFFSET + tmd_size, 0x20ull); const u64 max_cert_size = H3_OFFSET - cert_offset;