Support loading Wii Menu (BootStage) DOL files

Also adds support to elf2dol to extract the inner DOL
from a BootStage DOL.
This commit is contained in:
2025-06-23 18:21:42 -06:00
parent 8620099731
commit b56b399201
7 changed files with 526 additions and 357 deletions

View File

@@ -1,6 +1,6 @@
use std::{
cmp::min,
collections::BTreeMap,
collections::{BTreeMap, BTreeSet},
fmt::{Debug, Display, Formatter, UpperHex},
ops::{Add, AddAssign, BitAnd, Sub},
};
@@ -572,6 +572,26 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
Some((sda2_base, sda_base)) => {
obj.sda2_base = Some(sda2_base);
obj.sda_base = Some(sda_base);
obj.add_symbol(
ObjSymbol {
name: "_SDA2_BASE_".to_string(),
address: sda2_base as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
..Default::default()
},
true,
)?;
obj.add_symbol(
ObjSymbol {
name: "_SDA_BASE_".to_string(),
address: sda_base as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
..Default::default()
},
true,
)?;
Ok(true)
}
None => Ok(false),
@@ -581,7 +601,7 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
/// ProDG hardcodes .bss and .sbss section initialization in `entry`
/// This function locates the memset calls and returns a list of
/// (address, size) pairs for the .bss sections.
pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
pub fn locate_bss_memsets(obj: &ObjInfo) -> Result<Vec<(u32, u32)>> {
let mut bss_sections: Vec<(u32, u32)> = Vec::new();
let Some(entry) = obj.entry else {
return Ok(bss_sections);
@@ -632,3 +652,50 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
)?;
Ok(bss_sections)
}
/// Execute VM from specified entry point following inner-section branches and function calls,
/// noting all branch targets outside the current section.
pub fn locate_cross_section_branch_targets(
obj: &ObjInfo,
entry: SectionAddress,
) -> Result<BTreeSet<SectionAddress>> {
let mut branch_targets = BTreeSet::<SectionAddress>::new();
let mut executor = Executor::new(obj);
executor.push(entry, VM::new(), false);
executor.run(
obj,
|ExecCbData { executor, vm, result, ins_addr, section: _, ins: _, block_start: _ }| {
match result {
StepResult::Continue | StepResult::LoadStore { .. } => {
Ok(ExecCbResult::<()>::Continue)
}
StepResult::Illegal => bail!("Illegal instruction @ {}", ins_addr),
StepResult::Jump(target) => {
if let BranchTarget::Address(RelocationTarget::Address(addr)) = target {
if addr.section == entry.section {
executor.push(addr, vm.clone_all(), true);
} else {
branch_targets.insert(addr);
}
}
Ok(ExecCbResult::EndBlock)
}
StepResult::Branch(branches) => {
for branch in branches {
if let BranchTarget::Address(RelocationTarget::Address(addr)) =
branch.target
{
if addr.section == entry.section {
executor.push(addr, branch.vm, true);
} else {
branch_targets.insert(addr);
}
}
}
Ok(ExecCbResult::Continue)
}
}
},
)?;
Ok(branch_targets)
}

View File

@@ -97,8 +97,8 @@ fn check_prologue_sequence(
}
#[inline(always)]
fn is_stwu(ins: Ins) -> bool {
// stwu r1, d(r1)
ins.op == Opcode::Stwu && ins.field_rs() == 1 && ins.field_ra() == 1
// stwu[x] r1, d(r1)
matches!(ins.op, Opcode::Stwu | Opcode::Stwux) && ins.field_rs() == 1 && ins.field_ra() == 1
}
#[inline(always)]
fn is_stw(ins: Ins) -> bool {
@@ -213,7 +213,11 @@ impl FunctionSlices {
ins.op == Opcode::Or && ins.field_rd() == 1
}
if check_sequence(section, addr, Some(ins), &[(&is_mtlr, &is_addi), (&is_or, &is_mtlr)])? {
if check_sequence(section, addr, Some(ins), &[
(&is_mtlr, &is_addi),
(&is_mtlr, &is_or),
(&is_or, &is_mtlr),
])? {
if let Some(epilogue) = self.epilogue {
if epilogue != addr {
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr)
@@ -373,7 +377,14 @@ impl FunctionSlices {
function_end.or_else(|| self.end()),
)?;
log::debug!("-> size {}: {:?}", size, entries);
if (entries.contains(&next_address) || self.blocks.contains_key(&next_address))
let max_block = self
.blocks
.keys()
.next_back()
.copied()
.unwrap_or(next_address)
.max(next_address);
if entries.iter().any(|&addr| addr > function_start && addr <= max_block)
&& !entries.iter().any(|&addr| {
self.is_known_function(known_functions, addr)
.is_some_and(|fn_addr| fn_addr != function_start)
@@ -736,7 +747,7 @@ impl FunctionSlices {
}
}
// If we discovered a function prologue, known tail call.
if slices.prologue.is_some() {
if slices.prologue.is_some() || slices.has_r1_load {
log::trace!("Prologue discovered; known tail call: {:#010X}", addr);
return TailCallResult::Is;
}