Skip to content

Commit

Permalink
utils: pbo extract, unpack, inspect (#606)
Browse files Browse the repository at this point in the history
* utils: pbo extract, unpack, inspect

* fmt
  • Loading branch information
BrettMayson authored Nov 21, 2023
1 parent 4491221 commit 7dcb671
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 5 deletions.
2 changes: 2 additions & 0 deletions bin/src/commands/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub fn cli() -> Command {
.subcommand_required(false)
.arg_required_else_help(true)
.subcommand(utils::inspect::cli())
.subcommand(utils::pbo::cli())
.subcommand(utils::verify::cli())
}

Expand All @@ -19,6 +20,7 @@ pub fn cli() -> Command {
pub fn execute(matches: &ArgMatches) -> Result<(), Error> {
match matches.subcommand() {
Some(("inspect", matches)) => utils::inspect::execute(matches),
Some(("pbo", matches)) => utils::pbo::execute(matches),
Some(("verify", matches)) => utils::verify::execute(matches),
_ => unreachable!(),
}
Expand Down
13 changes: 10 additions & 3 deletions bin/src/utils/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,23 @@ pub fn bisign(mut file: File, path: &PathBuf) -> Result<BISign, Error> {
}

/// Prints information about a [`ReadablePbo`] to stdout
fn pbo(file: File) -> Result<(), Error> {
///
/// # Errors
/// [`hemtt_pbo::Error`] if the file is not a valid [`ReadablePbo`]
///
/// # Panics
/// If the file is not a valid [`ReadablePbo`]
pub fn pbo(file: File) -> Result<(), Error> {
let mut pbo = ReadablePbo::from(file)?;
println!("Properties");
for (key, value) in pbo.properties() {
println!(" - {key}: {value}");
}
println!("Checksum (SHA1)");
let stored = *pbo.checksum();
println!(" - Stored Hash: {stored:?}");
println!(" - Stored: {}", stored.hex());
let actual = pbo.gen_checksum().unwrap();
println!(" - Actual Hash: {actual:?}");
println!(" - Actual: {}", actual.hex());

let files = pbo.files();
println!("Files");
Expand Down
1 change: 1 addition & 0 deletions bin/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod inspect;
pub mod pbo;
pub mod verify;
44 changes: 44 additions & 0 deletions bin/src/utils/pbo/extract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::{fs::File, path::PathBuf};

use clap::{ArgMatches, Command};
use hemtt_pbo::ReadablePbo;

use crate::Error;

#[must_use]
pub fn cli() -> Command {
Command::new("extract")
.about("Extract a file from a PBO")
.arg(
clap::Arg::new("pbo")
.help("PBO file to extract from")
.required(true),
)
.arg(
clap::Arg::new("file")
.help("File to extract")
.required(true),
)
.arg(clap::Arg::new("output").help("Where to save the extracted file"))
}

/// Execute the extract command
///
/// # Errors
/// [`Error`] depending on the modules
pub fn execute(matches: &ArgMatches) -> Result<(), Error> {
let path = PathBuf::from(matches.get_one::<String>("pbo").expect("required"));
let mut pbo = ReadablePbo::from(File::open(path)?)?;
let file = matches.get_one::<String>("file").expect("required");
let Some(mut file) = pbo.file(file)? else {
error!("File `{file}` not found in PBO");
return Ok(());
};
let output = matches.get_one::<String>("output").map(PathBuf::from);
if let Some(output) = output {
std::io::copy(&mut file, &mut File::create(output)?)?;
} else {
std::io::copy(&mut file, &mut std::io::stdout())?;
}
Ok(())
}
44 changes: 44 additions & 0 deletions bin/src/utils/pbo/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::{fs::File, path::PathBuf};

use clap::{ArgMatches, Command};

use crate::Error;

use super::inspect::pbo;

mod extract;
mod unpack;

#[must_use]
pub fn cli() -> Command {
Command::new("pbo")
.about("Commands for PBO files")
.arg_required_else_help(true)
.subcommand(extract::cli())
.subcommand(unpack::cli())
.subcommand(
Command::new("inspect")
.about("Inspect a PBO")
.arg(clap::Arg::new("pbo").help("PBO to inspect").required(true)),
)
}

/// Execute the pbo command
///
/// # Errors
/// [`Error`] depending on the modules
///
/// # Panics
/// If the args are not present from clap
pub fn execute(matches: &ArgMatches) -> Result<(), Error> {
match matches.subcommand() {
Some(("extract", matches)) => extract::execute(matches),
Some(("unpack", matches)) => unpack::execute(matches),

Some(("inspect", matches)) => pbo(File::open(PathBuf::from(
matches.get_one::<String>("pbo").expect("required"),
))?),

_ => unreachable!(),
}
}
65 changes: 65 additions & 0 deletions bin/src/utils/pbo/unpack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::{
fs::{File, OpenOptions},
io::Write,
path::PathBuf,
};

use clap::{ArgMatches, Command};
use hemtt_pbo::ReadablePbo;

use crate::Error;

#[must_use]
pub fn cli() -> Command {
Command::new("unpack")
.about("Unpack a PBO")
.arg(
clap::Arg::new("pbo")
.help("PBO file to unpack")
.required(true),
)
.arg(
clap::Arg::new("output")
.help("Directory to unpack to")
.required(true),
)
}

/// Execute the unpack command
///
/// # Errors
/// [`Error`] depending on the modules
pub fn execute(matches: &ArgMatches) -> Result<(), Error> {
let path = PathBuf::from(matches.get_one::<String>("pbo").expect("required"));
let mut pbo = ReadablePbo::from(File::open(path)?)?;
let output = PathBuf::from(matches.get_one::<String>("output").expect("required"));
if output.exists() {
error!("Output directory already exists");
return Ok(());
}
std::fs::create_dir_all(&output)?;
for (key, value) in pbo.properties() {
debug!("{}: {}", key, value);
if key == "prefix" {
let mut file = File::create(output.join("$PBOPREFIX$"))?;
file.write_all(value.as_bytes())?;
} else {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.append(true)
.open(output.join("properties.txt"))?;
file.write_all(format!("{key}={value}\n").as_bytes())?;
}
}
for header in pbo.files() {
let path = output.join(header.filename().replace('\\', "/"));
std::fs::create_dir_all(path.parent().unwrap())?;
let mut out = File::create(path)?;
let mut file = pbo
.file(header.filename())?
.expect("file must exist if header exists");
std::io::copy(&mut file, &mut out)?;
}
Ok(())
}
4 changes: 2 additions & 2 deletions bin/src/utils/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ pub fn execute(matches: &ArgMatches) -> Result<(), Error> {
println!();
println!("PBO: {pbo_path:?}");
let stored = *pbo.checksum();
println!(" - Stored Hash: {stored:?}");
println!(" - Stored SHA1 Hash: {}", stored.hex());
let actual = pbo.gen_checksum().unwrap();
println!(" - Actual Hash: {actual:?}");
println!(" - Actual SHA1 Hash: {}", actual.hex());
println!(" - Properties");
for ext in pbo.properties() {
println!(" - {}: {}", ext.0, ext.1);
Expand Down
9 changes: 9 additions & 0 deletions libs/pbo/src/model/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ impl Checksum {
pub const fn as_bytes(&self) -> &[u8; 20] {
&self.0
}

#[must_use]
pub fn hex(&self) -> String {
let mut out = String::new();
for byte in &self.0 {
out.push_str(&format!("{byte:02x}"));
}
out
}
}

impl From<Vec<u8>> for Checksum {
Expand Down

0 comments on commit 7dcb671

Please sign in to comment.