diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index d12114c14c..eb2ba6222e 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -246,6 +246,39 @@ bool ES::LaunchTitle(u64 title_id, bool skip_reload) bool ES::LaunchIOS(u64 ios_title_id) { + // A real Wii goes through several steps before getting to MIOS. + // + // * The System Menu detects a GameCube disc and launches BC (1-100) instead of the game. + // * BC (similar to boot1) lowers the clock speed to the Flipper's and then launches boot2. + // * boot2 sees the lowered clock speed and launches MIOS (1-101) instead of the System Menu. + // + // Because we 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 == Titles::BC) + { + NOTICE_LOG(IOS, "BC: Launching MIOS..."); + return LaunchIOS(Titles::MIOS); + } + + // IOS checks whether the system title is installed and returns an error if it isn't. + // Unfortunately, we can't rely on titles being installed as we don't require system titles, + // so only have this check for MIOS (for which having the binary is *required*). + if (ios_title_id == Titles::MIOS) + { + const IOS::ES::TMDReader tmd = FindInstalledTMD(ios_title_id); + const IOS::ES::TicketReader ticket = FindSignedTicket(ios_title_id); + IOS::ES::Content content; + if (!tmd.IsValid() || !ticket.IsValid() || !tmd.GetContent(tmd.GetBootIndex(), &content) || + !m_ios.BootIOS(ios_title_id, GetContentPath(ios_title_id, content))) + { + PanicAlertT("Could not launch IOS %016" PRIx64 " because it is missing from the NAND.\n" + "The emulated software will likely hang now.", + ios_title_id); + return false; + } + return true; + } + return m_ios.BootIOS(ios_title_id); } diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index 073323d347..667e92e490 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -19,6 +19,7 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Core/Boot/DolReader.h" +#include "Core/Boot/ElfReader.h" #include "Core/CommonTitles.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -296,23 +297,60 @@ bool Kernel::BootstrapPPC(const std::string& boot_content_path) return true; } +struct ARMBinary final +{ + explicit ARMBinary(std::vector&& bytes) : m_bytes(std::move(bytes)) {} + bool IsValid() const + { + // The header is at least 0x10. + if (m_bytes.size() < 0x10) + return false; + return m_bytes.size() >= (GetHeaderSize() + GetElfOffset() + GetElfSize()); + } + + std::vector GetElf() const + { + const auto iterator = m_bytes.cbegin() + GetHeaderSize() + GetElfOffset(); + return std::vector(iterator, iterator + GetElfSize()); + } + + u32 GetHeaderSize() const { return Common::swap32(m_bytes.data()); } + u32 GetElfOffset() const { return Common::swap32(m_bytes.data() + 0x4); } + u32 GetElfSize() const { return Common::swap32(m_bytes.data() + 0x8); } +private: + std::vector m_bytes; +}; + // 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. Warning: this resets the kernel instance. -bool Kernel::BootIOS(const u64 ios_title_id) +// +// 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 +// 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) { - // A real Wii goes through several steps before getting to MIOS. - // - // * The System Menu detects a GameCube disc and launches BC (1-100) instead of the game. - // * BC (similar to boot1) lowers the clock speed to the Flipper's and then launches boot2. - // * boot2 sees the lowered clock speed and launches MIOS (1-101) instead of the System Menu. - // - // 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 == Titles::BC) + if (!boot_content_path.empty()) { - NOTICE_LOG(IOS, "BC: Launching MIOS..."); - return BootIOS(Titles::MIOS); + // Load the ARM binary to memory (if possible). + // Because we do not actually emulate the Starlet, only load the sections that are in MEM1. + + File::IOFile file{boot_content_path, "rb"}; + // TODO: should return IPC_ERROR_MAX. + if (file.GetSize() > 0xB00000) + return false; + + std::vector data(file.GetSize()); + if (!file.ReadBytes(data.data(), data.size())) + return false; + + ARMBinary binary{std::move(data)}; + if (!binary.IsValid()) + return false; + + ElfReader elf{binary.GetElf()}; + if (!elf.LoadIntoMemory(true)) + return false; } // Shut down the active IOS first before switching to the new one. diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index 73f17cbe7a..9280412698 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -109,7 +109,7 @@ public: u16 GetGidForPPC() const; bool BootstrapPPC(const std::string& boot_content_path); - bool BootIOS(u64 ios_title_id); + bool BootIOS(u64 ios_title_id, const std::string& boot_content_path = ""); u32 GetVersion() const; IOSC& GetIOSC(); diff --git a/Source/Core/Core/IOS/MIOS.cpp b/Source/Core/Core/IOS/MIOS.cpp index fc0e70541a..2f0f02a422 100644 --- a/Source/Core/Core/IOS/MIOS.cpp +++ b/Source/Core/Core/IOS/MIOS.cpp @@ -6,16 +6,12 @@ #include #include -#include #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Common/NandPaths.h" #include "Common/Swap.h" -#include "Core/Boot/ElfReader.h" -#include "Core/CommonTitles.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/DSPEmulator.h" @@ -24,10 +20,8 @@ #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" -#include "Core/IOS/ES/Formats.h" #include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" -#include "DiscIO/NANDContentLoader.h" namespace IOS { @@ -35,73 +29,6 @@ namespace HLE { namespace MIOS { -// Source: https://wiibrew.org/wiki/ARM_Binaries -struct ARMBinary final -{ - explicit ARMBinary(const std::vector& bytes); - explicit ARMBinary(std::vector&& bytes); - - bool IsValid() const; - std::vector GetElf() const; - u32 GetHeaderSize() const; - u32 GetElfOffset() const; - u32 GetElfSize() const; - -private: - std::vector m_bytes; -}; - -ARMBinary::ARMBinary(const std::vector& bytes) : m_bytes(bytes) -{ -} - -ARMBinary::ARMBinary(std::vector&& bytes) : m_bytes(std::move(bytes)) -{ -} - -bool ARMBinary::IsValid() const -{ - // The header is at least 0x10. - if (m_bytes.size() < 0x10) - return false; - return m_bytes.size() >= (GetHeaderSize() + GetElfOffset() + GetElfSize()); -} - -std::vector ARMBinary::GetElf() const -{ - const auto iterator = m_bytes.cbegin() + GetHeaderSize() + GetElfOffset(); - return std::vector(iterator, iterator + GetElfSize()); -} - -u32 ARMBinary::GetHeaderSize() const -{ - return Common::swap32(m_bytes.data()); -} - -u32 ARMBinary::GetElfOffset() const -{ - return Common::swap32(m_bytes.data() + 0x4); -} - -u32 ARMBinary::GetElfSize() const -{ - return Common::swap32(m_bytes.data() + 0x8); -} - -static std::vector GetMIOSBinary() -{ - const auto& loader = - DiscIO::NANDContentManager::Access().GetNANDLoader(Titles::MIOS, Common::FROM_SESSION_ROOT); - if (!loader.IsValid()) - return {}; - - const auto* content = loader.GetContentByIndex(loader.GetTMD().GetBootIndex()); - if (!content) - return {}; - - return content->m_Data->Get(); -} - static void ReinitHardware() { SConfig::GetInstance().bWii = false; @@ -125,22 +52,6 @@ bool Load() Memory::Write_U32(0x00000000, ADDRESS_INIT_SEMAPHORE); Memory::Write_U32(0x09142001, 0x3180); - ARMBinary mios{GetMIOSBinary()}; - if (!mios.IsValid()) - { - PanicAlertT("Failed to load MIOS. It is required for launching GameCube titles from Wii mode."); - Core::QueueHostJob(Core::Stop); - return false; - } - - ElfReader elf{mios.GetElf()}; - if (!elf.LoadIntoMemory(true)) - { - PanicAlertT("Failed to load MIOS ELF into memory."); - Core::QueueHostJob(Core::Stop); - return false; - } - ReinitHardware(); NOTICE_LOG(IOS, "Reinitialised hardware.");