mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-06-09 16:13:39 +00:00
Some games include a copy of MW runtime.c in RELs. Easy to check. Some games also include _savevr/ _restvr for AltiVec. This is weird but we can handle it as well.
305 lines
13 KiB
Rust
305 lines
13 KiB
Rust
use anyhow::{bail, ensure, Result};
|
|
use flagset::FlagSet;
|
|
use itertools::Itertools;
|
|
use memchr::memmem;
|
|
|
|
use crate::{
|
|
analysis::cfa::{AnalyzerState, FunctionInfo, SectionAddress},
|
|
obj::{
|
|
ObjInfo, ObjKind, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
|
ObjSymbolFlags, ObjSymbolKind,
|
|
},
|
|
};
|
|
|
|
pub trait AnalysisPass {
|
|
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()>;
|
|
}
|
|
|
|
pub struct FindTRKInterruptVectorTable {}
|
|
|
|
pub const TRK_TABLE_HEADER: &str = "Metrowerks Target Resident Kernel for PowerPC";
|
|
pub const TRK_TABLE_SIZE: u32 = 0x1F34; // always?
|
|
|
|
// TRK_MINNOW_DOLPHIN.a __exception.s
|
|
impl AnalysisPass for FindTRKInterruptVectorTable {
|
|
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
|
for (&start, _) in
|
|
state.functions.iter().filter(|(_, info)| info.analyzed && info.end.is_none())
|
|
{
|
|
let section = &obj.sections[start.section];
|
|
let data = match section.data_range(start.address, 0) {
|
|
Ok(ret) => ret,
|
|
Err(_) => continue,
|
|
};
|
|
if data.starts_with(TRK_TABLE_HEADER.as_bytes())
|
|
&& data[TRK_TABLE_HEADER.as_bytes().len()] == 0
|
|
{
|
|
log::debug!("Found gTRKInterruptVectorTable @ {:#010X}", start);
|
|
state.known_symbols.entry(start).or_default().push(ObjSymbol {
|
|
name: "gTRKInterruptVectorTable".to_string(),
|
|
address: start.address as u64,
|
|
section: Some(start.section),
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(FlagSet::from(ObjSymbolFlags::Global)),
|
|
..Default::default()
|
|
});
|
|
let end = start + TRK_TABLE_SIZE;
|
|
state.known_symbols.entry(end).or_default().push(ObjSymbol {
|
|
name: "gTRKInterruptVectorTableEnd".to_string(),
|
|
address: end.address as u64,
|
|
section: Some(start.section),
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(FlagSet::from(ObjSymbolFlags::Global)),
|
|
..Default::default()
|
|
});
|
|
|
|
return Ok(());
|
|
}
|
|
}
|
|
log::debug!("gTRKInterruptVectorTable not found");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct FindSaveRestSleds {}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
const SLEDS: [([u8; 8], &str, &str, u32, u32, u32); 6] = [
|
|
([0xd9, 0xcb, 0xff, 0x70, 0xd9, 0xeb, 0xff, 0x78], "__save_fpr", "_savefpr_", 14, 32, 4),
|
|
([0xc9, 0xcb, 0xff, 0x70, 0xc9, 0xeb, 0xff, 0x78], "__restore_fpr", "_restfpr_", 14, 32, 4),
|
|
([0x91, 0xcb, 0xff, 0xb8, 0x91, 0xeb, 0xff, 0xbc], "__save_gpr", "_savegpr_", 14, 32, 4),
|
|
([0x81, 0xcb, 0xff, 0xb8, 0x81, 0xeb, 0xff, 0xbc], "__restore_gpr", "_restgpr_", 14, 32, 4),
|
|
([0x39, 0x80, 0xff, 0x40, 0x7e, 0x8c, 0x01, 0xce], "_savevr", "_savev", 20, 32, 8),
|
|
([0x39, 0x80, 0xff, 0x40, 0x7e, 0x8c, 0x00, 0xce], "_restorevr", "_restv", 20, 32, 8),
|
|
];
|
|
|
|
// Runtime.PPCEABI.H.a runtime.c
|
|
impl AnalysisPass for FindSaveRestSleds {
|
|
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
|
for (section_index, section) in obj.sections.by_kind(ObjSectionKind::Code) {
|
|
for (needle, func, label, reg_start, reg_end, step_size) in SLEDS {
|
|
let Some(pos) = memmem::find(§ion.data, &needle) else {
|
|
continue;
|
|
};
|
|
let start = SectionAddress::new(section_index, section.address as u32 + pos as u32);
|
|
log::debug!("Found {} @ {:#010X}", func, start);
|
|
let sled_size = (reg_end - reg_start) * step_size + 4 /* blr */;
|
|
state.functions.insert(start, FunctionInfo {
|
|
analyzed: false,
|
|
end: Some(start + sled_size),
|
|
slices: None,
|
|
});
|
|
state.known_symbols.entry(start).or_default().push(ObjSymbol {
|
|
name: func.to_string(),
|
|
address: start.address as u64,
|
|
section: Some(start.section),
|
|
size: sled_size as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
kind: ObjSymbolKind::Function,
|
|
..Default::default()
|
|
});
|
|
for i in reg_start..reg_end {
|
|
let addr = start + (i - reg_start) * step_size;
|
|
state.known_symbols.entry(addr).or_default().push(ObjSymbol {
|
|
name: format!("{}{}", label, i),
|
|
address: addr.address as u64,
|
|
section: Some(start.section),
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
..Default::default()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct FindRelCtorsDtors {}
|
|
|
|
impl AnalysisPass for FindRelCtorsDtors {
|
|
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
|
ensure!(obj.kind == ObjKind::Relocatable);
|
|
// ensure!(!obj.unresolved_relocations.is_empty());
|
|
|
|
match (obj.sections.by_name(".ctors")?, obj.sections.by_name(".dtors")?) {
|
|
(Some(_), Some(_)) => return Ok(()),
|
|
(None, None) => {}
|
|
_ => bail!("Only one of .ctors and .dtors has been found?"),
|
|
}
|
|
|
|
let possible_sections = obj
|
|
.sections
|
|
.iter()
|
|
.filter(|&(index, section)| {
|
|
if section.section_known
|
|
|| state.known_sections.contains_key(&index)
|
|
|| !matches!(section.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData)
|
|
|| section.size < 4
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let mut current_address = section.address as u32;
|
|
let section_end = current_address + section.size as u32;
|
|
// Check that each word has a relocation to a function
|
|
// And the section ends with a null pointer
|
|
while let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
|
|
reloc.module_id == obj.module_id
|
|
&& reloc.section == section.elf_index as u8
|
|
&& reloc.address == current_address
|
|
&& reloc.kind == ObjRelocKind::Absolute
|
|
}) {
|
|
let Some((target_section_index, target_section)) = obj
|
|
.sections
|
|
.iter()
|
|
.find(|(_, section)| section.elf_index == reloc.target_section as usize)
|
|
else {
|
|
return false;
|
|
};
|
|
if target_section.kind != ObjSectionKind::Code
|
|
|| !state
|
|
.functions
|
|
.contains_key(&SectionAddress::new(target_section_index, reloc.addend))
|
|
{
|
|
return false;
|
|
}
|
|
current_address += 4;
|
|
if current_address >= section_end {
|
|
return false;
|
|
}
|
|
}
|
|
if current_address + 4 != section_end {
|
|
return false;
|
|
}
|
|
section.data_range(section_end - 4, section_end).ok() == Some(&[0; 4])
|
|
})
|
|
.collect_vec();
|
|
|
|
if possible_sections.len() != 2 {
|
|
log::debug!("Failed to find .ctors and .dtors");
|
|
return Ok(());
|
|
}
|
|
|
|
log::debug!(
|
|
"Found .ctors and .dtors: {}, {}",
|
|
possible_sections[0].0,
|
|
possible_sections[1].0
|
|
);
|
|
let ctors_section_index = possible_sections[0].0;
|
|
let ctors_address = SectionAddress::new(ctors_section_index, 0);
|
|
state.known_sections.insert(ctors_section_index, ".ctors".to_string());
|
|
state.known_symbols.entry(ctors_address).or_default().push(ObjSymbol {
|
|
name: "_ctors".to_string(),
|
|
section: Some(ctors_section_index),
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
..Default::default()
|
|
});
|
|
|
|
let dtors_section_index = possible_sections[1].0;
|
|
let dtors_address = SectionAddress::new(dtors_section_index, 0);
|
|
state.known_sections.insert(dtors_section_index, ".dtors".to_string());
|
|
state.known_symbols.entry(dtors_address).or_default().push(ObjSymbol {
|
|
name: "_dtors".to_string(),
|
|
section: Some(dtors_section_index),
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
..Default::default()
|
|
});
|
|
|
|
// Check for duplicate entries in .dtors, indicating __destroy_global_chain_reference
|
|
// let mut dtors_entries = vec![];
|
|
// let mut current_address = obj.sections[dtors_section_index].address as u32;
|
|
// let section_end = current_address + obj.sections[dtors_section_index].size as u32;
|
|
// while let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
|
|
// reloc.module_id == obj.module_id
|
|
// && reloc.section == obj.sections[dtors_section_index].elf_index as u8
|
|
// && reloc.address == current_address
|
|
// && reloc.kind == ObjRelocKind::Absolute
|
|
// }) {
|
|
// let Some((target_section_index, target_section)) = obj
|
|
// .sections
|
|
// .iter()
|
|
// .find(|(_, section)| section.elf_index == reloc.target_section as usize)
|
|
// else {
|
|
// bail!("Failed to find target section for .dtors entry");
|
|
// };
|
|
// if target_section.kind != ObjSectionKind::Code
|
|
// || !state
|
|
// .function_bounds
|
|
// .contains_key(&SectionAddress::new(target_section_index, reloc.addend))
|
|
// {
|
|
// bail!("Failed to find target function for .dtors entry");
|
|
// }
|
|
// dtors_entries.push(SectionAddress::new(target_section_index, reloc.addend));
|
|
// current_address += 4;
|
|
// if current_address >= section_end {
|
|
// bail!("Failed to find null terminator for .dtors");
|
|
// }
|
|
// }
|
|
// if current_address + 4 != section_end {
|
|
// bail!("Failed to find null terminator for .dtors");
|
|
// }
|
|
// if dtors_entries.len() != dtors_entries.iter().unique().count() {
|
|
// log::debug!("Found __destroy_global_chain_reference");
|
|
// state.known_symbols.insert(SectionAddress::new(dtors_section_index, 0), ObjSymbol {
|
|
// name: "__destroy_global_chain_reference".to_string(),
|
|
// demangled_name: None,
|
|
// address: 0,
|
|
// section: Some(dtors_section_index),
|
|
// size: 4,
|
|
// size_known: true,
|
|
// flags: ObjSymbolFlagSet(ObjSymbolFlags::Local.into()),
|
|
// kind: ObjSymbolKind::Object,
|
|
// align: None,
|
|
// data_kind: Default::default(),
|
|
// });
|
|
// }
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct FindRelRodataData {}
|
|
|
|
impl AnalysisPass for FindRelRodataData {
|
|
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
|
ensure!(obj.kind == ObjKind::Relocatable);
|
|
|
|
match (obj.sections.by_name(".rodata")?, obj.sections.by_name(".data")?) {
|
|
(None, None) => {}
|
|
_ => return Ok(()),
|
|
}
|
|
|
|
let possible_sections = obj
|
|
.sections
|
|
.iter()
|
|
.filter(|&(index, section)| {
|
|
!section.section_known
|
|
&& !state.known_sections.contains_key(&index)
|
|
&& matches!(section.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData)
|
|
})
|
|
.collect_vec();
|
|
|
|
if possible_sections.len() != 2 {
|
|
log::debug!("Failed to find .rodata and .data");
|
|
return Ok(());
|
|
}
|
|
|
|
log::debug!(
|
|
"Found .rodata and .data: {}, {}",
|
|
possible_sections[0].0,
|
|
possible_sections[1].0
|
|
);
|
|
let rodata_section_index = possible_sections[0].0;
|
|
state.known_sections.insert(rodata_section_index, ".rodata".to_string());
|
|
|
|
let data_section_index = possible_sections[1].0;
|
|
state.known_sections.insert(data_section_index, ".data".to_string());
|
|
|
|
Ok(())
|
|
}
|
|
}
|