Initial commit

This commit is contained in:
2022-11-27 01:37:29 -05:00
commit 636cbea59c
21 changed files with 2402 additions and 0 deletions

26
src/cmd/demangle.rs Normal file
View File

@@ -0,0 +1,26 @@
use anyhow::{Error, Result};
use argh::FromArgs;
use cwdemangle::{demangle, DemangleOptions};
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Demangle a CodeWarrior C++ symbol.
#[argh(subcommand, name = "demangle")]
pub struct Args {
#[argh(positional)]
/// symbol to demangle
symbol: String,
#[argh(switch)]
/// disable replacing `(void)` with `()`
keep_void: bool,
}
pub fn run(args: Args) -> Result<()> {
let options = DemangleOptions { omit_empty_parameters: !args.keep_void };
match demangle(args.symbol.as_str(), &options) {
Some(symbol) => {
println!("{}", symbol);
Ok(())
}
None => Err(Error::msg("Failed to demangle symbol")),
}
}

173
src/cmd/elf2dol.rs Normal file
View File

@@ -0,0 +1,173 @@
use std::{
fs::File,
io::{BufWriter, Seek, SeekFrom, Write},
};
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use memmap2::MmapOptions;
use object::{Architecture, Object, ObjectKind, ObjectSection, SectionKind};
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Converts an ELF file to a DOL file.
#[argh(subcommand, name = "elf2dol")]
pub struct Args {
#[argh(positional)]
/// path to input ELF
elf_file: String,
#[argh(positional)]
/// path to output DOL
dol_file: String,
}
#[derive(Debug, Clone, Default)]
pub struct DolSection {
pub offset: u32,
pub address: u32,
pub size: u32,
}
#[derive(Debug, Clone, Default)]
pub struct DolHeader {
pub text_sections: Vec<DolSection>,
pub data_sections: Vec<DolSection>,
pub bss_address: u32,
pub bss_size: u32,
pub entry_point: u32,
}
const MAX_TEXT_SECTIONS: usize = 7;
const MAX_DATA_SECTIONS: usize = 11;
const ZERO_BUF: [u8; 32] = [0u8; 32];
pub fn run(args: Args) -> Result<()> {
let elf_file = File::open(&args.elf_file)
.with_context(|| format!("Failed to open ELF file '{}'", args.elf_file))?;
let map = unsafe { MmapOptions::new().map(&elf_file) }
.with_context(|| format!("Failed to mmap binary: '{}'", args.elf_file))?;
let obj_file = object::read::File::parse(&*map)?;
match obj_file.architecture() {
Architecture::PowerPc => {}
arch => return Err(Error::msg(format!("Unexpected architecture: {:?}", arch))),
};
if obj_file.is_little_endian() {
return Err(Error::msg("Expected big endian"));
}
match obj_file.kind() {
ObjectKind::Executable => {}
kind => return Err(Error::msg(format!("Unexpected ELF type: {:?}", kind))),
}
let mut out = BufWriter::new(
File::create(&args.dol_file)
.with_context(|| format!("Failed to create DOL file '{}'", args.dol_file))?,
);
let mut header = DolHeader { entry_point: obj_file.entry() as u32, ..Default::default() };
let mut offset = 0x100u32;
// Text sections
for section in obj_file.sections() {
if section.kind() != SectionKind::Text {
continue;
}
let address = section.address() as u32;
let size = align32(section.size() as u32);
header.text_sections.push(DolSection { offset, address, size });
out.seek(SeekFrom::Start(offset as u64))?;
write_aligned(&mut out, section.data()?)?;
offset += size;
}
// Data sections
for section in obj_file.sections() {
if section.kind() != SectionKind::Data && section.kind() != SectionKind::ReadOnlyData {
continue;
}
let address = section.address() as u32;
let size = align32(section.size() as u32);
header.data_sections.push(DolSection { offset, address, size });
out.seek(SeekFrom::Start(offset as u64))?;
write_aligned(&mut out, section.data()?)?;
offset += size;
}
// BSS sections
for section in obj_file.sections() {
if section.kind() != SectionKind::UninitializedData {
continue;
}
let address = section.address() as u32;
let size = section.size() as u32;
if header.bss_address == 0 {
header.bss_address = address;
}
header.bss_size = (address + size) - header.bss_address;
}
if header.text_sections.len() > MAX_TEXT_SECTIONS {
return Err(Error::msg(format!(
"Too many text sections: {} / {}",
header.text_sections.len(),
MAX_TEXT_SECTIONS
)));
}
if header.data_sections.len() > MAX_DATA_SECTIONS {
return Err(Error::msg(format!(
"Too many data sections: {} / {}",
header.data_sections.len(),
MAX_DATA_SECTIONS
)));
}
// Offsets
out.rewind()?;
for section in &header.text_sections {
out.write_all(&section.offset.to_be_bytes())?;
}
out.seek(SeekFrom::Start(0x1c))?;
for section in &header.data_sections {
out.write_all(&section.offset.to_be_bytes())?;
}
// Addresses
out.seek(SeekFrom::Start(0x48))?;
for section in &header.text_sections {
out.write_all(&section.address.to_be_bytes())?;
}
out.seek(SeekFrom::Start(0x64))?;
for section in &header.data_sections {
out.write_all(&section.address.to_be_bytes())?;
}
// Sizes
out.seek(SeekFrom::Start(0x90))?;
for section in &header.text_sections {
out.write_all(&section.size.to_be_bytes())?;
}
out.seek(SeekFrom::Start(0xac))?;
for section in &header.data_sections {
out.write_all(&section.size.to_be_bytes())?;
}
// BSS + entry
out.seek(SeekFrom::Start(0xd8))?;
out.write_all(&header.bss_address.to_be_bytes())?;
out.write_all(&header.bss_size.to_be_bytes())?;
out.write_all(&header.entry_point.to_be_bytes())?;
Ok(())
}
#[inline]
fn align32(x: u32) -> u32 { (x + 31) & !31 }
#[inline]
fn write_aligned<T: Write>(out: &mut T, bytes: &[u8]) -> std::io::Result<()> {
let len = bytes.len() as u32;
let padding = align32(len) - len;
out.write_all(bytes)?;
if padding > 0 {
out.write_all(&ZERO_BUF[0..padding as usize])?;
}
Ok(())
}

