From 992144801b82f0e9781d3369853fa25dbc310c0a Mon Sep 17 00:00:00 2001 From: dece Date: Sat, 9 May 2020 20:21:36 +0200 Subject: [PATCH] bhf: WIP --- src/bin/ironring.rs | 6 +++ src/lib.rs | 2 + src/parsers/bhf.rs | 120 +++++++++++++++++++++++++++++++++++++++++++ src/parsers/bnd.rs | 34 +++++++----- src/unpackers/bhf.rs | 58 +++++++++++++++++++++ 5 files changed, 207 insertions(+), 13 deletions(-) create mode 100644 src/parsers/bhf.rs create mode 100644 src/unpackers/bhf.rs diff --git a/src/bin/ironring.rs b/src/bin/ironring.rs index 7086b33..3993691 100644 --- a/src/bin/ironring.rs +++ b/src/bin/ironring.rs @@ -212,3 +212,9 @@ fn cmd_bnd(args: &ArgMatches) -> i32 { _ => { 0 } } } + +fn cmd_bhf(args: &ArgMatches) -> i32 { + let file_path: &str = args.value_of("file").unwrap(); + let output_path: &str = args.value_of("output").unwrap(); + 0 +} diff --git a/src/lib.rs b/src/lib.rs index 9b2c894..2480aca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,14 @@ pub mod name_hashes; pub mod parsers { pub mod bhd; + pub mod bhf; pub mod bnd; pub mod common; pub mod dcx; } pub mod unpackers { pub mod bhd; + pub mod bhf; pub mod bnd; pub mod dcx; pub mod errors; diff --git a/src/parsers/bhf.rs b/src/parsers/bhf.rs new file mode 100644 index 0000000..18ab7e6 --- /dev/null +++ b/src/parsers/bhf.rs @@ -0,0 +1,120 @@ +use nom::IResult; +use nom::bytes::complete::{tag, take}; +use nom::multi::count; +use nom::number::complete::*; +use nom::sequence::tuple; + +use crate::parsers::bnd::{BinderOptions, format, use_be}; +use crate::parsers::common::{sjis_to_string, take_cstring}; + +#[derive(Debug)] +pub struct BhfHeader { + pub magic: Vec, + pub version: Vec, + pub raw_format: u8, + pub endianness: u8, // Unsure if byte or bit endianness or both. + pub unk0E: u8, + pub unk0F: u8, + pub num_files: u32, + pub unk14: u32, + pub unk18: u32, + pub unk1C: u32, +} + +impl BinderOptions for BhfHeader { + /// See `parsers::bnd::format` function. + fn format(&self) -> u8 { format(self.endianness, self.raw_format) } + + /// See `parsers::bnd::use_be` function. + fn use_be(&self) -> bool { use_be(self.endianness, self.format()) } +} + +fn parse_header(i: &[u8]) -> IResult<&[u8], BhfHeader> { + let (i, (magic, version, raw_format, endianness, u8_unks)) = + tuple((tag(b"BHF3"), take(8usize), le_u8, le_u8, count(le_u8, 2)))(i)?; + let format = format(endianness, raw_format); + let u32_parser = if use_be(endianness, format) { be_u32 } else { le_u32 }; + let (i, (num_files, last_unks)) = + tuple((u32_parser, count(u32_parser, 3)))(i)?; + Ok(( + i, + BhfHeader { + magic: magic.to_vec(), + version: version.to_vec(), + raw_format, + endianness, + unk0E: u8_unks[0], + unk0F: u8_unks[1], + num_files, + unk14: last_unks[0], + unk18: last_unks[1], + unk1C: last_unks[2], + } + )) +} + +#[derive(Debug)] +pub struct BhfFileInfo { + pub unk00: u8, + pub unk01: u8, + pub unk02: u8, + pub unk03: u8, + pub size: u32, + pub ofs_data: u32, + pub id: u32, + pub ofs_path: u32, + pub uncompressed_size: u32, + + pub path: Option, +} + +fn parse_file_info<'a>(i: &'a[u8], header: &BhfHeader) -> IResult<&'a[u8], BhfFileInfo> { + let u32_parser = if header.use_be() { be_u32 } else { le_u32 }; + let (i, (u8_unks, size, ofs_data)) = tuple((count(le_u8, 4), u32_parser, u32_parser))(i)?; + + let (i, id) = if header.has_ids() { u32_parser(i)? } else { (i, 0) }; + let (i, ofs_path) = if header.has_paths() { u32_parser(i)? } else { (i, 0) }; + let (i, uncompressed_size) = if header.has_uncomp_size() { u32_parser(i)? } else { (i, 0) }; + + Ok(( + i, + BhfFileInfo { + unk00: u8_unks[0], + unk01: u8_unks[1], + unk02: u8_unks[2], + unk03: u8_unks[3], + size, + ofs_data, + id, + ofs_path, + uncompressed_size, + path: None, + } + )) +} + +#[derive(Debug)] +pub struct Bhf { + pub header: BhfHeader, + pub file_infos: Vec, +} + +pub fn parse(i: &[u8]) -> IResult<&[u8], Bhf> { + let full_file = i; + let (i, header) = parse_header(i)?; + let (_, mut file_infos) = count( + |i| parse_file_info(i, &header), + header.num_files as usize + )(i)?; + if header.has_paths() { + for info in &mut file_infos { + let ofs_path = info.ofs_path as usize; + let (_, sjis_path) = take_cstring(&full_file[ofs_path..])?; + info.path = sjis_to_string(sjis_path); + if info.path.is_none() { + eprintln!("Failed to parse path: {:?}", sjis_path); + } + } + } + Ok((full_file, Bhf { header, file_infos })) +} diff --git a/src/parsers/bnd.rs b/src/parsers/bnd.rs index d9915b7..59bbb74 100644 --- a/src/parsers/bnd.rs +++ b/src/parsers/bnd.rs @@ -7,6 +7,7 @@ use nom::sequence::tuple; use crate::utils::bin::has_flag; use crate::parsers::common::{sjis_to_string, take_cstring}; +const FORMAT_BE: u8 = 0b00000001; const FORMAT_HAS_ID: u8 = 0b00000010; const FORMAT_HAS_NAME1: u8 = 0b00000100; const FORMAT_HAS_NAME2: u8 = 0b00001000; @@ -26,40 +27,47 @@ pub struct BndHeader { pub unk1C: u32, } -impl BndHeader { - /// Return format u8 with varying endianness managed. - pub fn format(&self) -> u8 { format(self.bit_endianness, self.raw_format) } - - /// Return whether parsing byte order is big endian or not. - pub fn use_be(&self) -> bool { use_be(self.endianness, self.format()) } +pub trait BinderOptions { + fn format(&self) -> u8; + fn use_be(&self) -> bool; /// Return whether files have IDs. - pub fn has_ids(&self) -> bool { + fn has_ids(&self) -> bool { has_flag(self.format(), FORMAT_HAS_ID) } /// Return whether files have paths. - pub fn has_paths(&self) -> bool { + fn has_paths(&self) -> bool { let format = self.format(); has_flag(format, FORMAT_HAS_NAME1) || has_flag(format, FORMAT_HAS_NAME2) } /// Return whether files have uncompressed size. - pub fn has_uncomp_size(&self) -> bool { + fn has_uncomp_size(&self) -> bool { has_flag(self.format(), FORMAT_HAS_UNCOMP_SIZE) } } -fn format(bit_en: u8, raw_format: u8) -> u8 { - if bit_en == 1 || has_flag(raw_format, 0x1) && !has_flag(raw_format, 0x80) { +impl BinderOptions for BndHeader { + /// See `format` function. + fn format(&self) -> u8 { format(self.bit_endianness, self.raw_format) } + + /// See `use_be` function. + fn use_be(&self) -> bool { use_be(self.endianness, self.format()) } +} + +/// Return format u8 with varying endianness managed. +pub fn format(bit_en: u8, raw_format: u8) -> u8 { + if bit_en == 1 || has_flag(raw_format, FORMAT_BE) && !has_flag(raw_format, 0x80) { raw_format } else { raw_format.reverse_bits() } } -fn use_be(en: u8, format: u8) -> bool { - en == 1 || has_flag(format, 0x1) +/// Return whether parsing byte order is big endian or not. +pub fn use_be(en: u8, format: u8) -> bool { + en == 1 || has_flag(format, FORMAT_BE) } fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> { diff --git a/src/unpackers/bhf.rs b/src/unpackers/bhf.rs new file mode 100644 index 0000000..583cb34 --- /dev/null +++ b/src/unpackers/bhf.rs @@ -0,0 +1,58 @@ +use std::fs; +use std::io::Read; +use std::path; + +use nom::Err::{Error as NomError, Failure as NomFailure}; + +use crate::parsers::bhf; +use crate::unpackers::errors::UnpackError; + +pub fn extract_bhf(bhf_path: &str) -> Result<(), UnpackError> { + let mut bhf_file = fs::File::open(bhf_path)?; + let file_len = bhf_file.metadata()?.len() as usize; + let mut bhf_data = vec![0u8; file_len]; + bhf_file.read_exact(&mut bhf_data)?; + let bhf = match bhf::parse(&bhf_data) { + Ok((_, bhf)) => { bhf } + Err(NomError(e)) | Err(NomFailure(e)) => return Err(UnpackError::parsing_err("BHF", e.1)), + e => return Err(UnpackError::Unknown(format!("Unknown error: {:?}", e))), + }; + + let bdt_path = get_bdt_for_bhf(bhf_path); + if bdt_path.is_none() { + return Err(UnpackError::Naming(format!("Can't find BDT for BHF: {}", bhf_path))) + } + Ok(()) +} + +fn get_bdt_for_bhf(bhf_path: &str) -> Option { + let mut path = path::PathBuf::from(bhf_path); + let ext = path.extension()?.to_str()?; + if !ext.ends_with("bhd") { + eprintln!("BHF extension does not end with bhd: {}", ext); + } + let bdtext = String::from(ext.trim_end_matches("bhd")) + "bdt"; + path.set_extension(bdtext); + Some(path) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_bdt_for_bhf() { + assert_eq!( + get_bdt_for_bhf("/map/m10/GI_Env_m10.tpfbhd").unwrap(), + path::PathBuf::from("/map/m10/GI_Env_m10.tpfbdt") + ); + // Weird case but why not. + assert_eq!( + get_bdt_for_bhf("/map/m10/GI_Env_m10.xxx").unwrap(), + path::PathBuf::from("/map/m10/GI_Env_m10.xxxbdt") + ); + + assert!(get_bdt_for_bhf("").is_none()); + assert!(get_bdt_for_bhf("/map/m10/GI_Env_m10").is_none()); + } +}