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/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 5ce32591c8..2953adbc1d 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -18,9 +18,12 @@ #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" +#include "Core/IOS/FS/FileSystemProxy.h" #include "Core/IOS/IOSC.h" #include "Core/IOS/Uids.h" #include "Core/IOS/VersionInfo.h" @@ -30,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; @@ -54,6 +54,31 @@ 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}, }}; + +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) +{ + 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,11 +102,57 @@ ESDevice::ESDevice(Kernel& ios, const std::string& device_name) : Device(ios, de FinishAllStaleImports(); - if (s_title_to_launch != 0) + if (Core::IsRunningAndStarted()) { - NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload."); - LaunchTitle(s_title_to_launch, true); - s_title_to_launch = 0; + 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(); }); + 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(); + + std::optional pending_launch_title_id; + + { + 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); } } @@ -223,7 +294,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) { m_title_context.Clear(); INFO_LOG_FMT(IOS_ES, "ES_Launch: Title context changed: (none)"); @@ -243,15 +314,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 LaunchPPCTitle(title_id, skip_reload); + return LaunchIOS(title_id, hang_ppc); + return LaunchPPCTitle(title_id); } -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. // @@ -264,7 +335,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. @@ -276,7 +347,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.", @@ -286,12 +357,27 @@ 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) +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()) @@ -312,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); + 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()); @@ -336,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) @@ -366,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 989474749d..b711fd93b9 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -43,8 +43,11 @@ 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); + bool LaunchTitle(u64 title_id, HangPPC hang_ppc = HangPPC::No); void DoState(PointerWrap& p) override; @@ -343,8 +346,8 @@ private: ContextArray::iterator FindActiveContext(s32 fd); ContextArray::iterator FindInactiveContext(); - bool LaunchIOS(u64 ios_title_id); - bool LaunchPPCTitle(u64 title_id, bool skip_reload); + bool LaunchIOS(u64 ios_title_id, HangPPC hang_ppc); + bool LaunchPPCTitle(u64 title_id); bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const; ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view, @@ -364,8 +367,14 @@ private: void FinishStaleImport(u64 title_id); void FinishAllStaleImports(); + void FinishInit(); + 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; @@ -380,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/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 = {}) diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 50b586e343..8cb05aa08a 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -64,9 +64,10 @@ 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; +static CoreTiming::EventType* s_event_finish_ios_boot; constexpr u32 ADDR_MEM1_SIZE = 0x3100; constexpr u32 ADDR_MEM1_SIM_SIZE = 0x3104; @@ -137,6 +138,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); @@ -170,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. @@ -260,9 +289,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(); } @@ -317,18 +343,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; } @@ -337,7 +364,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; @@ -345,15 +375,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; } @@ -382,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. @@ -389,14 +433,18 @@ private: // 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. + m_ipc_paused = true; + if (!boot_content_path.empty()) { // 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; @@ -405,12 +453,26 @@ 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 (hang_ppc == HangPPC::Yes) + ResetAndPausePPC(); + + if (Core::IsRunningAndStarted()) + CoreTiming::ScheduleEvent(GetIOSBootTicks(GetVersion()), s_event_finish_ios_boot, ios_title_id); + else + FinishIOSBoot(ios_title_id); + 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); @@ -658,17 +720,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)); @@ -678,7 +732,7 @@ void Kernel::HandleIPCEvent(u64 userdata) void Kernel::UpdateIPC() { - if (!IsReady()) + if (m_ipc_paused || !IsReady()) return; if (!m_request_queue.empty()) @@ -698,14 +752,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() @@ -740,6 +786,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); @@ -809,6 +856,13 @@ IOSC& Kernel::GetIOSC() return m_iosc; } +static void FinishPPCBootstrap(u64 userdata, s64 cycles_late) +{ + ReleasePPC(); + SConfig::OnNewTitleLoad(); + INFO_LOG_FMT(IOS, "Bootstrapping done."); +} + void Init() { s_event_enqueue = CoreTiming::RegisterEvent("IPCEvent", [](u64 userdata, s64) { @@ -826,6 +880,14 @@ void Init() device->EventNotify(); }); + ESDevice::InitializeEmulationState(); + + 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); @@ -842,6 +904,7 @@ void Init() void Shutdown() { s_ios.reset(); + ESDevice::FinalizeEmulationState(); } EmulationKernel* GetIOS() diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index a3a82bdb49..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,9 @@ 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; IOSC& GetIOSC(); @@ -142,7 +150,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,8 +172,8 @@ 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; + 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, diff --git a/Source/Core/DiscIO/DirectoryBlob.cpp b/Source/Core/DiscIO/DirectoryBlob.cpp index 65451a33d1..3a93983b4d 100644 --- a/Source/Core/DiscIO/DirectoryBlob.cpp +++ b/Source/Core/DiscIO/DirectoryBlob.cpp @@ -585,7 +585,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; @@ -595,7 +594,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;