Support for loading Ancast images

This commit is contained in:
GaryOderNichts 2022-10-21 23:19:40 +02:00
parent 79e09c3731
commit 82d20ce352
5 changed files with 215 additions and 2 deletions

View File

@ -0,0 +1,86 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/CommonTypes.h"
#include "Common/Crypto/SHA1.h"
// Magic number
constexpr u32 ANCAST_MAGIC = 0xEFA282D9;
// The location in memory where PPC ancast images are booted from
constexpr u32 ESPRESSO_ANCAST_LOCATION_PHYS = 0x01330000;
constexpr u32 ESPRESSO_ANCAST_LOCATION_VIRT = 0x81330000;
// The image type
enum AncastImageType
{
ANCAST_IMAGE_TYPE_ESPRESSO_WIIU = 0x11,
ANCAST_IMAGE_TYPE_ESPRESSO_WII = 0x13,
ANCAST_IMAGE_TYPE_STARBUCK_NAND = 0x21,
ANCAST_IMAGE_TYPE_STARBUCK_SD = 0x22,
};
// The console type of the image
enum AncastConsoleType
{
ANCAST_CONSOLE_TYPE_DEV = 0x01,
ANCAST_CONSOLE_TYPE_RETAIL = 0x02,
};
// Start of each header
struct AncastHeaderBlock
{
u32 magic;
u32 unknown;
u32 header_block_size;
u8 padding[0x14];
};
static_assert(sizeof(AncastHeaderBlock) == 0x20);
// Signature block for type 1
struct AncastSignatureBlockv1
{
u32 signature_type;
u8 signature[0x38];
u8 padding[0x44];
};
// Signature block for type 2
struct AncastSignatureBlockv2
{
u32 signature_type;
u8 signature[0x100];
u8 padding[0x7c];
};
// General info about the image
struct AncastInfoBlock
{
u32 unknown;
u32 image_type;
u32 console_type;
u32 body_size;
Common::SHA1::Digest body_hash;
u32 version;
u8 padding[0x38];
};
// The header of espresso ancast images
struct EspressoAncastHeader
{
AncastHeaderBlock header_block;
AncastSignatureBlockv1 signature_block;
AncastInfoBlock info_block;
};
static_assert(sizeof(EspressoAncastHeader) == 0x100);
// The header of starbuck ancast images
struct StarbuckAncastHeader
{
AncastHeaderBlock header_block;
AncastSignatureBlockv2 signature_block;
AncastInfoBlock info_block;
};
static_assert(sizeof(StarbuckAncastHeader) == 0x200);

View File

@ -10,6 +10,7 @@
#include "Common/IOFile.h"
#include "Common/Swap.h"
#include "Core/Boot/AncastTypes.h"
#include "Core/HW/Memmap.h"
DolReader::DolReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
@ -94,6 +95,17 @@ bool DolReader::Initialize(const std::vector<u8>& buffer)
}
}
// Check if this dol contains an ancast image
// The ancast image will always be in the first data section
m_is_ancast = false;
if (m_data_sections[0].size() > sizeof(EspressoAncastHeader) &&
m_dolheader.dataAddress[0] == ESPRESSO_ANCAST_LOCATION_VIRT)
{
// Check for the ancast magic
if (Common::swap32(m_data_sections[0].data()) == ANCAST_MAGIC)
m_is_ancast = true;
}
return true;
}
@ -102,6 +114,9 @@ bool DolReader::LoadIntoMemory(bool only_in_mem1) const
if (!m_is_valid)
return false;
if (m_is_ancast)
return LoadAncastIntoMemory();
// load all text (code) sections
for (size_t i = 0; i < m_text_sections.size(); ++i)
if (!m_text_sections[i].empty() &&
@ -124,3 +139,91 @@ bool DolReader::LoadIntoMemory(bool only_in_mem1) const
return true;
}
// On a real console this would be done in the Espresso bootrom
bool DolReader::LoadAncastIntoMemory() const
{
// The ancast image will always be in data section 0
const auto& section = m_data_sections[0];
const u32 section_address = m_dolheader.dataAddress[0];
const auto* header = reinterpret_cast<const EspressoAncastHeader*>(section.data());
// Verify header block size
if (Common::swap32(header->header_block.header_block_size) != sizeof(AncastHeaderBlock))
{
ERROR_LOG_FMT(BOOT, "Ancast: Invalid header block size: 0x{:x}",
Common::swap32(header->header_block.header_block_size));
return false;
}
// Make sure this is a PPC ancast image
if (Common::swap32(header->signature_block.signature_type) != 0x01)
{
ERROR_LOG_FMT(BOOT, "Ancast: Invalid signature type: 0x{:x}",
Common::swap32(header->signature_block.signature_type));
return false;
}
// Make sure this is a Wii-Mode ancast image
if (Common::swap32(header->info_block.image_type) != ANCAST_IMAGE_TYPE_ESPRESSO_WII)
{
ERROR_LOG_FMT(BOOT, "Ancast: Invalid image type: 0x{:x}",
Common::swap32(header->info_block.image_type));
return false;
}
// Verify the body size
const u32 body_size = Common::swap32(header->info_block.body_size);
if (body_size + sizeof(EspressoAncastHeader) > section.size())
{
ERROR_LOG_FMT(BOOT, "Ancast: Invalid body size: 0x{:x}", body_size);
return false;
}
// Verify the body hash
const auto digest =
Common::SHA1::CalculateDigest(section.data() + sizeof(EspressoAncastHeader), body_size);
if (digest != header->info_block.body_hash)
{
ERROR_LOG_FMT(BOOT, "Ancast: Body hash mismatch");
return false;
}
// Check if this is a retail or dev image
bool is_dev = false;
if (Common::swap32(header->info_block.console_type) == ANCAST_CONSOLE_TYPE_DEV)
{
is_dev = true;
}
else if (Common::swap32(header->info_block.console_type) != ANCAST_CONSOLE_TYPE_RETAIL)
{
ERROR_LOG_FMT(BOOT, "Ancast: Invalid console type: 0x{:x}",
Common::swap32(header->info_block.console_type));
return false;
}
// Decrypt the Ancast image
static constexpr u8 vwii_ancast_retail_key[0x10] = {0x2e, 0xfe, 0x8a, 0xbc, 0xed, 0xbb,
0x7b, 0xaa, 0xe3, 0xc0, 0xed, 0x92,
0xfa, 0x29, 0xf8, 0x66};
static constexpr u8 vwii_ancast_dev_key[0x10] = {0x26, 0xaf, 0xf4, 0xbb, 0xac, 0x88, 0xbb, 0x76,
0x9d, 0xfc, 0x54, 0xdd, 0x56, 0xd8, 0xef, 0xbd};
std::unique_ptr<Common::AES::Context> ctx =
Common::AES::CreateContextDecrypt(is_dev ? vwii_ancast_dev_key : vwii_ancast_retail_key);
static constexpr u8 vwii_ancast_iv[0x10] = {0x59, 0x6d, 0x5a, 0x9a, 0xd7, 0x05, 0xf9, 0x4f,
0xe1, 0x58, 0x02, 0x6f, 0xea, 0xa7, 0xb8, 0x87};
std::vector<u8> decrypted(body_size);
if (!ctx->Crypt(vwii_ancast_iv, section.data() + sizeof(EspressoAncastHeader), decrypted.data(),
body_size))
return false;
// Copy the Ancast header to the emu
Memory::CopyToEmu(section_address, header, sizeof(EspressoAncastHeader));
// Copy the decrypted body to the emu
Memory::CopyToEmu(section_address + sizeof(EspressoAncastHeader), decrypted.data(), body_size);
return true;
}

