mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 23:32:55 -05:00
163 lines
5.3 KiB
Rust
163 lines
5.3 KiB
Rust
use clap::{
|
|
error::{Error, ErrorKind},
|
|
Parser, Subcommand,
|
|
};
|
|
use luca_pak::Pak;
|
|
use std::{fs, path::PathBuf};
|
|
|
|
/// Utility to maniuplate PAK archive files from the LUCA System game engine by
|
|
/// Prototype Ltd.
|
|
#[derive(Parser)]
|
|
#[command(name = "PAK Utility")]
|
|
#[command(version, about, long_about = None)]
|
|
struct Cli {
|
|
#[arg(value_name = "PAK FILE")]
|
|
input: PathBuf,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Extracts the contents of a PAK file into a folder.
|
|
Extract {
|
|
/// Output folder for PAK contents
|
|
#[arg(value_name = "OUTPUT FOLDER")]
|
|
output: PathBuf,
|
|
},
|
|
|
|
/// Replace the entries in a PAK file
|
|
Replace {
|
|
/// Replace a whole folder, and output to another folder,
|
|
/// using a folder of replacements
|
|
#[arg(short, long)]
|
|
batch: bool,
|
|
|
|
/// The name of the file within the PAK you wish to replace.
|
|
/// If not provided, the filename will be used.
|
|
/// Incompatible with batch mode, and ID.
|
|
#[arg(short, long)]
|
|
name: Option<String>,
|
|
|
|
/// The ID of the file within the PAK you wish to replace.
|
|
/// If not provided, the filename will be used.
|
|
/// Incompatible with batch mode, and name.
|
|
#[arg(short, long)]
|
|
id: Option<u32>,
|
|
|
|
/// File or folder to use as a replacement
|
|
#[arg(value_name = "REPLACEMENT")]
|
|
replacement: PathBuf,
|
|
|
|
/// Output PAK file location
|
|
#[arg(value_name = "OUTPUT PATH")]
|
|
output: PathBuf,
|
|
},
|
|
}
|
|
|
|
fn main() {
|
|
let cli = Cli::parse();
|
|
|
|
let mut pak = match Pak::open(&cli.input) {
|
|
Ok(pak) => pak,
|
|
Err(err) => fmt_error(&format!("Could not open PAK file: {}", err)).exit(),
|
|
};
|
|
|
|
match cli.command {
|
|
Commands::Extract { output } => {
|
|
if output.exists() && !output.is_dir() {
|
|
fmt_error("The output given was not a directory").exit()
|
|
} else if !output.exists() {
|
|
fs::create_dir(&output).unwrap();
|
|
}
|
|
|
|
for entry in pak.entries() {
|
|
let mut outpath = output.clone();
|
|
outpath.push(entry.display_name());
|
|
entry.save(&outpath).unwrap();
|
|
}
|
|
}
|
|
Commands::Replace {
|
|
batch,
|
|
name,
|
|
id,
|
|
replacement,
|
|
output,
|
|
} => {
|
|
if id.is_some() && name.is_some() {
|
|
fmt_error("Cannot use ID and name together").exit()
|
|
}
|
|
if batch {
|
|
if name.is_some() || id.is_some() {
|
|
fmt_error("Cannot use name or ID with batch").exit()
|
|
}
|
|
|
|
if !replacement.is_dir() {
|
|
fmt_error("Batch replacement must be a directory").exit()
|
|
}
|
|
|
|
for entry in fs::read_dir(replacement).unwrap() {
|
|
let entry = entry.unwrap();
|
|
let search_name: String =
|
|
entry.path().file_name().unwrap().to_string_lossy().into();
|
|
|
|
let parsed_id: Option<u32> = search_name.parse().ok();
|
|
|
|
// Read in the replacement file to a vec
|
|
let rep_data: Vec<u8> = std::fs::read(entry.path()).unwrap();
|
|
|
|
// Try replacing by name, if that fails, replace by parsed ID
|
|
if pak.replace_by_name(search_name, &rep_data).is_err() {
|
|
fmt_error("Could not replace entry in PAK: Could not find name")
|
|
.print()
|
|
.unwrap()
|
|
} else if parsed_id.is_some()
|
|
&& pak.replace_by_id(parsed_id.unwrap(), &rep_data).is_err()
|
|
{
|
|
fmt_error("Could not replace entry in PAK: ID is invalid")
|
|
.print()
|
|
.unwrap()
|
|
}
|
|
}
|
|
} else {
|
|
if !replacement.is_file() {
|
|
fmt_error("Replacement input must be a file").exit()
|
|
}
|
|
|
|
let search_name = if let Some(name) = name {
|
|
name
|
|
} else {
|
|
replacement.file_name().unwrap().to_string_lossy().into()
|
|
};
|
|
|
|
let search_id = if id.is_some() {
|
|
id
|
|
} else if let Ok(id) = search_name.parse::<u32>() {
|
|
Some(id)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Read in the replacement file to a vec
|
|
let rep_data: Vec<u8> = std::fs::read(replacement).unwrap();
|
|
if id.is_some() {
|
|
if pak.replace_by_id(search_id.unwrap(), &rep_data).is_err() {
|
|
fmt_error("Could not replace entry in PAK: ID is invalid").exit()
|
|
}
|
|
} else if pak.replace_by_name(search_name, &rep_data).is_err() {
|
|
fmt_error("Could not replace entry in PAK: Could not find name").exit()
|
|
}
|
|
|
|
pak.save(&output).unwrap();
|
|
}
|
|
|
|
pak.save(&output).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn fmt_error(message: &str) -> Error {
|
|
Error::raw(ErrorKind::ValueValidation, format!("{}\n", message))
|
|
}
|