mirror of
https://github.com/Retropex/dolphin.git
synced 2025-06-03 16:02:33 +02:00

This implements MIOS's PPC bootstrapping functionality, which enables users to start a GameCube game from the Wii System Menu. Because we aren't doing Starlet LLE (and don't have a boot1), we can just jump to MIOS when the emulated software does an ES_LAUNCH or uses ioctlv 0x25 to launch BC. Note that the process is more complex on a real Wii and goes through several more steps before getting to MIOS: * The System Menu detects a GameCube disc and launches BC (1-100) instead of the game. [Dolphin does this too.] * BC, which is reportedly very similar to boot1, lowers the Hollywood 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. MIOS runs instead of IOS in GC mode and has an embedded GC IPL (which is the code actually responsible for loading the disc game) and a PPC bootstrap code. To get things working properly, we simply need to load both to memory, then jump to the bootstrap code at 0x3400. Obviously, because of the way this works, a real MIOS is required.
196 lines
5.1 KiB
C++
196 lines
5.1 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <cstring>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "Common/CommonFuncs.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/MsgHandler.h"
|
|
#include "Common/NandPaths.h"
|
|
#include "Core/Boot/Boot.h"
|
|
#include "Core/Boot/ElfReader.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/DSPEmulator.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HW/DSP.h"
|
|
#include "Core/HW/DVDInterface.h"
|
|
#include "Core/HW/DVDThread.h"
|
|
#include "Core/HW/Memmap.h"
|
|
#include "Core/HW/SystemTimers.h"
|
|
#include "Core/IOS/MIOS.h"
|
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
#include "DiscIO/NANDContentLoader.h"
|
|
#include "DiscIO/Volume.h"
|
|
|
|
namespace IOS
|
|
{
|
|
namespace HLE
|
|
{
|
|
namespace MIOS
|
|
{
|
|
constexpr u64 MIOS_TITLE_ID = 0x0000000100000101;
|
|
|
|
// Source: https://wiibrew.org/wiki/ARM_Binaries
|
|
struct ARMBinary final
|
|
{
|
|
explicit ARMBinary(const std::vector<u8>& bytes);
|
|
explicit ARMBinary(std::vector<u8>&& bytes);
|
|
|
|
bool IsValid() const;
|
|
std::vector<u8> GetElf() const;
|
|
u32 GetHeaderSize() const;
|
|
u32 GetElfOffset() const;
|
|
u32 GetElfSize() const;
|
|
|
|
private:
|
|
std::vector<u8> m_bytes;
|
|
};
|
|
|
|
ARMBinary::ARMBinary(const std::vector<u8>& bytes) : m_bytes(bytes)
|
|
{
|
|
}
|
|
|
|
ARMBinary::ARMBinary(std::vector<u8>&& 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<u8> ARMBinary::GetElf() const
|
|
{
|
|
const auto iterator = m_bytes.cbegin() + GetHeaderSize() + GetElfOffset();
|
|
return std::vector<u8>(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<u8> GetMIOSBinary()
|
|
{
|
|
const auto& loader =
|
|
DiscIO::CNANDContentManager::Access().GetNANDLoader(MIOS_TITLE_ID, Common::FROM_SESSION_ROOT);
|
|
if (!loader.IsValid())
|
|
return {};
|
|
|
|
const auto* content = loader.GetContentByIndex(loader.GetBootIndex());
|
|
if (!content)
|
|
return {};
|
|
|
|
return content->m_Data->Get();
|
|
}
|
|
|
|
static void ReinitHardware()
|
|
{
|
|
SConfig::GetInstance().bWii = false;
|
|
|
|
// IOS clears mem2 and overwrites it with pseudo-random data (for security).
|
|
std::memset(Memory::m_pEXRAM, 0, Memory::EXRAM_SIZE);
|
|
// MIOS appears to only reset the DI and the PPC.
|
|
DVDInterface::Reset();
|
|
PowerPC::Reset();
|
|
// Note: this is specific to Dolphin and is required because we initialised it in Wii mode.
|
|
DSP::Reinit(SConfig::GetInstance().bDSPHLE);
|
|
DSP::GetDSPEmulator()->Initialize(SConfig::GetInstance().bWii, SConfig::GetInstance().bDSPThread);
|
|
|
|
SystemTimers::ChangePPCClock(SystemTimers::Mode::GC);
|
|
}
|
|
|
|
static void UpdateRunningGame()
|
|
{
|
|
DVDThread::WaitUntilIdle();
|
|
const DiscIO::IVolume& volume = DVDInterface::GetVolume();
|
|
SConfig::GetInstance().m_BootType = SConfig::BOOT_MIOS;
|
|
SConfig::GetInstance().m_strName = volume.GetInternalName();
|
|
SConfig::GetInstance().m_strGameID = volume.GetGameID();
|
|
SConfig::GetInstance().m_revision = volume.GetRevision();
|
|
|
|
g_symbolDB.Clear();
|
|
CBoot::LoadMapFromFilename();
|
|
::HLE::Clear();
|
|
::HLE::PatchFunctions();
|
|
|
|
NOTICE_LOG(IOS, "Running game: %s (%s)", SConfig::GetInstance().m_strName.c_str(),
|
|
SConfig::GetInstance().m_strGameID.c_str());
|
|
}
|
|
|
|
constexpr u32 ADDRESS_INIT_SEMAPHORE = 0x30f8;
|
|
|
|
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;
|
|
}
|
|
|
|
std::vector<u8> elf_bytes = mios.GetElf();
|
|
ElfReader elf{elf_bytes.data()};
|
|
if (!elf.LoadIntoMemory(true))
|
|
{
|
|
PanicAlertT("Failed to load MIOS ELF into memory.");
|
|
Core::QueueHostJob(Core::Stop);
|
|
return false;
|
|
}
|
|
|
|
ReinitHardware();
|
|
NOTICE_LOG(IOS, "Reinitialised hardware.");
|
|
|
|
// Load symbols for the IPL if they exist.
|
|
g_symbolDB.Clear();
|
|
if (g_symbolDB.LoadMap(File::GetUserPath(D_MAPS_IDX) + "mios-ipl.map"))
|
|
{
|
|
::HLE::Clear();
|
|
::HLE::PatchFunctions();
|
|
}
|
|
|
|
const PowerPC::CoreMode core_mode = PowerPC::GetMode();
|
|
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
|
|
MSR = 0;
|
|
PC = 0x3400;
|
|
NOTICE_LOG(IOS, "Loaded MIOS and bootstrapped PPC.");
|
|
|
|
// IOS writes 0 to 0x30f8 before bootstrapping the PPC. Once started, the IPL eventually writes
|
|
// 0xdeadbeef there, then waits for it to be cleared by IOS before continuing.
|
|
while (Memory::Read_U32(ADDRESS_INIT_SEMAPHORE) != 0xdeadbeef)
|
|
PowerPC::SingleStep();
|
|
PowerPC::SetMode(core_mode);
|
|
|
|
Memory::Write_U32(0x00000000, ADDRESS_INIT_SEMAPHORE);
|
|
NOTICE_LOG(IOS, "IPL ready.");
|
|
UpdateRunningGame();
|
|
return true;
|
|
}
|
|
} // namespace MIOS
|
|
} // namespace HLE
|
|
} // namespace IOS
|