View File

@ -24,6 +24,7 @@ public:
bool IsValid() const override { return m_is_valid; }
bool IsWii() const override { return m_is_wii; }
bool IsAncast() const { return m_is_ancast; };
u32 GetEntryPoint() const override { return m_dolheader.entryPoint; }
bool LoadIntoMemory(bool only_in_mem1 = false) const override;
bool LoadSymbols() const override { return false; }
@ -57,7 +58,10 @@ private:
bool m_is_valid;
bool m_is_wii;
bool m_is_ancast;
// Copy sections to internal buffers
bool Initialize(const std::vector<u8>& buffer);
bool LoadAncastIntoMemory() const;
};

View File

@ -3,6 +3,7 @@ add_library(core
ActionReplay.h
ARDecrypt.cpp
ARDecrypt.h
Boot/AncastTypes.h
Boot/Boot_BS2Emu.cpp
Boot/Boot_WiiWAD.cpp
Boot/Boot.cpp

View File

@ -17,6 +17,7 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Timer.h"
#include "Core/Boot/AncastTypes.h"
#include "Core/Boot/DolReader.h"
#include "Core/Boot/ElfReader.h"
#include "Core/CommonTitles.h"
@ -206,6 +207,15 @@ static void ReleasePPC()
PC = 0x3400;
}
static void ReleasePPCAncast()
{
Memory::Write_U32(0, 0);
// On a real console the Espresso verifies and decrypts the Ancast image,
// then jumps to the decrypted ancast body.
// The Ancast loader already did this, so just jump to the decrypted body.
PC = ESPRESSO_ANCAST_LOCATION_VIRT + sizeof(EspressoAncastHeader);
}
void RAMOverrideForIOSMemoryValues(MemorySetupType setup_type)
{
// Don't touch anything if the feature isn't enabled.
@ -393,11 +403,14 @@ bool Kernel::BootstrapPPC(const std::string& boot_content_path)
// Reset the PPC and pause its execution until we're ready.
ResetAndPausePPC();
if (dol.IsAncast())
INFO_LOG_FMT(IOS, "BootstrapPPC: Loading ancast image");
if (!dol.LoadIntoMemory())
return false;
INFO_LOG_FMT(IOS, "BootstrapPPC: {}", boot_content_path);
CoreTiming::ScheduleEvent(ticks, s_event_finish_ppc_bootstrap);
CoreTiming::ScheduleEvent(ticks, s_event_finish_ppc_bootstrap, dol.IsAncast());
return true;
}
@ -865,7 +878,13 @@ IOSC& Kernel::GetIOSC()
static void FinishPPCBootstrap(u64 userdata, s64 cycles_late)
{
ReleasePPC();
// See Kernel::BootstrapPPC
const bool is_ancast = userdata == 1;
if (is_ancast)
ReleasePPCAncast();
else
ReleasePPC();
SConfig::OnNewTitleLoad();
INFO_LOG_FMT(IOS, "Bootstrapping done.");
}