Skip to content

Commit 9a6cb70

Browse files
committed
Add ar extract command
1 parent 4a84975 commit 9a6cb70

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "decomp-toolkit"
33
description = "Yet another GameCube/Wii decompilation toolkit."
44
authors = ["Luke Street <[email protected]>"]
55
license = "MIT OR Apache-2.0"
6-
version = "0.7.2"
6+
version = "0.7.3"
77
edition = "2021"
88
publish = false
99
repository = "https://github.com/encounter/decomp-toolkit"

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ project structure and build system that uses decomp-toolkit under the hood.
2020
- [Analyzer features](#analyzer-features)
2121
- [Commands](#commands)
2222
- [ar create](#ar-create)
23+
- [ar extract](#ar-extract)
2324
- [demangle](#demangle)
2425
- [dol info](#dol-info)
2526
- [dol split](#dol-split)
@@ -276,6 +277,26 @@ $ echo input_2.o >> rspfile
276277
$ dtk ar create out.a @rspfile
277278
```
278279

280+
### ar extract
281+
282+
Extracts the contents of static library (.a) files.
283+
284+
Accepts multiple files, glob patterns (e.g. `*.a`) and response files (e.g. `@rspfile`).
285+
286+
Options:
287+
- `-o`, `--out <output-dir>`: Output directory. Defaults to the current directory.
288+
- `-v`, `--verbose`: Verbose output.
289+
- `-q`, `--quiet`: Suppresses all output except errors.
290+
291+
```shell
292+
# Extracts to outdir
293+
$ dtk ar extract lib.a -o outdir
294+
295+
# With multiple inputs, extracts to separate directories
296+
# Extracts to outdir/lib1, outdir/lib2
297+
$ dtk ar extract lib1.a lib2.a -o outdir
298+
```
299+
279300
### demangle
280301

281302
Demangles CodeWarrior C++ symbols. A thin wrapper for [cwdemangle](https://github.com/encounter/cwdemangle).

src/cmd/ar.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55
path::PathBuf,
66
};
77

8-
use anyhow::{anyhow, bail, Result};
8+
use anyhow::{anyhow, bail, Context, Result};
99
use argp::FromArgs;
1010
use object::{Object, ObjectSymbol, SymbolScope};
1111

@@ -23,6 +23,7 @@ pub struct Args {
2323
#[argp(subcommand)]
2424
enum SubCommand {
2525
Create(CreateArgs),
26+
Extract(ExtractArgs),
2627
}
2728

2829
#[derive(FromArgs, PartialEq, Eq, Debug)]
@@ -37,9 +38,28 @@ pub struct CreateArgs {
3738
files: Vec<PathBuf>,
3839
}
3940

41+
#[derive(FromArgs, PartialEq, Eq, Debug)]
42+
/// Extracts a static library.
43+
#[argp(subcommand, name = "extract")]
44+
pub struct ExtractArgs {
45+
#[argp(positional)]
46+
/// input files
47+
files: Vec<PathBuf>,
48+
#[argp(option, short = 'o')]
49+
/// output directory
50+
out: Option<PathBuf>,
51+
#[argp(switch, short = 'q')]
52+
/// quiet output
53+
quiet: bool,
54+
#[argp(switch, short = 'v')]
55+
/// verbose output
56+
verbose: bool,
57+
}
58+
4059
pub fn run(args: Args) -> Result<()> {
4160
match args.command {
4261
SubCommand::Create(c_args) => create(c_args),
62+
SubCommand::Extract(c_args) => extract(c_args),
4363
}
4464
}
4565

@@ -87,3 +107,51 @@ fn create(args: CreateArgs) -> Result<()> {
87107
builder.into_inner()?.flush()?;
88108
Ok(())
89109
}
110+
111+
fn extract(args: ExtractArgs) -> Result<()> {
112+
// Process response files (starting with '@')
113+
let files = process_rsp(&args.files)?;
114+
115+
// Extract files
116+
let mut num_files = 0;
117+
for path in &files {
118+
let mut out_dir = if let Some(out) = &args.out { out.clone() } else { PathBuf::new() };
119+
// If there are multiple files, extract to separate directories
120+
if files.len() > 1 {
121+
out_dir
122+
.push(path.with_extension("").file_name().ok_or_else(|| anyhow!("No file name"))?);
123+
}
124+
std::fs::create_dir_all(&out_dir)?;
125+
if !args.quiet {
126+
println!("Extracting {} to {}", path.display(), out_dir.display());
127+
}
128+
129+
let file = map_file(path)?;
130+
let mut archive = ar::Archive::new(file.as_slice());
131+
while let Some(entry) = archive.next_entry() {
132+
let mut entry =
133+
entry.with_context(|| format!("Processing entry in {}", path.display()))?;
134+
let file_name = std::str::from_utf8(entry.header().identifier())?;
135+
if !args.quiet && args.verbose {
136+
println!("\t{}", file_name);
137+
}
138+
let mut file_path = out_dir.clone();
139+
for segment in file_name.split(&['/', '\\']) {
140+
file_path.push(sanitise_file_name::sanitise(segment));
141+
}
142+
if let Some(parent) = file_path.parent() {
143+
std::fs::create_dir_all(parent)?;
144+
}
145+
let mut file = File::create(&file_path)
146+
.with_context(|| format!("Failed to create file {}", file_path.display()))?;
147+
std::io::copy(&mut entry, &mut file)?;
148+
file.flush()?;
149+
150+
num_files += 1;
151+
}
152+
}
153+
if !args.quiet {
154+
println!("Extracted {} files", num_files);
155+
}
156+
Ok(())
157+
}

0 commit comments

Comments
 (0)