From 75ff6ddc7f0a88c0a5b97663371b3dc3c399ef54 Mon Sep 17 00:00:00 2001 From: dece Date: Fri, 8 May 2020 17:11:27 +0200 Subject: [PATCH] bnd: extract all entries in a dir Currently it does not preserve internal path dir structure ("N:\\..."). --- src/bin/ironring.rs | 6 ++--- src/parsers/bnd.rs | 8 ++++--- src/unpackers/bnd.rs | 51 +++++++++++++++++++++++++++++++++-------- src/unpackers/errors.rs | 1 + 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/bin/ironring.rs b/src/bin/ironring.rs index 373ad0c..b0cd407 100644 --- a/src/bin/ironring.rs +++ b/src/bin/ironring.rs @@ -9,7 +9,7 @@ use rir::{name_hashes, unpackers, utils}; fn main() { let default_namefilepath: &str = &get_default_namefilepath(); - let matches = App::new("Rusted Iron Ring") + let matches = App::new("Iron Ring") .setting(AppSettings::ArgRequiredElseHelp) .subcommand(SubCommand::with_name("bhd") .about("Extracts BHD/BDT contents") @@ -200,8 +200,8 @@ fn cmd_dcx(args: &ArgMatches) -> i32 { fn cmd_bnd(args: &ArgMatches) -> i32 { let file_path: &str = args.value_of("file").unwrap(); - let _output_path: &str = args.value_of("output").unwrap(); - match unpackers::bnd::load_bnd_file(file_path) { + let output_path: &str = args.value_of("output").unwrap(); + match unpackers::bnd::extract_bnd_file(file_path, output_path) { Err(e) => { eprintln!("Failed to extract BND: {:?}", e); return 1 } _ => { 0 } } diff --git a/src/parsers/bnd.rs b/src/parsers/bnd.rs index 12ae3f0..d9915b7 100644 --- a/src/parsers/bnd.rs +++ b/src/parsers/bnd.rs @@ -1,5 +1,3 @@ -use std::str; - use nom::IResult; use nom::bytes::complete::{tag, take}; use nom::multi::count; @@ -134,10 +132,14 @@ pub struct Bnd { pub file_infos: Vec, } +/// Parse a BND file to a BND struct. +/// +/// On success, returns the full BND data along with the Bnd struct +/// instead of the remaining data. pub fn parse(i: &[u8]) -> IResult<&[u8], Bnd> { let full_file = i; let (i, header) = parse_header(i)?; - let (i, mut file_infos) = count( + let (_, mut file_infos) = count( |i| parse_file_info(i, &header), header.num_files as usize )(i)?; diff --git a/src/unpackers/bnd.rs b/src/unpackers/bnd.rs index 20e2a8c..7c56db2 100644 --- a/src/unpackers/bnd.rs +++ b/src/unpackers/bnd.rs @@ -1,5 +1,5 @@ use std::fs; -use std::io::Read; +use std::io::{Read, Write}; use std::path; use nom::Err::{Error as NomError, Failure as NomFailure}; @@ -11,9 +11,9 @@ use crate::utils::fs as fs_utils; /// Extract BND file contents to disk. /// /// Wraps around `extract_bnd` to load the BND from disk. -pub fn extract_bnd_file(bnd_path: &str, output_path: &str) -> Result<(), UnpackError> { +pub fn extract_bnd_file(bnd_path: &str, output_dir: &str) -> Result<(), UnpackError> { let (bnd, data) = load_bnd_file(bnd_path)?; - extract_bnd(bnd, data, output_path)?; + extract_bnd(&bnd, &data, output_dir)?; Ok(()) } @@ -22,13 +22,43 @@ pub fn extract_bnd_file(bnd_path: &str, output_path: &str) -> Result<(), UnpackE /// Files in the BND are written in the output_path directory, creating it if needed, without /// preserving directory structure. If the BND do not contain paths, it will be named after its ID. /// If it does not have IDs, consecutive integers will be used. -pub fn extract_bnd(bnd: bnd::Bnd, data: Vec, output_path: &str) -> Result<(), UnpackError> { - let output_path = path::Path::new(output_path); - fs_utils::ensure_dir_exists(output_path)?; - for info in &bnd.file_infos { - // TODO +pub fn extract_bnd(bnd: &bnd::Bnd, bnd_data: &Vec, output_dir: &str) -> Result<(), UnpackError> { + let output_dir = path::Path::new(output_dir); + fs_utils::ensure_dir_exists(output_dir)?; + for file_info in &bnd.file_infos { + extract_bnd_entry(file_info, bnd_data, output_dir)?; } - //output_file.write_all(&decomp_data)?; + Ok(()) +} + +/// Extract a file contained in a BND using its BndFileInfo. +/// +/// The info struct must have a valid internal path. +fn extract_bnd_entry( + file_info: &bnd::BndFileInfo, + bnd_data: &Vec, + output_dir: &path::Path +) -> Result<(), UnpackError> { + if file_info.path.is_none() { + return Err(UnpackError::Naming("No path for BND entry.".to_owned())); + } + + let ofs_start = file_info.ofs_data as usize; + let ofs_end = (file_info.ofs_data + file_info.size) as usize; + let data = &bnd_data[ofs_start..ofs_end]; + + let internal_path = file_info.path.to_owned().unwrap(); + // For now, do not keep internal dir structure and use only file name. + let file_name = if let Some(last_sep_index) = internal_path.rfind('\\') { + &internal_path[last_sep_index as usize + 1usize..] + } else { + &internal_path + }; + let mut file_path = output_dir.to_path_buf(); + file_path.push(file_name); + + let mut output_file = fs::File::create(file_path)?; + output_file.write_all(&data)?; Ok(()) } @@ -36,6 +66,7 @@ pub fn extract_bnd(bnd: bnd::Bnd, data: Vec, output_path: &str) -> Result<() /// /// Wraps around `load_bnd` to load the BND from disk. It returns the /// parsed BND metadata and the whole file as a byte vector. +/// Wraps around `load_bnd` to load the BND from disk. pub fn load_bnd_file(bnd_path: &str) -> Result<(bnd::Bnd, Vec), UnpackError> { let mut bnd_file = fs::File::open(bnd_path)?; let file_len = bnd_file.metadata()?.len() as usize; @@ -47,7 +78,7 @@ pub fn load_bnd_file(bnd_path: &str) -> Result<(bnd::Bnd, Vec), UnpackError> /// Load a BND file from a bytes slice. pub fn load_bnd(bnd_data: &[u8]) -> Result { let (_, bnd) = match bnd::parse(bnd_data) { - Ok(result) => { result } + Ok(result) => result, Err(NomError(e)) | Err(NomFailure(e)) => { let reason = unpackers_errors::get_nom_error_reason(e.1); return Err(UnpackError::Parsing("BND parsing failed: ".to_owned() + &reason)) diff --git a/src/unpackers/errors.rs b/src/unpackers/errors.rs index acc7b32..17aaa1e 100644 --- a/src/unpackers/errors.rs +++ b/src/unpackers/errors.rs @@ -5,6 +5,7 @@ pub enum UnpackError { Io(io::Error), Parsing(String), Compression(String), + Naming(String), Unknown(String), }