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] 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;