Boot executables lent from where they lie

A DOL or ELF on the host is read where it lies in its mapped file, or
read once into a malloc'd buffer where it cannot be mapped; one handed
over by the caller is used in place, freed by the reader only when it
takes ownership. A DOL's sections are a fixed array of pointers into
it, copied straight into emulated memory, and an ancast body is
decrypted straight into emulated memory behind its header. An ELF,
whose headers are swapped in place, is copied out of a mapping only
then; the ARM binary IOS boots is swapped where it was read, its ELF
never copied out.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 03:48:40 +01:00
1 parent ce91925b34
commit 7ac17da8f9
7 files changed
+161 -108

No files matched your search

+42 -8
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <array>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <numeric>
@@ -651,23 +652,56 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
}
BootExecutableReader::BootExecutableReader(const std::string& file_name)
: BootExecutableReader(File::IOFile{file_name, "rb"})
: m_file(file_name, "rb")
{
u64 size = 0;
const u8* map = m_file.GetMappedPtr(&size);
if (map)
{
m_bytes = map;
m_size = size_t(size);
return;
}
size = m_file.GetSize();
if (!size || !(m_mutable = static_cast<u8*>(std::malloc(size_t(size)))))
return;
m_owned = true;
if (!m_file.ReadBytes(m_mutable, size_t(size)))
size = 0;
m_file.Close();
m_bytes = m_mutable;
m_size = size_t(size);
}
BootExecutableReader::BootExecutableReader(u8* data, size_t size, bool take_ownership)
: m_bytes(data), m_size(data ? size : 0), m_mutable(data), m_owned(take_ownership)
{
}
BootExecutableReader::BootExecutableReader(File::IOFile file)
BootExecutableReader::~BootExecutableReader()
{
file.Seek(0, File::SeekOrigin::Begin);
m_bytes.resize(file.GetSize());
file.ReadBytes(m_bytes.data(), m_bytes.size());
if (m_owned)
std::free(m_mutable);
}
BootExecutableReader::BootExecutableReader(std::vector<u8> bytes) : m_bytes(std::move(bytes))
u8* BootExecutableReader::MutableBytes()
{
if (!m_mutable && m_size)
{
m_mutable = static_cast<u8*>(std::malloc(m_size));
if (!m_mutable)
{
m_size = 0;
return nullptr;
}
std::memcpy(m_mutable, m_bytes, m_size);
m_owned = true;
m_bytes = m_mutable;
m_file.Close();
}
return m_mutable;
}
BootExecutableReader::~BootExecutableReader() = default;
void StateFlags::UpdateChecksum()
{
constexpr size_t length_in_bytes = sizeof(StateFlags) - 4;
+18 -3
View File
@@ -12,6 +12,7 @@
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/IOFile.h"
#include "Core/IOS/IOSC.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"
@@ -169,11 +170,16 @@ private:
class BootExecutableReader
{
public:
// The executable lent from the file's mapping where it has one, else read into memory.
explicit BootExecutableReader(const std::string& file_name);
explicit BootExecutableReader(File::IOFile file);
explicit BootExecutableReader(std::vector<u8> buffer);
// The executable at data, lent by the caller, who keeps it alive and lets the reader
// rewrite it in place; freed with free() by the reader when take_ownership.
BootExecutableReader(u8* data, size_t size, bool take_ownership);
virtual ~BootExecutableReader();
BootExecutableReader(const BootExecutableReader&) = delete;
BootExecutableReader& operator=(const BootExecutableReader&) = delete;
virtual u32 GetEntryPoint() const = 0;
virtual bool IsValid() const = 0;
virtual bool IsWii() const = 0;
@@ -182,7 +188,16 @@ public:
const std::string& filename) const = 0;
protected:
std::vector<u8> m_bytes;
// The executable's octets, read-only.
const u8* m_bytes = nullptr;
size_t m_size = 0;
// The same octets, writable, copied out of a mapping on first use.
u8* MutableBytes();
private:
File::IOFile m_file;
u8* m_mutable = nullptr;
bool m_owned = false;
};
struct StateFlags
+37 -41
View File
@@ -5,8 +5,6 @@
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#include "Common/Align.h"
#include "Common/BitUtils.h"
@@ -16,29 +14,25 @@
#include "Core/HW/Memmap.h"
#include "Core/System.h"
DolReader::DolReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
{
m_is_valid = Initialize(m_bytes);
}
DolReader::DolReader(File::IOFile file) : BootExecutableReader(std::move(file))
{
m_is_valid = Initialize(m_bytes);
}
DolReader::DolReader(const std::string& filename) : BootExecutableReader(filename)
{
m_is_valid = Initialize(m_bytes);
m_is_valid = Initialize();
}
DolReader::DolReader(u8* data, size_t size, bool take_ownership)
: BootExecutableReader(data, size, take_ownership)
{
m_is_valid = Initialize();
}
DolReader::~DolReader() = default;
bool DolReader::Initialize(std::span<const u8> buffer)
bool DolReader::Initialize()
{
if (buffer.size() < sizeof(DolHeader) || buffer.size() > UINT32_MAX)
if (m_size < sizeof(DolHeader) || m_size > UINT32_MAX)
return false;
std::memcpy(&m_dolheader, buffer.data(), sizeof(DolHeader));
std::memcpy(&m_dolheader, m_bytes, sizeof(DolHeader));
// swap memory
u32* p = (u32*)&m_dolheader;
@@ -67,15 +61,16 @@ bool DolReader::Initialize(std::span<const u8> buffer)
i);
}
if (buffer.size() < m_dolheader.m_text_offset[i] + section_size)
if (m_size < m_dolheader.m_text_offset[i] + section_size)
return false;
auto& section = m_sections.emplace_back();
LoadableSection& section = m_sections[m_num_sections++];
section.m_address = m_dolheader.m_text_address[i];
section.m_header_section_size = m_dolheader.m_text_size[i];
section.m_data = buffer.subspan(m_dolheader.m_text_offset[i], section_size);
section.m_data = m_bytes + m_dolheader.m_text_offset[i];
section.m_size = static_cast<u32>(section_size);
const u8* const begin = section.m_data.data();
const u8* const begin = section.m_data;
const u8* const end = begin + section_size;
for (const u8* ptr = begin; ptr < end; ptr += 4)
{
@@ -102,21 +97,22 @@ bool DolReader::Initialize(std::span<const u8> buffer)
i);
}
if (buffer.size() < section_offset + section_size)
if (m_size < section_offset + section_size)
return false;
auto& section = m_sections.emplace_back();
LoadableSection& section = m_sections[m_num_sections++];
section.m_address = m_dolheader.m_data_address[i];
section.m_header_section_size = m_dolheader.m_data_size[i];
section.m_data = buffer.subspan(section_offset, section_size);
section.m_data = m_bytes + section_offset;
section.m_size = static_cast<u32>(section_size);
// Check if this dol contains an ancast image
// The ancast image will always be in the first data section
if (i == 0 && section.m_header_section_size > sizeof(EspressoAncastHeader) &&
section.m_address == ESPRESSO_ANCAST_LOCATION_VIRT &&
Common::swap32(section.m_data.data()) == ANCAST_MAGIC)
Common::swap32(section.m_data) == ANCAST_MAGIC)
{
m_ancast_index = m_sections.size() - 1;
m_ancast_index = m_num_sections - 1;
}
}
@@ -133,13 +129,14 @@ bool DolReader::LoadIntoMemory(Core::System& system, bool only_in_mem1) const
auto& memory = system.GetMemory();
// load all loadable sections
for (auto& section : m_sections)
// load all loadable sections, straight from the file's mapping where it has one
for (int i = 0; i < m_num_sections; i++)
{
if (only_in_mem1 && section.m_address + section.m_data.size() >= memory.GetRamSizeReal())
const LoadableSection& section = m_sections[i];
if (only_in_mem1 && section.m_address + section.m_size >= memory.GetRamSizeReal())
continue;
memory.CopyToEmu(section.m_address, section.m_data.data(), section.m_data.size());
memory.CopyToEmu(section.m_address, section.m_data, section.m_size);
}
return true;
@@ -148,10 +145,10 @@ bool DolReader::LoadIntoMemory(Core::System& system, bool only_in_mem1) const
// On a real console this would be done in the Espresso bootrom
bool DolReader::LoadAncastIntoMemory(Core::System& system) const
{
const LoadableSection& section = m_sections[m_ancast_index.value()];
const LoadableSection& section = m_sections[m_ancast_index];
const u32 section_address = m_dolheader.m_data_address[0];
const EspressoAncastHeader header =
Common::BitCastPtr<EspressoAncastHeader>(section.m_data.data());
Common::BitCastPtr<EspressoAncastHeader>(section.m_data);
// Verify header block size
if (Common::swap32(header.header_block.header_block_size) != sizeof(AncastHeaderBlock))
@@ -187,7 +184,7 @@ bool DolReader::LoadAncastIntoMemory(Core::System& system) const
// Verify the body hash
const auto digest = Common::SHA1::CalculateDigest(
section.m_data.data() + sizeof(EspressoAncastHeader), body_size);
section.m_data + sizeof(EspressoAncastHeader), body_size);
if (digest != header.info_block.body_hash)
{
ERROR_LOG_FMT(BOOT, "Ancast: Body hash mismatch");
@@ -218,18 +215,17 @@ bool DolReader::LoadAncastIntoMemory(Core::System& system) const
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.m_data.data() + sizeof(EspressoAncastHeader),
decrypted.data(), body_size))
return false;
auto& memory = system.GetMemory();
// Copy the Ancast header to the emu
// The body is decrypted straight into emulated memory, behind its header.
u8* const body =
memory.GetPointerForRange(section_address + sizeof(EspressoAncastHeader), body_size);
if (!body ||
!ctx->Crypt(vwii_ancast_iv, section.m_data + sizeof(EspressoAncastHeader), body, body_size))
{
return false;
}
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;
}
+8 -16
View File
@@ -3,30 +3,21 @@
#pragma once
#include <optional>
#include <span>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/Boot/Boot.h"
namespace File
{
class IOFile;
}
class DolReader final : public BootExecutableReader
{
public:
explicit DolReader(const std::string& filename);
explicit DolReader(File::IOFile file);
explicit DolReader(std::vector<u8> buffer);
DolReader(u8* data, size_t size, bool take_ownership);
~DolReader() override;
bool IsValid() const override { return m_is_valid; }
bool IsWii() const override { return m_is_wii; }
bool IsAncast() const { return m_ancast_index.has_value(); }
bool IsAncast() const { return m_ancast_index >= 0; }
u32 GetEntryPoint() const override { return m_dolheader.m_entrypoint; }
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
@@ -61,19 +52,20 @@ private:
struct LoadableSection
{
const u8* m_data;
u32 m_size;
u32 m_address;
u32 m_header_section_size;
std::span<const u8> m_data;
};
std::vector<LoadableSection> m_sections;
LoadableSection m_sections[DOL_NUM_TEXT + DOL_NUM_DATA];
int m_num_sections = 0;
int m_ancast_index = -1;
bool m_is_valid;
bool m_is_wii;
std::optional<std::size_t> m_ancast_index;
// Copy sections to internal buffers
bool Initialize(std::span<const u8> buffer);
bool Initialize();
bool LoadAncastIntoMemory(Core::System& system) const;
};
+15 -15
View File
@@ -69,34 +69,34 @@ static void byteswapSection(Elf32_Shdr& sec)
bswap(sec.sh_type);
}
ElfReader::ElfReader(std::vector<u8> buffer) : BootExecutableReader(std::move(buffer))
{
m_is_valid = Initialize();
}
ElfReader::ElfReader(File::IOFile file) : BootExecutableReader(std::move(file))
{
m_is_valid = Initialize();
}
ElfReader::ElfReader(const std::string& filename) : BootExecutableReader(filename)
{
m_is_valid = Initialize();
}
ElfReader::ElfReader(u8* data, size_t size, bool take_ownership)
: BootExecutableReader(data, size, take_ownership)
{
m_is_valid = Initialize();
}
ElfReader::~ElfReader() = default;
bool ElfReader::Initialize()
{
if (m_bytes.size() < sizeof(Elf32_Ehdr))
if (m_size < sizeof(Elf32_Ehdr))
{
ERROR_LOG_FMT(BOOT, "ELF file is too small.");
return false;
}
base = reinterpret_cast<char*>(m_bytes.data());
base32 = reinterpret_cast<u32*>(m_bytes.data());
header = reinterpret_cast<Elf32_Ehdr*>(m_bytes.data());
// the headers are swapped in place
u8* const bytes = MutableBytes();
if (!bytes)
return false;
base = reinterpret_cast<char*>(bytes);
base32 = reinterpret_cast<u32*>(bytes);
header = reinterpret_cast<Elf32_Ehdr*>(bytes);
if (header->e_ident[EI_MAG0] != ELFMAG0 || header->e_ident[EI_MAG1] != ELFMAG1 ||
header->e_ident[EI_MAG2] != ELFMAG2 || header->e_ident[EI_MAG3] != ELFMAG3 ||
header->e_ident[EI_CLASS] != ELFCLASS32 || header->e_ident[EI_DATA] != ELFDATA2MSB)
@@ -108,7 +108,7 @@ bool ElfReader::Initialize()
byteswapHeader(*header);
const auto is_range_valid = [this](size_t offset, size_t size) {
return offset <= m_bytes.size() && size <= m_bytes.size() - offset;
return offset <= m_size && size <= m_size - offset;
};
if (header->e_ehsize != sizeof(Elf32_Ehdr) ||
(header->e_phnum != 0 && header->e_phentsize != sizeof(Elf32_Phdr)) ||
+1 -2
View File
@@ -26,8 +26,7 @@ class ElfReader final : public BootExecutableReader
{
public:
explicit ElfReader(const std::string& filename);
explicit ElfReader(File::IOFile file);
explicit ElfReader(std::vector<u8> buffer);
ElfReader(u8* data, size_t size, bool take_ownership);
~ElfReader() override;
u32 Read32(int off) const { return base32[off >> 2]; }
// Quick accessors
+40 -23
View File
@@ -4,6 +4,7 @@
#include "Core/IOS/IOS.h"
#include <algorithm>
#include <cstdlib>
#include <array>
#include <map>
#include <memory>
@@ -401,20 +402,29 @@ u16 EmulationKernel::GetGidForPPC() const
return m_ppc_gid;
}
static std::vector<u8> ReadBootContent(FSCore& fs, const std::string& path, size_t max_size,
Ticks ticks = {})
// The file, in memory from malloc() for the caller to free; nullptr when it cannot be read or
// is larger than a nonzero max_size.
static u8* ReadBootContent(FSCore& fs, const std::string& path, size_t max_size, size_t* size,
Ticks ticks = {})
{
const auto fd = fs.Open(0, 0, path, FS::Mode::Read, {}, ticks);
u8* buffer;
if (fd.Get() < 0)
return {};
return nullptr;
const size_t file_size = fs.GetFileStatus(fd.Get(), ticks)->size;
if (max_size != 0 && file_size > max_size)
return {};
if (!file_size || (max_size != 0 && file_size > max_size))
return nullptr;
std::vector<u8> buffer(file_size);
if (!fs.Read(fd.Get(), buffer.data(), buffer.size(), ticks))
return {};
buffer = static_cast<u8*>(std::malloc(file_size));
if (!buffer)
return nullptr;
if (!fs.Read(fd.Get(), buffer, file_size, ticks))
{
std::free(buffer);
return nullptr;
}
*size = file_size;
return buffer;
}
@@ -425,7 +435,9 @@ bool EmulationKernel::BootstrapPPC(const std::string& boot_content_path)
// Seeking and processing overhead is ignored as most time is spent reading from the NAND.
u64 ticks = 0;
const DolReader dol{ReadBootContent(GetFSCore(), boot_content_path, 0, &ticks)};
size_t dol_size = 0;
u8* const dol_bytes = ReadBootContent(GetFSCore(), boot_content_path, 0, &dol_size, &ticks);
const DolReader dol{dol_bytes, dol_size, true};
if (!dol.IsValid())
return false;
@@ -447,29 +459,32 @@ bool EmulationKernel::BootstrapPPC(const std::string& boot_content_path)
return true;
}
// An ARM boot binary: a header, then the ELF the PPC side loads.
struct ARMBinary final
{
explicit ARMBinary(std::vector<u8>&& bytes) : m_bytes(std::move(bytes)) {}
ARMBinary(u8* bytes, size_t size) : m_bytes(bytes), m_size(bytes ? size : 0) {}
~ARMBinary() { std::free(m_bytes); }
ARMBinary(const ARMBinary&) = delete;
ARMBinary& operator=(const ARMBinary&) = delete;
bool IsValid() const
{
// The header is at least 0x10.
if (m_bytes.size() < 0x10)
if (m_size < 0x10)
return false;
return m_bytes.size() >= (GetHeaderSize() + GetElfOffset() + GetElfSize());
return m_size >= u64(GetHeaderSize()) + GetElfOffset() + GetElfSize();
}
std::vector<u8> GetElf() const
{
const auto iterator = m_bytes.cbegin() + GetHeaderSize() + GetElfOffset();
return std::vector<u8>(iterator, iterator + GetElfSize());
}
// The ELF where it lies in the binary, for a reader to rewrite in place.
u8* GetElf() { return m_bytes + GetHeaderSize() + GetElfOffset(); }
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); }
u32 GetHeaderSize() const { return Common::swap32(m_bytes); }
u32 GetElfOffset() const { return Common::swap32(m_bytes + 0x4); }
u32 GetElfSize() const { return Common::swap32(m_bytes + 0x8); }
private:
std::vector<u8> m_bytes;
u8* m_bytes;
size_t m_size;
};
static void FinishIOSBoot(Core::System& system, u64 ios_title_id)
@@ -506,11 +521,13 @@ bool EmulationKernel::BootIOS(const u64 ios_title_id, HangPPC hang_ppc,
// Load the ARM binary to memory (if possible).
// Because we do not actually emulate the Starlet, only load the sections that are in MEM1.
ARMBinary binary{ReadBootContent(GetFSCore(), boot_content_path, 0xB00000)};
size_t binary_size = 0;
u8* const binary_bytes = ReadBootContent(GetFSCore(), boot_content_path, 0xB00000, &binary_size);
ARMBinary binary{binary_bytes, binary_size};
if (!binary.IsValid())
return false;
ElfReader elf{binary.GetElf()};
ElfReader elf{binary.GetElf(), binary.GetElfSize(), false};
if (!elf.LoadIntoMemory(m_system, true))
return false;
}