Improvements to REL & map support
- Fix symbols.txt align attribute - Fully support nested RARC files & transparent Yaz0 decompression - Guess symbol visibility for maps without link map - Add module name config - Add manual force_active config - Quiet option for shasum - `symbols_known` and `fill_gaps` config - Allow disabling .comment generation per-unit (`comment:0`) - Various minor fixes - Add `rarc` and `yaz0` commands
This commit is contained in:
parent
f9f7fb2e1e
commit
e3857d3212
|
@ -19,11 +19,6 @@ SECTIONS
|
||||||
__ArenaHi = $ARENAHI;
|
__ArenaHi = $ARENAHI;
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCEFILES
|
|
||||||
{
|
|
||||||
$FORCEFILES
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEACTIVE
|
FORCEACTIVE
|
||||||
{
|
{
|
||||||
$FORCEACTIVE
|
$FORCEACTIVE
|
||||||
|
|
|
@ -19,8 +19,3 @@ FORCEACTIVE
|
||||||
_epilog
|
_epilog
|
||||||
$FORCEACTIVE
|
$FORCEACTIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCEFILES
|
|
||||||
{
|
|
||||||
$FORCEFILES
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub fn detect_objects(obj: &mut ObjInfo) -> Result<()> {
|
||||||
let mut replace_symbols = vec![];
|
let mut replace_symbols = vec![];
|
||||||
for (idx, symbol) in obj.symbols.for_section(section_index) {
|
for (idx, symbol) in obj.symbols.for_section(section_index) {
|
||||||
let mut symbol = symbol.clone();
|
let mut symbol = symbol.clone();
|
||||||
if is_linker_generated_label(&symbol.name) {
|
if is_linker_generated_label(&symbol.name) || symbol.name.starts_with("..") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let expected_size = match symbol.data_kind {
|
let expected_size = match symbol.data_kind {
|
||||||
|
@ -124,6 +124,11 @@ pub fn detect_strings(obj: &mut ObjInfo) -> Result<()> {
|
||||||
.for_section(section_index)
|
.for_section(section_index)
|
||||||
.filter(|(_, sym)| sym.data_kind == ObjDataKind::Unknown)
|
.filter(|(_, sym)| sym.data_kind == ObjDataKind::Unknown)
|
||||||
{
|
{
|
||||||
|
if symbol.name.starts_with("@stringBase") {
|
||||||
|
symbols_set.push((symbol_idx, ObjDataKind::StringTable, symbol.size as usize));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let data = section.symbol_data(symbol)?;
|
let data = section.symbol_data(symbol)?;
|
||||||
match is_string(data) {
|
match is_string(data) {
|
||||||
StringResult::None => {}
|
StringResult::None => {}
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl AnalysisPass for FindTRKInterruptVectorTable {
|
||||||
if data.starts_with(TRK_TABLE_HEADER.as_bytes())
|
if data.starts_with(TRK_TABLE_HEADER.as_bytes())
|
||||||
&& data[TRK_TABLE_HEADER.as_bytes().len()] == 0
|
&& data[TRK_TABLE_HEADER.as_bytes().len()] == 0
|
||||||
{
|
{
|
||||||
log::info!("Found gTRKInterruptVectorTable @ {:#010X}", start);
|
log::debug!("Found gTRKInterruptVectorTable @ {:#010X}", start);
|
||||||
state.known_symbols.insert(start, ObjSymbol {
|
state.known_symbols.insert(start, ObjSymbol {
|
||||||
name: "gTRKInterruptVectorTable".to_string(),
|
name: "gTRKInterruptVectorTable".to_string(),
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::{
|
analysis::{
|
||||||
|
@ -272,21 +272,21 @@ fn apply_ctors_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_dtors_signatures(obj: &mut ObjInfo) -> Result<()> {
|
fn apply_dtors_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||||
let Some((_, symbol)) = obj.symbols.by_name("_dtors")? else {
|
let (dtors_section_index, dtors_section) =
|
||||||
for symbol in obj.symbols.iter() {
|
if let Some((_, symbol)) = obj.symbols.by_name("_dtors")? {
|
||||||
println!("{:?} {:#010X} {}", symbol.section, symbol.address, symbol.name);
|
let section_index =
|
||||||
}
|
symbol.section.ok_or_else(|| anyhow!("Missing _dtors symbol section"))?;
|
||||||
bail!("Missing _dtors symbol");
|
(section_index, &obj.sections[section_index])
|
||||||
// return Ok(());
|
} else if let Some((section_index, section)) = obj.sections.by_name(".dtors")? {
|
||||||
};
|
(section_index, section)
|
||||||
let dtors_section_index =
|
} else {
|
||||||
symbol.section.ok_or_else(|| anyhow!("Missing _dtors symbol section"))?;
|
return Ok(());
|
||||||
let dtors_section = &obj.sections[dtors_section_index];
|
};
|
||||||
// __destroy_global_chain_reference + null pointer
|
// __destroy_global_chain_reference + null pointer
|
||||||
if dtors_section.size < 8 {
|
if dtors_section.size < 8 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let address = symbol.address;
|
let address = dtors_section.address;
|
||||||
let dgc_target = read_address(obj, dtors_section, address as u32).ok();
|
let dgc_target = read_address(obj, dtors_section, address as u32).ok();
|
||||||
let fce_target = read_address(obj, dtors_section, address as u32 + 4).ok();
|
let fce_target = read_address(obj, dtors_section, address as u32 + 4).ok();
|
||||||
let mut found_dgc = false;
|
let mut found_dgc = false;
|
||||||
|
@ -319,7 +319,7 @@ fn apply_dtors_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
found_dgc = true;
|
found_dgc = true;
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Failed to match __destroy_global_chain signature ({:#010X})", dgc_target);
|
log::debug!("Failed to match __destroy_global_chain signature ({:#010X})", dgc_target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,3 +445,40 @@ pub fn apply_signatures_post(obj: &mut ObjInfo) -> Result<()> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create _ctors and _dtors symbols if missing
|
||||||
|
pub fn update_ctors_dtors(obj: &mut ObjInfo) -> Result<()> {
|
||||||
|
if obj.symbols.by_name("_ctors")?.is_none() {
|
||||||
|
if let Some((section_index, section)) = obj.sections.by_name(".ctors")? {
|
||||||
|
obj.symbols.add_direct(ObjSymbol {
|
||||||
|
name: "_ctors".to_string(),
|
||||||
|
demangled_name: None,
|
||||||
|
address: section.address,
|
||||||
|
section: Some(section_index),
|
||||||
|
size: 0,
|
||||||
|
size_known: true,
|
||||||
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||||
|
kind: ObjSymbolKind::Unknown,
|
||||||
|
align: None,
|
||||||
|
data_kind: Default::default(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obj.symbols.by_name("_dtors")?.is_none() {
|
||||||
|
if let Some((section_index, section)) = obj.sections.by_name(".dtors")? {
|
||||||
|
obj.symbols.add_direct(ObjSymbol {
|
||||||
|
name: "_dtors".to_string(),
|
||||||
|
demangled_name: None,
|
||||||
|
address: section.address,
|
||||||
|
section: Some(section_index),
|
||||||
|
size: 0,
|
||||||
|
size_known: true,
|
||||||
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||||
|
kind: ObjSymbolKind::Unknown,
|
||||||
|
align: None,
|
||||||
|
data_kind: Default::default(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -433,7 +433,9 @@ impl Tracker {
|
||||||
|| self.sda2_base == Some(addr)
|
|| self.sda2_base == Some(addr)
|
||||||
|| self.sda_base == Some(addr)
|
|| self.sda_base == Some(addr)
|
||||||
{
|
{
|
||||||
return Some(SectionAddress::new(usize::MAX, addr));
|
let section_index =
|
||||||
|
obj.sections.at_address(addr).ok().map(|(idx, _)| idx).unwrap_or(usize::MAX);
|
||||||
|
return Some(SectionAddress::new(section_index, addr));
|
||||||
}
|
}
|
||||||
// if addr > 0x80000000 && addr < 0x80003100 {
|
// if addr > 0x80000000 && addr < 0x80003100 {
|
||||||
// return true;
|
// return true;
|
||||||
|
|
|
@ -60,8 +60,8 @@ fn create(args: CreateArgs) -> Result<()> {
|
||||||
Entry::Vacant(e) => e.insert(Vec::new()),
|
Entry::Vacant(e) => e.insert(Vec::new()),
|
||||||
Entry::Occupied(_) => bail!("Duplicate file name '{path_str}'"),
|
Entry::Occupied(_) => bail!("Duplicate file name '{path_str}'"),
|
||||||
};
|
};
|
||||||
let mmap = map_file(path)?;
|
let file = map_file(path)?;
|
||||||
let obj = object::File::parse(&*mmap)?;
|
let obj = object::File::parse(file.as_slice())?;
|
||||||
for symbol in obj.symbols() {
|
for symbol in obj.symbols() {
|
||||||
if symbol.scope() == SymbolScope::Dynamic {
|
if symbol.scope() == SymbolScope::Dynamic {
|
||||||
entries.push(symbol.name_bytes()?.to_vec());
|
entries.push(symbol.name_bytes()?.to_vec());
|
||||||
|
|
341
src/cmd/dol.rs
341
src/cmd/dol.rs
|
@ -1,8 +1,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
cmp::min,
|
||||||
collections::{btree_map::Entry, hash_map, BTreeMap, HashMap},
|
collections::{btree_map::Entry, hash_map, BTreeMap, HashMap},
|
||||||
|
ffi::OsStr,
|
||||||
fs,
|
fs,
|
||||||
fs::{DirBuilder, File},
|
fs::DirBuilder,
|
||||||
io::Write,
|
io::Write,
|
||||||
mem::take,
|
mem::take,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -24,12 +26,13 @@ use crate::{
|
||||||
AnalysisPass, FindRelCtorsDtors, FindRelRodataData, FindSaveRestSleds,
|
AnalysisPass, FindRelCtorsDtors, FindRelRodataData, FindSaveRestSleds,
|
||||||
FindTRKInterruptVectorTable,
|
FindTRKInterruptVectorTable,
|
||||||
},
|
},
|
||||||
signatures::{apply_signatures, apply_signatures_post},
|
signatures::{apply_signatures, apply_signatures_post, update_ctors_dtors},
|
||||||
tracker::Tracker,
|
tracker::Tracker,
|
||||||
},
|
},
|
||||||
|
cmd::shasum::file_sha1_string,
|
||||||
obj::{
|
obj::{
|
||||||
best_match_for_reloc, ObjDataKind, ObjInfo, ObjReloc, ObjRelocKind, ObjSectionKind,
|
ObjDataKind, ObjInfo, ObjReloc, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||||
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope, SymbolIndex,
|
ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope, SymbolIndex,
|
||||||
},
|
},
|
||||||
util::{
|
util::{
|
||||||
asm::write_asm,
|
asm::write_asm,
|
||||||
|
@ -41,10 +44,13 @@ use crate::{
|
||||||
dep::DepFile,
|
dep::DepFile,
|
||||||
dol::process_dol,
|
dol::process_dol,
|
||||||
elf::{process_elf, write_elf},
|
elf::{process_elf, write_elf},
|
||||||
file::{buf_writer, decompress_if_needed, map_file, touch, verify_hash, Reader},
|
file::{
|
||||||
|
buf_reader, buf_writer, decompress_if_needed, map_file, touch, verify_hash,
|
||||||
|
FileIterator, Reader,
|
||||||
|
},
|
||||||
lcf::{asm_path_for_unit, generate_ldscript, obj_path_for_unit},
|
lcf::{asm_path_for_unit, generate_ldscript, obj_path_for_unit},
|
||||||
map::apply_map_file,
|
map::apply_map_file,
|
||||||
rel::process_rel,
|
rel::{process_rel, process_rel_header},
|
||||||
rso::{process_rso, DOL_SECTION_ABS, DOL_SECTION_NAMES},
|
rso::{process_rso, DOL_SECTION_ABS, DOL_SECTION_NAMES},
|
||||||
split::{is_linker_generated_object, split_obj, update_splits},
|
split::{is_linker_generated_object, split_obj, update_splits},
|
||||||
IntoCow, ToCow,
|
IntoCow, ToCow,
|
||||||
|
@ -66,6 +72,7 @@ enum SubCommand {
|
||||||
Split(SplitArgs),
|
Split(SplitArgs),
|
||||||
Diff(DiffArgs),
|
Diff(DiffArgs),
|
||||||
Apply(ApplyArgs),
|
Apply(ApplyArgs),
|
||||||
|
Config(ConfigArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
@ -128,6 +135,18 @@ pub struct ApplyArgs {
|
||||||
map_file: PathBuf,
|
map_file: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
/// Generates a project configuration file from a DOL (& RELs).
|
||||||
|
#[argp(subcommand, name = "config")]
|
||||||
|
pub struct ConfigArgs {
|
||||||
|
#[argp(positional)]
|
||||||
|
/// object files
|
||||||
|
objects: Vec<PathBuf>,
|
||||||
|
#[argp(option, short = 'o')]
|
||||||
|
/// output config YAML file
|
||||||
|
out_file: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bool_true() -> bool { true }
|
fn bool_true() -> bool { true }
|
||||||
|
|
||||||
|
@ -195,15 +214,21 @@ pub struct ProjectConfig {
|
||||||
pub detect_strings: bool,
|
pub detect_strings: bool,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
pub write_asm: bool,
|
pub write_asm: bool,
|
||||||
/// Adds all objects to FORCEFILES in the linker script.
|
|
||||||
#[serde(default)]
|
|
||||||
pub auto_force_files: bool,
|
|
||||||
/// Specifies the start of the common BSS section.
|
/// Specifies the start of the common BSS section.
|
||||||
pub common_start: Option<u32>,
|
pub common_start: Option<u32>,
|
||||||
|
/// Disables all analysis passes that yield new symbols,
|
||||||
|
/// and instead assumes that all symbols are known.
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbols_known: bool,
|
||||||
|
/// Fills gaps between symbols with
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
pub fill_gaps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct ModuleConfig {
|
pub struct ModuleConfig {
|
||||||
|
/// Object name. If not specified, the file name without extension will be used.
|
||||||
|
pub name: Option<String>,
|
||||||
#[serde(with = "path_slash_serde")]
|
#[serde(with = "path_slash_serde")]
|
||||||
pub object: PathBuf,
|
pub object: PathBuf,
|
||||||
pub hash: Option<String>,
|
pub hash: Option<String>,
|
||||||
|
@ -213,6 +238,9 @@ pub struct ModuleConfig {
|
||||||
pub symbols: Option<PathBuf>,
|
pub symbols: Option<PathBuf>,
|
||||||
#[serde(with = "path_slash_serde_option", default)]
|
#[serde(with = "path_slash_serde_option", default)]
|
||||||
pub map: Option<PathBuf>,
|
pub map: Option<PathBuf>,
|
||||||
|
/// Forces the given symbols to be active in the linker script.
|
||||||
|
#[serde(default)]
|
||||||
|
pub force_active: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleConfig {
|
impl ModuleConfig {
|
||||||
|
@ -231,7 +259,9 @@ impl ModuleConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> Cow<'_, str> { self.file_prefix() }
|
pub fn name(&self) -> Cow<'_, str> {
|
||||||
|
self.name.as_ref().map(|n| n.as_str().to_cow()).unwrap_or_else(|| self.file_prefix())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -264,12 +294,12 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
SubCommand::Split(c_args) => split(c_args),
|
SubCommand::Split(c_args) => split(c_args),
|
||||||
SubCommand::Diff(c_args) => diff(c_args),
|
SubCommand::Diff(c_args) => diff(c_args),
|
||||||
SubCommand::Apply(c_args) => apply(c_args),
|
SubCommand::Apply(c_args) => apply(c_args),
|
||||||
|
SubCommand::Config(c_args) => config(c_args),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_selfile(obj: &mut ObjInfo, selfile: &Path) -> Result<()> {
|
fn apply_selfile(obj: &mut ObjInfo, buf: &[u8]) -> Result<()> {
|
||||||
log::info!("Loading {}", selfile.display());
|
let rso = process_rso(&mut Reader::new(buf))?;
|
||||||
let rso = process_rso(selfile)?;
|
|
||||||
for symbol in rso.symbols.iter() {
|
for symbol in rso.symbols.iter() {
|
||||||
let dol_section_index = match symbol.section {
|
let dol_section_index = match symbol.section {
|
||||||
Some(section) => section,
|
Some(section) => section,
|
||||||
|
@ -347,12 +377,16 @@ fn apply_selfile(obj: &mut ObjInfo, selfile: &Path) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(args: InfoArgs) -> Result<()> {
|
fn info(args: InfoArgs) -> Result<()> {
|
||||||
let mut obj = process_dol(&args.dol_file)?;
|
let mut obj = {
|
||||||
|
let file = map_file(&args.dol_file)?;
|
||||||
|
let data = decompress_if_needed(file.as_slice())?;
|
||||||
|
process_dol(data.as_ref(), "")?
|
||||||
|
};
|
||||||
apply_signatures(&mut obj)?;
|
apply_signatures(&mut obj)?;
|
||||||
|
|
||||||
let mut state = AnalyzerState::default();
|
let mut state = AnalyzerState::default();
|
||||||
state.detect_functions(&obj)?;
|
state.detect_functions(&obj)?;
|
||||||
log::info!("Discovered {} functions", state.function_slices.len());
|
log::debug!("Discovered {} functions", state.function_slices.len());
|
||||||
|
|
||||||
FindTRKInterruptVectorTable::execute(&mut state, &obj)?;
|
FindTRKInterruptVectorTable::execute(&mut state, &obj)?;
|
||||||
FindSaveRestSleds::execute(&mut state, &obj)?;
|
FindSaveRestSleds::execute(&mut state, &obj)?;
|
||||||
|
@ -361,7 +395,9 @@ fn info(args: InfoArgs) -> Result<()> {
|
||||||
apply_signatures_post(&mut obj)?;
|
apply_signatures_post(&mut obj)?;
|
||||||
|
|
||||||
if let Some(selfile) = &args.selfile {
|
if let Some(selfile) = &args.selfile {
|
||||||
apply_selfile(&mut obj, selfile)?;
|
let file = map_file(selfile)?;
|
||||||
|
let data = decompress_if_needed(file.as_slice())?;
|
||||||
|
apply_selfile(&mut obj, data.as_ref())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{}:", obj.name);
|
println!("{}:", obj.name);
|
||||||
|
@ -434,14 +470,10 @@ fn update_symbols(obj: &mut ObjInfo, modules: &ModuleMap<'_>) -> Result<()> {
|
||||||
.get_elf_index(rel_reloc.target_section as usize)
|
.get_elf_index(rel_reloc.target_section as usize)
|
||||||
.ok_or_else(|| anyhow!("Failed to locate REL section {}", rel_reloc.target_section))?;
|
.ok_or_else(|| anyhow!("Failed to locate REL section {}", rel_reloc.target_section))?;
|
||||||
|
|
||||||
let target_symbols = obj
|
if let Some((symbol_index, symbol)) = obj.symbols.for_relocation(
|
||||||
.symbols
|
SectionAddress::new(target_section_index, rel_reloc.addend),
|
||||||
.at_section_address(target_section_index, rel_reloc.addend)
|
rel_reloc.kind,
|
||||||
.filter(|(_, s)| s.referenced_by(rel_reloc.kind))
|
)? {
|
||||||
.collect_vec();
|
|
||||||
let target_symbol = best_match_for_reloc(target_symbols, rel_reloc.kind);
|
|
||||||
|
|
||||||
if let Some((symbol_index, symbol)) = target_symbol {
|
|
||||||
// Update symbol
|
// Update symbol
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"Found symbol in section {} at {:#010X}: {}",
|
"Found symbol in section {} at {:#010X}: {}",
|
||||||
|
@ -524,16 +556,15 @@ fn create_relocations(obj: &mut ObjInfo, modules: &ModuleMap<'_>, dol_obj: &ObjI
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let target_symbols = target_obj
|
let Some((symbol_index, symbol)) = target_obj.symbols.for_relocation(
|
||||||
.symbols
|
SectionAddress::new(target_section_index, rel_reloc.addend),
|
||||||
.at_section_address(target_section_index, rel_reloc.addend)
|
rel_reloc.kind,
|
||||||
.filter(|(_, s)| s.referenced_by(rel_reloc.kind))
|
)?
|
||||||
.collect_vec();
|
|
||||||
let Some((symbol_index, symbol)) = best_match_for_reloc(target_symbols, rel_reloc.kind)
|
|
||||||
else {
|
else {
|
||||||
bail!(
|
bail!(
|
||||||
"Couldn't find module {} symbol in section {} at {:#010X}",
|
"Couldn't find module {} ({}) symbol in section {} at {:#010X}",
|
||||||
rel_reloc.module_id,
|
rel_reloc.module_id,
|
||||||
|
target_obj.name,
|
||||||
rel_reloc.target_section,
|
rel_reloc.target_section,
|
||||||
rel_reloc.addend
|
rel_reloc.addend
|
||||||
);
|
);
|
||||||
|
@ -625,11 +656,15 @@ fn resolve_external_relocations(
|
||||||
type AnalyzeResult = (ObjInfo, Vec<PathBuf>);
|
type AnalyzeResult = (ObjInfo, Vec<PathBuf>);
|
||||||
|
|
||||||
fn load_analyze_dol(config: &ProjectConfig) -> Result<AnalyzeResult> {
|
fn load_analyze_dol(config: &ProjectConfig) -> Result<AnalyzeResult> {
|
||||||
// log::info!("Loading {}", config.object.display());
|
log::debug!("Loading {}", config.base.object.display());
|
||||||
if let Some(hash_str) = &config.base.hash {
|
let mut obj = {
|
||||||
verify_hash(&config.base.object, hash_str)?;
|
let file = map_file(&config.base.object)?;
|
||||||
}
|
let data = decompress_if_needed(file.as_slice())?;
|
||||||
let mut obj = process_dol(&config.base.object)?;
|
if let Some(hash_str) = &config.base.hash {
|
||||||
|
verify_hash(data.as_ref(), hash_str)?;
|
||||||
|
}
|
||||||
|
process_dol(data.as_ref(), config.base.name().as_ref())?
|
||||||
|
};
|
||||||
let mut dep = vec![config.base.object.clone()];
|
let mut dep = vec![config.base.object.clone()];
|
||||||
|
|
||||||
if let Some(comment_version) = config.mw_comment_version {
|
if let Some(comment_version) = config.mw_comment_version {
|
||||||
|
@ -651,29 +686,38 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result<AnalyzeResult> {
|
||||||
dep.push(symbols_path.clone());
|
dep.push(symbols_path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move before symbols?
|
if !config.symbols_known {
|
||||||
debug!("Performing signature analysis");
|
// TODO move before symbols?
|
||||||
apply_signatures(&mut obj)?;
|
debug!("Performing signature analysis");
|
||||||
|
apply_signatures(&mut obj)?;
|
||||||
|
|
||||||
if !config.quick_analysis {
|
if !config.quick_analysis {
|
||||||
let mut state = AnalyzerState::default();
|
let mut state = AnalyzerState::default();
|
||||||
debug!("Detecting function boundaries");
|
debug!("Detecting function boundaries");
|
||||||
state.detect_functions(&obj)?;
|
state.detect_functions(&obj)?;
|
||||||
|
|
||||||
FindTRKInterruptVectorTable::execute(&mut state, &obj)?;
|
FindTRKInterruptVectorTable::execute(&mut state, &obj)?;
|
||||||
FindSaveRestSleds::execute(&mut state, &obj)?;
|
FindSaveRestSleds::execute(&mut state, &obj)?;
|
||||||
state.apply(&mut obj)?;
|
state.apply(&mut obj)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_signatures_post(&mut obj)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_signatures_post(&mut obj)?;
|
|
||||||
|
|
||||||
if let Some(selfile) = &config.selfile {
|
if let Some(selfile) = &config.selfile {
|
||||||
|
log::info!("Loading {}", selfile.display());
|
||||||
|
let file = map_file(selfile)?;
|
||||||
|
let data = decompress_if_needed(file.as_slice())?;
|
||||||
if let Some(hash) = &config.selfile_hash {
|
if let Some(hash) = &config.selfile_hash {
|
||||||
verify_hash(selfile, hash)?;
|
verify_hash(data.as_ref(), hash)?;
|
||||||
}
|
}
|
||||||
apply_selfile(&mut obj, selfile)?;
|
apply_selfile(&mut obj, data.as_ref())?;
|
||||||
dep.push(selfile.clone());
|
dep.push(selfile.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create _ctors and _dtors symbols if missing
|
||||||
|
update_ctors_dtors(&mut obj)?;
|
||||||
|
|
||||||
Ok((obj, dep))
|
Ok((obj, dep))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,7 +735,7 @@ fn split_write_obj(
|
||||||
debug!("Applying relocations");
|
debug!("Applying relocations");
|
||||||
tracker.apply(obj, false)?;
|
tracker.apply(obj, false)?;
|
||||||
|
|
||||||
if config.detect_objects {
|
if !config.symbols_known && config.detect_objects {
|
||||||
debug!("Detecting object boundaries");
|
debug!("Detecting object boundaries");
|
||||||
detect_objects(obj)?;
|
detect_objects(obj)?;
|
||||||
}
|
}
|
||||||
|
@ -702,7 +746,11 @@ fn split_write_obj(
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Adjusting splits");
|
debug!("Adjusting splits");
|
||||||
update_splits(obj, if obj.module_id == 0 { config.common_start } else { None })?;
|
update_splits(
|
||||||
|
obj,
|
||||||
|
if obj.module_id == 0 { config.common_start } else { None },
|
||||||
|
config.fill_gaps,
|
||||||
|
)?;
|
||||||
|
|
||||||
if !no_update {
|
if !no_update {
|
||||||
debug!("Writing configuration");
|
debug!("Writing configuration");
|
||||||
|
@ -741,29 +789,32 @@ fn split_write_obj(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate ldscript.lcf
|
// Generate ldscript.lcf
|
||||||
fs::write(&out_config.ldscript, generate_ldscript(obj, config.auto_force_files)?)?;
|
fs::write(&out_config.ldscript, generate_ldscript(obj, &module_config.force_active)?)?;
|
||||||
|
|
||||||
debug!("Writing disassembly");
|
if config.write_asm {
|
||||||
let asm_dir = out_dir.join("asm");
|
debug!("Writing disassembly");
|
||||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
let asm_dir = out_dir.join("asm");
|
||||||
let out_path = asm_dir.join(asm_path_for_unit(&unit.name));
|
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||||
|
let out_path = asm_dir.join(asm_path_for_unit(&unit.name));
|
||||||
|
|
||||||
let mut w = buf_writer(&out_path)?;
|
let mut w = buf_writer(&out_path)?;
|
||||||
write_asm(&mut w, split_obj)
|
write_asm(&mut w, split_obj)
|
||||||
.with_context(|| format!("Failed to write {}", out_path.display()))?;
|
.with_context(|| format!("Failed to write {}", out_path.display()))?;
|
||||||
w.flush()?;
|
w.flush()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(out_config)
|
Ok(out_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Result<AnalyzeResult> {
|
fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Result<AnalyzeResult> {
|
||||||
debug!("Loading {}", module_config.object.display());
|
debug!("Loading {}", module_config.object.display());
|
||||||
if let Some(hash_str) = &module_config.hash {
|
|
||||||
verify_hash(&module_config.object, hash_str)?;
|
|
||||||
}
|
|
||||||
let map = map_file(&module_config.object)?;
|
let map = map_file(&module_config.object)?;
|
||||||
let buf = decompress_if_needed(&map)?;
|
let buf = decompress_if_needed(map.as_slice())?;
|
||||||
let (_, mut module_obj) = process_rel(&mut Reader::new(buf.as_ref()))?;
|
if let Some(hash_str) = &module_config.hash {
|
||||||
|
verify_hash(buf.as_ref(), hash_str)?;
|
||||||
|
}
|
||||||
|
let (_, mut module_obj) =
|
||||||
|
process_rel(&mut Reader::new(buf.as_ref()), module_config.name().as_ref())?;
|
||||||
|
|
||||||
if let Some(comment_version) = config.mw_comment_version {
|
if let Some(comment_version) = config.mw_comment_version {
|
||||||
module_obj.mw_comment = Some(MWComment::new(comment_version)?);
|
module_obj.mw_comment = Some(MWComment::new(comment_version)?);
|
||||||
|
@ -785,16 +836,22 @@ fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Res
|
||||||
dep.push(symbols_path.clone());
|
dep.push(symbols_path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Analyzing module {}", module_obj.module_id);
|
if !config.symbols_known {
|
||||||
if !config.quick_analysis {
|
debug!("Analyzing module {}", module_obj.module_id);
|
||||||
let mut state = AnalyzerState::default();
|
if !config.quick_analysis {
|
||||||
state.detect_functions(&module_obj)?;
|
let mut state = AnalyzerState::default();
|
||||||
FindRelCtorsDtors::execute(&mut state, &module_obj)?;
|
state.detect_functions(&module_obj)?;
|
||||||
FindRelRodataData::execute(&mut state, &module_obj)?;
|
FindRelCtorsDtors::execute(&mut state, &module_obj)?;
|
||||||
state.apply(&mut module_obj)?;
|
FindRelRodataData::execute(&mut state, &module_obj)?;
|
||||||
|
state.apply(&mut module_obj)?;
|
||||||
|
}
|
||||||
|
apply_signatures(&mut module_obj)?;
|
||||||
|
apply_signatures_post(&mut module_obj)?;
|
||||||
}
|
}
|
||||||
apply_signatures(&mut module_obj)?;
|
|
||||||
apply_signatures_post(&mut module_obj)?;
|
// Create _ctors and _dtors symbols if missing
|
||||||
|
update_ctors_dtors(&mut module_obj)?;
|
||||||
|
|
||||||
Ok((module_obj, dep))
|
Ok((module_obj, dep))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -805,18 +862,32 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
|
|
||||||
let command_start = Instant::now();
|
let command_start = Instant::now();
|
||||||
info!("Loading {}", args.config.display());
|
info!("Loading {}", args.config.display());
|
||||||
let mut config_file = File::open(&args.config)
|
let mut config: ProjectConfig = {
|
||||||
.with_context(|| format!("Failed to open config file '{}'", args.config.display()))?;
|
let mut config_file = buf_reader(&args.config)?;
|
||||||
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
serde_yaml::from_reader(&mut config_file)?
|
||||||
|
};
|
||||||
|
|
||||||
|
for module_config in config.modules.iter_mut() {
|
||||||
|
let file = map_file(&module_config.object)?;
|
||||||
|
let buf = decompress_if_needed(file.as_slice())?;
|
||||||
|
if let Some(hash_str) = &module_config.hash {
|
||||||
|
verify_hash(buf.as_ref(), hash_str)?;
|
||||||
|
} else {
|
||||||
|
module_config.hash = Some(file_sha1_string(&mut Reader::new(buf.as_ref()))?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let out_config_path = args.out_dir.join("config.json");
|
let out_config_path = args.out_dir.join("config.json");
|
||||||
let mut dep = DepFile::new(out_config_path.clone());
|
let mut dep = DepFile::new(out_config_path.clone());
|
||||||
|
|
||||||
let module_count = config.modules.len() + 1;
|
let module_count = config.modules.len() + 1;
|
||||||
|
let num_threads = min(rayon::current_num_threads(), module_count);
|
||||||
info!(
|
info!(
|
||||||
"Loading and analyzing {} modules (using {} threads)",
|
"Loading and analyzing {} module{} (using {} thread{})",
|
||||||
module_count,
|
module_count,
|
||||||
rayon::current_num_threads()
|
if module_count == 1 { "" } else { "s" },
|
||||||
|
num_threads,
|
||||||
|
if num_threads == 1 { "" } else { "s" }
|
||||||
);
|
);
|
||||||
let mut dol_result: Option<Result<AnalyzeResult>> = None;
|
let mut dol_result: Option<Result<AnalyzeResult>> = None;
|
||||||
let mut modules_result: Option<Result<Vec<AnalyzeResult>>> = None;
|
let mut modules_result: Option<Result<Vec<AnalyzeResult>>> = None;
|
||||||
|
@ -871,11 +942,13 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
let module_ids = modules.keys().cloned().collect_vec();
|
let module_ids = modules.keys().cloned().collect_vec();
|
||||||
|
|
||||||
// Create any missing symbols (referenced from other modules) and set FORCEACTIVE
|
// Create any missing symbols (referenced from other modules) and set FORCEACTIVE
|
||||||
update_symbols(&mut obj, &modules)?;
|
if !config.symbols_known {
|
||||||
for &module_id in &module_ids {
|
update_symbols(&mut obj, &modules)?;
|
||||||
let (module_config, mut module_obj) = modules.remove(&module_id).unwrap();
|
for &module_id in &module_ids {
|
||||||
update_symbols(&mut module_obj, &modules)?;
|
let (module_config, mut module_obj) = modules.remove(&module_id).unwrap();
|
||||||
modules.insert(module_id, (module_config, module_obj));
|
update_symbols(&mut module_obj, &modules)?;
|
||||||
|
modules.insert(module_id, (module_config, module_obj));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create relocations to symbols in other modules
|
// Create relocations to symbols in other modules
|
||||||
|
@ -1135,12 +1208,18 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
|
||||||
|
|
||||||
fn diff(args: DiffArgs) -> Result<()> {
|
fn diff(args: DiffArgs) -> Result<()> {
|
||||||
log::info!("Loading {}", args.config.display());
|
log::info!("Loading {}", args.config.display());
|
||||||
let mut config_file = File::open(&args.config)
|
let mut config_file = buf_reader(&args.config)?;
|
||||||
.with_context(|| format!("Failed to open config file '{}'", args.config.display()))?;
|
|
||||||
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
||||||
|
|
||||||
log::info!("Loading {}", config.base.object.display());
|
log::info!("Loading {}", config.base.object.display());
|
||||||
let mut obj = process_dol(&config.base.object)?;
|
let mut obj = {
|
||||||
|
let file = map_file(&config.base.object)?;
|
||||||
|
let data = decompress_if_needed(file.as_slice())?;
|
||||||
|
if let Some(hash_str) = &config.base.hash {
|
||||||
|
verify_hash(data.as_ref(), hash_str)?;
|
||||||
|
}
|
||||||
|
process_dol(data.as_ref(), config.base.name().as_ref())?
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(symbols_path) = &config.base.symbols {
|
if let Some(symbols_path) = &config.base.symbols {
|
||||||
apply_symbols_file(symbols_path, &mut obj)?;
|
apply_symbols_file(symbols_path, &mut obj)?;
|
||||||
|
@ -1267,6 +1346,8 @@ fn diff(args: DiffArgs) -> Result<()> {
|
||||||
orig_sym.size,
|
orig_sym.size,
|
||||||
orig_sym.address
|
orig_sym.address
|
||||||
);
|
);
|
||||||
|
log::error!("Original: {}", hex::encode_upper(orig_data));
|
||||||
|
log::error!("Linked: {}", hex::encode_upper(linked_data));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1277,12 +1358,18 @@ fn diff(args: DiffArgs) -> Result<()> {
|
||||||
|
|
||||||
fn apply(args: ApplyArgs) -> Result<()> {
|
fn apply(args: ApplyArgs) -> Result<()> {
|
||||||
log::info!("Loading {}", args.config.display());
|
log::info!("Loading {}", args.config.display());
|
||||||
let mut config_file = File::open(&args.config)
|
let mut config_file = buf_reader(&args.config)?;
|
||||||
.with_context(|| format!("Failed to open config file '{}'", args.config.display()))?;
|
|
||||||
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
||||||
|
|
||||||
log::info!("Loading {}", config.base.object.display());
|
log::info!("Loading {}", config.base.object.display());
|
||||||
let mut obj = process_dol(&config.base.object)?;
|
let mut obj = {
|
||||||
|
let file = map_file(&config.base.object)?;
|
||||||
|
let data = decompress_if_needed(file.as_slice())?;
|
||||||
|
if let Some(hash_str) = &config.base.hash {
|
||||||
|
verify_hash(data.as_ref(), hash_str)?;
|
||||||
|
}
|
||||||
|
process_dol(data.as_ref(), config.base.name().as_ref())?
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(symbols_path) = &config.base.symbols {
|
if let Some(symbols_path) = &config.base.symbols {
|
||||||
if !apply_symbols_file(symbols_path, &mut obj)? {
|
if !apply_symbols_file(symbols_path, &mut obj)? {
|
||||||
|
@ -1429,3 +1516,75 @@ fn apply(args: ApplyArgs) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn config(args: ConfigArgs) -> Result<()> {
|
||||||
|
let mut config = ProjectConfig {
|
||||||
|
base: ModuleConfig {
|
||||||
|
name: None,
|
||||||
|
object: Default::default(),
|
||||||
|
hash: None,
|
||||||
|
splits: None,
|
||||||
|
symbols: None,
|
||||||
|
map: None,
|
||||||
|
force_active: vec![],
|
||||||
|
},
|
||||||
|
selfile: None,
|
||||||
|
selfile_hash: None,
|
||||||
|
mw_comment_version: None,
|
||||||
|
quick_analysis: false,
|
||||||
|
modules: vec![],
|
||||||
|
detect_objects: true,
|
||||||
|
detect_strings: true,
|
||||||
|
write_asm: true,
|
||||||
|
common_start: None,
|
||||||
|
symbols_known: false,
|
||||||
|
fill_gaps: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut modules = BTreeMap::<u32, ModuleConfig>::new();
|
||||||
|
for result in FileIterator::new(&args.objects)? {
|
||||||
|
let (path, entry) = result?;
|
||||||
|
log::info!("Loading {}", path.display());
|
||||||
|
|
||||||
|
match path.extension() {
|
||||||
|
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("dol")) => {
|
||||||
|
config.base.object = path;
|
||||||
|
config.base.hash = Some(file_sha1_string(&mut entry.as_reader())?);
|
||||||
|
}
|
||||||
|
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rel")) => {
|
||||||
|
let header = process_rel_header(&mut entry.as_reader())?;
|
||||||
|
modules.insert(header.module_id, ModuleConfig {
|
||||||
|
name: None,
|
||||||
|
object: path,
|
||||||
|
hash: Some(file_sha1_string(&mut entry.as_reader())?),
|
||||||
|
splits: None,
|
||||||
|
symbols: None,
|
||||||
|
map: None,
|
||||||
|
force_active: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("sel")) => {
|
||||||
|
config.selfile = Some(path);
|
||||||
|
config.selfile_hash = Some(file_sha1_string(&mut entry.as_reader())?);
|
||||||
|
}
|
||||||
|
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rso")) => {
|
||||||
|
config.modules.push(ModuleConfig {
|
||||||
|
name: None,
|
||||||
|
object: path,
|
||||||
|
hash: Some(file_sha1_string(&mut entry.as_reader())?),
|
||||||
|
splits: None,
|
||||||
|
symbols: None,
|
||||||
|
map: None,
|
||||||
|
force_active: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => bail!("Unknown file extension: '{}'", path.display()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.modules.extend(modules.into_values());
|
||||||
|
|
||||||
|
let mut out = buf_writer(&args.out_file)?;
|
||||||
|
serde_yaml::to_writer(&mut out, &config)?;
|
||||||
|
out.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -49,9 +49,10 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump(args: DumpArgs) -> Result<()> {
|
fn dump(args: DumpArgs) -> Result<()> {
|
||||||
let mmap = map_file(&args.in_file)?;
|
let file = map_file(&args.in_file)?;
|
||||||
if mmap.starts_with(b"!<arch>\n") {
|
let buf = file.as_slice();
|
||||||
let mut archive = ar::Archive::new(&*mmap);
|
if buf.starts_with(b"!<arch>\n") {
|
||||||
|
let mut archive = ar::Archive::new(buf);
|
||||||
while let Some(result) = archive.next_entry() {
|
while let Some(result) = archive.next_entry() {
|
||||||
let mut e = match result {
|
let mut e = match result {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
|
@ -85,7 +86,7 @@ fn dump(args: DumpArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let obj_file = object::read::File::parse(&*mmap)?;
|
let obj_file = object::read::File::parse(buf)?;
|
||||||
let debug_section = obj_file
|
let debug_section = obj_file
|
||||||
.section_by_name(".debug")
|
.section_by_name(".debug")
|
||||||
.ok_or_else(|| anyhow!("Failed to locate .debug section"))?;
|
.ok_or_else(|| anyhow!("Failed to locate .debug section"))?;
|
||||||
|
|
|
@ -43,8 +43,8 @@ const MAX_TEXT_SECTIONS: usize = 7;
|
||||||
const MAX_DATA_SECTIONS: usize = 11;
|
const MAX_DATA_SECTIONS: usize = 11;
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
let map = map_file(&args.elf_file)?;
|
let file = map_file(&args.elf_file)?;
|
||||||
let obj_file = object::read::File::parse(&*map)?;
|
let obj_file = object::read::File::parse(file.as_slice())?;
|
||||||
match obj_file.architecture() {
|
match obj_file.architecture() {
|
||||||
Architecture::PowerPc => {}
|
Architecture::PowerPc => {}
|
||||||
arch => bail!("Unexpected architecture: {arch:?}"),
|
arch => bail!("Unexpected architecture: {arch:?}"),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use argp::FromArgs;
|
||||||
use cwdemangle::{demangle, DemangleOptions};
|
use cwdemangle::{demangle, DemangleOptions};
|
||||||
|
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
file::{map_file, map_reader},
|
file::map_file,
|
||||||
map::{process_map, SymbolEntry, SymbolRef},
|
map::{process_map, SymbolEntry, SymbolRef},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entries(args: EntriesArgs) -> Result<()> {
|
fn entries(args: EntriesArgs) -> Result<()> {
|
||||||
let map = map_file(&args.map_file)?;
|
let file = map_file(&args.map_file)?;
|
||||||
let entries = process_map(map_reader(&map))?;
|
let entries = process_map(&mut file.as_reader())?;
|
||||||
match entries.unit_entries.get_vec(&args.unit) {
|
match entries.unit_entries.get_vec(&args.unit) {
|
||||||
Some(vec) => {
|
Some(vec) => {
|
||||||
for symbol_ref in vec {
|
for symbol_ref in vec {
|
||||||
|
@ -108,8 +108,8 @@ fn entries(args: EntriesArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbol(args: SymbolArgs) -> Result<()> {
|
fn symbol(args: SymbolArgs) -> Result<()> {
|
||||||
let map = map_file(&args.map_file)?;
|
let file = map_file(&args.map_file)?;
|
||||||
let entries = process_map(map_reader(&map))?;
|
let entries = process_map(&mut file.as_reader())?;
|
||||||
let opt_ref: Option<(SymbolRef, SymbolEntry)> = None;
|
let opt_ref: Option<(SymbolRef, SymbolEntry)> = None;
|
||||||
|
|
||||||
_ = entries;
|
_ = entries;
|
||||||
|
@ -165,8 +165,8 @@ fn symbol(args: SymbolArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn order(args: OrderArgs) -> Result<()> {
|
fn order(args: OrderArgs) -> Result<()> {
|
||||||
let map = map_file(&args.map_file)?;
|
let file = map_file(&args.map_file)?;
|
||||||
let entries = process_map(map_reader(&map))?;
|
let entries = process_map(&mut file.as_reader())?;
|
||||||
|
|
||||||
_ = entries;
|
_ = entries;
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -179,8 +179,8 @@ fn order(args: OrderArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slices(args: SlicesArgs) -> Result<()> {
|
fn slices(args: SlicesArgs) -> Result<()> {
|
||||||
let map = map_file(&args.map_file)?;
|
let file = map_file(&args.map_file)?;
|
||||||
let entries = process_map(map_reader(&map))?;
|
let entries = process_map(&mut file.as_reader())?;
|
||||||
|
|
||||||
_ = entries;
|
_ = entries;
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -213,8 +213,8 @@ fn slices(args: SlicesArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbols(args: SymbolsArgs) -> Result<()> {
|
fn symbols(args: SymbolsArgs) -> Result<()> {
|
||||||
let map = map_file(&args.map_file)?;
|
let file = map_file(&args.map_file)?;
|
||||||
let entries = process_map(map_reader(&map))?;
|
let entries = process_map(&mut file.as_reader())?;
|
||||||
|
|
||||||
_ = entries;
|
_ = entries;
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -6,6 +6,8 @@ pub mod elf;
|
||||||
pub mod elf2dol;
|
pub mod elf2dol;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod metroidbuildinfo;
|
pub mod metroidbuildinfo;
|
||||||
|
pub mod rarc;
|
||||||
pub mod rel;
|
pub mod rel;
|
||||||
pub mod rso;
|
pub mod rso;
|
||||||
pub mod shasum;
|
pub mod shasum;
|
||||||
|
pub mod yaz0;
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
use std::{fs, fs::DirBuilder, path::PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use argp::FromArgs;
|
||||||
|
|
||||||
|
use crate::util::{
|
||||||
|
file::{decompress_if_needed, map_file},
|
||||||
|
rarc::{Node, RarcReader},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
/// Commands for processing RSO files.
|
||||||
|
#[argp(subcommand, name = "rarc")]
|
||||||
|
pub struct Args {
|
||||||
|
#[argp(subcommand)]
|
||||||
|
command: SubCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
#[argp(subcommand)]
|
||||||
|
enum SubCommand {
|
||||||
|
List(ListArgs),
|
||||||
|
Extract(ExtractArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
/// Views RARC file information.
|
||||||
|
#[argp(subcommand, name = "list")]
|
||||||
|
pub struct ListArgs {
|
||||||
|
#[argp(positional)]
|
||||||
|
/// RARC file
|
||||||
|
file: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
/// Extracts RARC file contents.
|
||||||
|
#[argp(subcommand, name = "extract")]
|
||||||
|
pub struct ExtractArgs {
|
||||||
|
#[argp(positional)]
|
||||||
|
/// RARC file
|
||||||
|
file: PathBuf,
|
||||||
|
#[argp(option, short = 'o')]
|
||||||
|
/// output directory
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(args: Args) -> Result<()> {
|
||||||
|
match args.command {
|
||||||
|
SubCommand::List(c_args) => list(c_args),
|
||||||
|
SubCommand::Extract(c_args) => extract(c_args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(args: ListArgs) -> Result<()> {
|
||||||
|
let file = map_file(&args.file)?;
|
||||||
|
let rarc = RarcReader::new(&mut file.as_reader())?;
|
||||||
|
|
||||||
|
let mut current_path = PathBuf::new();
|
||||||
|
for node in rarc.nodes() {
|
||||||
|
match node {
|
||||||
|
Node::DirectoryBegin { name } => {
|
||||||
|
current_path.push(name.name);
|
||||||
|
}
|
||||||
|
Node::DirectoryEnd { name: _ } => {
|
||||||
|
current_path.pop();
|
||||||
|
}
|
||||||
|
Node::File { name, offset, size } => {
|
||||||
|
let path = current_path.join(name.name);
|
||||||
|
println!("{}: {} bytes, offset {:#X}", path.display(), size, offset);
|
||||||
|
}
|
||||||
|
Node::CurrentDirectory => {}
|
||||||
|
Node::ParentDirectory => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract(args: ExtractArgs) -> Result<()> {
|
||||||
|
let file = map_file(&args.file)?;
|
||||||
|
let rarc = RarcReader::new(&mut file.as_reader())?;
|
||||||
|
|
||||||
|
let mut current_path = PathBuf::new();
|
||||||
|
for node in rarc.nodes() {
|
||||||
|
match node {
|
||||||
|
Node::DirectoryBegin { name } => {
|
||||||
|
current_path.push(name.name);
|
||||||
|
}
|
||||||
|
Node::DirectoryEnd { name: _ } => {
|
||||||
|
current_path.pop();
|
||||||
|
}
|
||||||
|
Node::File { name, offset, size } => {
|
||||||
|
let file_data = decompress_if_needed(
|
||||||
|
&file.as_slice()[offset as usize..offset as usize + size as usize],
|
||||||
|
)?;
|
||||||
|
let file_path = current_path.join(&name.name);
|
||||||
|
let output_path =
|
||||||
|
args.output.as_ref().map(|p| p.join(&file_path)).unwrap_or_else(|| file_path);
|
||||||
|
if let Some(parent) = output_path.parent() {
|
||||||
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
|
}
|
||||||
|
fs::write(&output_path, file_data)
|
||||||
|
.with_context(|| format!("Failed to write file '{}'", output_path.display()))?;
|
||||||
|
}
|
||||||
|
Node::CurrentDirectory => {}
|
||||||
|
Node::ParentDirectory => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -93,6 +93,9 @@ pub struct MakeArgs {
|
||||||
#[argp(option, short = 'c')]
|
#[argp(option, short = 'c')]
|
||||||
/// (optional) project configuration file
|
/// (optional) project configuration file
|
||||||
config: Option<PathBuf>,
|
config: Option<PathBuf>,
|
||||||
|
#[argp(switch, short = 'w')]
|
||||||
|
/// disable warnings
|
||||||
|
no_warn: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
|
@ -121,26 +124,27 @@ fn make(args: MakeArgs) -> Result<()> {
|
||||||
if let Some(config_path) = &args.config {
|
if let Some(config_path) = &args.config {
|
||||||
let config: ProjectConfig = serde_yaml::from_reader(&mut buf_reader(config_path)?)?;
|
let config: ProjectConfig = serde_yaml::from_reader(&mut buf_reader(config_path)?)?;
|
||||||
for module_config in &config.modules {
|
for module_config in &config.modules {
|
||||||
if let Some(hash_str) = &module_config.hash {
|
|
||||||
verify_hash(&module_config.object, hash_str)?;
|
|
||||||
}
|
|
||||||
let map = map_file(&module_config.object)?;
|
let map = map_file(&module_config.object)?;
|
||||||
let buf = decompress_if_needed(&map)?;
|
let buf = decompress_if_needed(map.as_slice())?;
|
||||||
|
if let Some(hash_str) = &module_config.hash {
|
||||||
|
verify_hash(buf.as_ref(), hash_str)?;
|
||||||
|
}
|
||||||
let header = process_rel_header(&mut Reader::new(buf.as_ref()))?;
|
let header = process_rel_header(&mut Reader::new(buf.as_ref()))?;
|
||||||
existing_headers.insert(header.module_id, header);
|
existing_headers.insert(header.module_id, header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let files = process_rsp(&args.files)?;
|
let paths = process_rsp(&args.files)?;
|
||||||
info!("Loading {} modules", files.len());
|
info!("Loading {} modules", paths.len());
|
||||||
|
|
||||||
// Load all modules
|
// Load all modules
|
||||||
let handles = files.iter().map(map_file).collect::<Result<Vec<_>>>()?;
|
let files = paths.iter().map(map_file).collect::<Result<Vec<_>>>()?;
|
||||||
let modules = handles
|
let modules = files
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.zip(&files)
|
.zip(&paths)
|
||||||
.map(|(map, path)| {
|
.map(|(file, path)| {
|
||||||
load_obj(map).with_context(|| format!("Failed to load '{}'", path.display()))
|
load_obj(file.as_slice())
|
||||||
|
.with_context(|| format!("Failed to load '{}'", path.display()))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
@ -194,7 +198,7 @@ fn make(args: MakeArgs) -> Result<()> {
|
||||||
address: address as u32,
|
address: address as u32,
|
||||||
module_id: target_module_id as u32,
|
module_id: target_module_id as u32,
|
||||||
target_section: target_symbol.section_index().unwrap().0 as u8,
|
target_section: target_symbol.section_index().unwrap().0 as u8,
|
||||||
addend: target_symbol.address() as u32,
|
addend: (target_symbol.address() as i64 + reloc.addend()) as u32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +215,7 @@ fn make(args: MakeArgs) -> Result<()> {
|
||||||
// Write RELs
|
// Write RELs
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for (((module_id, module), path), relocations) in
|
for (((module_id, module), path), relocations) in
|
||||||
modules.iter().enumerate().zip(&files).skip(1).zip(relocations)
|
modules.iter().enumerate().zip(&paths).skip(1).zip(relocations)
|
||||||
{
|
{
|
||||||
let name =
|
let name =
|
||||||
path.file_stem().unwrap_or(OsStr::new("[unknown]")).to_str().unwrap_or("[invalid]");
|
path.file_stem().unwrap_or(OsStr::new("[unknown]")).to_str().unwrap_or("[invalid]");
|
||||||
|
@ -224,6 +228,7 @@ fn make(args: MakeArgs) -> Result<()> {
|
||||||
align: None,
|
align: None,
|
||||||
bss_align: None,
|
bss_align: None,
|
||||||
section_count: None,
|
section_count: None,
|
||||||
|
quiet: args.no_warn,
|
||||||
};
|
};
|
||||||
if let Some(existing_module) = existing_headers.get(&(module_id as u32)) {
|
if let Some(existing_module) = existing_headers.get(&(module_id as u32)) {
|
||||||
info.version = existing_module.version;
|
info.version = existing_module.version;
|
||||||
|
@ -248,9 +253,9 @@ fn make(args: MakeArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(args: InfoArgs) -> Result<()> {
|
fn info(args: InfoArgs) -> Result<()> {
|
||||||
let map = map_file(args.rel_file)?;
|
let file = map_file(args.rel_file)?;
|
||||||
let buf = decompress_if_needed(&map)?;
|
let buf = decompress_if_needed(file.as_slice())?;
|
||||||
let (header, mut module_obj) = process_rel(&mut Reader::new(buf.as_ref()))?;
|
let (header, mut module_obj) = process_rel(&mut Reader::new(buf.as_ref()), "")?;
|
||||||
|
|
||||||
let mut state = AnalyzerState::default();
|
let mut state = AnalyzerState::default();
|
||||||
state.detect_functions(&module_obj)?;
|
state.detect_functions(&module_obj)?;
|
||||||
|
@ -312,7 +317,12 @@ const fn align32(x: u32) -> u32 { (x + 31) & !31 }
|
||||||
|
|
||||||
fn merge(args: MergeArgs) -> Result<()> {
|
fn merge(args: MergeArgs) -> Result<()> {
|
||||||
log::info!("Loading {}", args.dol_file.display());
|
log::info!("Loading {}", args.dol_file.display());
|
||||||
let mut obj = process_dol(&args.dol_file)?;
|
let mut obj = {
|
||||||
|
let file = map_file(&args.dol_file)?;
|
||||||
|
let buf = decompress_if_needed(file.as_slice())?;
|
||||||
|
let name = args.dol_file.file_stem().map(|s| s.to_string_lossy()).unwrap_or_default();
|
||||||
|
process_dol(buf.as_ref(), name.as_ref())?
|
||||||
|
};
|
||||||
|
|
||||||
log::info!("Performing signature analysis");
|
log::info!("Performing signature analysis");
|
||||||
apply_signatures(&mut obj)?;
|
apply_signatures(&mut obj)?;
|
||||||
|
@ -323,7 +333,8 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||||
for result in FileIterator::new(&args.rel_files)? {
|
for result in FileIterator::new(&args.rel_files)? {
|
||||||
let (path, entry) = result?;
|
let (path, entry) = result?;
|
||||||
log::info!("Loading {}", path.display());
|
log::info!("Loading {}", path.display());
|
||||||
let (_, obj) = process_rel(&mut entry.as_reader())?;
|
let name = path.file_stem().map(|s| s.to_string_lossy()).unwrap_or_default();
|
||||||
|
let (_, obj) = process_rel(&mut entry.as_reader(), name.as_ref())?;
|
||||||
match module_map.entry(obj.module_id) {
|
match module_map.entry(obj.module_id) {
|
||||||
btree_map::Entry::Vacant(e) => e.insert(obj),
|
btree_map::Entry::Vacant(e) => e.insert(obj),
|
||||||
btree_map::Entry::Occupied(_) => bail!("Duplicate module ID {}", obj.module_id),
|
btree_map::Entry::Occupied(_) => bail!("Duplicate module ID {}", obj.module_id),
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
|
|
||||||
use crate::util::rso::process_rso;
|
use crate::util::{
|
||||||
|
file::{decompress_if_needed, map_file, Reader},
|
||||||
|
rso::process_rso,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Commands for processing RSO files.
|
/// Commands for processing RSO files.
|
||||||
|
@ -35,7 +38,12 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(args: InfoArgs) -> Result<()> {
|
fn info(args: InfoArgs) -> Result<()> {
|
||||||
let rso = process_rso(args.rso_file)?;
|
let rso = {
|
||||||
|
let file = map_file(&args.rso_file)?;
|
||||||
|
let data = decompress_if_needed(file.as_slice())
|
||||||
|
.with_context(|| format!("Failed to decompress '{}'", args.rso_file.display()))?;
|
||||||
|
process_rso(&mut Reader::new(data.as_ref()))?
|
||||||
|
};
|
||||||
println!("Read RSO module {}", rso.name);
|
println!("Read RSO module {}", rso.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use argp::FromArgs;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
use crate::util::file::{process_rsp, touch};
|
use crate::util::file::{open_file, process_rsp, touch};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
/// Print or check SHA1 (160-bit) checksums.
|
/// Print or check SHA1 (160-bit) checksums.
|
||||||
|
@ -24,18 +24,20 @@ pub struct Args {
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o')]
|
||||||
/// touch output file on successful check
|
/// touch output file on successful check
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
|
#[argp(switch, short = 'q')]
|
||||||
|
/// only print failures and a summary
|
||||||
|
quiet: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_BUF_SIZE: usize = 8192;
|
const DEFAULT_BUF_SIZE: usize = 8192;
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
for path in process_rsp(&args.files)? {
|
for path in process_rsp(&args.files)? {
|
||||||
let file = File::open(&path)
|
let mut file = open_file(&path)?;
|
||||||
.with_context(|| format!("Failed to open file '{}'", path.display()))?;
|
|
||||||
if args.check {
|
if args.check {
|
||||||
check(file)?
|
check(&args, &mut BufReader::new(file))?
|
||||||
} else {
|
} else {
|
||||||
hash(file, &path)?
|
hash(&mut file, &path)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(out_path) = args.output {
|
if let Some(out_path) = args.output {
|
||||||
|
@ -45,8 +47,8 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(file: File) -> Result<()> {
|
fn check<R: BufRead>(args: &Args, reader: &mut R) -> Result<()> {
|
||||||
let reader = BufReader::new(file);
|
let mut matches = 0usize;
|
||||||
let mut mismatches = 0usize;
|
let mut mismatches = 0usize;
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let line = match line {
|
let line = match line {
|
||||||
|
@ -63,16 +65,23 @@ fn check(file: File) -> Result<()> {
|
||||||
hex::decode_to_slice(hash, &mut hash_bytes)
|
hex::decode_to_slice(hash, &mut hash_bytes)
|
||||||
.with_context(|| format!("Invalid line: {line}"))?;
|
.with_context(|| format!("Invalid line: {line}"))?;
|
||||||
|
|
||||||
let file =
|
let found_hash = file_sha1(
|
||||||
File::open(file_name).with_context(|| format!("Failed to open file '{file_name}'"))?;
|
&mut File::open(file_name)
|
||||||
let found_hash = file_sha1(file)?;
|
.with_context(|| format!("Failed to open file '{file_name}'"))?,
|
||||||
|
)?;
|
||||||
if hash_bytes == found_hash.as_ref() {
|
if hash_bytes == found_hash.as_ref() {
|
||||||
println!("{}: {}", file_name, "OK".green());
|
if !args.quiet {
|
||||||
|
println!("{}: {}", file_name, "OK".green());
|
||||||
|
}
|
||||||
|
matches += 1;
|
||||||
} else {
|
} else {
|
||||||
println!("{}: {}", file_name, "FAILED".red());
|
println!("{}: {}", file_name, "FAILED".red());
|
||||||
mismatches += 1;
|
mismatches += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if args.quiet && matches > 0 {
|
||||||
|
println!("{} files {}", matches, "OK".green());
|
||||||
|
}
|
||||||
if mismatches != 0 {
|
if mismatches != 0 {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
|
@ -83,8 +92,8 @@ fn check(file: File) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(file: File, path: &Path) -> Result<()> {
|
fn hash<R: Read>(reader: &mut R, path: &Path) -> Result<()> {
|
||||||
let hash = file_sha1(file)?;
|
let hash = file_sha1(reader)?;
|
||||||
let mut hash_buf = [0u8; 40];
|
let mut hash_buf = [0u8; 40];
|
||||||
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
|
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
|
||||||
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
|
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
|
||||||
|
@ -92,14 +101,22 @@ fn hash(file: File, path: &Path) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_sha1(mut file: File) -> Result<sha1::digest::Output<Sha1>> {
|
pub fn file_sha1<R: Read>(reader: &mut R) -> Result<sha1::digest::Output<Sha1>> {
|
||||||
let mut buf = [0u8; DEFAULT_BUF_SIZE];
|
let mut buf = [0u8; DEFAULT_BUF_SIZE];
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
Ok(loop {
|
Ok(loop {
|
||||||
let read = file.read(&mut buf).context("File read failed")?;
|
let read = reader.read(&mut buf).context("File read failed")?;
|
||||||
if read == 0 {
|
if read == 0 {
|
||||||
break hasher.finalize();
|
break hasher.finalize();
|
||||||
}
|
}
|
||||||
hasher.update(&buf[0..read]);
|
hasher.update(&buf[0..read]);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn file_sha1_string<R: Read>(reader: &mut R) -> Result<String> {
|
||||||
|
let hash = file_sha1(reader)?;
|
||||||
|
let mut hash_buf = [0u8; 40];
|
||||||
|
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
|
||||||
|
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
|
||||||
|
Ok(hash_str.to_string())
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use argp::FromArgs;
|
||||||
|
|
||||||
|
use crate::util::{
|
||||||
|
file::{decompress_reader, open_file, process_rsp},
|
||||||
|
IntoCow, ToCow,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
/// Commands for processing YAZ0-compressed files.
|
||||||
|
#[argp(subcommand, name = "yaz0")]
|
||||||
|
pub struct Args {
|
||||||
|
#[argp(subcommand)]
|
||||||
|
command: SubCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
#[argp(subcommand)]
|
||||||
|
enum SubCommand {
|
||||||
|
Decompress(DecompressArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
/// Decompresses YAZ0-compressed files.
|
||||||
|
#[argp(subcommand, name = "decompress")]
|
||||||
|
pub struct DecompressArgs {
|
||||||
|
#[argp(positional)]
|
||||||
|
/// YAZ0-compressed files
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
#[argp(option, short = 'o')]
|
||||||
|
/// Output directory. If not specified, decompresses in-place.
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(args: Args) -> Result<()> {
|
||||||
|
match args.command {
|
||||||
|
SubCommand::Decompress(args) => decompress(args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress(args: DecompressArgs) -> Result<()> {
|
||||||
|
for path in process_rsp(&args.files)? {
|
||||||
|
let data = decompress_reader(&mut open_file(&path)?)?;
|
||||||
|
let out_path = if let Some(output) = &args.output {
|
||||||
|
output.join(path.file_name().unwrap()).into_cow()
|
||||||
|
} else {
|
||||||
|
path.as_path().to_cow()
|
||||||
|
};
|
||||||
|
fs::write(out_path.as_ref(), data)
|
||||||
|
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -82,9 +82,11 @@ enum SubCommand {
|
||||||
Elf2Dol(cmd::elf2dol::Args),
|
Elf2Dol(cmd::elf2dol::Args),
|
||||||
// Map(cmd::map::Args),
|
// Map(cmd::map::Args),
|
||||||
MetroidBuildInfo(cmd::metroidbuildinfo::Args),
|
MetroidBuildInfo(cmd::metroidbuildinfo::Args),
|
||||||
|
Rarc(cmd::rarc::Args),
|
||||||
Rel(cmd::rel::Args),
|
Rel(cmd::rel::Args),
|
||||||
Rso(cmd::rso::Args),
|
Rso(cmd::rso::Args),
|
||||||
Shasum(cmd::shasum::Args),
|
Shasum(cmd::shasum::Args),
|
||||||
|
Yaz0(cmd::yaz0::Args),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -127,9 +129,11 @@ fn main() {
|
||||||
SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args),
|
SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args),
|
||||||
// SubCommand::Map(c_args) => cmd::map::run(c_args),
|
// SubCommand::Map(c_args) => cmd::map::run(c_args),
|
||||||
SubCommand::MetroidBuildInfo(c_args) => cmd::metroidbuildinfo::run(c_args),
|
SubCommand::MetroidBuildInfo(c_args) => cmd::metroidbuildinfo::run(c_args),
|
||||||
|
SubCommand::Rarc(c_args) => cmd::rarc::run(c_args),
|
||||||
SubCommand::Rel(c_args) => cmd::rel::run(c_args),
|
SubCommand::Rel(c_args) => cmd::rel::run(c_args),
|
||||||
SubCommand::Rso(c_args) => cmd::rso::run(c_args),
|
SubCommand::Rso(c_args) => cmd::rso::run(c_args),
|
||||||
SubCommand::Shasum(c_args) => cmd::shasum::run(c_args),
|
SubCommand::Shasum(c_args) => cmd::shasum::run(c_args),
|
||||||
|
SubCommand::Yaz0(c_args) => cmd::yaz0::run(c_args),
|
||||||
});
|
});
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
eprintln!("Failed: {e:?}");
|
eprintln!("Failed: {e:?}");
|
||||||
|
|
|
@ -172,6 +172,9 @@ impl ObjSection {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn symbol_data(&self, symbol: &ObjSymbol) -> Result<&[u8]> {
|
pub fn symbol_data(&self, symbol: &ObjSymbol) -> Result<&[u8]> {
|
||||||
|
if symbol.size == 0 {
|
||||||
|
return Ok(&[]);
|
||||||
|
}
|
||||||
self.data_range(symbol.address as u32, symbol.address as u32 + symbol.size as u32)
|
self.data_range(symbol.address as u32, symbol.address as u32 + symbol.size as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -329,7 +329,13 @@ impl ObjSymbols {
|
||||||
self.at_section_address(section_idx, addr)
|
self.at_section_address(section_idx, addr)
|
||||||
.filter(|(_, sym)| sym.kind == kind)
|
.filter(|(_, sym)| sym.kind == kind)
|
||||||
.at_most_one()
|
.at_most_one()
|
||||||
.map_err(|_| anyhow!("Multiple symbols of kind {:?} at address {:#010X}", kind, addr))
|
.map_err(|e| {
|
||||||
|
let symbols = e.map(|(_, s)| s).collect_vec();
|
||||||
|
for symbol in symbols {
|
||||||
|
log::error!("{:?}", symbol);
|
||||||
|
}
|
||||||
|
anyhow!("Multiple symbols of kind {:?} at address {:#010X}", kind, addr)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over all in address ascending order, excluding ABS symbols
|
// Iterate over all in address ascending order, excluding ABS symbols
|
||||||
|
|
|
@ -198,8 +198,7 @@ impl CommentSym {
|
||||||
let mut active_flags = 0;
|
let mut active_flags = 0;
|
||||||
if symbol.flags.is_force_active()
|
if symbol.flags.is_force_active()
|
||||||
|| (force_active
|
|| (force_active
|
||||||
&& matches!(symbol.kind, ObjSymbolKind::Function | ObjSymbolKind::Object)
|
&& matches!(symbol.kind, ObjSymbolKind::Function | ObjSymbolKind::Object))
|
||||||
&& symbol.flags.is_global())
|
|
||||||
{
|
{
|
||||||
active_flags |= 0x8;
|
active_flags |= 0x8;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,24 @@ use crate::{
|
||||||
ObjDataKind, ObjInfo, ObjKind, ObjSectionKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet,
|
ObjDataKind, ObjInfo, ObjKind, ObjSectionKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet,
|
||||||
ObjSymbolFlags, ObjSymbolKind, ObjUnit,
|
ObjSymbolFlags, ObjSymbolKind, ObjUnit,
|
||||||
},
|
},
|
||||||
util::file::{buf_writer, map_file, map_reader},
|
util::{
|
||||||
|
file::{buf_writer, map_file},
|
||||||
|
split::default_section_align,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parse_hex(s: &str) -> Result<u32, ParseIntError> {
|
fn parse_hex(s: &str) -> Result<u32, ParseIntError> {
|
||||||
u32::from_str_radix(s.trim_start_matches("0x"), 16)
|
if s.starts_with("0x") {
|
||||||
|
u32::from_str_radix(s.trim_start_matches("0x"), 16)
|
||||||
|
} else {
|
||||||
|
s.parse::<u32>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_symbols_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<bool> {
|
pub fn apply_symbols_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<bool> {
|
||||||
Ok(if path.as_ref().is_file() {
|
Ok(if path.as_ref().is_file() {
|
||||||
let map = map_file(path)?;
|
let file = map_file(path)?;
|
||||||
for result in map_reader(&map).lines() {
|
for result in file.as_reader().lines() {
|
||||||
let line = match result {
|
let line = match result {
|
||||||
Ok(line) => line,
|
Ok(line) => line,
|
||||||
Err(e) => bail!("Failed to process symbols file: {e:?}"),
|
Err(e) => bail!("Failed to process symbols file: {e:?}"),
|
||||||
|
@ -80,6 +87,10 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymb
|
||||||
align: None,
|
align: None,
|
||||||
data_kind: Default::default(),
|
data_kind: Default::default(),
|
||||||
};
|
};
|
||||||
|
// TODO move somewhere common
|
||||||
|
if symbol.name.starts_with("..") {
|
||||||
|
symbol.flags.0 |= ObjSymbolFlags::ForceActive;
|
||||||
|
}
|
||||||
let attrs = captures["attrs"].split(' ');
|
let attrs = captures["attrs"].split(' ');
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
if let Some((name, value)) = attr.split_once(':') {
|
if let Some((name, value)) = attr.split_once(':') {
|
||||||
|
@ -188,7 +199,7 @@ fn write_symbol<W: Write>(w: &mut W, obj: &ObjInfo, symbol: &ObjSymbol) -> Resul
|
||||||
write!(w, " scope:{scope}")?;
|
write!(w, " scope:{scope}")?;
|
||||||
}
|
}
|
||||||
if let Some(align) = symbol.align {
|
if let Some(align) = symbol.align {
|
||||||
write!(w, " align:{align:#X}")?;
|
write!(w, " align:{align}")?;
|
||||||
}
|
}
|
||||||
if let Some(kind) = symbol_data_kind_to_str(symbol.data_kind) {
|
if let Some(kind) = symbol_data_kind_to_str(symbol.data_kind) {
|
||||||
write!(w, " data:{kind}")?;
|
write!(w, " data:{kind}")?;
|
||||||
|
@ -342,9 +353,11 @@ pub fn write_splits<W: Write>(w: &mut W, obj: &ObjInfo, all: bool) -> Result<()>
|
||||||
split_iter.peek().map(|&(_, _, addr, _)| addr).unwrap_or(0)
|
split_iter.peek().map(|&(_, _, addr, _)| addr).unwrap_or(0)
|
||||||
};
|
};
|
||||||
write!(w, "\t{:<11} start:{:#010X} end:{:#010X}", section.name, addr, end)?;
|
write!(w, "\t{:<11} start:{:#010X} end:{:#010X}", section.name, addr, end)?;
|
||||||
// if let Some(align) = split.align {
|
if let Some(align) = split.align {
|
||||||
// write!(w, " align:{}", align)?;
|
if align != default_section_align(section) as u32 {
|
||||||
// }
|
write!(w, " align:{}", align)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
if split.common {
|
if split.common {
|
||||||
write!(w, " common")?;
|
write!(w, " common")?;
|
||||||
}
|
}
|
||||||
|
@ -446,7 +459,7 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"align" => {
|
"align" => {
|
||||||
section.align = Some(u32::from_str(value)?);
|
section.align = Some(parse_hex(value)?);
|
||||||
}
|
}
|
||||||
_ => bail!("Unknown section attribute '{attr}'"),
|
_ => bail!("Unknown section attribute '{attr}'"),
|
||||||
}
|
}
|
||||||
|
@ -475,7 +488,7 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
|
||||||
match attr {
|
match attr {
|
||||||
"start" => start = Some(parse_hex(value)?),
|
"start" => start = Some(parse_hex(value)?),
|
||||||
"end" => end = Some(parse_hex(value)?),
|
"end" => end = Some(parse_hex(value)?),
|
||||||
"align" => section.align = Some(u32::from_str(value)?),
|
"align" => section.align = Some(parse_hex(value)?),
|
||||||
"rename" => section.rename = Some(value.to_string()),
|
"rename" => section.rename = Some(value.to_string()),
|
||||||
_ => bail!("Unknown split attribute '{attr}'"),
|
_ => bail!("Unknown split attribute '{attr}'"),
|
||||||
}
|
}
|
||||||
|
@ -509,8 +522,8 @@ enum SplitState {
|
||||||
|
|
||||||
pub fn apply_splits_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<bool> {
|
pub fn apply_splits_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<bool> {
|
||||||
Ok(if path.as_ref().is_file() {
|
Ok(if path.as_ref().is_file() {
|
||||||
let map = map_file(path)?;
|
let file = map_file(path)?;
|
||||||
apply_splits(map_reader(&map), obj)?;
|
apply_splits(file.as_reader(), obj)?;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -587,8 +600,10 @@ pub fn apply_splits<R: BufRead>(r: R, obj: &mut ObjInfo) -> Result<()> {
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
let section = obj.sections.get_mut(section_index).unwrap();
|
let section = obj.sections.get_mut(section_index).unwrap();
|
||||||
|
let section_end = (section.address + section.size) as u32;
|
||||||
ensure!(
|
ensure!(
|
||||||
section.contains_range(start..end),
|
section.contains_range(start..end)
|
||||||
|
|| (start == section_end && end == section_end),
|
||||||
"Section {} ({:#010X}..{:#010X}) does not contain range {:#010X}..{:#010X}",
|
"Section {} ({:#010X}..{:#010X}) does not contain range {:#010X}..{:#010X}",
|
||||||
name,
|
name,
|
||||||
section.address,
|
section.address,
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
use std::{io::Write, path::PathBuf};
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use path_slash::PathBufExt;
|
use path_slash::PathBufExt;
|
||||||
|
|
||||||
|
use crate::util::file::split_path;
|
||||||
|
|
||||||
pub struct DepFile {
|
pub struct DepFile {
|
||||||
pub name: PathBuf,
|
pub name: PathBuf,
|
||||||
pub dependencies: Vec<PathBuf>,
|
pub dependencies: Vec<PathBuf>,
|
||||||
|
@ -10,13 +16,22 @@ pub struct DepFile {
|
||||||
impl DepFile {
|
impl DepFile {
|
||||||
pub fn new(name: PathBuf) -> Self { Self { name, dependencies: vec![] } }
|
pub fn new(name: PathBuf) -> Self { Self { name, dependencies: vec![] } }
|
||||||
|
|
||||||
pub fn push(&mut self, dependency: PathBuf) { self.dependencies.push(dependency); }
|
pub fn push<P: AsRef<Path>>(&mut self, dependency: P) {
|
||||||
|
let path = split_path(dependency.as_ref())
|
||||||
|
.map(|(p, _)| p)
|
||||||
|
.unwrap_or_else(|_| dependency.as_ref().to_path_buf());
|
||||||
|
self.dependencies.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extend(&mut self, dependencies: Vec<PathBuf>) { self.dependencies.extend(dependencies); }
|
pub fn extend(&mut self, dependencies: Vec<PathBuf>) {
|
||||||
|
self.dependencies.extend(dependencies.iter().map(|dependency| {
|
||||||
|
split_path(dependency).map(|(p, _)| p).unwrap_or_else(|_| dependency.clone())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
|
||||||
write!(w, "{}:", self.name.to_slash_lossy())?;
|
write!(w, "{}:", self.name.to_slash_lossy())?;
|
||||||
for dep in &self.dependencies {
|
for dep in self.dependencies.iter().unique() {
|
||||||
write!(w, " \\\n {}", dep.to_slash_lossy())?;
|
write!(w, " \\\n {}", dep.to_slash_lossy())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::BTreeMap, path::Path};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Result};
|
use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use dol::{Dol, DolSection, DolSectionType};
|
use dol::{Dol, DolSection, DolSectionType};
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||||
ObjSymbolFlags, ObjSymbolKind,
|
ObjSymbolFlags, ObjSymbolKind,
|
||||||
},
|
},
|
||||||
util::file::{map_file, map_reader},
|
util::file::Reader,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_TEXT_SECTIONS: usize = 7;
|
const MAX_TEXT_SECTIONS: usize = 7;
|
||||||
|
@ -22,17 +22,8 @@ fn read_u32(dol: &Dol, addr: u32) -> Result<u32> {
|
||||||
Ok(u32::from_be_bytes(dol.virtual_data_at(addr, 4)?.try_into()?))
|
Ok(u32::from_be_bytes(dol.virtual_data_at(addr, 4)?.try_into()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_dol<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
||||||
let name = path
|
let dol = Dol::read_from(Reader::new(buf))?;
|
||||||
.as_ref()
|
|
||||||
.file_name()
|
|
||||||
.and_then(|filename| filename.to_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string();
|
|
||||||
let dol = {
|
|
||||||
let mmap = map_file(path)?;
|
|
||||||
Dol::read_from(map_reader(&mmap))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Locate _rom_copy_info
|
// Locate _rom_copy_info
|
||||||
let first_rom_section = dol
|
let first_rom_section = dol
|
||||||
|
@ -331,8 +322,13 @@ pub fn process_dol<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create object
|
// Create object
|
||||||
let mut obj =
|
let mut obj = ObjInfo::new(
|
||||||
ObjInfo::new(ObjKind::Executable, ObjArchitecture::PowerPc, name, vec![], sections);
|
ObjKind::Executable,
|
||||||
|
ObjArchitecture::PowerPc,
|
||||||
|
name.to_string(),
|
||||||
|
vec![],
|
||||||
|
sections,
|
||||||
|
);
|
||||||
obj.entry = Some(dol.header.entry_point as u64);
|
obj.entry = Some(dol.header.entry_point as u64);
|
||||||
|
|
||||||
// Generate _rom_copy_info symbol
|
// Generate _rom_copy_info symbol
|
||||||
|
|
|
@ -41,8 +41,8 @@ enum BoundaryState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
let mmap = map_file(path)?;
|
let file = map_file(path)?;
|
||||||
let obj_file = object::read::File::parse(&*mmap)?;
|
let obj_file = object::read::File::parse(file.as_slice())?;
|
||||||
let architecture = match obj_file.architecture() {
|
let architecture = match obj_file.architecture() {
|
||||||
Architecture::PowerPc => ObjArchitecture::PowerPc,
|
Architecture::PowerPc => ObjArchitecture::PowerPc,
|
||||||
arch => bail!("Unexpected architecture: {arch:?}"),
|
arch => bail!("Unexpected architecture: {arch:?}"),
|
||||||
|
|
198
src/util/file.rs
198
src/util/file.rs
|
@ -1,36 +1,105 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
ffi::OsStr,
|
||||||
fs::{DirBuilder, File, OpenOptions},
|
fs::{DirBuilder, File, OpenOptions},
|
||||||
io::{BufRead, BufReader, BufWriter, Cursor, Read},
|
io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, SeekFrom},
|
||||||
path::{Path, PathBuf},
|
path::{Component, Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use binrw::io::{TakeSeek, TakeSeekExt};
|
||||||
use byteorder::ReadBytesExt;
|
use byteorder::ReadBytesExt;
|
||||||
use filetime::{set_file_mtime, FileTime};
|
use filetime::{set_file_mtime, FileTime};
|
||||||
use memmap2::{Mmap, MmapOptions};
|
use memmap2::{Mmap, MmapOptions};
|
||||||
use path_slash::PathBufExt;
|
use path_slash::PathBufExt;
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
use crate::{
|
use crate::util::{rarc, rarc::Node, yaz0, IntoCow, ToCow};
|
||||||
cmd::shasum::file_sha1,
|
|
||||||
util::{rarc, rarc::Node, yaz0, IntoCow, ToCow},
|
pub struct MappedFile {
|
||||||
};
|
mmap: Mmap,
|
||||||
|
offset: u64,
|
||||||
|
len: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MappedFile {
|
||||||
|
pub fn new(mmap: Mmap, offset: u64, len: u64) -> Self { Self { mmap, offset, len } }
|
||||||
|
|
||||||
|
pub fn as_reader(&self) -> Reader { Reader::new(self.as_slice()) }
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
&self.mmap[self.offset as usize..self.offset as usize + self.len as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> u64 { self.len }
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool { self.len == 0 }
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> Mmap { self.mmap }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_path<P: AsRef<Path>>(path: P) -> Result<(PathBuf, Option<PathBuf>)> {
|
||||||
|
let mut base_path = PathBuf::new();
|
||||||
|
let mut sub_path: Option<PathBuf> = None;
|
||||||
|
for component in path.as_ref().components() {
|
||||||
|
if let Component::Normal(str) = component {
|
||||||
|
let str = str.to_str().ok_or(anyhow!("Path is not valid UTF-8"))?;
|
||||||
|
if let Some((a, b)) = str.split_once(':') {
|
||||||
|
base_path.push(a);
|
||||||
|
sub_path = Some(PathBuf::from(b));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(sub_path) = &mut sub_path {
|
||||||
|
sub_path.push(component);
|
||||||
|
} else {
|
||||||
|
base_path.push(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((base_path, sub_path))
|
||||||
|
}
|
||||||
|
|
||||||
/// Opens a memory mapped file.
|
/// Opens a memory mapped file.
|
||||||
pub fn map_file<P: AsRef<Path>>(path: P) -> Result<Mmap> {
|
pub fn map_file<P: AsRef<Path>>(path: P) -> Result<MappedFile> {
|
||||||
let file = File::open(&path)
|
let (base_path, sub_path) = split_path(path)?;
|
||||||
.with_context(|| format!("Failed to open file '{}'", path.as_ref().display()))?;
|
let file = File::open(&base_path)
|
||||||
let map = unsafe { MmapOptions::new().map(&file) }
|
.with_context(|| format!("Failed to open file '{}'", base_path.display()))?;
|
||||||
.with_context(|| format!("Failed to mmap file: '{}'", path.as_ref().display()))?;
|
let mmap = unsafe { MmapOptions::new().map(&file) }
|
||||||
Ok(map)
|
.with_context(|| format!("Failed to mmap file: '{}'", base_path.display()))?;
|
||||||
|
let (offset, len) = if let Some(sub_path) = sub_path {
|
||||||
|
let rarc = rarc::RarcReader::new(&mut Reader::new(&*mmap))
|
||||||
|
.with_context(|| format!("Failed to read RARC '{}'", base_path.display()))?;
|
||||||
|
rarc.find_file(&sub_path)?.map(|(o, s)| (o, s as u64)).ok_or_else(|| {
|
||||||
|
anyhow!("File '{}' not found in '{}'", sub_path.display(), base_path.display())
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
(0, mmap.len() as u64)
|
||||||
|
};
|
||||||
|
Ok(MappedFile { mmap, offset, len })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type OpenedFile = TakeSeek<File>;
|
||||||
|
|
||||||
|
/// Opens a file (not memory mapped).
|
||||||
|
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<OpenedFile> {
|
||||||
|
let (base_path, sub_path) = split_path(path)?;
|
||||||
|
let mut file = File::open(&base_path)
|
||||||
|
.with_context(|| format!("Failed to open file '{}'", base_path.display()))?;
|
||||||
|
let (offset, size) = if let Some(sub_path) = sub_path {
|
||||||
|
let rarc = rarc::RarcReader::new(&mut BufReader::new(&file))
|
||||||
|
.with_context(|| format!("Failed to read RARC '{}'", base_path.display()))?;
|
||||||
|
rarc.find_file(&sub_path)?.map(|(o, s)| (o, s as u64)).ok_or_else(|| {
|
||||||
|
anyhow!("File '{}' not found in '{}'", sub_path.display(), base_path.display())
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
(0, file.seek(SeekFrom::End(0))?)
|
||||||
|
};
|
||||||
|
file.seek(SeekFrom::Start(offset))?;
|
||||||
|
Ok(file.take_seek(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Reader<'a> = Cursor<&'a [u8]>;
|
pub type Reader<'a> = Cursor<&'a [u8]>;
|
||||||
|
|
||||||
/// Creates a reader for the memory mapped file.
|
|
||||||
#[inline]
|
|
||||||
pub fn map_reader(mmap: &Mmap) -> Reader { Reader::new(&**mmap) }
|
|
||||||
|
|
||||||
/// Creates a buffered reader around a file (not memory mapped).
|
/// Creates a buffered reader around a file (not memory mapped).
|
||||||
pub fn buf_reader<P: AsRef<Path>>(path: P) -> Result<BufReader<File>> {
|
pub fn buf_reader<P: AsRef<Path>>(path: P) -> Result<BufReader<File>> {
|
||||||
let file = File::open(&path)
|
let file = File::open(&path)
|
||||||
|
@ -49,19 +118,19 @@ pub fn buf_writer<P: AsRef<Path>>(path: P) -> Result<BufWriter<File>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a string with known size at the specified offset.
|
/// Reads a string with known size at the specified offset.
|
||||||
pub fn read_string(reader: &mut Reader, off: u64, size: usize) -> Result<String> {
|
pub fn read_string<R: Read + Seek>(reader: &mut R, off: u64, size: usize) -> Result<String> {
|
||||||
let mut data = vec![0u8; size];
|
let mut data = vec![0u8; size];
|
||||||
let pos = reader.position();
|
let pos = reader.stream_position()?;
|
||||||
reader.set_position(off);
|
reader.seek(SeekFrom::Start(off))?;
|
||||||
reader.read_exact(&mut data)?;
|
reader.read_exact(&mut data)?;
|
||||||
reader.set_position(pos);
|
reader.seek(SeekFrom::Start(pos))?;
|
||||||
Ok(String::from_utf8(data)?)
|
Ok(String::from_utf8(data)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a zero-terminated string at the specified offset.
|
/// Reads a zero-terminated string at the specified offset.
|
||||||
pub fn read_c_string(reader: &mut Reader, off: u64) -> Result<String> {
|
pub fn read_c_string<R: Read + Seek>(reader: &mut R, off: u64) -> Result<String> {
|
||||||
let pos = reader.position();
|
let pos = reader.stream_position()?;
|
||||||
reader.set_position(off);
|
reader.seek(SeekFrom::Start(off))?;
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
loop {
|
loop {
|
||||||
let b = reader.read_u8()?;
|
let b = reader.read_u8()?;
|
||||||
|
@ -70,7 +139,7 @@ pub fn read_c_string(reader: &mut Reader, off: u64) -> Result<String> {
|
||||||
}
|
}
|
||||||
s.push(b as char);
|
s.push(b as char);
|
||||||
}
|
}
|
||||||
reader.set_position(pos);
|
reader.seek(SeekFrom::Start(pos))?;
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,15 +171,16 @@ pub fn process_rsp(files: &[PathBuf]) -> Result<Vec<PathBuf>> {
|
||||||
/// Iterator over files in a RARC archive.
|
/// Iterator over files in a RARC archive.
|
||||||
struct RarcIterator {
|
struct RarcIterator {
|
||||||
file: Mmap,
|
file: Mmap,
|
||||||
|
base_path: PathBuf,
|
||||||
paths: Vec<(PathBuf, u64, u32)>,
|
paths: Vec<(PathBuf, u64, u32)>,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RarcIterator {
|
impl RarcIterator {
|
||||||
pub fn new(file: Mmap, base_path: &Path) -> Result<Self> {
|
pub fn new(file: Mmap, base_path: &Path) -> Result<Self> {
|
||||||
let reader = rarc::RarcReader::new(map_reader(&file))?;
|
let reader = rarc::RarcReader::new(&mut Reader::new(&*file))?;
|
||||||
let paths = Self::collect_paths(&reader, base_path);
|
let paths = Self::collect_paths(&reader, base_path);
|
||||||
Ok(Self { file, paths, index: 0 })
|
Ok(Self { file, base_path: base_path.to_owned(), paths, index: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_paths(reader: &rarc::RarcReader, base_path: &Path) -> Vec<(PathBuf, u64, u32)> {
|
fn collect_paths(reader: &rarc::RarcReader, base_path: &Path) -> Vec<(PathBuf, u64, u32)> {
|
||||||
|
@ -157,7 +227,7 @@ impl Iterator for RarcIterator {
|
||||||
|
|
||||||
/// A file entry, either a memory mapped file or an owned buffer.
|
/// A file entry, either a memory mapped file or an owned buffer.
|
||||||
pub enum FileEntry {
|
pub enum FileEntry {
|
||||||
Map(Mmap),
|
MappedFile(MappedFile),
|
||||||
Buffer(Vec<u8>),
|
Buffer(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +235,7 @@ impl FileEntry {
|
||||||
/// Creates a reader for the file.
|
/// Creates a reader for the file.
|
||||||
pub fn as_reader(&self) -> Reader {
|
pub fn as_reader(&self) -> Reader {
|
||||||
match self {
|
match self {
|
||||||
Self::Map(map) => map_reader(map),
|
Self::MappedFile(file) => file.as_reader(),
|
||||||
Self::Buffer(slice) => Reader::new(slice),
|
Self::Buffer(slice) => Reader::new(slice),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,7 +258,12 @@ impl FileIterator {
|
||||||
fn next_rarc(&mut self) -> Option<Result<(PathBuf, FileEntry)>> {
|
fn next_rarc(&mut self) -> Option<Result<(PathBuf, FileEntry)>> {
|
||||||
if let Some(rarc) = &mut self.rarc {
|
if let Some(rarc) = &mut self.rarc {
|
||||||
match rarc.next() {
|
match rarc.next() {
|
||||||
Some(Ok((path, buf))) => return Some(Ok((path, FileEntry::Buffer(buf)))),
|
Some(Ok((path, buf))) => {
|
||||||
|
let mut path_str = rarc.base_path.as_os_str().to_os_string();
|
||||||
|
path_str.push(OsStr::new(":"));
|
||||||
|
path_str.push(path.as_os_str());
|
||||||
|
return Some(Ok((path, FileEntry::Buffer(buf))));
|
||||||
|
}
|
||||||
Some(Err(err)) => return Some(Err(err)),
|
Some(Err(err)) => return Some(Err(err)),
|
||||||
None => self.rarc = None,
|
None => self.rarc = None,
|
||||||
}
|
}
|
||||||
|
@ -209,20 +284,29 @@ impl FileIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_file(&mut self, map: Mmap, path: PathBuf) -> Option<Result<(PathBuf, FileEntry)>> {
|
fn handle_file(
|
||||||
if map.len() <= 4 {
|
&mut self,
|
||||||
return Some(Ok((path, FileEntry::Map(map))));
|
file: MappedFile,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Option<Result<(PathBuf, FileEntry)>> {
|
||||||
|
let buf = file.as_slice();
|
||||||
|
if buf.len() <= 4 {
|
||||||
|
return Some(Ok((path, FileEntry::MappedFile(file))));
|
||||||
}
|
}
|
||||||
|
|
||||||
match &map[0..4] {
|
match &buf[0..4] {
|
||||||
b"Yaz0" => self.handle_yaz0(map, path),
|
b"Yaz0" => self.handle_yaz0(file.as_reader(), path),
|
||||||
b"RARC" => self.handle_rarc(map, path),
|
b"RARC" => self.handle_rarc(file.into_inner(), path),
|
||||||
_ => Some(Ok((path, FileEntry::Map(map)))),
|
_ => Some(Ok((path, FileEntry::MappedFile(file)))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_yaz0(&mut self, map: Mmap, path: PathBuf) -> Option<Result<(PathBuf, FileEntry)>> {
|
fn handle_yaz0(
|
||||||
Some(match yaz0::decompress_file(&mut map_reader(&map)) {
|
&mut self,
|
||||||
|
mut reader: Reader,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Option<Result<(PathBuf, FileEntry)>> {
|
||||||
|
Some(match yaz0::decompress_file(&mut reader) {
|
||||||
Ok(buf) => Ok((path, FileEntry::Buffer(buf))),
|
Ok(buf) => Ok((path, FileEntry::Buffer(buf))),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
})
|
})
|
||||||
|
@ -262,20 +346,38 @@ pub fn decompress_if_needed(buf: &[u8]) -> Result<Cow<[u8]>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_hash<P: AsRef<Path>>(path: P, hash_str: &str) -> Result<()> {
|
pub fn decompress_reader<R: Read + Seek>(reader: &mut R) -> Result<Vec<u8>> {
|
||||||
let mut hash_bytes = [0u8; 20];
|
let mut magic = [0u8; 4];
|
||||||
hex::decode_to_slice(hash_str, &mut hash_bytes)
|
if reader.read_exact(&mut magic).is_err() {
|
||||||
.with_context(|| format!("Invalid SHA-1 '{hash_str}'"))?;
|
reader.seek(SeekFrom::Start(0))?;
|
||||||
let file = File::open(path.as_ref())
|
let mut buf = vec![];
|
||||||
.with_context(|| format!("Failed to open file '{}'", path.as_ref().display()))?;
|
reader.read_to_end(&mut buf)?;
|
||||||
let found_hash = file_sha1(file)?;
|
return Ok(buf);
|
||||||
if found_hash.as_ref() == hash_bytes {
|
}
|
||||||
|
Ok(if magic == *b"Yaz0" {
|
||||||
|
reader.seek(SeekFrom::Start(0))?;
|
||||||
|
yaz0::decompress_file(reader)?
|
||||||
|
} else {
|
||||||
|
let mut buf = magic.to_vec();
|
||||||
|
reader.read_to_end(&mut buf)?;
|
||||||
|
buf
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_hash(buf: &[u8], expected_str: &str) -> Result<()> {
|
||||||
|
let mut expected_bytes = [0u8; 20];
|
||||||
|
hex::decode_to_slice(expected_str, &mut expected_bytes)
|
||||||
|
.with_context(|| format!("Invalid SHA-1 '{expected_str}'"))?;
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(buf);
|
||||||
|
let hash_bytes = hasher.finalize();
|
||||||
|
if hash_bytes.as_ref() == expected_bytes {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"Hash mismatch: expected {}, but was {}",
|
"Hash mismatch: expected {}, but was {}",
|
||||||
hex::encode(hash_bytes),
|
hex::encode(expected_bytes),
|
||||||
hex::encode(found_hash)
|
hex::encode(hash_bytes)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ use crate::obj::{ObjInfo, ObjKind};
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn align_up(value: u32, align: u32) -> u32 { (value + (align - 1)) & !(align - 1) }
|
const fn align_up(value: u32, align: u32) -> u32 { (value + (align - 1)) & !(align - 1) }
|
||||||
|
|
||||||
pub fn generate_ldscript(obj: &ObjInfo, auto_force_files: bool) -> Result<String> {
|
pub fn generate_ldscript(obj: &ObjInfo, force_active: &[String]) -> Result<String> {
|
||||||
if obj.kind == ObjKind::Relocatable {
|
if obj.kind == ObjKind::Relocatable {
|
||||||
return generate_ldscript_partial(obj, auto_force_files);
|
return generate_ldscript_partial(obj, force_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
let origin = obj.sections.iter().map(|(_, s)| s.address).min().unwrap();
|
let origin = obj.sections.iter().map(|(_, s)| s.address).min().unwrap();
|
||||||
|
@ -54,7 +54,7 @@ pub fn generate_ldscript(obj: &ObjInfo, auto_force_files: bool) -> Result<String
|
||||||
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
|
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut force_active = vec![];
|
let mut force_active = force_active.to_vec();
|
||||||
for symbol in obj.symbols.iter() {
|
for symbol in obj.symbols.iter() {
|
||||||
if symbol.flags.is_force_active() && symbol.flags.is_global() && !symbol.flags.is_no_write()
|
if symbol.flags.is_force_active() && symbol.flags.is_global() && !symbol.flags.is_no_write()
|
||||||
{
|
{
|
||||||
|
@ -66,7 +66,7 @@ pub fn generate_ldscript(obj: &ObjInfo, auto_force_files: bool) -> Result<String
|
||||||
let last_section_name = obj.sections.iter().next_back().unwrap().1.name.clone();
|
let last_section_name = obj.sections.iter().next_back().unwrap().1.name.clone();
|
||||||
let last_section_symbol = format!("_f_{}", last_section_name.trim_start_matches('.'));
|
let last_section_symbol = format!("_f_{}", last_section_name.trim_start_matches('.'));
|
||||||
|
|
||||||
let mut out = include_str!("../../assets/ldscript.lcf")
|
let out = include_str!("../../assets/ldscript.lcf")
|
||||||
.replace("$ORIGIN", &format!("{:#X}", origin))
|
.replace("$ORIGIN", &format!("{:#X}", origin))
|
||||||
.replace("$SECTIONS", §ion_defs)
|
.replace("$SECTIONS", §ion_defs)
|
||||||
.replace("$LAST_SECTION_SYMBOL", &last_section_symbol)
|
.replace("$LAST_SECTION_SYMBOL", &last_section_symbol)
|
||||||
|
@ -74,15 +74,10 @@ pub fn generate_ldscript(obj: &ObjInfo, auto_force_files: bool) -> Result<String
|
||||||
.replace("$STACKSIZE", &format!("{:#X}", stack_size))
|
.replace("$STACKSIZE", &format!("{:#X}", stack_size))
|
||||||
.replace("$FORCEACTIVE", &force_active.join("\n "))
|
.replace("$FORCEACTIVE", &force_active.join("\n "))
|
||||||
.replace("$ARENAHI", &format!("{:#X}", obj.arena_hi.unwrap_or(0x81700000)));
|
.replace("$ARENAHI", &format!("{:#X}", obj.arena_hi.unwrap_or(0x81700000)));
|
||||||
out = if auto_force_files {
|
|
||||||
out.replace("$FORCEFILES", &force_files.join("\n "))
|
|
||||||
} else {
|
|
||||||
out.replace("$FORCEFILES", "")
|
|
||||||
};
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_ldscript_partial(obj: &ObjInfo, auto_force_files: bool) -> Result<String> {
|
pub fn generate_ldscript_partial(obj: &ObjInfo, force_active: &[String]) -> Result<String> {
|
||||||
let section_defs =
|
let section_defs =
|
||||||
obj.sections.iter().map(|(_, s)| format!("{} :{{}}", s.name)).join("\n ");
|
obj.sections.iter().map(|(_, s)| format!("{} :{{}}", s.name)).join("\n ");
|
||||||
|
|
||||||
|
@ -92,7 +87,7 @@ pub fn generate_ldscript_partial(obj: &ObjInfo, auto_force_files: bool) -> Resul
|
||||||
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
|
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut force_active = vec![];
|
let mut force_active = force_active.to_vec();
|
||||||
for symbol in obj.symbols.iter() {
|
for symbol in obj.symbols.iter() {
|
||||||
if symbol.flags.is_force_active() && symbol.flags.is_global() && !symbol.flags.is_no_write()
|
if symbol.flags.is_force_active() && symbol.flags.is_global() && !symbol.flags.is_no_write()
|
||||||
{
|
{
|
||||||
|
@ -100,14 +95,9 @@ pub fn generate_ldscript_partial(obj: &ObjInfo, auto_force_files: bool) -> Resul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out = include_str!("../../assets/ldscript_partial.lcf")
|
let out = include_str!("../../assets/ldscript_partial.lcf")
|
||||||
.replace("$SECTIONS", §ion_defs)
|
.replace("$SECTIONS", §ion_defs)
|
||||||
.replace("$FORCEACTIVE", &force_active.join("\n "));
|
.replace("$FORCEACTIVE", &force_active.join("\n "));
|
||||||
out = if auto_force_files {
|
|
||||||
out.replace("$FORCEFILES", &force_files.join("\n "))
|
|
||||||
} else {
|
|
||||||
out.replace("$FORCEFILES", "")
|
|
||||||
};
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unused_mut)]
|
#![allow(unused_mut)]
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::BufRead,
|
io::BufRead,
|
||||||
mem::replace,
|
mem::replace,
|
||||||
|
@ -10,13 +10,14 @@ use std::{
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Error, Result};
|
use anyhow::{anyhow, bail, ensure, Error, Result};
|
||||||
use cwdemangle::{demangle, DemangleOptions};
|
use cwdemangle::{demangle, DemangleOptions};
|
||||||
|
use flagset::FlagSet;
|
||||||
use multimap::MultiMap;
|
use multimap::MultiMap;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
obj::{ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
|
obj::{ObjInfo, ObjKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
|
||||||
util::file::{map_file, map_reader},
|
util::{file::map_file, nested::NestedVec},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
@ -35,7 +36,7 @@ pub enum SymbolVisibility {
|
||||||
Weak,
|
Weak,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct SymbolEntry {
|
pub struct SymbolEntry {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub demangled: Option<String>,
|
pub demangled: Option<String>,
|
||||||
|
@ -254,6 +255,35 @@ impl StateMachine {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self) -> Result<()> {
|
||||||
|
// If we didn't find a link map, guess symbol visibility
|
||||||
|
if !self.has_link_map {
|
||||||
|
let mut symbol_occurrences = HashMap::<String, usize>::new();
|
||||||
|
for symbol in self.result.section_symbols.values().flat_map(|v| v.values().flatten()) {
|
||||||
|
if symbol.visibility != SymbolVisibility::Local {
|
||||||
|
*symbol_occurrences.entry(symbol.name.clone()).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for symbol in
|
||||||
|
self.result.section_symbols.values_mut().flat_map(|v| v.values_mut().flatten())
|
||||||
|
{
|
||||||
|
if symbol.visibility == SymbolVisibility::Unknown {
|
||||||
|
if symbol.name.starts_with('.') // ...rodata.0
|
||||||
|
|| symbol.name.starts_with('@') // @123
|
||||||
|
|| symbol.name.starts_with("__sinit")
|
||||||
|
|| symbol_occurrences.get(&symbol.name).cloned().unwrap_or(0) > 1
|
||||||
|
{
|
||||||
|
symbol.visibility = SymbolVisibility::Local;
|
||||||
|
} else {
|
||||||
|
symbol.visibility = SymbolVisibility::Global;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn process_link_map_entry(
|
fn process_link_map_entry(
|
||||||
captures: Captures,
|
captures: Captures,
|
||||||
state: &mut LinkMapState,
|
state: &mut LinkMapState,
|
||||||
|
@ -427,8 +457,7 @@ impl StateMachine {
|
||||||
|
|
||||||
let address = u32::from_str_radix(captures["addr"].trim(), 16)?;
|
let address = u32::from_str_radix(captures["addr"].trim(), 16)?;
|
||||||
let size = u32::from_str_radix(captures["size"].trim(), 16)?;
|
let size = u32::from_str_radix(captures["size"].trim(), 16)?;
|
||||||
let align =
|
let align = captures.name("align").and_then(|m| m.as_str().trim().parse::<u32>().ok());
|
||||||
captures.name("align").and_then(|m| u32::from_str_radix(m.as_str().trim(), 16).ok());
|
|
||||||
|
|
||||||
if state.current_unit.as_ref() != Some(&tu) || sym_name == state.current_section {
|
if state.current_unit.as_ref() != Some(&tu) || sym_name == state.current_section {
|
||||||
state.current_unit = Some(tu.clone());
|
state.current_unit = Some(tu.clone());
|
||||||
|
@ -461,8 +490,7 @@ impl StateMachine {
|
||||||
} else {
|
} else {
|
||||||
SymbolVisibility::Unknown
|
SymbolVisibility::Unknown
|
||||||
};
|
};
|
||||||
let kind = if sym_name.starts_with('.') {
|
let kind = if sym_name == state.current_section {
|
||||||
visibility = SymbolVisibility::Local;
|
|
||||||
SymbolKind::Section
|
SymbolKind::Section
|
||||||
} else if size > 0 {
|
} else if size > 0 {
|
||||||
if is_code_section(&state.current_section) {
|
if is_code_section(&state.current_section) {
|
||||||
|
@ -484,12 +512,7 @@ impl StateMachine {
|
||||||
align,
|
align,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match state.symbols.entry(address) {
|
state.symbols.nested_push(address, entry);
|
||||||
btree_map::Entry::Occupied(e) => e.into_mut().push(entry),
|
|
||||||
btree_map::Entry::Vacant(e) => {
|
|
||||||
e.insert(vec![entry]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,7 +563,7 @@ impl StateMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_map<R: BufRead>(reader: R) -> Result<MapInfo> {
|
pub fn process_map<R: BufRead>(reader: &mut R) -> Result<MapInfo> {
|
||||||
let mut sm = StateMachine {
|
let mut sm = StateMachine {
|
||||||
state: ProcessMapState::None,
|
state: ProcessMapState::None,
|
||||||
result: Default::default(),
|
result: Default::default(),
|
||||||
|
@ -554,12 +577,13 @@ pub fn process_map<R: BufRead>(reader: R) -> Result<MapInfo> {
|
||||||
}
|
}
|
||||||
let state = replace(&mut sm.state, ProcessMapState::None);
|
let state = replace(&mut sm.state, ProcessMapState::None);
|
||||||
sm.end_state(state)?;
|
sm.end_state(state)?;
|
||||||
|
sm.finalize()?;
|
||||||
Ok(sm.result)
|
Ok(sm.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_map_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<()> {
|
pub fn apply_map_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<()> {
|
||||||
let file = map_file(&path)?;
|
let file = map_file(&path)?;
|
||||||
let info = process_map(map_reader(&file))?;
|
let info = process_map(&mut file.as_reader())?;
|
||||||
apply_map(&info, obj)
|
apply_map(&info, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -622,7 +646,7 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
||||||
.map(|(addr, _)| *addr)
|
.map(|(addr, _)| *addr)
|
||||||
.unwrap_or_else(|| (section.address + section.size) as u32);
|
.unwrap_or_else(|| (section.address + section.size) as u32);
|
||||||
section.splits.push(*addr, ObjSplit {
|
section.splits.push(*addr, ObjSplit {
|
||||||
unit: unit.clone(),
|
unit: unit.replace(' ', "/"),
|
||||||
end: next,
|
end: next,
|
||||||
align: None,
|
align: None,
|
||||||
common: false,
|
common: false,
|
||||||
|
@ -637,6 +661,16 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
||||||
|
|
||||||
fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usize>) -> Result<()> {
|
fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usize>) -> Result<()> {
|
||||||
let demangled_name = demangle(&symbol_entry.name, &DemangleOptions::default());
|
let demangled_name = demangle(&symbol_entry.name, &DemangleOptions::default());
|
||||||
|
let mut flags: FlagSet<ObjSymbolFlags> = match symbol_entry.visibility {
|
||||||
|
SymbolVisibility::Unknown => Default::default(),
|
||||||
|
SymbolVisibility::Global => ObjSymbolFlags::Global.into(),
|
||||||
|
SymbolVisibility::Local => ObjSymbolFlags::Local.into(),
|
||||||
|
SymbolVisibility::Weak => ObjSymbolFlags::Weak.into(),
|
||||||
|
};
|
||||||
|
// TODO move somewhere common
|
||||||
|
if symbol_entry.name.starts_with("..") {
|
||||||
|
flags |= ObjSymbolFlags::ForceActive;
|
||||||
|
}
|
||||||
obj.add_symbol(
|
obj.add_symbol(
|
||||||
ObjSymbol {
|
ObjSymbol {
|
||||||
name: symbol_entry.name.clone(),
|
name: symbol_entry.name.clone(),
|
||||||
|
@ -645,12 +679,7 @@ fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usi
|
||||||
section,
|
section,
|
||||||
size: symbol_entry.size as u64,
|
size: symbol_entry.size as u64,
|
||||||
size_known: symbol_entry.size != 0,
|
size_known: symbol_entry.size != 0,
|
||||||
flags: ObjSymbolFlagSet(match symbol_entry.visibility {
|
flags: ObjSymbolFlagSet(flags),
|
||||||
SymbolVisibility::Unknown => Default::default(),
|
|
||||||
SymbolVisibility::Global => ObjSymbolFlags::Global.into(),
|
|
||||||
SymbolVisibility::Local => ObjSymbolFlags::Local.into(),
|
|
||||||
SymbolVisibility::Weak => ObjSymbolFlags::Weak.into(),
|
|
||||||
}),
|
|
||||||
kind: match symbol_entry.kind {
|
kind: match symbol_entry.kind {
|
||||||
SymbolKind::Function => ObjSymbolKind::Function,
|
SymbolKind::Function => ObjSymbolKind::Function,
|
||||||
SymbolKind::Object => ObjSymbolKind::Object,
|
SymbolKind::Object => ObjSymbolKind::Object,
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
// Source: https://github.com/Julgodis/picori/blob/650da9f4fe6050b39b80d5360416591c748058d5/src/rarc.rs
|
// Source: https://github.com/Julgodis/picori/blob/650da9f4fe6050b39b80d5360416591c748058d5/src/rarc.rs
|
||||||
// License: MIT
|
// License: MIT
|
||||||
// Modified to use `std::io::Cursor<&[u8]>` and `byteorder`
|
// Modified to use `std::io::Cursor<&[u8]>` and `byteorder`
|
||||||
use std::{collections::HashMap, fmt::Display};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt::Display,
|
||||||
|
io::{Read, Seek, SeekFrom},
|
||||||
|
path::{Component, Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, ensure, Result};
|
use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||||
|
|
||||||
use crate::util::file::{read_c_string, Reader};
|
use crate::util::file::read_c_string;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NamedHash {
|
pub struct NamedHash {
|
||||||
|
@ -62,17 +67,16 @@ struct RarcNode {
|
||||||
pub count: u32,
|
pub count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RarcReader<'a> {
|
pub struct RarcReader {
|
||||||
reader: Reader<'a>,
|
|
||||||
directories: Vec<RarcDirectory>,
|
directories: Vec<RarcDirectory>,
|
||||||
nodes: HashMap<NamedHash, RarcNode>,
|
nodes: HashMap<NamedHash, RarcNode>,
|
||||||
root_node: NamedHash,
|
root_node: NamedHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RarcReader<'a> {
|
impl RarcReader {
|
||||||
/// Creates a new RARC reader.
|
/// Creates a new RARC reader.
|
||||||
pub fn new(mut reader: Reader<'a>) -> Result<Self> {
|
pub fn new<R: Read + Seek>(reader: &mut R) -> Result<Self> {
|
||||||
let base = reader.position();
|
let base = reader.stream_position()?;
|
||||||
|
|
||||||
let magic = reader.read_u32::<LittleEndian>()?;
|
let magic = reader.read_u32::<LittleEndian>()?;
|
||||||
let _file_length = reader.read_u32::<BigEndian>()?;
|
let _file_length = reader.read_u32::<BigEndian>()?;
|
||||||
|
@ -101,7 +105,7 @@ impl<'a> RarcReader<'a> {
|
||||||
let data_base = base + file_offset as u64;
|
let data_base = base + file_offset as u64;
|
||||||
let mut directories = Vec::with_capacity(directory_count as usize);
|
let mut directories = Vec::with_capacity(directory_count as usize);
|
||||||
for i in 0..directory_count {
|
for i in 0..directory_count {
|
||||||
reader.set_position(directory_base + 20 * i as u64);
|
reader.seek(SeekFrom::Start(directory_base + 20 * i as u64))?;
|
||||||
let index = reader.read_u16::<BigEndian>()?;
|
let index = reader.read_u16::<BigEndian>()?;
|
||||||
let name_hash = reader.read_u16::<BigEndian>()?;
|
let name_hash = reader.read_u16::<BigEndian>()?;
|
||||||
let _ = reader.read_u16::<BigEndian>()?; // 0x200 for folders, 0x1100 for files
|
let _ = reader.read_u16::<BigEndian>()?; // 0x200 for folders, 0x1100 for files
|
||||||
|
@ -114,7 +118,7 @@ impl<'a> RarcReader<'a> {
|
||||||
let offset = string_table_offset as u64;
|
let offset = string_table_offset as u64;
|
||||||
let offset = offset + name_offset as u64;
|
let offset = offset + name_offset as u64;
|
||||||
ensure!((name_offset as u32) < string_table_length, "invalid string table offset");
|
ensure!((name_offset as u32) < string_table_length, "invalid string table offset");
|
||||||
read_c_string(&mut reader, base + offset)
|
read_c_string(reader, base + offset)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if index == 0xFFFF {
|
if index == 0xFFFF {
|
||||||
|
@ -139,7 +143,7 @@ impl<'a> RarcReader<'a> {
|
||||||
let mut root_node: Option<NamedHash> = None;
|
let mut root_node: Option<NamedHash> = None;
|
||||||
let mut nodes = HashMap::with_capacity(node_count as usize);
|
let mut nodes = HashMap::with_capacity(node_count as usize);
|
||||||
for i in 0..node_count {
|
for i in 0..node_count {
|
||||||
reader.set_position(node_base + 16 * i as u64);
|
reader.seek(SeekFrom::Start(node_base + 16 * i as u64))?;
|
||||||
let _identifier = reader.read_u32::<BigEndian>()?;
|
let _identifier = reader.read_u32::<BigEndian>()?;
|
||||||
let name_offset = reader.read_u32::<BigEndian>()?;
|
let name_offset = reader.read_u32::<BigEndian>()?;
|
||||||
let name_hash = reader.read_u16::<BigEndian>()?;
|
let name_hash = reader.read_u16::<BigEndian>()?;
|
||||||
|
@ -158,7 +162,7 @@ impl<'a> RarcReader<'a> {
|
||||||
let offset = string_table_offset as u64;
|
let offset = string_table_offset as u64;
|
||||||
let offset = offset + name_offset as u64;
|
let offset = offset + name_offset as u64;
|
||||||
ensure!(name_offset < string_table_length, "invalid string table offset");
|
ensure!(name_offset < string_table_length, "invalid string table offset");
|
||||||
read_c_string(&mut reader, base + offset)
|
read_c_string(reader, base + offset)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
// FIXME: this assumes that the root node is the first node in the list
|
// FIXME: this assumes that the root node is the first node in the list
|
||||||
|
@ -171,23 +175,49 @@ impl<'a> RarcReader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(root_node) = root_node {
|
if let Some(root_node) = root_node {
|
||||||
Ok(Self { reader, directories, nodes, root_node })
|
Ok(Self { directories, nodes, root_node })
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no root node"))
|
Err(anyhow!("no root node"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data for a file.
|
|
||||||
pub fn file_data(&mut self, offset: u64, size: u32) -> Result<&'a [u8]> {
|
|
||||||
ensure!(offset + size as u64 <= self.reader.get_ref().len() as u64, "out of bounds");
|
|
||||||
Ok(&self.reader.get_ref()[offset as usize..offset as usize + size as usize])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a iterator over the nodes in the RARC file.
|
/// Get a iterator over the nodes in the RARC file.
|
||||||
pub fn nodes(&self) -> Nodes<'_, '_> {
|
pub fn nodes(&self) -> Nodes<'_> {
|
||||||
let root_node = self.root_node.clone();
|
let root_node = self.root_node.clone();
|
||||||
Nodes { parent: self, stack: vec![NodeState::Begin(root_node)] }
|
Nodes { parent: self, stack: vec![NodeState::Begin(root_node)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find a file in the RARC file.
|
||||||
|
pub fn find_file<P: AsRef<Path>>(&self, path: P) -> Result<Option<(u64, u32)>> {
|
||||||
|
let mut cmp_path = PathBuf::new();
|
||||||
|
for component in path.as_ref().components() {
|
||||||
|
match component {
|
||||||
|
Component::Normal(name) => cmp_path.push(name.to_ascii_lowercase()),
|
||||||
|
Component::RootDir => {}
|
||||||
|
component => bail!("Invalid path component: {:?}", component),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_path = PathBuf::new();
|
||||||
|
for node in self.nodes() {
|
||||||
|
match node {
|
||||||
|
Node::DirectoryBegin { name } => {
|
||||||
|
current_path.push(name.name.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
Node::DirectoryEnd { name: _ } => {
|
||||||
|
current_path.pop();
|
||||||
|
}
|
||||||
|
Node::File { name, offset, size } => {
|
||||||
|
if current_path.join(name.name.to_ascii_lowercase()) == cmp_path {
|
||||||
|
return Ok(Some((offset, size)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Node::CurrentDirectory => {}
|
||||||
|
Node::ParentDirectory => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node in an RARC file.
|
/// A node in an RARC file.
|
||||||
|
@ -211,12 +241,12 @@ enum NodeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the nodes in an RARC file.
|
/// An iterator over the nodes in an RARC file.
|
||||||
pub struct Nodes<'parent, 'a> {
|
pub struct Nodes<'parent> {
|
||||||
parent: &'parent RarcReader<'a>,
|
parent: &'parent RarcReader,
|
||||||
stack: Vec<NodeState>,
|
stack: Vec<NodeState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'parent, 'a> Iterator for Nodes<'parent, 'a> {
|
impl<'parent> Iterator for Nodes<'parent> {
|
||||||
type Item = Node;
|
type Item = Node;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
io::{Read, Seek, Write},
|
io::{Read, Seek, SeekFrom, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
ObjArchitecture, ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
|
ObjArchitecture, ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
|
||||||
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||||
},
|
},
|
||||||
util::{file::Reader, IntoCow},
|
util::IntoCow,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Do not relocate anything, but accumulate the offset field for the next relocation offset calculation.
|
/// Do not relocate anything, but accumulate the offset field for the next relocation offset calculation.
|
||||||
|
@ -136,14 +136,14 @@ struct RelRelocRaw {
|
||||||
addend: u32,
|
addend: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_rel_header(reader: &mut Reader) -> Result<RelHeader> {
|
pub fn process_rel_header<R: Read + Seek>(reader: &mut R) -> Result<RelHeader> {
|
||||||
RelHeader::read_be(reader).context("Failed to read REL header")
|
RelHeader::read_be(reader).context("Failed to read REL header")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_rel(reader: &mut Reader) -> Result<(RelHeader, ObjInfo)> {
|
pub fn process_rel<R: Read + Seek>(reader: &mut R, name: &str) -> Result<(RelHeader, ObjInfo)> {
|
||||||
let header = process_rel_header(reader)?;
|
let header = process_rel_header(reader)?;
|
||||||
let mut sections = Vec::with_capacity(header.num_sections as usize);
|
let mut sections = Vec::with_capacity(header.num_sections as usize);
|
||||||
reader.set_position(header.section_info_offset as u64);
|
reader.seek(SeekFrom::Start(header.section_info_offset as u64))?;
|
||||||
let mut found_text = false;
|
let mut found_text = false;
|
||||||
let mut total_bss_size = 0;
|
let mut total_bss_size = 0;
|
||||||
for idx in 0..header.num_sections {
|
for idx in 0..header.num_sections {
|
||||||
|
@ -158,13 +158,13 @@ pub fn process_rel(reader: &mut Reader) -> Result<(RelHeader, ObjInfo)> {
|
||||||
let data = if offset == 0 {
|
let data = if offset == 0 {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
let position = reader.position();
|
let position = reader.stream_position()?;
|
||||||
reader.set_position(offset as u64);
|
reader.seek(SeekFrom::Start(offset as u64))?;
|
||||||
let mut data = vec![0u8; size as usize];
|
let mut data = vec![0u8; size as usize];
|
||||||
reader.read_exact(&mut data).with_context(|| {
|
reader.read_exact(&mut data).with_context(|| {
|
||||||
format!("Failed to read REL section {} data with size {:#X}", idx, size)
|
format!("Failed to read REL section {} data with size {:#X}", idx, size)
|
||||||
})?;
|
})?;
|
||||||
reader.set_position(position);
|
reader.seek(SeekFrom::Start(position))?;
|
||||||
data
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -236,8 +236,8 @@ pub fn process_rel(reader: &mut Reader) -> Result<(RelHeader, ObjInfo)> {
|
||||||
let mut unresolved_relocations = Vec::new();
|
let mut unresolved_relocations = Vec::new();
|
||||||
let mut imp_idx = 0;
|
let mut imp_idx = 0;
|
||||||
let imp_end = (header.imp_offset + header.imp_size) as u64;
|
let imp_end = (header.imp_offset + header.imp_size) as u64;
|
||||||
reader.set_position(header.imp_offset as u64);
|
reader.seek(SeekFrom::Start(header.imp_offset as u64))?;
|
||||||
while reader.position() < imp_end {
|
while reader.stream_position()? < imp_end {
|
||||||
let import = RelImport::read_be(reader)?;
|
let import = RelImport::read_be(reader)?;
|
||||||
|
|
||||||
if imp_idx == 0 {
|
if imp_idx == 0 {
|
||||||
|
@ -261,8 +261,8 @@ pub fn process_rel(reader: &mut Reader) -> Result<(RelHeader, ObjInfo)> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = reader.position();
|
let position = reader.stream_position()?;
|
||||||
reader.set_position(import.offset as u64);
|
reader.seek(SeekFrom::Start(import.offset as u64))?;
|
||||||
let mut address = 0u32;
|
let mut address = 0u32;
|
||||||
let mut section = u8::MAX;
|
let mut section = u8::MAX;
|
||||||
loop {
|
loop {
|
||||||
|
@ -306,14 +306,14 @@ pub fn process_rel(reader: &mut Reader) -> Result<(RelHeader, ObjInfo)> {
|
||||||
};
|
};
|
||||||
unresolved_relocations.push(reloc);
|
unresolved_relocations.push(reloc);
|
||||||
}
|
}
|
||||||
reader.set_position(position);
|
reader.seek(SeekFrom::Start(position))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("Read REL ID {}", header.module_id);
|
log::debug!("Read REL ID {}", header.module_id);
|
||||||
let mut obj = ObjInfo::new(
|
let mut obj = ObjInfo::new(
|
||||||
ObjKind::Relocatable,
|
ObjKind::Relocatable,
|
||||||
ObjArchitecture::PowerPc,
|
ObjArchitecture::PowerPc,
|
||||||
String::new(),
|
name.to_string(),
|
||||||
symbols,
|
symbols,
|
||||||
sections,
|
sections,
|
||||||
);
|
);
|
||||||
|
@ -400,6 +400,8 @@ pub struct RelWriteInfo {
|
||||||
/// Override the number of sections in the file.
|
/// Override the number of sections in the file.
|
||||||
/// Useful for matching RELs that included debug sections.
|
/// Useful for matching RELs that included debug sections.
|
||||||
pub section_count: Option<usize>,
|
pub section_count: Option<usize>,
|
||||||
|
/// If true, don't print warnings about overriding values.
|
||||||
|
pub quiet: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PERMITTED_SECTIONS: [&str; 7] =
|
const PERMITTED_SECTIONS: [&str; 7] =
|
||||||
|
@ -463,20 +465,20 @@ pub fn write_rel<W: Write>(
|
||||||
|
|
||||||
// Apply overrides
|
// Apply overrides
|
||||||
if let Some(section_count) = info.section_count {
|
if let Some(section_count) = info.section_count {
|
||||||
if section_count != num_sections as usize {
|
if section_count != num_sections as usize && !info.quiet {
|
||||||
warn!(from = num_sections, to = section_count, "Overriding section count");
|
warn!(from = num_sections, to = section_count, "Overriding section count");
|
||||||
}
|
}
|
||||||
num_sections = section_count as u32;
|
num_sections = section_count as u32;
|
||||||
}
|
}
|
||||||
if info.version >= 2 {
|
if info.version >= 2 {
|
||||||
if let Some(align_override) = info.align {
|
if let Some(align_override) = info.align {
|
||||||
if align_override != align {
|
if align_override != align && !info.quiet {
|
||||||
warn!(from = align, to = align_override, "Overriding alignment");
|
warn!(from = align, to = align_override, "Overriding alignment");
|
||||||
}
|
}
|
||||||
align = align_override;
|
align = align_override;
|
||||||
}
|
}
|
||||||
if let Some(bss_align_override) = info.bss_align {
|
if let Some(bss_align_override) = info.bss_align {
|
||||||
if bss_align_override != bss_align {
|
if bss_align_override != bss_align && !info.quiet {
|
||||||
warn!(from = bss_align, to = bss_align_override, "Overriding BSS alignment");
|
warn!(from = bss_align, to = bss_align_override, "Overriding BSS alignment");
|
||||||
}
|
}
|
||||||
bss_align = bss_align_override;
|
bss_align = bss_align_override;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{io::Read, path::Path};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
use anyhow::{anyhow, ensure, Result};
|
use anyhow::{anyhow, ensure, Result};
|
||||||
use byteorder::{BigEndian, ReadBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||||
ObjSymbolFlags, ObjSymbolKind,
|
ObjSymbolFlags, ObjSymbolKind,
|
||||||
},
|
},
|
||||||
util::file::{map_file, map_reader, read_c_string, read_string},
|
util::file::{read_c_string, read_string},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// For RSO references to the DOL, the sections are hardcoded.
|
/// For RSO references to the DOL, the sections are hardcoded.
|
||||||
|
@ -32,10 +32,7 @@ pub const DOL_SECTION_NAMES: [Option<&str>; 14] = [
|
||||||
/// ABS symbol section index.
|
/// ABS symbol section index.
|
||||||
pub const DOL_SECTION_ABS: u32 = 65521;
|
pub const DOL_SECTION_ABS: u32 = 65521;
|
||||||
|
|
||||||
pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
pub fn process_rso<R: Read + Seek>(reader: &mut R) -> Result<ObjInfo> {
|
||||||
let mmap = map_file(path)?;
|
|
||||||
let mut reader = map_reader(&mmap);
|
|
||||||
|
|
||||||
ensure!(reader.read_u32::<BigEndian>()? == 0, "Expected 'next' to be 0");
|
ensure!(reader.read_u32::<BigEndian>()? == 0, "Expected 'next' to be 0");
|
||||||
ensure!(reader.read_u32::<BigEndian>()? == 0, "Expected 'prev' to be 0");
|
ensure!(reader.read_u32::<BigEndian>()? == 0, "Expected 'prev' to be 0");
|
||||||
let num_sections = reader.read_u32::<BigEndian>()?;
|
let num_sections = reader.read_u32::<BigEndian>()?;
|
||||||
|
@ -64,7 +61,7 @@ pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
let import_table_name_offset = reader.read_u32::<BigEndian>()?;
|
let import_table_name_offset = reader.read_u32::<BigEndian>()?;
|
||||||
|
|
||||||
let mut sections = Vec::with_capacity(num_sections as usize);
|
let mut sections = Vec::with_capacity(num_sections as usize);
|
||||||
reader.set_position(section_info_offset as u64);
|
reader.seek(SeekFrom::Start(section_info_offset as u64))?;
|
||||||
let mut total_bss_size = 0;
|
let mut total_bss_size = 0;
|
||||||
for idx in 0..num_sections {
|
for idx in 0..num_sections {
|
||||||
let offset = reader.read_u32::<BigEndian>()?;
|
let offset = reader.read_u32::<BigEndian>()?;
|
||||||
|
@ -79,11 +76,11 @@ pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
let data = if offset == 0 {
|
let data = if offset == 0 {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
let position = reader.position();
|
let position = reader.stream_position()?;
|
||||||
reader.set_position(offset as u64);
|
reader.seek(SeekFrom::Start(offset as u64))?;
|
||||||
let mut data = vec![0u8; size as usize];
|
let mut data = vec![0u8; size as usize];
|
||||||
reader.read_exact(&mut data)?;
|
reader.read_exact(&mut data)?;
|
||||||
reader.set_position(position);
|
reader.seek(SeekFrom::Start(position))?;
|
||||||
data
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,8 +145,8 @@ pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
add_symbol(epilog_section, epilog_offset, "_epilog")?;
|
add_symbol(epilog_section, epilog_offset, "_epilog")?;
|
||||||
add_symbol(unresolved_section, unresolved_offset, "_unresolved")?;
|
add_symbol(unresolved_section, unresolved_offset, "_unresolved")?;
|
||||||
|
|
||||||
reader.set_position(external_rel_offset as u64);
|
reader.seek(SeekFrom::Start(external_rel_offset as u64))?;
|
||||||
while reader.position() < (external_rel_offset + external_rel_size) as u64 {
|
while reader.stream_position()? < (external_rel_offset + external_rel_size) as u64 {
|
||||||
let offset = reader.read_u32::<BigEndian>()?;
|
let offset = reader.read_u32::<BigEndian>()?;
|
||||||
let id_and_type = reader.read_u32::<BigEndian>()?;
|
let id_and_type = reader.read_u32::<BigEndian>()?;
|
||||||
let id = (id_and_type & 0xFFFFFF00) >> 8;
|
let id = (id_and_type & 0xFFFFFF00) >> 8;
|
||||||
|
@ -164,10 +161,10 @@ pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.set_position(export_table_offset as u64);
|
reader.seek(SeekFrom::Start(export_table_offset as u64))?;
|
||||||
while reader.position() < (export_table_offset + export_table_size) as u64 {
|
while reader.stream_position()? < (export_table_offset + export_table_size) as u64 {
|
||||||
let name_off = reader.read_u32::<BigEndian>()?;
|
let name_off = reader.read_u32::<BigEndian>()?;
|
||||||
let name = read_c_string(&mut reader, (export_table_name_offset + name_off) as u64)?;
|
let name = read_c_string(reader, (export_table_name_offset + name_off) as u64)?;
|
||||||
let sym_off = reader.read_u32::<BigEndian>()?;
|
let sym_off = reader.read_u32::<BigEndian>()?;
|
||||||
let section_idx = reader.read_u32::<BigEndian>()?;
|
let section_idx = reader.read_u32::<BigEndian>()?;
|
||||||
let hash_n = reader.read_u32::<BigEndian>()?;
|
let hash_n = reader.read_u32::<BigEndian>()?;
|
||||||
|
@ -207,10 +204,10 @@ pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
data_kind: Default::default(),
|
data_kind: Default::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
reader.set_position(import_table_offset as u64);
|
reader.seek(SeekFrom::Start(import_table_offset as u64))?;
|
||||||
while reader.position() < (import_table_offset + import_table_size) as u64 {
|
while reader.stream_position()? < (import_table_offset + import_table_size) as u64 {
|
||||||
let name_off = reader.read_u32::<BigEndian>()?;
|
let name_off = reader.read_u32::<BigEndian>()?;
|
||||||
let name = read_c_string(&mut reader, (import_table_name_offset + name_off) as u64)?;
|
let name = read_c_string(reader, (import_table_name_offset + name_off) as u64)?;
|
||||||
let sym_off = reader.read_u32::<BigEndian>()?;
|
let sym_off = reader.read_u32::<BigEndian>()?;
|
||||||
let section_idx = reader.read_u32::<BigEndian>()?;
|
let section_idx = reader.read_u32::<BigEndian>()?;
|
||||||
log::debug!("Import: {}, sym off: {}, section: {}", name, sym_off, section_idx);
|
log::debug!("Import: {}, sym off: {}, section: {}", name, sym_off, section_idx);
|
||||||
|
@ -218,7 +215,7 @@ pub fn process_rso<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
|
|
||||||
let name = match name_offset {
|
let name = match name_offset {
|
||||||
0 => String::new(),
|
0 => String::new(),
|
||||||
_ => read_string(&mut reader, name_offset as u64, name_size as usize)?,
|
_ => read_string(reader, name_offset as u64, name_size as usize)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let obj = ObjInfo::new(ObjKind::Relocatable, ObjArchitecture::PowerPc, name, symbols, sections);
|
let obj = ObjInfo::new(ObjKind::Relocatable, ObjArchitecture::PowerPc, name, symbols, sections);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{min, Ordering},
|
cmp::{max, min, Ordering},
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -629,7 +629,7 @@ const fn align_up(value: u32, align: u32) -> u32 { (value + (align - 1)) & !(ali
|
||||||
/// - Creating splits for gaps between existing splits
|
/// - Creating splits for gaps between existing splits
|
||||||
/// - Resolving a new object link order
|
/// - Resolving a new object link order
|
||||||
#[instrument(level = "debug", skip(obj))]
|
#[instrument(level = "debug", skip(obj))]
|
||||||
pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>) -> Result<()> {
|
pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>, fill_gaps: bool) -> Result<()> {
|
||||||
// Create splits for extab and extabindex entries
|
// Create splits for extab and extabindex entries
|
||||||
if let Some((section_index, section)) = obj.sections.by_name("extabindex")? {
|
if let Some((section_index, section)) = obj.sections.by_name("extabindex")? {
|
||||||
let start = SectionAddress::new(section_index, section.address as u32);
|
let start = SectionAddress::new(section_index, section.address as u32);
|
||||||
|
@ -663,8 +663,10 @@ pub fn update_splits(obj: &mut ObjInfo, common_start: Option<u32>) -> Result<()>
|
||||||
// Ensure splits don't overlap symbols or each other
|
// Ensure splits don't overlap symbols or each other
|
||||||
validate_splits(obj)?;
|
validate_splits(obj)?;
|
||||||
|
|
||||||
// Add symbols to beginning of any split that doesn't start with a symbol
|
if fill_gaps {
|
||||||
add_padding_symbols(obj)?;
|
// Add symbols to beginning of any split that doesn't start with a symbol
|
||||||
|
add_padding_symbols(obj)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve link order
|
// Resolve link order
|
||||||
obj.link_order = resolve_link_order(obj)?;
|
obj.link_order = resolve_link_order(obj)?;
|
||||||
|
@ -779,7 +781,9 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
if let Some(comment_version) = unit.comment_version {
|
if let Some(comment_version) = unit.comment_version {
|
||||||
split_obj.mw_comment = Some(MWComment::new(comment_version)?);
|
if comment_version > 0 {
|
||||||
|
split_obj.mw_comment = Some(MWComment::new(comment_version)?);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
split_obj.mw_comment = obj.mw_comment.clone();
|
split_obj.mw_comment = obj.mw_comment.clone();
|
||||||
}
|
}
|
||||||
|
@ -833,8 +837,20 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
.ok_or_else(|| anyhow!("Unit '{}' not in link order", split.unit))?;
|
.ok_or_else(|| anyhow!("Unit '{}' not in link order", split.unit))?;
|
||||||
|
|
||||||
// Calculate & verify section alignment
|
// Calculate & verify section alignment
|
||||||
let mut align =
|
let mut align = split.align.unwrap_or_else(|| {
|
||||||
split.align.map(u64::from).unwrap_or_else(|| default_section_align(section));
|
let default_align = default_section_align(section) as u32;
|
||||||
|
max(
|
||||||
|
// Maximum alignment of any symbol in this split
|
||||||
|
obj.symbols
|
||||||
|
.for_section_range(section_index, current_address.address..file_end.address)
|
||||||
|
.filter(|&(_, s)| s.size_known && s.size > 0)
|
||||||
|
.filter_map(|(_, s)| s.align)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(default_align),
|
||||||
|
default_align,
|
||||||
|
)
|
||||||
|
}) as u64;
|
||||||
|
|
||||||
if current_address & (align as u32 - 1) != 0 {
|
if current_address & (align as u32 - 1) != 0 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Alignment for {} {} expected {}, but starts at {:#010X}",
|
"Alignment for {} {} expected {}, but starts at {:#010X}",
|
||||||
|
@ -877,7 +893,7 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
let mut comm_addr = current_address;
|
let mut comm_addr = current_address;
|
||||||
for (symbol_idx, symbol) in obj
|
for (symbol_idx, symbol) in obj
|
||||||
.symbols
|
.symbols
|
||||||
.for_section_range(section_index, current_address.address..file_end.address)
|
.for_section_range(section_index, current_address.address..=file_end.address)
|
||||||
.filter(|&(_, s)| {
|
.filter(|&(_, s)| {
|
||||||
s.section == Some(section_index) && !is_linker_generated_label(&s.name)
|
s.section == Some(section_index) && !is_linker_generated_label(&s.name)
|
||||||
})
|
})
|
||||||
|
@ -886,6 +902,15 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
continue; // should never happen?
|
continue; // should never happen?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO hack for gTRKInterruptVectorTableEnd
|
||||||
|
if (symbol.address == file_end.address as u64
|
||||||
|
&& symbol.name != "gTRKInterruptVectorTableEnd")
|
||||||
|
|| (symbol.address == current_address.address as u64
|
||||||
|
&& symbol.name == "gTRKInterruptVectorTableEnd")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if split.common && symbol.address as u32 > comm_addr.address {
|
if split.common && symbol.address as u32 > comm_addr.address {
|
||||||
// HACK: Add padding for common bug
|
// HACK: Add padding for common bug
|
||||||
file.symbols.add_direct(ObjSymbol {
|
file.symbols.add_direct(ObjSymbol {
|
||||||
|
@ -907,7 +932,7 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
name: symbol.name.clone(),
|
name: symbol.name.clone(),
|
||||||
demangled_name: symbol.demangled_name.clone(),
|
demangled_name: symbol.demangled_name.clone(),
|
||||||
address: if split.common {
|
address: if split.common {
|
||||||
4
|
symbol.align.unwrap_or(4) as u64
|
||||||
} else {
|
} else {
|
||||||
symbol.address - current_address.address as u64
|
symbol.address - current_address.address as u64
|
||||||
},
|
},
|
||||||
|
@ -920,7 +945,7 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
symbol.flags
|
symbol.flags
|
||||||
},
|
},
|
||||||
kind: symbol.kind,
|
kind: symbol.kind,
|
||||||
align: if split.common { Some(4) } else { symbol.align },
|
align: symbol.align,
|
||||||
data_kind: symbol.data_kind,
|
data_kind: symbol.data_kind,
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
|
@ -1081,7 +1106,7 @@ pub fn default_section_align(section: &ObjSection) -> u64 {
|
||||||
ObjSectionKind::Code => 4,
|
ObjSectionKind::Code => 4,
|
||||||
_ => match section.name.as_str() {
|
_ => match section.name.as_str() {
|
||||||
".ctors" | ".dtors" | "extab" | "extabindex" => 4,
|
".ctors" | ".dtors" | "extab" | "extabindex" => 4,
|
||||||
".sbss" => 4, // ?
|
".sbss" => 8, // ?
|
||||||
_ => 8,
|
_ => 8,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue