mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 19:04:07 +00:00
Rewrite unpak command
This commit is contained in:
parent
8d009c784e
commit
953be3726c
6 changed files with 207 additions and 84 deletions
|
@ -21,4 +21,6 @@ sha1 = "0.10.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
|
clap = { version = "4.1.4", features = ["derive"] }
|
||||||
paste = "1.0.11"
|
paste = "1.0.11"
|
||||||
|
path-clean = "0.1.0"
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
pub fn list(path: String, key: Option<String>) -> Result<(), unpak::Error> {
|
|
||||||
for file in super::load_pak(path, key)?.files() {
|
|
||||||
println!("{file}");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
mod list;
|
|
||||||
mod unpack;
|
|
||||||
mod version;
|
|
||||||
pub use {list::list, unpack::unpack, version::version};
|
|
||||||
|
|
||||||
fn load_pak(
|
|
||||||
path: String,
|
|
||||||
key: Option<String>,
|
|
||||||
) -> Result<unpak::PakReader<std::io::BufReader<std::fs::File>>, unpak::Error> {
|
|
||||||
use aes::cipher::KeyInit;
|
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
|
||||||
let key = key
|
|
||||||
.map(|k| {
|
|
||||||
general_purpose::STANDARD
|
|
||||||
.decode(k)
|
|
||||||
.as_ref()
|
|
||||||
.map_err(|_| unpak::Error::Base64)
|
|
||||||
.and_then(|bytes| {
|
|
||||||
aes::Aes256Dec::new_from_slice(bytes).map_err(|_| unpak::Error::Aes)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
unpak::PakReader::new_any(
|
|
||||||
std::io::BufReader::new(std::fs::OpenOptions::new().read(true).open(&path)?),
|
|
||||||
key,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
pub fn unpack(path: String, key: Option<String>) -> Result<(), unpak::Error> {
|
|
||||||
let folder = std::path::Path::new(
|
|
||||||
std::path::Path::new(&path)
|
|
||||||
.file_stem()
|
|
||||||
.and_then(|name| name.to_str())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
let mut pak = super::load_pak(path.clone(), key)?;
|
|
||||||
for file in pak.files() {
|
|
||||||
std::fs::create_dir_all(folder.join(&file).parent().expect("will be a file"))?;
|
|
||||||
match pak.read_file(&file, &mut std::fs::File::create(folder.join(&file))?) {
|
|
||||||
Ok(_) => println!("{file}"),
|
|
||||||
Err(e) => eprintln!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
pub fn version(path: String, key: Option<String>) -> Result<(), unpak::Error> {
|
|
||||||
println!("{}", super::load_pak(path, key)?.version());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,36 +1,212 @@
|
||||||
mod subcommands;
|
use std::fs::{self, File};
|
||||||
|
use std::io::{self, BufReader, BufWriter};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
fn main() {
|
use clap::{Parser, Subcommand};
|
||||||
let mut args = std::env::args();
|
use path_clean::PathClean;
|
||||||
args.next();
|
|
||||||
let Some(path) = args.next() else {
|
#[derive(Parser, Debug)]
|
||||||
help()
|
struct ActionInfo {
|
||||||
};
|
/// Input .pak path
|
||||||
// can't map key to &[u8] because refers to owned data
|
#[arg(index = 1)]
|
||||||
if let Err(e) = match args.next().unwrap_or_default().as_str() {
|
input: String,
|
||||||
"version" => subcommands::version(path, args.next()),
|
|
||||||
"list" => subcommands::list(path, args.next()),
|
/// Base64 encoded AES encryption key if the pak is encrypted
|
||||||
"unpack" | "" => subcommands::unpack(path, args.next()),
|
#[arg(short, long)]
|
||||||
"help" | _ => help(),
|
aes_key: Option<String>,
|
||||||
} {
|
}
|
||||||
eprintln!("{e}")
|
|
||||||
|
#[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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), unpak::Error> {
|
||||||
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help() -> ! {
|
fn aes_key(key: &str) -> Result<aes::Aes256Dec, unpak::Error> {
|
||||||
println!("{HELP}");
|
use aes::cipher::KeyInit;
|
||||||
std::process::exit(0)
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
general_purpose::STANDARD
|
||||||
|
.decode(key)
|
||||||
|
.as_ref()
|
||||||
|
.map_err(|_| unpak::Error::Base64)
|
||||||
|
.and_then(|bytes| aes::Aes256Dec::new_from_slice(bytes).map_err(|_| unpak::Error::Aes))
|
||||||
}
|
}
|
||||||
|
|
||||||
const HELP: &str = r"
|
fn info(args: ActionInfo) -> Result<(), unpak::Error> {
|
||||||
usage:
|
let pak = unpak::PakReader::new_any(
|
||||||
unpak <file> <subcommand> <optional AES key>
|
BufReader::new(File::open(&args.input)?),
|
||||||
OR
|
args.aes_key.map(|k| aes_key(k.as_str())).transpose()?,
|
||||||
drag the file onto the executable
|
)?;
|
||||||
|
println!("mount point: {}", pak.mount_point());
|
||||||
|
println!("version: {}", pak.version());
|
||||||
|
println!("version major: {}", pak.version().version_major());
|
||||||
|
println!("{} file entries", pak.files().len());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
subcommands:
|
fn list(args: ActionInfo) -> Result<(), unpak::Error> {
|
||||||
help - show this message
|
let pak = unpak::PakReader::new_any(
|
||||||
unpack - decompress the pak
|
BufReader::new(File::open(&args.input)?),
|
||||||
list - print the files in the pak
|
args.aes_key.map(|k| aes_key(k.as_str())).transpose()?,
|
||||||
version - print the version of the pak
|
)?;
|
||||||
";
|
for f in pak.files() {
|
||||||
|
println!("{f}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack(args: ActionUnpack) -> Result<(), unpak::Error> {
|
||||||
|
let mut pak = unpak::PakReader::new_any(
|
||||||
|
BufReader::new(File::open(&args.input)?),
|
||||||
|
args.aes_key.map(|k| aes_key(k.as_str())).transpose()?,
|
||||||
|
)?;
|
||||||
|
let output = args.output.as_ref().map(Path::new).unwrap_or_else(|| {
|
||||||
|
Path::new(
|
||||||
|
Path::new(&args.input)
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.expect("could not get pak file name"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
match fs::create_dir(output) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}?;
|
||||||
|
if output.read_dir()?.next().is_some() {
|
||||||
|
return Err(unpak::Error::Other("output directory not empty"));
|
||||||
|
}
|
||||||
|
let mount_point = PathBuf::from(pak.mount_point());
|
||||||
|
let prefix = Path::new(&args.strip_prefix);
|
||||||
|
for file in pak.files() {
|
||||||
|
let file_path = output.join(
|
||||||
|
mount_point
|
||||||
|
.join(&file)
|
||||||
|
.strip_prefix(prefix)
|
||||||
|
.map_err(|_| unpak::Error::Other("prefix does not match"))?,
|
||||||
|
);
|
||||||
|
if !file_path.clean().starts_with(output) {
|
||||||
|
return Err(unpak::Error::Other(
|
||||||
|
"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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack(args: ActionPack) -> Result<(), unpak::Error> {
|
||||||
|
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<()> {
|
||||||
|
if dir.is_dir() {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
let input_path = Path::new(&args.input);
|
||||||
|
let mut paths = vec![];
|
||||||
|
collect_files(&mut paths, input_path)?;
|
||||||
|
paths.sort();
|
||||||
|
|
||||||
|
let mut pak = unpak::PakWriter::new(
|
||||||
|
BufWriter::new(File::create(output)?),
|
||||||
|
None,
|
||||||
|
unpak::Version::V8B,
|
||||||
|
args.mount_point,
|
||||||
|
);
|
||||||
|
|
||||||
|
for p in paths {
|
||||||
|
pak.write_file(
|
||||||
|
&p.strip_prefix(input_path)
|
||||||
|
.expect("file not in input directory")
|
||||||
|
.to_string_lossy(),
|
||||||
|
&mut BufReader::new(File::open(&p)?),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pak.write_index()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue