mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-24 15:01:16 +01:00
Merge pull request #9545 from leoetlino/es-launch-timings
IOS: Improve timing accuracy for title launches (both ARM and PPC)
This commit is contained in:
commit
8d2b0fff8a
@ -436,11 +436,7 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||||||
if (!EmulatedBS2(config.bWii, *volume))
|
if (!EmulatedBS2(config.bWii, *volume))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Try to load the symbol map if there is one, and then scan it for
|
SConfig::OnNewTitleLoad();
|
||||||
// and eventually replace code
|
|
||||||
if (LoadMapFromFilename())
|
|
||||||
HLE::PatchFunctions();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,9 +478,11 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||||||
SetupGCMemory();
|
SetupGCMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SConfig::OnNewTitleLoad();
|
||||||
|
|
||||||
PC = executable.reader->GetEntryPoint();
|
PC = executable.reader->GetEntryPoint();
|
||||||
|
|
||||||
if (executable.reader->LoadSymbols() || LoadMapFromFilename())
|
if (executable.reader->LoadSymbols())
|
||||||
{
|
{
|
||||||
UpdateDebugger_MapLoaded();
|
UpdateDebugger_MapLoaded();
|
||||||
HLE::PatchFunctions();
|
HLE::PatchFunctions();
|
||||||
@ -495,13 +493,21 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||||||
bool operator()(const DiscIO::VolumeWAD& wad) const
|
bool operator()(const DiscIO::VolumeWAD& wad) const
|
||||||
{
|
{
|
||||||
SetDefaultDisc();
|
SetDefaultDisc();
|
||||||
return Boot_WiiWAD(wad);
|
if (!Boot_WiiWAD(wad))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SConfig::OnNewTitleLoad();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(const BootParameters::NANDTitle& nand_title) const
|
bool operator()(const BootParameters::NANDTitle& nand_title) const
|
||||||
{
|
{
|
||||||
SetDefaultDisc();
|
SetDefaultDisc();
|
||||||
return BootNANDTitle(nand_title.id);
|
if (!BootNANDTitle(nand_title.id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SConfig::OnNewTitleLoad();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(const BootParameters::IPL& ipl) const
|
bool operator()(const BootParameters::IPL& ipl) const
|
||||||
@ -525,9 +531,7 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||||||
SetDisc(DiscIO::CreateDisc(ipl.disc->path), ipl.disc->auto_disc_change_paths);
|
SetDisc(DiscIO::CreateDisc(ipl.disc->path), ipl.disc->auto_disc_change_paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LoadMapFromFilename())
|
SConfig::OnNewTitleLoad();
|
||||||
HLE::PatchFunctions();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,8 +548,6 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||||||
if (!std::visit(BootTitle(), boot->parameters))
|
if (!std::visit(BootTitle(), boot->parameters))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
PatchEngine::LoadPatches();
|
|
||||||
HLE::PatchFixedFunctions();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,8 +706,14 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
|
|||||||
Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision));
|
Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision));
|
||||||
|
|
||||||
if (Core::IsRunning())
|
if (Core::IsRunning())
|
||||||
|
DolphinAnalytics::Instance().ReportGameStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SConfig::OnNewTitleLoad()
|
||||||
{
|
{
|
||||||
// TODO: have a callback mechanism for title changes?
|
if (!Core::IsRunning())
|
||||||
|
return;
|
||||||
|
|
||||||
if (!g_symbolDB.IsEmpty())
|
if (!g_symbolDB.IsEmpty())
|
||||||
{
|
{
|
||||||
g_symbolDB.Clear();
|
g_symbolDB.Clear();
|
||||||
@ -717,8 +723,6 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
|
|||||||
HLE::Reload();
|
HLE::Reload();
|
||||||
PatchEngine::Reload();
|
PatchEngine::Reload();
|
||||||
HiresTexture::Update();
|
HiresTexture::Update();
|
||||||
DolphinAnalytics::Instance().ReportGameStart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SConfig::LoadDefaults()
|
void SConfig::LoadDefaults()
|
||||||
|
@ -197,6 +197,10 @@ struct SConfig
|
|||||||
void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition);
|
void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition);
|
||||||
void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform);
|
void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform);
|
||||||
void SetRunningGameMetadata(const std::string& game_id);
|
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();
|
void LoadDefaults();
|
||||||
static std::string MakeGameID(std::string_view file_name);
|
static std::string MakeGameID(std::string_view file_name);
|
||||||
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
|
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
|
||||||
|
@ -18,9 +18,12 @@
|
|||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Core/CommonTitles.h"
|
#include "Core/CommonTitles.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
#include "Core/Core.h"
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
#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"
|
||||||
@ -30,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;
|
||||||
@ -54,6 +54,31 @@ constexpr std::array<DirectoryToCreate, 9> s_directories_to_create = {{
|
|||||||
{"/meta", 0, public_modes, SYSMENU_UID, SYSMENU_GID},
|
{"/meta", 0, public_modes, SYSMENU_UID, SYSMENU_GID},
|
||||||
{"/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_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
|
} // namespace
|
||||||
|
|
||||||
ESDevice::ESDevice(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
|
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();
|
FinishAllStaleImports();
|
||||||
|
|
||||||
if (s_title_to_launch != 0)
|
if (Core::IsRunningAndStarted())
|
||||||
{
|
{
|
||||||
NOTICE_LOG_FMT(IOS, "Re-launching title after IOS reload.");
|
CoreTiming::RemoveEvent(s_finish_init_event);
|
||||||
LaunchTitle(s_title_to_launch, true);
|
CoreTiming::ScheduleEvent(GetESBootTicks(m_ios.GetVersion()), s_finish_init_event);
|
||||||
s_title_to_launch = 0;
|
}
|
||||||
|
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<u64> 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);
|
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();
|
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)");
|
||||||
@ -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
|
// 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
|
// 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.
|
// 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)
|
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);
|
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.
|
// 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)
|
if (ios_title_id == Titles::BC)
|
||||||
{
|
{
|
||||||
NOTICE_LOG_FMT(IOS, "BC: Launching MIOS...");
|
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.
|
// 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);
|
const ES::TicketReader ticket = FindSignedTicket(ios_title_id);
|
||||||
ES::Content content;
|
ES::Content content;
|
||||||
if (!tmd.IsValid() || !ticket.IsValid() || !tmd.GetContent(tmd.GetBootIndex(), &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"
|
PanicAlertFmtT("Could not launch IOS {0:016x} because it is missing from the NAND.\n"
|
||||||
"The emulated software will likely hang now.",
|
"The emulated software will likely hang now.",
|
||||||
@ -286,12 +357,27 @@ bool ESDevice::LaunchIOS(u64 ios_title_id)
|
|||||||
return true;
|
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<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())
|
||||||
@ -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,
|
// 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);
|
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());
|
||||||
|
|
||||||
@ -336,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)
|
||||||
@ -366,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)
|
||||||
|
@ -43,8 +43,11 @@ class ESDevice final : public Device
|
|||||||
public:
|
public:
|
||||||
ESDevice(Kernel& ios, const std::string& device_name);
|
ESDevice(Kernel& ios, const std::string& device_name);
|
||||||
|
|
||||||
|
static void InitializeEmulationState();
|
||||||
|
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, bool skip_reload = false);
|
bool LaunchTitle(u64 title_id, HangPPC hang_ppc = HangPPC::No);
|
||||||
|
|
||||||
void DoState(PointerWrap& p) override;
|
void DoState(PointerWrap& p) override;
|
||||||
|
|
||||||
@ -343,8 +346,8 @@ private:
|
|||||||
ContextArray::iterator FindActiveContext(s32 fd);
|
ContextArray::iterator FindActiveContext(s32 fd);
|
||||||
ContextArray::iterator FindInactiveContext();
|
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 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,
|
||||||
@ -364,8 +367,14 @@ private:
|
|||||||
void FinishStaleImport(u64 title_id);
|
void FinishStaleImport(u64 title_id);
|
||||||
void FinishAllStaleImports();
|
void FinishAllStaleImports();
|
||||||
|
|
||||||
|
void FinishInit();
|
||||||
|
|
||||||
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;
|
||||||
@ -380,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
|
||||||
|
@ -610,15 +610,35 @@ IPCReply FSDevice::GetAttribute(const Handle& handle, const IOCtlRequest& reques
|
|||||||
return GetFSReply(IPC_SUCCESS, ticks);
|
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)
|
IPCReply FSDevice::DeleteFile(const Handle& handle, const IOCtlRequest& request)
|
||||||
{
|
{
|
||||||
if (request.buffer_in_size < 64)
|
if (request.buffer_in_size < 64)
|
||||||
return GetFSReply(ConvertResult(ResultCode::Invalid));
|
return GetFSReply(ConvertResult(ResultCode::Invalid));
|
||||||
|
|
||||||
const std::string path = Memory::GetString(request.buffer_in, 64);
|
const std::string path = Memory::GetString(request.buffer_in, 64);
|
||||||
const ResultCode result = m_ios.GetFS()->Delete(handle.uid, handle.gid, path);
|
return MakeIPCReply(
|
||||||
LogResult(result, "Delete({})", path);
|
[&](Ticks ticks) { return ConvertResult(DeleteFile(handle.uid, handle.gid, path, ticks)); });
|
||||||
return GetReplyForSuperblockOperation(m_ios.GetVersion(), result);
|
}
|
||||||
|
|
||||||
|
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)
|
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 old_path = Memory::GetString(request.buffer_in, 64);
|
||||||
const std::string new_path = Memory::GetString(request.buffer_in + 64, 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);
|
return MakeIPCReply([&](Ticks ticks) {
|
||||||
LogResult(result, "Rename({}, {})", old_path, new_path);
|
return ConvertResult(RenameFile(handle.uid, handle.gid, old_path, new_path, ticks));
|
||||||
return GetReplyForSuperblockOperation(m_ios.GetVersion(), result);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
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<ISFSParams>(request);
|
const auto params = GetParams<ISFSParams>(request);
|
||||||
if (!params)
|
if (!params)
|
||||||
return GetFSReply(ConvertResult(params.Error()));
|
return GetFSReply(ConvertResult(params.Error()));
|
||||||
|
return MakeIPCReply([&](Ticks ticks) {
|
||||||
const ResultCode result = m_ios.GetFS()->CreateFile(handle.uid, handle.gid, params->path,
|
return ConvertResult(
|
||||||
params->attribute, params->modes);
|
CreateFile(handle.uid, handle.gid, params->path, params->attribute, params->modes));
|
||||||
LogResult(result, "CreateFile({})", params->path);
|
});
|
||||||
return GetReplyForSuperblockOperation(m_ios.GetVersion(), result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPCReply FSDevice::SetFileVersionControl(const Handle& handle, const IOCtlRequest& request)
|
IPCReply FSDevice::SetFileVersionControl(const Handle& handle, const IOCtlRequest& request)
|
||||||
|
@ -35,7 +35,13 @@ public:
|
|||||||
s32 Write(u64 fd, const u8* data, u32 size, std::optional<u32> ipc_buffer_addr = {},
|
s32 Write(u64 fd, const u8* data, u32 size, std::optional<u32> ipc_buffer_addr = {},
|
||||||
Ticks ticks = {});
|
Ticks ticks = {});
|
||||||
s32 Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks = {});
|
s32 Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks = {});
|
||||||
|
|
||||||
FS::Result<FS::FileStatus> GetFileStatus(u64 fd, Ticks ticks = {});
|
FS::Result<FS::FileStatus> 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 <typename T>
|
template <typename T>
|
||||||
s32 Read(u64 fd, T* data, size_t count, Ticks ticks = {})
|
s32 Read(u64 fd, T* data, size_t count, Ticks ticks = {})
|
||||||
|
@ -64,9 +64,10 @@ namespace IOS::HLE
|
|||||||
static std::unique_ptr<EmulationKernel> s_ios;
|
static std::unique_ptr<EmulationKernel> s_ios;
|
||||||
|
|
||||||
constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL;
|
constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL;
|
||||||
constexpr u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL;
|
|
||||||
static CoreTiming::EventType* s_event_enqueue;
|
static CoreTiming::EventType* s_event_enqueue;
|
||||||
static CoreTiming::EventType* s_event_sdio_notify;
|
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_SIZE = 0x3100;
|
||||||
constexpr u32 ADDR_MEM1_SIM_SIZE = 0x3104;
|
constexpr u32 ADDR_MEM1_SIM_SIZE = 0x3104;
|
||||||
@ -137,6 +138,12 @@ static bool SetupMemory(u64 ios_title_id, MemorySetupType setup_type)
|
|||||||
return true;
|
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_physical_size, ADDR_MEM1_SIZE);
|
||||||
Memory::Write_U32(target_imv->mem1_simulated_size, ADDR_MEM1_SIM_SIZE);
|
Memory::Write_U32(target_imv->mem1_simulated_size, ADDR_MEM1_SIM_SIZE);
|
||||||
Memory::Write_U32(target_imv->mem1_end, ADDR_MEM1_END);
|
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;
|
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)
|
void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type)
|
||||||
{
|
{
|
||||||
// Don't touch anything if the feature isn't enabled.
|
// Don't touch anything if the feature isn't enabled.
|
||||||
@ -260,9 +289,6 @@ EmulationKernel::EmulationKernel(u64 title_id) : Kernel(title_id)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IOS re-inits IPC and sends a dummy ack during its boot process.
|
|
||||||
EnqueueIPCAcknowledgement(0);
|
|
||||||
|
|
||||||
AddCoreDevices();
|
AddCoreDevices();
|
||||||
AddStaticDevices();
|
AddStaticDevices();
|
||||||
}
|
}
|
||||||
@ -317,18 +343,19 @@ u16 Kernel::GetGidForPPC() const
|
|||||||
return m_ppc_gid;
|
return m_ppc_gid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<u8> ReadBootContent(FS::FileSystem* fs, const std::string& path, size_t max_size)
|
static std::vector<u8> ReadBootContent(FSDevice* fs, const std::string& path, size_t max_size,
|
||||||
|
Ticks ticks = {})
|
||||||
{
|
{
|
||||||
const auto file = fs->OpenFile(0, 0, path, FS::Mode::Read);
|
const s64 fd = fs->Open(0, 0, path, FS::Mode::Read, {}, ticks);
|
||||||
if (!file)
|
if (fd < 0)
|
||||||
return {};
|
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)
|
if (max_size != 0 && file_size > max_size)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::vector<u8> buffer(file_size);
|
std::vector<u8> buffer(file_size);
|
||||||
if (!file->Read(buffer.data(), buffer.size()))
|
if (!fs->Read(fd, buffer.data(), buffer.size(), ticks))
|
||||||
return {};
|
return {};
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
@ -337,7 +364,10 @@ static std::vector<u8> ReadBootContent(FS::FileSystem* fs, const std::string& pa
|
|||||||
// Unlike 0x42, IOS will set up some constants in memory before booting the PPC.
|
// Unlike 0x42, IOS will set up some constants in memory before booting the PPC.
|
||||||
bool Kernel::BootstrapPPC(const std::string& boot_content_path)
|
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())
|
if (!dol.IsValid())
|
||||||
return false;
|
return false;
|
||||||
@ -345,15 +375,14 @@ bool Kernel::BootstrapPPC(const std::string& boot_content_path)
|
|||||||
if (!SetupMemory(m_title_id, MemorySetupType::Full))
|
if (!SetupMemory(m_title_id, MemorySetupType::Full))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Reset the PPC and pause its execution until we're ready.
|
||||||
|
ResetAndPausePPC();
|
||||||
|
|
||||||
if (!dol.LoadIntoMemory())
|
if (!dol.LoadIntoMemory())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// NAND titles start with address translation off at 0x3400 (via the PPC bootstub)
|
INFO_LOG_FMT(IOS, "BootstrapPPC: {}", boot_content_path);
|
||||||
// The state of other CPU registers (like the BAT registers) doesn't matter much
|
CoreTiming::ScheduleEvent(ticks, s_event_finish_ppc_bootstrap);
|
||||||
// because the realmode code at 0x3400 initializes everything itself anyway.
|
|
||||||
MSR.Hex = 0;
|
|
||||||
PC = 0x3400;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,6 +411,21 @@ private:
|
|||||||
std::vector<u8> m_bytes;
|
std::vector<u8> 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<EmulationKernel>(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.
|
// 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
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// 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())
|
if (!boot_content_path.empty())
|
||||||
{
|
{
|
||||||
// Load the ARM binary to memory (if possible).
|
// Load the ARM binary to memory (if possible).
|
||||||
// Because we do not actually emulate the Starlet, only load the sections that are in MEM1.
|
// 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())
|
if (!binary.IsValid())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -405,12 +453,26 @@ bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_pat
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut down the active IOS first before switching to the new one.
|
if (hang_ppc == HangPPC::Yes)
|
||||||
s_ios.reset();
|
ResetAndPausePPC();
|
||||||
s_ios = std::make_unique<EmulationKernel>(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;
|
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> device)
|
void Kernel::AddDevice(std::unique_ptr<Device> device)
|
||||||
{
|
{
|
||||||
ASSERT(device->GetDeviceType() == Device::DeviceType::Static);
|
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);
|
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)
|
void Kernel::HandleIPCEvent(u64 userdata)
|
||||||
{
|
{
|
||||||
if (userdata & ENQUEUE_ACKNOWLEDGEMENT_FLAG)
|
if (userdata & ENQUEUE_REQUEST_FLAG)
|
||||||
m_ack_queue.push_back(static_cast<u32>(userdata));
|
|
||||||
else if (userdata & ENQUEUE_REQUEST_FLAG)
|
|
||||||
m_request_queue.push_back(static_cast<u32>(userdata));
|
m_request_queue.push_back(static_cast<u32>(userdata));
|
||||||
else
|
else
|
||||||
m_reply_queue.push_back(static_cast<u32>(userdata));
|
m_reply_queue.push_back(static_cast<u32>(userdata));
|
||||||
@ -678,7 +732,7 @@ void Kernel::HandleIPCEvent(u64 userdata)
|
|||||||
|
|
||||||
void Kernel::UpdateIPC()
|
void Kernel::UpdateIPC()
|
||||||
{
|
{
|
||||||
if (!IsReady())
|
if (m_ipc_paused || !IsReady())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!m_request_queue.empty())
|
if (!m_request_queue.empty())
|
||||||
@ -698,14 +752,6 @@ void Kernel::UpdateIPC()
|
|||||||
m_reply_queue.pop_front();
|
m_reply_queue.pop_front();
|
||||||
return;
|
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()
|
void Kernel::UpdateDevices()
|
||||||
@ -740,6 +786,7 @@ void Kernel::DoState(PointerWrap& p)
|
|||||||
p.Do(m_request_queue);
|
p.Do(m_request_queue);
|
||||||
p.Do(m_reply_queue);
|
p.Do(m_reply_queue);
|
||||||
p.Do(m_last_reply_time);
|
p.Do(m_last_reply_time);
|
||||||
|
p.Do(m_ipc_paused);
|
||||||
p.Do(m_title_id);
|
p.Do(m_title_id);
|
||||||
p.Do(m_ppc_uid);
|
p.Do(m_ppc_uid);
|
||||||
p.Do(m_ppc_gid);
|
p.Do(m_ppc_gid);
|
||||||
@ -809,6 +856,13 @@ IOSC& Kernel::GetIOSC()
|
|||||||
return m_iosc;
|
return m_iosc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void FinishPPCBootstrap(u64 userdata, s64 cycles_late)
|
||||||
|
{
|
||||||
|
ReleasePPC();
|
||||||
|
SConfig::OnNewTitleLoad();
|
||||||
|
INFO_LOG_FMT(IOS, "Bootstrapping done.");
|
||||||
|
}
|
||||||
|
|
||||||
void Init()
|
void Init()
|
||||||
{
|
{
|
||||||
s_event_enqueue = CoreTiming::RegisterEvent("IPCEvent", [](u64 userdata, s64) {
|
s_event_enqueue = CoreTiming::RegisterEvent("IPCEvent", [](u64 userdata, s64) {
|
||||||
@ -826,6 +880,14 @@ void Init()
|
|||||||
device->EventNotify();
|
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 =
|
DIDevice::s_finish_executing_di_command =
|
||||||
CoreTiming::RegisterEvent("FinishDICommand", DIDevice::FinishDICommandCallback);
|
CoreTiming::RegisterEvent("FinishDICommand", DIDevice::FinishDICommandCallback);
|
||||||
|
|
||||||
@ -842,6 +904,7 @@ void Init()
|
|||||||
void Shutdown()
|
void Shutdown()
|
||||||
{
|
{
|
||||||
s_ios.reset();
|
s_ios.reset();
|
||||||
|
ESDevice::FinalizeEmulationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
EmulationKernel* GetIOS()
|
EmulationKernel* GetIOS()
|
||||||
|
@ -97,6 +97,12 @@ enum class MemorySetupType
|
|||||||
Full,
|
Full,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class HangPPC : bool
|
||||||
|
{
|
||||||
|
No = false,
|
||||||
|
Yes = true,
|
||||||
|
};
|
||||||
|
|
||||||
void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type);
|
void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type);
|
||||||
|
|
||||||
void WriteReturnValue(s32 value, u32 address);
|
void WriteReturnValue(s32 value, u32 address);
|
||||||
@ -132,7 +138,9 @@ public:
|
|||||||
u16 GetGidForPPC() const;
|
u16 GetGidForPPC() const;
|
||||||
|
|
||||||
bool BootstrapPPC(const std::string& boot_content_path);
|
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;
|
u32 GetVersion() const;
|
||||||
|
|
||||||
IOSC& GetIOSC();
|
IOSC& GetIOSC();
|
||||||
@ -142,7 +150,6 @@ protected:
|
|||||||
|
|
||||||
void ExecuteIPCCommand(u32 address);
|
void ExecuteIPCCommand(u32 address);
|
||||||
std::optional<IPCReply> HandleIPCCommand(const Request& request);
|
std::optional<IPCReply> HandleIPCCommand(const Request& request);
|
||||||
void EnqueueIPCAcknowledgement(u32 address, int cycles_in_future = 0);
|
|
||||||
|
|
||||||
void AddDevice(std::unique_ptr<Device> device);
|
void AddDevice(std::unique_ptr<Device> device);
|
||||||
void AddCoreDevices();
|
void AddCoreDevices();
|
||||||
@ -165,8 +172,8 @@ protected:
|
|||||||
using IPCMsgQueue = std::deque<u32>;
|
using IPCMsgQueue = std::deque<u32>;
|
||||||
IPCMsgQueue m_request_queue; // ppc -> arm
|
IPCMsgQueue m_request_queue; // ppc -> arm
|
||||||
IPCMsgQueue m_reply_queue; // arm -> ppc
|
IPCMsgQueue m_reply_queue; // arm -> ppc
|
||||||
IPCMsgQueue m_ack_queue; // arm -> ppc
|
|
||||||
u64 m_last_reply_time = 0;
|
u64 m_last_reply_time = 0;
|
||||||
|
bool m_ipc_paused = false;
|
||||||
|
|
||||||
IOSC m_iosc;
|
IOSC m_iosc;
|
||||||
std::shared_ptr<FS::FileSystem> m_fs;
|
std::shared_ptr<FS::FileSystem> m_fs;
|
||||||
|
@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
|||||||
static std::thread g_save_thread;
|
static std::thread g_save_thread;
|
||||||
|
|
||||||
// Don't forget to increase this after doing changes on the savestate system
|
// 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.
|
// Maps savestate versions to Dolphin versions.
|
||||||
// Versions after 42 don't need to be added to this list,
|
// Versions after 42 don't need to be added to this list,
|
||||||
|
@ -585,7 +585,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;
|
||||||
|
|
||||||
@ -595,7 +594,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…
x
Reference in New Issue
Block a user