diff --git a/Source/Core/Core/Boot/Boot_WiiWAD.cpp b/Source/Core/Core/Boot/Boot_WiiWAD.cpp index 5b3be7c4dc..612ddc8fe8 100644 --- a/Source/Core/Core/Boot/Boot_WiiWAD.cpp +++ b/Source/Core/Core/Boot/Boot_WiiWAD.cpp @@ -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 pDolLoader = std::make_unique(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 pVolume(DiscIO::CreateVolumeFromFilename(_pFilename)); if (pVolume != nullptr) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 57a430e7a3..27a36c9670 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -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 pDolLoader = - std::make_unique(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 diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 045fc75f36..ae1c7848cc 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -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; diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index f90bd765b2..d9474d92f1 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -21,6 +21,11 @@ namespace ES { constexpr size_t CONTENT_VIEW_SIZE = 0x10; +bool IsTitleType(u64 title_id, TitleType title_type) +{ + return static_cast(title_id >> 32) == static_cast(title_type); +} + TMDReader::TMDReader(const std::vector& bytes) : m_bytes(bytes) { } diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index b6d2985cc2..297a18abda 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -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 { diff --git a/Source/Core/Core/IOS/IPC.cpp b/Source/Core/Core/IOS/IPC.cpp index b74ed1c996..1ebedf5d3d 100644 --- a/Source/Core/Core/IOS/IPC.cpp +++ b/Source/Core/Core/IOS/IPC.cpp @@ -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(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("/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(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; } diff --git a/Source/Core/Core/IOS/IPC.h b/Source/Core/Core/IOS/IPC.h index 5bbd10befd..39acad15fa 100644 --- a/Source/Core/Core/IOS/IPC.h +++ b/Source/Core/Core/IOS/IPC.h @@ -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);