Add `ar extract` command

This commit is contained in:
Luke Street 2024-02-09 15:00:09 -07:00
parent 4a84975648
commit 9a6cb70ff8
4 changed files with 92 additions and 3 deletions

2
Cargo.lock generated
View File

@ -295,7 +295,7 @@ dependencies = [
[[package]] [[package]]
name = "decomp-toolkit" name = "decomp-toolkit"
version = "0.7.2" version = "0.7.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ar", "ar",

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit." description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
version = "0.7.2" version = "0.7.3"
edition = "2021" edition = "2021"
publish = false publish = false
repository = "https://github.com/encounter/decomp-toolkit" repository = "https://github.com/encounter/decomp-toolkit"

View File

@ -20,6 +20,7 @@ project structure and build system that uses decomp-toolkit under the hood.
- [Analyzer features](#analyzer-features) - [Analyzer features](#analyzer-features)
- [Commands](#commands) - [Commands](#commands)
- [ar create](#ar-create) - [ar create](#ar-create)
- [ar extract](#ar-extract)
- [demangle](#demangle) - [demangle](#demangle)
- [dol info](#dol-info) - [dol info](#dol-info)
- [dol split](#dol-split) - [dol split](#dol-split)
@ -276,6 +277,26 @@ $ echo input_2.o >> rspfile
$ dtk ar create out.a @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-dir>`: 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 ### demangle
Demangles CodeWarrior C++ symbols. A thin wrapper for [cwdemangle](https://github.com/encounter/cwdemangle). Demangles CodeWarrior C++ symbols. A thin wrapper for [cwdemangle](https://github.com/encounter/cwdemangle).

View File

@ -5,7 +5,7 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Context, Result};
use argp::FromArgs; use argp::FromArgs;
use object::{Object, ObjectSymbol, SymbolScope}; use object::{Object, ObjectSymbol, SymbolScope};
@ -23,6 +23,7 @@ pub struct Args {
#[argp(subcommand)] #[argp(subcommand)]
enum SubCommand { enum SubCommand {
Create(CreateArgs), Create(CreateArgs),
Extract(ExtractArgs),
} }
#[derive(FromArgs, PartialEq, Eq, Debug)] #[derive(FromArgs, PartialEq, Eq, Debug)]
@ -37,9 +38,28 @@ pub struct CreateArgs {
files: Vec<PathBuf>, files: Vec<PathBuf>,
} }
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Extracts a static library.
#[argp(subcommand, name = "extract")]
pub struct ExtractArgs {
#[argp(positional)]
/// input files
files: Vec<PathBuf>,
#[argp(option, short = 'o')]
/// output directory
out: Option<PathBuf>,
#[argp(switch, short = 'q')]
/// quiet output
quiet: bool,
#[argp(switch, short = 'v')]
/// verbose output
verbose: bool,
}
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
match args.command { match args.command {
SubCommand::Create(c_args) => create(c_args), 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()?; builder.into_inner()?.flush()?;
Ok(()) 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(())
}