From 353d5e6651cf3012d330507b272eeaaca8fe22a8 Mon Sep 17 00:00:00 2001 From: Dece Date: Wed, 15 Apr 2020 02:59:50 +0200 Subject: [PATCH] bhd: unpack one or several BHD/BDT at once --- src/main.rs | 121 ++++++++++++++++++++++++++++--------------- src/name_hashes.rs | 3 +- src/parsers/bhd.rs | 16 ------ src/unpackers/bhd.rs | 75 +++++++++++++++++++++++++++ src/utils/fs.rs | 14 +++++ 5 files changed, 170 insertions(+), 59 deletions(-) create mode 100644 src/unpackers/bhd.rs create mode 100644 src/utils/fs.rs diff --git a/src/main.rs b/src/main.rs index 3b387c2..9b09f61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,20 @@ -use std::env::current_exe; -use std::fs::File; -use std::io::{Error, Read}; -use std::path::{Path, PathBuf}; +use std::env; +use std::fs; +use std::path; +use std::process; -//extern crate clap; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; -//extern crate nom; -use nom::Err::{Error as NomError, Failure as NomFailure}; - mod name_hashes; mod parsers { pub mod bhd; } -use parsers::*; +mod unpackers { + pub mod bhd; +} +mod utils { + pub mod fs; +} fn main() { let default_namefilepath: &str = &get_default_namefilepath(); @@ -35,49 +36,87 @@ fn main() { .takes_value(true) .required(false) .default_value(default_namefilepath))) + .subcommand(SubCommand::with_name("bhds") + .about("Extracts all BHD/BDT content (alphabetically) in a folder") + .arg(Arg::with_name("folder") + .takes_value(true) + .required(true)) + .arg(Arg::with_name("output") + .short("o") + .long("output") + .takes_value(true) + .required(true)) + .arg(Arg::with_name("namefile") + .short("n") + .long("names") + .takes_value(true) + .required(false) + .default_value(default_namefilepath))) .get_matches(); - match matches.subcommand() { - ("bhd", Some(s)) => { cmd_bhd(s).unwrap(); } - _ => {} - } + process::exit(match matches.subcommand() { + ("bhd", Some(s)) => { cmd_bhd(s) } + ("bhds", Some(s)) => { cmd_bhds(s) } + _ => { 0 } + }) } fn get_default_namefilepath() -> String { - let programpath: PathBuf = current_exe().unwrap(); - let programdir: &Path = programpath.parent().unwrap(); - let mut namefilepath: PathBuf = PathBuf::from(programdir); - namefilepath.push("res/namefile.json"); - String::from(namefilepath.to_str().unwrap()) + let program_path: path::PathBuf = env::current_exe().unwrap(); + let program_dir: &path::Path = program_path.parent().unwrap(); + let mut namefile_path: path::PathBuf = path::PathBuf::from(program_dir); + namefile_path.push("res/namefile.json"); + String::from(namefile_path.to_str().unwrap()) } -fn cmd_bhd(args: &ArgMatches) -> Result::<(), Error> { - let filepath: &str = args.value_of("file").unwrap(); - let outputpath: &str = args.value_of("output").unwrap(); - let namefilepath: &str = args.value_of("namefile").unwrap(); - let mut bhd_file = File::open(filepath)?; - let file_len = bhd_file.metadata()?.len() as usize; - let mut bhd_data = vec![0u8; file_len]; - bhd_file.read_exact(&mut bhd_data)?; +fn cmd_bhd(args: &ArgMatches) -> i32 { + let file_path: &str = args.value_of("file").unwrap(); + let output_path: &str = args.value_of("output").unwrap(); - let bhd = match bhd::parse(&bhd_data) { - Ok((_, bhd)) => { bhd } - Err(NomError(e)) | Err(NomFailure(e)) => { - let (_, kind) = e; - let reason = format!("{:?} {:?}", kind, kind.description()); - eprintln!("BHD parsing failed: {}", reason); return Ok(()) - } - e => { - eprintln!("Unknown error: {:?}", e); return Ok(()) - } + let namefile_path: &str = args.value_of("namefile").unwrap(); + let names = match name_hashes::load_name_map(namefile_path) { + Ok(n) => { n } + Err(e) => { eprintln!("Failed to load namefile: {:?}", e); return 1 } }; - let names = name_hashes::load_name_map(&namefilepath)?; + return match unpackers::bhd::extract_bhd(file_path, &names, output_path) { + Err(e) => { eprintln!("Failed to extract BHD: {:?}", e); 1 } + _ => { 0 } + } +} - let bdt_filepath = PathBuf::from(filepath).with_extension("bdt"); - let bdt_file = File::open(bdt_filepath.to_str().unwrap())?; +fn cmd_bhds(args: &ArgMatches) -> i32 { + let folder_path: &str = args.value_of("folder").unwrap(); + let output_path: &str = args.value_of("output").unwrap(); - bhd::extract(&bhd, &bdt_file, &names, &outputpath); + let namefile_path: &str = args.value_of("namefile").unwrap(); + let names = match name_hashes::load_name_map(namefile_path) { + Ok(n) => { n } + Err(e) => { eprintln!("Failed to load namefile: {:?}", e); return 1 } + }; - Ok(()) + let entries = match fs::read_dir(folder_path) { + Ok(o) => { o } + Err(e) => { eprintln!("Cannot read folder content: {:?}", e); return 1 } + }; + let mut bhd_paths = vec!(); + for entry in entries { + if !entry.is_ok() { + continue + } + let path = entry.unwrap().path(); + match path.extension() { + Some(e) => { if e == "bhd5" { bhd_paths.push(path); } } + _ => {} + } + } + bhd_paths.sort(); + + for bhd_path in bhd_paths { + match unpackers::bhd::extract_bhd(bhd_path.to_str().unwrap(), &names, output_path) { + Err(e) => { eprintln!("Failed to extract BHD: {:?}", e); return 1 } + _ => {} + } + } + return 0 } diff --git a/src/name_hashes.rs b/src/name_hashes.rs index 4065eb3..79fa2b5 100644 --- a/src/name_hashes.rs +++ b/src/name_hashes.rs @@ -2,12 +2,11 @@ use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Error}; -extern crate num_bigint; use num_bigint::BigUint; -extern crate num_traits; use num_traits::identities::Zero; /// Compute the weird hash for a string. Same mechanic since DeS. +#[allow(dead_code)] pub fn hash(s: &str) -> u32 { let s = s.to_lowercase(); let mut val = BigUint::zero(); diff --git a/src/parsers/bhd.rs b/src/parsers/bhd.rs index 6786868..ba099bd 100644 --- a/src/parsers/bhd.rs +++ b/src/parsers/bhd.rs @@ -1,8 +1,3 @@ -use std::collections::HashMap; -use std::fs; -use std::io; - -extern crate nom; use nom::combinator::verify; use nom::multi::count; use nom::number::complete::*; @@ -102,14 +97,3 @@ pub fn parse(i: &[u8]) -> IResult<&[u8], Bhd> { }, )) } - -/// Extract files from a BHD/BDT pair. -pub fn extract( - bhd: &Bhd, - bdt_file: &fs::File, - names: &HashMap, - outputpath: &str, -) -> Result<(), io::Error> { - fs::create_dir(outputpath)?; - Ok(()) -} diff --git a/src/unpackers/bhd.rs b/src/unpackers/bhd.rs new file mode 100644 index 0000000..8b2133c --- /dev/null +++ b/src/unpackers/bhd.rs @@ -0,0 +1,75 @@ +use std::collections::HashMap; +use std::fs; +use std::io::{self, Read, Seek, Write}; +use std::path; + +use nom::Err::{Error as NomError, Failure as NomFailure}; + +use crate::name_hashes; +use crate::parsers::bhd; +use crate::utils::fs as fs_utils; + +/// Parse a BHD file and extract its content. +pub fn extract_bhd( + bhd_path: &str, + names: &HashMap, + output_path: &str +) -> Result<(), io::Error> { + let mut bhd_file = fs::File::open(bhd_path)?; + let file_len = bhd_file.metadata()?.len() as usize; + let mut bhd_data = vec![0u8; file_len]; + bhd_file.read_exact(&mut bhd_data)?; + let bhd = match bhd::parse(&bhd_data) { + Ok((_, bhd)) => { bhd } + Err(NomError(e)) | Err(NomFailure(e)) => { + let (_, kind) = e; + let reason = format!("{:?} {:?}", kind, kind.description()); + eprintln!("BHD parsing failed: {}", reason); return Ok(()) + } + e => { + eprintln!("Unknown error: {:?}", e); return Ok(()) + } + }; + + let bdt_path = path::PathBuf::from(bhd_path).with_extension("bdt"); + let mut bdt_file = fs::File::open(bdt_path.to_str().unwrap())?; + + extract_files(&bhd, &mut bdt_file, &names, &output_path)?; + Ok(()) +} + +/// Extract files from a BHD/BDT pair. +pub fn extract_files( + bhd: &bhd::Bhd, + bdt_file: &mut fs::File, + names: &HashMap, + output_path: &str, +) -> Result<(), io::Error> { + let output_path = path::Path::new(output_path); + fs_utils::ensure_dir_exists(output_path)?; + + for bucket in &bhd.buckets { + for entry in bucket { + bdt_file.seek(io::SeekFrom::Start(entry.offset))?; + let mut data = vec![0; entry.size as usize]; + bdt_file.read_exact(&mut data)?; + + let hash_str = name_hashes::hash_as_string(entry.hash); + let rel_path: &str = match names.get(&hash_str) { + Some(path) => { + path.trim_start_matches("/") + } + _ => { + eprintln!("No name for {}, using hash as name.", hash_str); + &hash_str + } + }; + let file_path = output_path.join(rel_path); + fs_utils::ensure_dir_exists(file_path.parent().unwrap())?; + let mut output_file = fs::File::create(file_path)?; + output_file.write_all(&data)?; + } + } + + Ok(()) +} diff --git a/src/utils/fs.rs b/src/utils/fs.rs new file mode 100644 index 0000000..971df9a --- /dev/null +++ b/src/utils/fs.rs @@ -0,0 +1,14 @@ +use std::fs; +use std::io; +use std::path::Path; + +/// Ensure a directory exists, creating it with parents if necessary. +pub fn ensure_dir_exists(path: &Path) -> Result<(), io::Error> { + if !path.is_dir() { + if path.exists() { + return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Not a directory.")); + } + fs::create_dir_all(&path)?; + } + Ok(()) +}