140
src/cmd/map.rs Normal file
View File

@@ -0,0 +1,140 @@
use std::{fs::File, io::BufReader};
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use crate::util::map::{process_map, SymbolEntry, SymbolRef};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing CodeWarrior maps.
#[argh(subcommand, name = "map")]
pub struct Args {
#[argh(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommand {
Entries(EntriesArgs),
Symbol(SymbolArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Displays all entries for a particular TU.
#[argh(subcommand, name = "entries")]
pub struct EntriesArgs {
#[argh(positional)]
/// path to input map
map_file: String,
#[argh(positional)]
/// TU to display entries for
unit: String,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Displays all references to a symbol.
#[argh(subcommand, name = "symbol")]
pub struct SymbolArgs {
#[argh(positional)]
/// path to input map
map_file: String,
#[argh(positional)]
/// symbol to display references for
symbol: String,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Entries(c_args) => entries(c_args),
SubCommand::Symbol(c_args) => symbol(c_args),
}
}
fn entries(args: EntriesArgs) -> Result<()> {
let reader = BufReader::new(
File::open(&args.map_file)
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
);
let entries = process_map(reader)?;
match entries.unit_entries.get_vec(&args.unit) {
Some(vec) => {
for symbol_ref in vec {
if symbol_ref.name.starts_with('@') {
continue;
}
if let Some(symbol) = entries.symbols.get(symbol_ref) {
println!("{}", symbol.demangled.as_ref().unwrap_or(&symbol.name));
} else {
println!("Symbol not found: {}", symbol_ref.name);
}
}
}
None => {
return Err(Error::msg(format!(
"Failed to find entries for TU '{}' in map",
args.unit
)));
}
}
Ok(())
}
fn symbol(args: SymbolArgs) -> Result<()> {
let reader = BufReader::new(
File::open(&args.map_file)
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
);
let entries = process_map(reader)?;
let mut opt_ref: Option<(SymbolRef, SymbolEntry)> = None;
for (symbol_ref, entry) in &entries.symbols {
if symbol_ref.name == args.symbol {
if opt_ref.is_some() {
return Err(Error::msg(format!("Symbol '{}' found in multiple TUs", args.symbol)));
}
opt_ref = Some((symbol_ref.clone(), entry.clone()));
}
}
match opt_ref {
Some((symbol_ref, symbol)) => {
println!("Located symbol {}", symbol.demangled.as_ref().unwrap_or(&symbol.name));
if let Some(vec) = entries.entry_references.get_vec(&symbol_ref) {
println!("\nReferences:");
for x in vec {
if let Some(reference) = entries.symbols.get(x) {
println!(
">>> {} ({:?},{:?}) [{}]",
reference.demangled.as_ref().unwrap_or(&reference.name),
reference.kind,
reference.visibility,
reference.unit
);
} else {
println!(">>> {} (NOT FOUND)", x.name);
}
}
}
if let Some(vec) = entries.entry_referenced_from.get_vec(&symbol_ref) {
println!("\nReferenced from:");
for x in vec {
if let Some(reference) = entries.symbols.get(x) {
println!(
">>> {} ({:?}, {:?}) [{}]",
reference.demangled.as_ref().unwrap_or(&reference.name),
reference.kind,
reference.visibility,
reference.unit
);
} else {
println!(">>> {} (NOT FOUND)", x.name);
}
}
}
println!("\n");
}
None => {
return Err(Error::msg(format!("Failed to find symbol '{}' in map", args.symbol)));
}
}
Ok(())
}

View File

@@ -0,0 +1,47 @@
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use memchr::memmem;
use memmap2::MmapOptions;
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Sets the MetroidBuildInfo tag value in a given binary.
#[argh(subcommand, name = "metroidbuildinfo")]
pub struct Args {
#[argh(positional)]
/// path to source binary
binary: String,
#[argh(positional)]
/// path to build info string
build_info: String,
}
const BUILD_STRING_MAX: usize = 35;
const BUILD_STRING_TAG: &str = "!#$MetroidBuildInfo!#$";
pub fn run(args: Args) -> Result<()> {
let build_string = std::fs::read_to_string(&args.build_info)
.with_context(|| format!("Failed to read build info string from '{}'", args.build_info))?;
let build_string_trim = build_string.trim_end();
if build_string_trim.as_bytes().len() > BUILD_STRING_MAX {
return Err(Error::msg(format!(
"Build string '{}' is greater than maximum size of {}",
build_string_trim, BUILD_STRING_MAX
)));
}
let binary_file = std::fs::File::options()
.read(true)
.write(true)
.open(&args.binary)
.with_context(|| format!("Failed to open binary for writing: '{}'", args.binary))?;
let mut map = unsafe { MmapOptions::new().map_mut(&binary_file) }
.with_context(|| format!("Failed to mmap binary: '{}'", args.binary))?;
let start = match memmem::find(&map, BUILD_STRING_TAG.as_bytes()) {
Some(idx) => idx + BUILD_STRING_TAG.as_bytes().len(),
None => return Err(Error::msg("Failed to find build string tag in binary")),
};
let end = start + build_string_trim.as_bytes().len();
map[start..end].copy_from_slice(build_string_trim.as_bytes());
map[end] = 0;
Ok(())
}

5
src/cmd/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub(crate) mod demangle;
pub(crate) mod elf2dol;
pub(crate) mod map;
pub(crate) mod metroidbuildinfo;
pub(crate) mod shasum;

88
src/cmd/shasum.rs Normal file
View File

@@ -0,0 +1,88 @@
use std::{
fs::File,
io::{BufRead, BufReader, Read},
};
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use sha1::{Digest, Sha1};
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Print or check SHA1 (160-bit) checksums.
#[argh(subcommand, name = "shasum")]
pub struct Args {
#[argh(switch, short = 'c')]
/// check SHA sums against given list
check: bool,
#[argh(positional)]
/// path to file
file: String,
}
const DEFAULT_BUF_SIZE: usize = 8192;
pub fn run(args: Args) -> Result<()> {
let file =
File::open(&args.file).with_context(|| format!("Failed to open file '{}'", args.file))?;
if args.check {
check(args, file)
} else {
hash(args, file)
}
}
fn check(_args: Args, file: File) -> Result<()> {
let reader = BufReader::new(file);
let mut mismatches = 0usize;
for line in reader.lines() {
let line = match line {
Ok(line) => line,
Err(e) => return Err(Error::msg(format!("File read failed: {}", e))),
};
let (hash, file_name) =
line.split_once(' ').ok_or_else(|| Error::msg(format!("Invalid line: {}", line)))?;
let file_name = match file_name.chars().next() {
Some(' ') | Some('*') => &file_name[1..],
_ => return Err(Error::msg(format!("Invalid line: {}", line))),
};
let mut hash_bytes = [0u8; 20];
hex::decode_to_slice(hash, &mut hash_bytes)
.with_context(|| format!("Invalid line: {}", line))?;
let file = File::open(file_name)
.with_context(|| format!("Failed to open file '{}'", file_name))?;
let found_hash = file_sha1(file)?;
if hash_bytes == found_hash.as_ref() {
println!("{}: OK", file_name);
} else {
println!("{}: FAILED", file_name);
mismatches += 1;
}
}
if mismatches != 0 {
eprintln!("WARNING: {} computed checksum did NOT match", mismatches);
std::process::exit(1);
}
Ok(())
}
fn hash(args: Args, file: File) -> Result<()> {
let hash = file_sha1(file)?;
let mut hash_buf = [0u8; 40];
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
.map_err(|e| Error::msg(format!("Failed to encode hash: {}", e)))?;
println!("{} {}", hash_str, args.file);
Ok(())
}
fn file_sha1(mut file: File) -> Result<sha1::digest::Output<Sha1>> {
let mut buf = [0u8; DEFAULT_BUF_SIZE];
let mut hasher = Sha1::new();
Ok(loop {
let read = file.read(&mut buf).context("File read failed")?;
if read == 0 {
break hasher.finalize();
}
hasher.update(&buf[0..read]);
})
}