Merge pull request #4906 from leoetlino/es-launch

IOS: Handle ES_Launch more accurately
This commit is contained in:
Anthony 2017-02-27 11:51:43 -08:00 committed by GitHub
commit 7ac95c2673
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);