IOS/ES: Emulate /sys/launch.sys for more accurate timings
Also gets rid of one static variable
This commit is contained in:
parent
bdaac718ac
commit
aef0760efe
|
@ -23,6 +23,7 @@
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
#include "Core/IOS/ES/Formats.h"
|
#include "Core/IOS/ES/Formats.h"
|
||||||
#include "Core/IOS/FS/FileSystem.h"
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
|
#include "Core/IOS/FS/FileSystemProxy.h"
|
||||||
#include "Core/IOS/IOSC.h"
|
#include "Core/IOS/IOSC.h"
|
||||||
#include "Core/IOS/Uids.h"
|
#include "Core/IOS/Uids.h"
|
||||||
#include "Core/IOS/VersionInfo.h"
|
#include "Core/IOS/VersionInfo.h"
|
||||||
|
@ -32,9 +33,6 @@ namespace IOS::HLE
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys).
|
|
||||||
u64 s_title_to_launch;
|
|
||||||
|
|
||||||
struct DirectoryToCreate
|
struct DirectoryToCreate
|
||||||
{
|
{
|
||||||
const char* path;
|
const char* path;
|
||||||
|
@ -57,7 +55,13 @@ constexpr std::array<DirectoryToCreate, 9> s_directories_to_create = {{
|
||||||
{"/wfs", 0, {FS::Mode::ReadWrite, FS::Mode::None, FS::Mode::None}, PID_UNKNOWN, PID_UNKNOWN},
|
{"/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_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)
|
constexpr SystemTimers::TimeBaseTick GetESBootTicks(u32 ios_version)
|
||||||
{
|
{
|
||||||
|
@ -113,22 +117,42 @@ void ESDevice::InitializeEmulationState()
|
||||||
{
|
{
|
||||||
s_finish_init_event = CoreTiming::RegisterEvent(
|
s_finish_init_event = CoreTiming::RegisterEvent(
|
||||||
"IOS-ESFinishInit", [](u64, s64) { GetIOS()->GetES()->FinishInit(); });
|
"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()
|
void ESDevice::FinalizeEmulationState()
|
||||||
{
|
{
|
||||||
s_finish_init_event = nullptr;
|
s_finish_init_event = nullptr;
|
||||||
|
s_reload_ios_for_ppc_launch_event = nullptr;
|
||||||
|
s_bootstrap_ppc_for_launch_event = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESDevice::FinishInit()
|
void ESDevice::FinishInit()
|
||||||
{
|
{
|
||||||
m_ios.InitIPC();
|
m_ios.InitIPC();
|
||||||
|
|
||||||
if (s_title_to_launch != 0)
|
std::optional<u64> pending_launch_title_id;
|
||||||
|
|
||||||
{
|
{
|
||||||
NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload.");
|
const auto launch_file =
|
||||||
LaunchTitle(s_title_to_launch, HangPPC::No, true);
|
m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, LAUNCH_FILE_PATH, FS::Mode::Read);
|
||||||
s_title_to_launch = 0;
|
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);
|
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();
|
m_title_context.Clear();
|
||||||
INFO_LOG_FMT(IOS_ES, "ES_Launch: Title context changed: (none)");
|
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)
|
if (IsTitleType(title_id, ES::TitleType::System) && title_id != Titles::SYSTEM_MENU)
|
||||||
return LaunchIOS(title_id, hang_ppc);
|
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)
|
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);
|
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<u8> 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);
|
const ES::TicketReader ticket = FindSignedTicket(title_id);
|
||||||
|
|
||||||
if (!tmd.IsValid() || !ticket.IsValid())
|
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,
|
// 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
|
// 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.
|
// again and the PPC will be bootstrapped then.
|
||||||
if (!skip_reload)
|
//
|
||||||
|
// 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)
|
||||||
const u64 required_ios = tmd.GetIOSId();
|
{
|
||||||
return LaunchTitle(required_ios, HangPPC::Yes);
|
PanicAlertFmt("LaunchPPCTitle: Failed to write launch file");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const u64 required_ios = tmd.GetIOSId();
|
||||||
|
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<u8>(SPACE_FILE_SIZE), &ticks);
|
||||||
|
|
||||||
m_title_context.Update(tmd, ticket, DiscIO::Platform::WiiWAD);
|
m_title_context.Update(tmd, ticket, DiscIO::Platform::WiiWAD);
|
||||||
INFO_LOG_FMT(IOS_ES, "LaunchPPCTitle: Title context changed: {:016x}", tmd.GetTitleId());
|
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))
|
if (!tmd.GetContent(tmd.GetBootIndex(), &content))
|
||||||
return false;
|
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)
|
void ESDevice::Context::DoState(PointerWrap& p)
|
||||||
|
@ -413,6 +485,8 @@ void ESDevice::DoState(PointerWrap& p)
|
||||||
|
|
||||||
for (auto& context : m_contexts)
|
for (auto& context : m_contexts)
|
||||||
context.DoState(p);
|
context.DoState(p);
|
||||||
|
|
||||||
|
p.Do(m_pending_ppc_boot_content_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESDevice::ContextArray::iterator ESDevice::FindActiveContext(s32 fd)
|
ESDevice::ContextArray::iterator ESDevice::FindActiveContext(s32 fd)
|
||||||
|
|
|
@ -47,7 +47,7 @@ public:
|
||||||
static void FinalizeEmulationState();
|
static void FinalizeEmulationState();
|
||||||
|
|
||||||
ReturnCode DIVerify(const ES::TMDReader& tmd, const ES::TicketReader& ticket);
|
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;
|
void DoState(PointerWrap& p) override;
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ private:
|
||||||
ContextArray::iterator FindInactiveContext();
|
ContextArray::iterator FindInactiveContext();
|
||||||
|
|
||||||
bool LaunchIOS(u64 ios_title_id, HangPPC hang_ppc);
|
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;
|
bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const;
|
||||||
|
|
||||||
ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view,
|
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;
|
std::string GetContentPath(u64 title_id, const ES::Content& content, Ticks ticks = {}) const;
|
||||||
|
|
||||||
|
s32 WriteSystemFile(const std::string& path, const std::vector<u8>& data, Ticks ticks = {});
|
||||||
|
s32 WriteLaunchFile(const ES::TMDReader& tmd, Ticks ticks = {});
|
||||||
|
bool BootstrapPPC();
|
||||||
|
|
||||||
struct OpenedContent
|
struct OpenedContent
|
||||||
{
|
{
|
||||||
bool m_opened = false;
|
bool m_opened = false;
|
||||||
|
@ -385,5 +389,6 @@ private:
|
||||||
|
|
||||||
ContextArray m_contexts;
|
ContextArray m_contexts;
|
||||||
TitleContext m_title_context{};
|
TitleContext m_title_context{};
|
||||||
|
std::string m_pending_ppc_boot_content_path;
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -151,6 +151,8 @@ struct Ticket
|
||||||
static_assert(sizeof(Ticket) == 0x2A4, "Ticket has the wrong size");
|
static_assert(sizeof(Ticket) == 0x2A4, "Ticket has the wrong size");
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
constexpr u32 MAX_TMD_SIZE = 0x49e4;
|
||||||
|
|
||||||
class SignedBlobReader
|
class SignedBlobReader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -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);
|
return fmt::format("{}/{:08x}.app", Common::GetTitleContentPath(title_id), content.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s32 ESDevice::WriteSystemFile(const std::string& path, const std::vector<u8>& 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
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -584,7 +584,6 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
|
||||||
constexpr u32 TICKET_OFFSET = 0x0;
|
constexpr u32 TICKET_OFFSET = 0x0;
|
||||||
constexpr u32 TICKET_SIZE = 0x2a4;
|
constexpr u32 TICKET_SIZE = 0x2a4;
|
||||||
constexpr u32 TMD_OFFSET = 0x2c0;
|
constexpr u32 TMD_OFFSET = 0x2c0;
|
||||||
constexpr u32 MAX_TMD_SIZE = 0x49e4;
|
|
||||||
constexpr u32 H3_OFFSET = 0x4000;
|
constexpr u32 H3_OFFSET = 0x4000;
|
||||||
constexpr u32 H3_SIZE = 0x18000;
|
constexpr u32 H3_SIZE = 0x18000;
|
||||||
|
|
||||||
|
@ -594,7 +593,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
|
||||||
partition_address + TICKET_OFFSET, TICKET_SIZE, partition_root + "ticket.bin");
|
partition_address + TICKET_OFFSET, TICKET_SIZE, partition_root + "ticket.bin");
|
||||||
|
|
||||||
const u64 tmd_size = m_nonpartition_contents.CheckSizeAndAdd(
|
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 cert_offset = Common::AlignUp(TMD_OFFSET + tmd_size, 0x20ull);
|
||||||
const u64 max_cert_size = H3_OFFSET - cert_offset;
|
const u64 max_cert_size = H3_OFFSET - cert_offset;
|
||||||
|
|
Loading…
Reference in New Issue