Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

utils: pbo extract, unpack, inspect #606

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading