Merge pull request #4906 from leoetlino/es-launch
IOS: Handle ES_Launch more accurately
This commit is contained in:
commit
7ac95c2673
|
@ -11,11 +11,9 @@
|
|||
#include "Common/NandPaths.h"
|
||||
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/Boot/Boot_DOL.h"
|
||||
#include "Core/IOS/FS/FileIO.h"
|
||||
#include "Core/IOS/IPC.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
@ -88,25 +86,11 @@ bool CBoot::Boot_WiiWAD(const std::string& _pFilename)
|
|||
|
||||
if (!SetupWiiMemory(ContentLoader.GetTMD().GetIOSId()))
|
||||
return false;
|
||||
// DOL
|
||||
const DiscIO::SNANDContent* pContent =
|
||||
ContentLoader.GetContentByIndex(ContentLoader.GetTMD().GetBootIndex());
|
||||
if (pContent == nullptr)
|
||||
return false;
|
||||
|
||||
IOS::HLE::SetDefaultContentFile(_pFilename);
|
||||
|
||||
std::unique_ptr<CDolLoader> pDolLoader = std::make_unique<CDolLoader>(pContent->m_Data->Get());
|
||||
if (!pDolLoader->IsValid())
|
||||
if (!IOS::HLE::BootstrapPPC(ContentLoader))
|
||||
return false;
|
||||
|
||||
pDolLoader->Load();
|
||||
// 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 = 0;
|
||||
PC = 0x3400;
|
||||
|
||||
// Load patches and run startup patches
|
||||
const std::unique_ptr<DiscIO::IVolume> pVolume(DiscIO::CreateVolumeFromFilename(_pFilename));
|
||||
if (pVolume != nullptr)
|
||||
|
|
|
@ -79,6 +79,50 @@ void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv,
|
|||
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, new_iv, input, output);
|
||||
}
|
||||
|
||||
bool ES::LaunchTitle(u64 title_id, bool skip_reload) const
|
||||
{
|
||||
NOTICE_LOG(IOS_ES, "Launching title %016" PRIx64 "...", title_id);
|
||||
|
||||
// ES_Launch should probably reset the whole state, which at least means closing all open files.
|
||||
// leaving them open through ES_Launch may cause hangs and other funky behavior
|
||||
// (supposedly when trying to re-open those files).
|
||||
DiscIO::CNANDContentManager::Access().ClearCache();
|
||||
|
||||
if (IsTitleType(title_id, IOS::ES::TitleType::System) && title_id != TITLEID_SYSMENU)
|
||||
return LaunchIOS(title_id);
|
||||
return LaunchPPCTitle(title_id, skip_reload);
|
||||
}
|
||||
|
||||
bool ES::LaunchIOS(u64 ios_title_id) const
|
||||
{
|
||||
return Reload(ios_title_id);
|
||||
}
|
||||
|
||||
bool ES::LaunchPPCTitle(u64 title_id, bool skip_reload) const
|
||||
{
|
||||
const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id);
|
||||
if (!content_loader.IsValid())
|
||||
{
|
||||
PanicAlertT("Could not launch title %016" PRIx64 " because it is missing from the NAND.\n"
|
||||
"The emulated software will likely hang now.",
|
||||
title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
SetTitleToLaunch(title_id);
|
||||
const u64 required_ios = content_loader.GetTMD().GetIOSId();
|
||||
return LaunchTitle(required_ios);
|
||||
}
|
||||
|
||||
SetDefaultContentFile(Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT));
|
||||
return BootstrapPPC(content_loader);
|
||||
}
|
||||
|
||||
void ES::OpenInternal()
|
||||
{
|
||||
auto& contentLoader = DiscIO::CNANDContentManager::Access().GetNANDLoader(m_ContentFile);
|
||||
|
@ -971,7 +1015,6 @@ IPCCommandResult ES::Decrypt(const IOCtlVRequest& request)
|
|||
IPCCommandResult ES::Launch(const IOCtlVRequest& request)
|
||||
{
|
||||
_dbg_assert_(IOS_ES, request.in_vectors.size() == 2);
|
||||
bool bSuccess = false;
|
||||
|
||||
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
|
||||
u32 view = Memory::Read_U32(request.in_vectors[1].address);
|
||||
|
@ -980,73 +1023,18 @@ IPCCommandResult ES::Launch(const IOCtlVRequest& request)
|
|||
u64 titleid = Memory::Read_U64(request.in_vectors[1].address + 16);
|
||||
u16 access = Memory::Read_U16(request.in_vectors[1].address + 24);
|
||||
|
||||
NOTICE_LOG(IOS_ES, "IOCTL_ES_LAUNCH %016" PRIx64 " %08x %016" PRIx64 " %08x %016" PRIx64 " %04x",
|
||||
TitleID, view, ticketid, devicetype, titleid, access);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_LAUNCH %016" PRIx64 " %08x %016" PRIx64 " %08x %016" PRIx64 " %04x",
|
||||
TitleID, view, ticketid, devicetype, titleid, access);
|
||||
|
||||
// ES_LAUNCH should probably reset thw whole state, which at least means closing all open files.
|
||||
// leaving them open through ES_LAUNCH may cause hangs and other funky behavior
|
||||
// (supposedly when trying to re-open those files).
|
||||
DiscIO::CNANDContentManager::Access().ClearCache();
|
||||
|
||||
u64 ios_to_load = 0;
|
||||
std::string tContentFile;
|
||||
if ((u32)(TitleID >> 32) == 0x00000001 && TitleID != TITLEID_SYSMENU)
|
||||
{
|
||||
ios_to_load = TitleID;
|
||||
bSuccess = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(TitleID);
|
||||
if (ContentLoader.IsValid())
|
||||
{
|
||||
ios_to_load = ContentLoader.GetTMD().GetIOSId();
|
||||
|
||||
u32 bootInd = ContentLoader.GetTMD().GetBootIndex();
|
||||
const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(bootInd);
|
||||
if (pContent)
|
||||
{
|
||||
tContentFile = Common::GetTitleContentPath(TitleID, Common::FROM_SESSION_ROOT);
|
||||
std::unique_ptr<CDolLoader> pDolLoader =
|
||||
std::make_unique<CDolLoader>(pContent->m_Data->Get());
|
||||
|
||||
if (pDolLoader->IsValid())
|
||||
{
|
||||
pDolLoader->Load();
|
||||
// TODO: Check why sysmenu does not load the DOL correctly
|
||||
// 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 = 0;
|
||||
PC = 0x3400;
|
||||
bSuccess = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertT("IOCTL_ES_LAUNCH: The DOL file is invalid!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bSuccess)
|
||||
{
|
||||
PanicAlertT(
|
||||
"IOCTL_ES_LAUNCH: Game tried to reload a title that is not available in your NAND dump\n"
|
||||
"TitleID %016" PRIx64 ".\n Dolphin will likely hang now.",
|
||||
TitleID);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetAfterLaunch(ios_to_load);
|
||||
SetDefaultContentFile(tContentFile);
|
||||
}
|
||||
// IOS replies to the request through the mailbox on failure, and acks if the launch succeeds.
|
||||
// Note: Launch will potentially reset the whole IOS state -- including this ES instance.
|
||||
if (!LaunchTitle(TitleID))
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
|
||||
// Generate a "reply" to the IPC command. ES_LAUNCH is unique because it
|
||||
// involves restarting IOS; IOS generates two acknowledgements in a row.
|
||||
// Note: If we just reset the PPC, don't write anything to the command buffer. This
|
||||
// could clobber the DOL we just loaded.
|
||||
// Note: If the launch succeeded, we should not write anything to the command buffer as
|
||||
// IOS does not even reply unless it failed.
|
||||
EnqueueCommandAcknowledgement(request.address, 0);
|
||||
return GetNoReply();
|
||||
}
|
||||
|
@ -1061,16 +1049,13 @@ IPCCommandResult ES::LaunchBC(const IOCtlVRequest& request)
|
|||
if (GetVersion() == 0x101)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
ResetAfterLaunch(0x0000000100000100);
|
||||
if (!LaunchTitle(0x0000000100000100))
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
|
||||
EnqueueCommandAcknowledgement(request.address, 0);
|
||||
return GetNoReply();
|
||||
}
|
||||
|
||||
void ES::ResetAfterLaunch(const u64 ios_to_load) const
|
||||
{
|
||||
Reload(ios_to_load);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::CheckKoreaRegion(const IOCtlVRequest& request)
|
||||
{
|
||||
// note by DacoTaco : name is unknown, I just tried to name it SOMETHING.
|
||||
|
@ -1130,7 +1115,7 @@ IPCCommandResult ES::GetOwnedTitleCount(const IOCtlVRequest& request)
|
|||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id)
|
||||
const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id) const
|
||||
{
|
||||
// for WADs, the passed title id and the stored title id match; along with m_ContentFile being set
|
||||
// to the
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
ES(u32 device_id, const std::string& device_name);
|
||||
|
||||
void LoadWAD(const std::string& _rContentFile);
|
||||
bool LaunchTitle(u64 title_id, bool skip_reload = false) const;
|
||||
|
||||
// Internal implementation of the ES_DECRYPT ioctlv.
|
||||
void DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output);
|
||||
|
@ -190,9 +191,11 @@ private:
|
|||
IPCCommandResult DIGetTicketView(const IOCtlVRequest& request);
|
||||
IPCCommandResult GetOwnedTitleCount(const IOCtlVRequest& request);
|
||||
|
||||
void ResetAfterLaunch(u64 ios_to_load) const;
|
||||
bool LaunchIOS(u64 ios_title_id) const;
|
||||
bool LaunchPPCTitle(u64 title_id, bool skip_reload) const;
|
||||
|
||||
const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id) const;
|
||||
|
||||
const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id);
|
||||
u32 OpenTitleContent(u32 CFD, u64 TitleID, u16 Index);
|
||||
|
||||
using CContentAccessMap = std::map<u32, SContentAccess>;
|
||||
|
|
|
@ -21,6 +21,11 @@ namespace ES
|
|||
{
|
||||
constexpr size_t CONTENT_VIEW_SIZE = 0x10;
|
||||
|
||||
bool IsTitleType(u64 title_id, TitleType title_type)
|
||||
{
|
||||
return static_cast<u32>(title_id >> 32) == static_cast<u32>(title_type);
|
||||
}
|
||||
|
||||
TMDReader::TMDReader(const std::vector<u8>& bytes) : m_bytes(bytes)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -18,6 +18,19 @@ namespace IOS
|
|||
{
|
||||
namespace ES
|
||||
{
|
||||
enum class TitleType : u32
|
||||
{
|
||||
System = 0x00000001,
|
||||
Game = 0x00010000,
|
||||
Channel = 0x00010001,
|
||||
SystemChannel = 0x00010002,
|
||||
GameWithChannel = 0x00010004,
|
||||
DLC = 0x00010005,
|
||||
HiddenChannel = 0x00010008,
|
||||
};
|
||||
|
||||
bool IsTitleType(u64 title_id, TitleType title_type);
|
||||
|
||||
#pragma pack(push, 4)
|
||||
struct TMDHeader
|
||||
{
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Boot/Boot_DOL.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
|
@ -62,6 +63,8 @@
|
|||
#include "Core/IOS/USB/USB_VEN/VEN.h"
|
||||
#include "Core/IOS/WFS/WFSI.h"
|
||||
#include "Core/IOS/WFS/WFSSRV.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
|
||||
namespace CoreTiming
|
||||
{
|
||||
|
@ -92,10 +95,17 @@ static CoreTiming::EventType* s_event_sdio_notify;
|
|||
static u64 s_last_reply_time;
|
||||
|
||||
static u64 s_active_title_id;
|
||||
static u64 s_title_to_launch;
|
||||
|
||||
static constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL;
|
||||
static constexpr u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL;
|
||||
|
||||
enum class MemorySetupType
|
||||
{
|
||||
IOSReload,
|
||||
Full,
|
||||
};
|
||||
|
||||
struct IosMemoryValues
|
||||
{
|
||||
u16 ios_number;
|
||||
|
@ -492,7 +502,7 @@ u32 GetVersion()
|
|||
return static_cast<u32>(s_active_title_id);
|
||||
}
|
||||
|
||||
static bool SetupMemory(u64 ios_title_id)
|
||||
static bool SetupMemory(u64 ios_title_id, MemorySetupType setup_type)
|
||||
{
|
||||
auto target_imv = std::find_if(
|
||||
ios_memory_values.begin(), ios_memory_values.end(),
|
||||
|
@ -504,6 +514,32 @@ static bool SetupMemory(u64 ios_title_id)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (setup_type == MemorySetupType::IOSReload)
|
||||
{
|
||||
Memory::Write_U32(target_imv->ios_version, ADDR_IOS_VERSION);
|
||||
|
||||
// These values are written by the IOS kernel as part of its boot process (for IOS28 and newer).
|
||||
//
|
||||
// This works in a slightly different way on a real console: older IOS versions (< IOS28) all
|
||||
// have the same range (933E0000 - 93400000), thus they don't write it at boot and just inherit
|
||||
// all values. However, the range has changed since IOS28. To make things work properly
|
||||
// after a reload, newer IOSes always write the legacy range before loading an IOS kernel;
|
||||
// the new IOS either updates the range (>= IOS28) or inherits it (< IOS28).
|
||||
//
|
||||
// We can skip this convoluted process and just write the correct range directly.
|
||||
Memory::Write_U32(target_imv->mem2_physical_size, ADDR_MEM2_SIZE);
|
||||
Memory::Write_U32(target_imv->mem2_simulated_size, ADDR_MEM2_SIM_SIZE);
|
||||
Memory::Write_U32(target_imv->mem2_end, ADDR_MEM2_END);
|
||||
Memory::Write_U32(target_imv->mem2_arena_begin, ADDR_MEM2_ARENA_BEGIN);
|
||||
Memory::Write_U32(target_imv->mem2_arena_end, ADDR_MEM2_ARENA_END);
|
||||
Memory::Write_U32(target_imv->ipc_buffer_begin, ADDR_IPC_BUFFER_BEGIN);
|
||||
Memory::Write_U32(target_imv->ipc_buffer_end, ADDR_IPC_BUFFER_END);
|
||||
Memory::Write_U32(target_imv->unknown_begin, ADDR_UNKNOWN_BEGIN);
|
||||
Memory::Write_U32(target_imv->unknown_end, ADDR_UNKNOWN_END);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -586,10 +622,20 @@ static void AddStaticDevices()
|
|||
AddDevice<Device::WFSI>("/dev/wfsi");
|
||||
}
|
||||
|
||||
// IOS used by the latest System Menu (4.3).
|
||||
constexpr u64 IOS80_TITLE_ID = 0x0000000100000050;
|
||||
|
||||
void Init()
|
||||
{
|
||||
s_event_enqueue = CoreTiming::RegisterEvent("IPCEvent", EnqueueEvent);
|
||||
s_event_sdio_notify = CoreTiming::RegisterEvent("SDIO_EventNotify", SDIO_EventNotify_CPUThread);
|
||||
|
||||
// On a Wii, boot2 launches the system menu IOS, which then launches the system menu
|
||||
// (which bootstraps the PPC). This means that after a normal boot process, the constants
|
||||
// in the 0x3100 region will always have been set up.
|
||||
// This is necessary because booting games from the game list skips a significant part
|
||||
// of a Wii's boot process.
|
||||
SetupMemory(IOS80_TITLE_ID, MemorySetupType::Full);
|
||||
}
|
||||
|
||||
void Reset(const bool clear_devices)
|
||||
|
@ -625,6 +671,9 @@ void Shutdown()
|
|||
constexpr u64 BC_TITLE_ID = 0x0000000100000100;
|
||||
constexpr u64 MIOS_TITLE_ID = 0x0000000100000101;
|
||||
|
||||
// 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.
|
||||
bool Reload(const u64 ios_title_id)
|
||||
{
|
||||
// A real Wii goes through several steps before getting to MIOS.
|
||||
|
@ -636,18 +685,67 @@ bool Reload(const u64 ios_title_id)
|
|||
// Because we currently don't have boot1 and boot2, and BC is only ever used to launch MIOS
|
||||
// (indirectly via boot2), we can just launch MIOS when BC is launched.
|
||||
if (ios_title_id == BC_TITLE_ID)
|
||||
{
|
||||
NOTICE_LOG(IOS, "BC: Launching MIOS...");
|
||||
return Reload(MIOS_TITLE_ID);
|
||||
}
|
||||
|
||||
if (!SetupMemory(ios_title_id))
|
||||
if (!SetupMemory(ios_title_id, MemorySetupType::IOSReload))
|
||||
return false;
|
||||
|
||||
s_active_title_id = ios_title_id;
|
||||
Reset(true);
|
||||
|
||||
if (ios_title_id == MIOS_TITLE_ID)
|
||||
{
|
||||
// MIOS is a special case. It does not have the same syscalls as regular IOSes
|
||||
// and writes the magic values at a different time (here) in the boot process.
|
||||
SetupMemory(ios_title_id, MemorySetupType::Full);
|
||||
return MIOS::Load();
|
||||
}
|
||||
|
||||
AddStaticDevices();
|
||||
|
||||
if (s_title_to_launch != 0)
|
||||
{
|
||||
NOTICE_LOG(IOS, "Re-launching title after IOS reload.");
|
||||
s_es_handles[0]->LaunchTitle(s_title_to_launch, true);
|
||||
s_title_to_launch = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetTitleToLaunch(const u64 title_id)
|
||||
{
|
||||
s_title_to_launch = title_id;
|
||||
}
|
||||
|
||||
// This corresponds to syscall 0x41, which loads a binary from the NAND and bootstraps the PPC.
|
||||
// Unlike 0x42, IOS will set up some constants in memory before booting the PPC.
|
||||
bool BootstrapPPC(const DiscIO::CNANDContentLoader& content_loader)
|
||||
{
|
||||
if (!content_loader.IsValid())
|
||||
return false;
|
||||
|
||||
const auto* content = content_loader.GetContentByIndex(content_loader.GetTMD().GetBootIndex());
|
||||
if (!content)
|
||||
return false;
|
||||
|
||||
const auto dol_loader = std::make_unique<CDolLoader>(content->m_Data->Get());
|
||||
if (!dol_loader->IsValid())
|
||||
return false;
|
||||
|
||||
if (!SetupMemory(s_active_title_id, MemorySetupType::Full))
|
||||
return false;
|
||||
|
||||
dol_loader->Load();
|
||||
|
||||
// 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 = 0;
|
||||
PC = 0x3400;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
|
||||
class PointerWrap;
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
class CNANDContentLoader;
|
||||
}
|
||||
|
||||
namespace IOS
|
||||
{
|
||||
namespace ES
|
||||
|
@ -57,10 +62,14 @@ void Reset(bool clear_devices = false);
|
|||
// Shutdown
|
||||
void Shutdown();
|
||||
|
||||
// Reload IOS (to a possibly different version); set up memory and devices.
|
||||
// Reload IOS (to a possibly different version); write the new version to 0x3140 and set up devices.
|
||||
bool Reload(u64 ios_title_id);
|
||||
u32 GetVersion();
|
||||
|
||||
bool BootstrapPPC(const DiscIO::CNANDContentLoader& content_loader);
|
||||
// This sets a title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys).
|
||||
void SetTitleToLaunch(u64 title_id);
|
||||
|
||||
// Do State
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
|
|
Loading…
Reference in New Issue