1
0
Fork 0
mirror of https://github.com/xavo95/repak.git synced 2025-03-13 15:07:49 +00:00
repak/repak_cli/src/main.rs

223 lines
6 KiB
Rust
Raw Normal View History

2023-02-01 03:41:36 +00:00
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter};
use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand};
use path_clean::PathClean;
#[derive(Parser, Debug)]
struct ActionInfo {
/// Input .pak path
#[arg(index = 1)]
input: String,
/// Base64 encoded AES encryption key if the pak is encrypted
#[arg(short, long)]
aes_key: Option<String>,
}
#[derive(Parser, Debug)]
struct ActionList {
/// Input .pak path
#[arg(index = 1)]
input: String,
/// Base64 encoded AES encryption key if the pak is encrypted
#[arg(short, long)]
aes_key: Option<String>,
}
#[derive(Parser, Debug)]
struct ActionUnpack {
/// Input .pak path
#[arg(index = 1)]
input: String,
/// Output directory. Defaults to next to input pak
#[arg(index = 2)]
output: Option<String>,
/// Prefix to strip from entry path
#[arg(short, long, default_value = "../../../")]
strip_prefix: String,
/// Base64 encoded AES encryption key if the pak is encrypted
#[arg(short, long)]
aes_key: Option<String>,
2023-02-01 04:55:37 +00:00
/// Verbose
#[arg(short, long, default_value = "false")]
verbose: bool,
2023-02-01 03:41:36 +00:00
}
#[derive(Parser, Debug)]
struct ActionPack {
/// Input directory
#[arg(index = 1)]
input: String,
/// Output directory. Defaults to next to input dir
#[arg(index = 2)]
output: Option<String>,
/// Mount point
#[arg(short, long, default_value = "../../../")]
mount_point: String,
2023-02-01 04:55:37 +00:00
/// Verbose
#[arg(short, long, default_value = "false")]
verbose: bool,
2023-02-01 03:41:36 +00:00
}
#[derive(Subcommand, Debug)]
enum Action {
/// Print .pak info
Info(ActionInfo),
/// List .pak files
List(ActionInfo),
/// Unpack .pak file
Unpack(ActionUnpack),
/// Pack directory into .pak file
Pack(ActionPack),
}
#[derive(Parser, Debug)]
#[command(author, version)]
struct Args {
#[command(subcommand)]
action: Action,
}
2023-02-01 23:44:57 +00:00
fn main() -> Result<(), repak::Error> {
2023-02-01 03:41:36 +00:00
let args = Args::parse();
match args.action {
Action::Info(args) => info(args),
Action::List(args) => list(args),
Action::Unpack(args) => unpack(args),
Action::Pack(args) => pack(args),
2023-01-14 18:32:06 +00:00
}
}
2023-02-01 23:44:57 +00:00
fn aes_key(key: &str) -> Result<aes::Aes256Dec, repak::Error> {
2023-02-01 03:41:36 +00:00
use aes::cipher::KeyInit;
use base64::{engine::general_purpose, Engine as _};
general_purpose::STANDARD
.decode(key)
.as_ref()
2023-02-01 23:44:57 +00:00
.map_err(|_| repak::Error::Base64)
.and_then(|bytes| aes::Aes256Dec::new_from_slice(bytes).map_err(|_| repak::Error::Aes))
2023-02-01 03:41:36 +00:00
}
2023-02-01 23:44:57 +00:00
fn info(args: ActionInfo) -> Result<(), repak::Error> {
let pak = repak::PakReader::new_any(
2023-02-01 03:41:36 +00:00
BufReader::new(File::open(&args.input)?),
args.aes_key.map(|k| aes_key(k.as_str())).transpose()?,
)?;
println!("mount point: {}", pak.mount_point());
println!("version: {}", pak.version());
println!("version major: {}", pak.version().version_major());
println!("{} file entries", pak.files().len());
Ok(())
2023-01-14 18:32:06 +00:00
}
2023-02-01 23:44:57 +00:00
fn list(args: ActionInfo) -> Result<(), repak::Error> {
let pak = repak::PakReader::new_any(
2023-02-01 03:41:36 +00:00
BufReader::new(File::open(&args.input)?),
args.aes_key.map(|k| aes_key(k.as_str())).transpose()?,
)?;
for f in pak.files() {
println!("{f}");
}
Ok(())
}
2023-01-14 18:32:06 +00:00
2023-02-01 23:44:57 +00:00
fn unpack(args: ActionUnpack) -> Result<(), repak::Error> {
let mut pak = repak::PakReader::new_any(
2023-02-01 03:41:36 +00:00
BufReader::new(File::open(&args.input)?),
args.aes_key.map(|k| aes_key(k.as_str())).transpose()?,
)?;
let output = args
.output
.map(PathBuf::from)
.unwrap_or_else(|| Path::new(&args.input).with_extension(""));
match fs::create_dir(&output) {
2023-02-01 03:41:36 +00:00
Ok(_) => Ok(()),
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),
Err(e) => Err(e),
}?;
if output.read_dir()?.next().is_some() {
2023-02-01 23:44:57 +00:00
return Err(repak::Error::Other("output directory not empty"));
2023-02-01 03:41:36 +00:00
}
let mount_point = PathBuf::from(pak.mount_point());
let prefix = Path::new(&args.strip_prefix);
for file in pak.files() {
2023-02-01 04:55:37 +00:00
if args.verbose {
println!("extracting {}", &file);
}
2023-02-01 03:41:36 +00:00
let file_path = output.join(
mount_point
.join(&file)
.strip_prefix(prefix)
2023-02-01 23:44:57 +00:00
.map_err(|_| repak::Error::Other("prefix does not match"))?,
2023-02-01 03:41:36 +00:00
);
if !file_path.clean().starts_with(&output) {
2023-02-01 23:44:57 +00:00
return Err(repak::Error::Other(
2023-02-01 03:41:36 +00:00
"tried to write file outside of output directory",
));
}
fs::create_dir_all(file_path.parent().expect("will be a file"))?;
pak.read_file(&file, &mut fs::File::create(file_path)?)?;
}
Ok(())
}
2023-02-01 23:44:57 +00:00
fn pack(args: ActionPack) -> Result<(), repak::Error> {
2023-02-01 03:41:36 +00:00
let output = args
.output
.map(PathBuf::from)
.unwrap_or_else(|| Path::new(&args.input).with_extension("pak"));
fn collect_files(paths: &mut Vec<PathBuf>, dir: &Path) -> io::Result<()> {
2023-02-01 05:14:39 +00:00
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_files(paths, &path)?;
} else {
paths.push(entry.path());
2023-02-01 03:41:36 +00:00
}
}
Ok(())
}
let input_path = Path::new(&args.input);
2023-02-01 05:14:39 +00:00
if !input_path.is_dir() {
2023-02-01 23:44:57 +00:00
return Err(repak::Error::Other("input is not a directory"));
2023-02-01 05:14:39 +00:00
}
2023-02-01 03:41:36 +00:00
let mut paths = vec![];
collect_files(&mut paths, input_path)?;
paths.sort();
2023-02-01 23:44:57 +00:00
let mut pak = repak::PakWriter::new(
2023-02-01 03:41:36 +00:00
BufWriter::new(File::create(output)?),
None,
2023-02-01 23:44:57 +00:00
repak::Version::V8B,
2023-02-01 03:41:36 +00:00
args.mount_point,
);
for p in paths {
2023-02-01 04:55:37 +00:00
let rel = &p
.strip_prefix(input_path)
.expect("file not in input directory")
.to_string_lossy();
if args.verbose {
println!("packing {}", &rel);
}
2023-02-01 05:14:52 +00:00
pak.write_file(rel, &mut BufReader::new(File::open(&p)?))?;
2023-02-01 03:41:36 +00:00
}
pak.write_index()?;
Ok(())
}