bhd: unpack one or several BHD/BDT at once
This commit is contained in:
parent
ce85e8e898
commit
353d5e6651
127
src/main.rs
127
src/main.rs
|
@ -1,19 +1,20 @@
|
||||||
use std::env::current_exe;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs;
|
||||||
use std::io::{Error, Read};
|
use std::path;
|
||||||
use std::path::{Path, PathBuf};
|
use std::process;
|
||||||
|
|
||||||
//extern crate clap;
|
|
||||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
//extern crate nom;
|
|
||||||
use nom::Err::{Error as NomError, Failure as NomFailure};
|
|
||||||
|
|
||||||
mod name_hashes;
|
mod name_hashes;
|
||||||
mod parsers {
|
mod parsers {
|
||||||
pub mod bhd;
|
pub mod bhd;
|
||||||
}
|
}
|
||||||
use parsers::*;
|
mod unpackers {
|
||||||
|
pub mod bhd;
|
||||||
|
}
|
||||||
|
mod utils {
|
||||||
|
pub mod fs;
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let default_namefilepath: &str = &get_default_namefilepath();
|
let default_namefilepath: &str = &get_default_namefilepath();
|
||||||
|
@ -35,49 +36,87 @@ fn main() {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(false)
|
.required(false)
|
||||||
.default_value(default_namefilepath)))
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
match matches.subcommand() {
|
process::exit(match matches.subcommand() {
|
||||||
("bhd", Some(s)) => { cmd_bhd(s).unwrap(); }
|
("bhd", Some(s)) => { cmd_bhd(s) }
|
||||||
_ => {}
|
("bhds", Some(s)) => { cmd_bhds(s) }
|
||||||
}
|
_ => { 0 }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_default_namefilepath() -> String {
|
fn get_default_namefilepath() -> String {
|
||||||
let programpath: PathBuf = current_exe().unwrap();
|
let program_path: path::PathBuf = env::current_exe().unwrap();
|
||||||
let programdir: &Path = programpath.parent().unwrap();
|
let program_dir: &path::Path = program_path.parent().unwrap();
|
||||||
let mut namefilepath: PathBuf = PathBuf::from(programdir);
|
let mut namefile_path: path::PathBuf = path::PathBuf::from(program_dir);
|
||||||
namefilepath.push("res/namefile.json");
|
namefile_path.push("res/namefile.json");
|
||||||
String::from(namefilepath.to_str().unwrap())
|
String::from(namefile_path.to_str().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_bhd(args: &ArgMatches) -> Result::<(), Error> {
|
fn cmd_bhd(args: &ArgMatches) -> i32 {
|
||||||
let filepath: &str = args.value_of("file").unwrap();
|
let file_path: &str = args.value_of("file").unwrap();
|
||||||
let outputpath: &str = args.value_of("output").unwrap();
|
let output_path: &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)?;
|
|
||||||
|
|
||||||
let bhd = match bhd::parse(&bhd_data) {
|
let namefile_path: &str = args.value_of("namefile").unwrap();
|
||||||
Ok((_, bhd)) => { bhd }
|
let names = match name_hashes::load_name_map(namefile_path) {
|
||||||
Err(NomError(e)) | Err(NomFailure(e)) => {
|
Ok(n) => { n }
|
||||||
let (_, kind) = e;
|
Err(e) => { eprintln!("Failed to load namefile: {:?}", e); return 1 }
|
||||||
let reason = format!("{:?} {:?}", kind, kind.description());
|
|
||||||
eprintln!("BHD parsing failed: {}", reason); return Ok(())
|
|
||||||
}
|
|
||||||
e => {
|
|
||||||
eprintln!("Unknown error: {:?}", e); return Ok(())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 }
|
||||||
let bdt_filepath = PathBuf::from(filepath).with_extension("bdt");
|
_ => { 0 }
|
||||||
let bdt_file = File::open(bdt_filepath.to_str().unwrap())?;
|
}
|
||||||
|
}
|
||||||
bhd::extract(&bhd, &bdt_file, &names, &outputpath);
|
|
||||||
|
fn cmd_bhds(args: &ArgMatches) -> i32 {
|
||||||
Ok(())
|
let folder_path: &str = args.value_of("folder").unwrap();
|
||||||
|
let output_path: &str = args.value_of("output").unwrap();
|
||||||
|
|
||||||
|
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 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,11 @@ use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader, Error};
|
use std::io::{BufRead, BufReader, Error};
|
||||||
|
|
||||||
extern crate num_bigint;
|
|
||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
extern crate num_traits;
|
|
||||||
use num_traits::identities::Zero;
|
use num_traits::identities::Zero;
|
||||||
|
|
||||||
/// Compute the weird hash for a string. Same mechanic since DeS.
|
/// Compute the weird hash for a string. Same mechanic since DeS.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn hash(s: &str) -> u32 {
|
pub fn hash(s: &str) -> u32 {
|
||||||
let s = s.to_lowercase();
|
let s = s.to_lowercase();
|
||||||
let mut val = BigUint::zero();
|
let mut val = BigUint::zero();
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
extern crate nom;
|
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::count;
|
use nom::multi::count;
|
||||||
use nom::number::complete::*;
|
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<String, String>,
|
|
||||||
outputpath: &str,
|
|
||||||
) -> Result<(), io::Error> {
|
|
||||||
fs::create_dir(outputpath)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
75
src/unpackers/bhd.rs
Normal file
75
src/unpackers/bhd.rs
Normal file
|
@ -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<String, String>,
|
||||||
|
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<String, String>,
|
||||||
|
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(())
|
||||||
|
}
|
14
src/utils/fs.rs
Normal file
14
src/utils/fs.rs
Normal file
|
@ -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(())
|
||||||
|
}
|
Reference in a new issue