IOS: Handle ES_Launch more accurately

This commit fixes ES_Launch to work mostly the same as the real IOS
(except temporary, internal files such as /sys/launch.sys and title
handling; the latter will be handled in a future PR).

First of all, this adds two IOS functions, which correspond to two
IOS syscalls: 0x41 (boot_ppc) and 0x42 (boot_ios).

boot_ios() writes the new version to 0x3140, loads the new kernel,
which then proceeds to reinit IPC and load modules as part of its
boot process. Note that this doesn't include writing to any of the
other constants in the 0x3100 region.
In Dolphin, this is implemented by changing the active IOS
version variable, writing to 0x3140 and resetting all devices. This
has exactly the same effect as the real syscall.

The other syscall, boot_ppc(), writes code to the EXI boot buffer,
pokes all constants to memory before bootstrapping the PPC with a
binary from the NAND.
We skip the low level stuff and just load the DOL to memory (and set
the PPC's PC to 0x3400), which is essentially what IOS does.

The other change is mostly related to how ES_Launch is handled.

With a real IOS, if the launched title type is 00000001 (system) and
the title is not 1-2 (System Menu), ES calls boot_ios().

Otherwise, ES handles the launch as a PPC title. It reads the TMD
to determine the required IOS version. If it is the same, boot_ppc()
is called directly. If not, ES saves the title to launch to the NAND
before launching the new IOS. After the new IOS has finished booting,
it will notice the flag and then launch the requested title.

What this commit does is really just implement this logic into IOS HLE.
The result is a fix for a regression introduced by SetupMemory,
where reloading an IOS would have overwritten some OS constants.
This fixes booting games from the disc channel.
This commit is contained in:
Léo Lam 2017-02-06 18:52:08 +01:00
parent d666363ac4
commit fff4634a1c
7 changed files with 190 additions and 93 deletions

View File

@ -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)

View File

@ -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

View File

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

View File

@ -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)
{
}

View File

@ -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
{

View File

@ -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;
}

View File

@ -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);