From 9a6cb70ff88ff5a953674281450a022eb64cbd48 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 9 Feb 2024 15:00:09 -0700 Subject: [PATCH] Add `ar extract` command --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 21 ++++++++++++++++ src/cmd/ar.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ed26d6..ff0aa16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,7 +295,7 @@ dependencies = [ [[package]] name = "decomp-toolkit" -version = "0.7.2" +version = "0.7.3" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index e30f23c..17d282d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "decomp-toolkit" description = "Yet another GameCube/Wii decompilation toolkit." authors = ["Luke Street "] license = "MIT OR Apache-2.0" -version = "0.7.2" +version = "0.7.3" edition = "2021" publish = false repository = "https://github.com/encounter/decomp-toolkit" diff --git a/README.md b/README.md index eb7e8b7..04034f5 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ project structure and build system that uses decomp-toolkit under the hood. - [Analyzer features](#analyzer-features) - [Commands](#commands) - [ar create](#ar-create) + - [ar extract](#ar-extract) - [demangle](#demangle) - [dol info](#dol-info) - [dol split](#dol-split) @@ -276,6 +277,26 @@ $ echo input_2.o >> rspfile $ dtk ar create out.a @rspfile ``` +### ar extract + +Extracts the contents of static library (.a) files. + +Accepts multiple files, glob patterns (e.g. `*.a`) and response files (e.g. `@rspfile`). + +Options: +- `-o`, `--out `: Output directory. Defaults to the current directory. +- `-v`, `--verbose`: Verbose output. +- `-q`, `--quiet`: Suppresses all output except errors. + +```shell +# Extracts to outdir +$ dtk ar extract lib.a -o outdir + +# With multiple inputs, extracts to separate directories +# Extracts to outdir/lib1, outdir/lib2 +$ dtk ar extract lib1.a lib2.a -o outdir +``` + ### demangle Demangles CodeWarrior C++ symbols. A thin wrapper for [cwdemangle](https://github.com/encounter/cwdemangle). diff --git a/src/cmd/ar.rs b/src/cmd/ar.rs index afccfb8..fb2a946 100644 --- a/src/cmd/ar.rs +++ b/src/cmd/ar.rs @@ -5,7 +5,7 @@ use std::{ path::PathBuf, }; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use argp::FromArgs; use object::{Object, ObjectSymbol, SymbolScope}; @@ -23,6 +23,7 @@ pub struct Args { #[argp(subcommand)] enum SubCommand { Create(CreateArgs), + Extract(ExtractArgs), } #[derive(FromArgs, PartialEq, Eq, Debug)] @@ -37,9 +38,28 @@ pub struct CreateArgs { files: Vec, } +#[derive(FromArgs, PartialEq, Eq, Debug)] +/// Extracts a static library. +#[argp(subcommand, name = "extract")] +pub struct ExtractArgs { + #[argp(positional)] + /// input files + files: Vec, + #[argp(option, short = 'o')] + /// output directory + out: Option, + #[argp(switch, short = 'q')] + /// quiet output + quiet: bool, + #[argp(switch, short = 'v')] + /// verbose output + verbose: bool, +} + pub fn run(args: Args) -> Result<()> { match args.command { SubCommand::Create(c_args) => create(c_args), + SubCommand::Extract(c_args) => extract(c_args), } } @@ -87,3 +107,51 @@ fn create(args: CreateArgs) -> Result<()> { builder.into_inner()?.flush()?; Ok(()) } + +fn extract(args: ExtractArgs) -> Result<()> { + // Process response files (starting with '@') + let files = process_rsp(&args.files)?; + + // Extract files + let mut num_files = 0; + for path in &files { + let mut out_dir = if let Some(out) = &args.out { out.clone() } else { PathBuf::new() }; + // If there are multiple files, extract to separate directories + if files.len() > 1 { + out_dir + .push(path.with_extension("").file_name().ok_or_else(|| anyhow!("No file name"))?); + } + std::fs::create_dir_all(&out_dir)?; + if !args.quiet { + println!("Extracting {} to {}", path.display(), out_dir.display()); + } + + let file = map_file(path)?; + let mut archive = ar::Archive::new(file.as_slice()); + while let Some(entry) = archive.next_entry() { + let mut entry = + entry.with_context(|| format!("Processing entry in {}", path.display()))?; + let file_name = std::str::from_utf8(entry.header().identifier())?; + if !args.quiet && args.verbose { + println!("\t{}", file_name); + } + let mut file_path = out_dir.clone(); + for segment in file_name.split(&['/', '\\']) { + file_path.push(sanitise_file_name::sanitise(segment)); + } + if let Some(parent) = file_path.parent() { + std::fs::create_dir_all(parent)?; + } + let mut file = File::create(&file_path) + .with_context(|| format!("Failed to create file {}", file_path.display()))?; + std::io::copy(&mut entry, &mut file)?; + file.flush()?; + + num_files += 1; + } + } + if !args.quiet { + println!("Extracted {} files", num_files); + } + Ok(()) +}