use std::{fs::File, io::Write as _, path::PathBuf};
use anyhow::Result;
use clap::{Parser, Subcommand};
use psarc2::{PlaystationArchive, read::Config, toc::DecryptionKey};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, propagate_version = true)]
struct Cli {
path: PathBuf,
#[arg(global(true), value_parser = parse_hex_arg::<32>, short = 'k', long, requires("decryption_iv"))]
decryption_key: Option<[u8; 32]>,
#[arg(global(true), value_parser = parse_hex_arg::<16>, short = 'i', long, requires("decryption_key"))]
decryption_iv: Option<[u8; 16]>,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
List,
ExtractAll {
target: PathBuf,
},
Extract {
path: String,
target: PathBuf,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
let file = File::open(cli.path)?;
let config = Config {
decryption_key: cli
.decryption_key
.zip(cli.decryption_iv)
.map(|(key, iv)| DecryptionKey { key, iv }),
};
let mut archive = PlaystationArchive::with_config(config, file)?;
match cli.command {
Commands::List => archive
.paths()
.for_each(|file| println!("{}", file.display())),
Commands::Extract { path, target } => {
let extracted = archive.by_path(&path)?;
let mut target_file = File::create(&target)?;
target_file.write_all(&extracted.into_inner())?;
println!("written to {}", target.display());
}
Commands::ExtractAll { target } => archive.extract(target)?,
}
Ok(())
}
fn parse_hex_arg<const N: usize>(argument: &str) -> Result<[u8; N], String> {
let hex_data = argument.strip_prefix("0x").unwrap_or(argument);
let bytes = hex::decode(hex_data)
.map_err(|error| format!("Invalid hex string '{argument}': {error}"))?;
let len = bytes.len();
bytes
.try_into()
.map_err(|_| format!("Expected {N} bytes ({} hex chars), got {len} bytes", N * 2))
}