diff --git a/Cargo.lock b/Cargo.lock index 0ac466e..f800f72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,11 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +[[package]] +name = "ar" +version = "0.8.0" +source = "git+https://github.com/bjorn3/rust-ar.git?branch=do_not_remove_cg_clif_ranlib#de9ab0e56bf3a208381d342aa5b60f9ff2891648" + [[package]] name = "argh" version = "0.1.9" @@ -149,9 +154,10 @@ dependencies = [ [[package]] name = "decomp-toolkit" -version = "0.2.0" +version = "0.2.2" dependencies = [ "anyhow", + "ar", "argh", "base16ct", "cwdemangle", diff --git a/Cargo.toml b/Cargo.toml index 3d3b56c..0444971 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "decomp-toolkit" description = "GameCube/Wii decompilation project tools." authors = ["Luke Street "] license = "MIT OR Apache-2.0" -version = "0.2.1" +version = "0.2.2" edition = "2021" publish = false build = "build.rs" @@ -22,6 +22,7 @@ strip = "debuginfo" [dependencies] anyhow = "1.0.64" +ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "do_not_remove_cg_clif_ranlib" } argh = "0.1.8" base16ct = "0.1.1" cwdemangle = "0.1.3" diff --git a/src/cmd/ar.rs b/src/cmd/ar.rs new file mode 100644 index 0000000..f8e4807 --- /dev/null +++ b/src/cmd/ar.rs @@ -0,0 +1,110 @@ +use std::{ + collections::{btree_map::Entry, BTreeMap}, + fs::File, + io::{BufRead, BufReader, BufWriter, Write}, + path::PathBuf, +}; + +use anyhow::{Context, Error, Result}; +use argh::FromArgs; +use object::{Object, ObjectSymbol}; + +#[derive(FromArgs, PartialEq, Debug)] +/// Commands for processing static libraries. +#[argh(subcommand, name = "ar")] +pub struct Args { + #[argh(subcommand)] + command: SubCommand, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubCommand { + Create(CreateArgs), +} + +#[derive(FromArgs, PartialEq, Eq, Debug)] +/// Creates a static library. +#[argh(subcommand, name = "create")] +pub struct CreateArgs { + #[argh(positional)] + /// output file + out: PathBuf, + #[argh(positional)] + /// input files + files: Vec, +} + +pub fn run(args: Args) -> Result<()> { + match args.command { + SubCommand::Create(c_args) => create(c_args), + } +} + +fn create(args: CreateArgs) -> Result<()> { + // Process response files (starting with '@') + let mut files = Vec::with_capacity(args.files.len()); + for path in args.files { + let path_str = path.to_str().ok_or_else(|| { + Error::msg(format!("'{}' is not valid UTF-8", path.to_string_lossy())) + })?; + match path_str.strip_prefix('@') { + Some(rsp_file) => { + let reader = BufReader::new( + File::open(rsp_file) + .with_context(|| format!("Failed to open file '{}'", rsp_file))?, + ); + for result in reader.lines() { + let line = result?; + if !line.is_empty() { + files.push(PathBuf::from(line)); + } + } + } + None => { + files.push(path); + } + } + } + + // Build identifiers & symbol table + let mut identifiers = Vec::with_capacity(files.len()); + let mut symbol_table = BTreeMap::new(); + for path in &files { + let file_name = path.file_name().ok_or_else(|| { + Error::msg(format!("'{}' is not a file path", path.to_string_lossy())) + })?; + let file_name = file_name.to_str().ok_or_else(|| { + Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy())) + })?; + let identifier = file_name.as_bytes().to_vec(); + identifiers.push(identifier.clone()); + + let entries = match symbol_table.entry(identifier) { + Entry::Vacant(e) => e.insert(Vec::new()), + Entry::Occupied(_) => { + return Err(Error::msg(format!("Duplicate file name '{file_name}'"))) + } + }; + let object_file = File::open(path) + .with_context(|| format!("Failed to open object file '{}'", path.to_string_lossy()))?; + let map = unsafe { memmap2::MmapOptions::new().map(&object_file) } + .with_context(|| format!("Failed to mmap object file: '{}'", path.to_string_lossy()))?; + let obj = object::File::parse(map.as_ref())?; + for symbol in obj.symbols() { + if symbol.is_global() { + entries.push(symbol.name_bytes()?.to_vec()); + } + } + } + + // Write archive + let out = BufWriter::new(File::create(&args.out)?); + let mut builder = + ar::GnuBuilder::new(out, identifiers, ar::GnuSymbolTableFormat::Size32, symbol_table)?; + for path in files { + builder.append_path(path)?; + } + builder.into_inner()?.flush()?; + Ok(()) +} diff --git a/src/cmd/elf.rs b/src/cmd/elf.rs index ea17e1e..dcaee53 100644 --- a/src/cmd/elf.rs +++ b/src/cmd/elf.rs @@ -3,6 +3,7 @@ use std::{ fs, fs::File, io::{BufWriter, Write}, + path::PathBuf, }; use anyhow::{Context, Error, Result}; @@ -10,7 +11,7 @@ use argh::FromArgs; use object::{ write::{SectionId, SymbolId}, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags, - SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolSection, + SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection, }; use crate::util::{asm::write_asm, elf::process_elf}; @@ -36,10 +37,10 @@ enum SubCommand { pub struct DisasmArgs { #[argh(positional)] /// input file - elf_file: String, + elf_file: PathBuf, #[argh(positional)] /// output directory - out_dir: String, + out_dir: PathBuf, } #[derive(FromArgs, PartialEq, Eq, Debug)] @@ -48,10 +49,10 @@ pub struct DisasmArgs { pub struct FixupArgs { #[argh(positional)] /// input file - in_file: String, + in_file: PathBuf, #[argh(positional)] /// output file - out_file: String, + out_file: PathBuf, } pub fn run(args: Args) -> Result<()> { @@ -83,6 +84,8 @@ fn file_name_from_unit(str: &str) -> String { format!("{}.o", str.strip_prefix('/').unwrap_or(&str)) } +const ASM_SUFFIX: &[u8] = " (asm)".as_bytes(); + fn fixup(args: FixupArgs) -> Result<()> { let in_buf = fs::read(&args.in_file).context("Failed to open input file")?; let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?; @@ -90,11 +93,36 @@ fn fixup(args: FixupArgs) -> Result<()> { object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness()); // Write file symbol(s) first + let mut file_symbol_found = false; for symbol in in_file.symbols() { if symbol.kind() != SymbolKind::File { continue; } - out_file.add_symbol(to_write_symbol(&symbol, &[])?); + let mut out_symbol = to_write_symbol(&symbol, &[])?; + out_symbol.name.append(&mut ASM_SUFFIX.to_vec()); + out_file.add_symbol(out_symbol); + file_symbol_found = true; + break; + } + if !file_symbol_found { + let file_name = args.in_file.file_name().ok_or_else(|| { + Error::msg(format!("'{}' is not a file path", args.in_file.to_string_lossy())) + })?; + let file_name = file_name.to_str().ok_or_else(|| { + Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy())) + })?; + let mut name_bytes = file_name.as_bytes().to_vec(); + name_bytes.append(&mut ASM_SUFFIX.to_vec()); + out_file.add_symbol(object::write::Symbol { + name: name_bytes, + value: 0, + size: 0, + kind: SymbolKind::File, + scope: SymbolScope::Compilation, + weak: false, + section: object::write::SymbolSection::Absolute, + flags: SymbolFlags::None, + }); } // Write section symbols & sections diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index d529b95..e3f676e 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod ar; pub(crate) mod demangle; pub(crate) mod elf; pub(crate) mod elf2dol; diff --git a/src/main.rs b/src/main.rs index 3004e1b..1ec01a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ struct TopLevel { #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand)] enum SubCommand { + Ar(cmd::ar::Args), Demangle(cmd::demangle::Args), Elf(cmd::elf::Args), Elf2Dol(cmd::elf2dol::Args), @@ -29,6 +30,7 @@ fn main() { let args: TopLevel = argh_version::from_env(); let result = match args.command { + SubCommand::Ar(c_args) => cmd::ar::run(c_args), SubCommand::Demangle(c_args) => cmd::demangle::run(c_args), SubCommand::Elf(c_args) => cmd::elf::run(c_args), SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args), diff --git a/src/util/asm.rs b/src/util/asm.rs index 92ec3c9..b94d8f6 100644 --- a/src/util/asm.rs +++ b/src/util/asm.rs @@ -1,7 +1,6 @@ use std::{ cmp::{min, Ordering}, collections::{btree_map, hash_map::Entry, BTreeMap, HashMap}, - fmt::Display, fs, fs::{DirBuilder, File}, io::{BufWriter, Write}, @@ -29,7 +28,7 @@ struct SymbolEntry { kind: SymbolEntryKind, } -pub fn write_asm + Display>(path: P, obj: &ObjInfo) -> Result<()> { +pub fn write_asm>(path: P, obj: &ObjInfo) -> Result<()> { let mut file_map = HashMap::>::new(); let asm_dir = path.as_ref().join("asm"); diff --git a/src/util/elf.rs b/src/util/elf.rs index 9d18650..6dc5902 100644 --- a/src/util/elf.rs +++ b/src/util/elf.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, fmt::Display, fs::File, path::Path}; +use std::{collections::BTreeMap, fs::File, path::Path}; use anyhow::{Context, Error, Result}; use cwdemangle::demangle; @@ -19,11 +19,13 @@ use crate::util::obj::{ ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, }; -pub fn process_elf + Display>(path: P) -> Result { - let elf_file = - File::open(&path).with_context(|| format!("Failed to open ELF file '{path}'"))?; - let map = unsafe { MmapOptions::new().map(&elf_file) } - .with_context(|| format!("Failed to mmap ELF file: '{path}'"))?; +pub fn process_elf>(path: P) -> Result { + let elf_file = File::open(&path).with_context(|| { + format!("Failed to open ELF file '{}'", path.as_ref().to_string_lossy()) + })?; + let map = unsafe { MmapOptions::new().map(&elf_file) }.with_context(|| { + format!("Failed to mmap ELF file: '{}'", path.as_ref().to_string_lossy()) + })?; let obj_file = object::read::File::parse(&*map)?; let architecture = match obj_file.architecture() { Architecture::PowerPc => ObjArchitecture::PowerPc,