This commit is contained in:
dece 2020-05-09 20:21:36 +02:00
parent a6c07e1ae4
commit 992144801b
5 changed files with 207 additions and 13 deletions

View file

@ -212,3 +212,9 @@ fn cmd_bnd(args: &ArgMatches) -> i32 {
_ => { 0 } _ => { 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
}

View file

@ -3,12 +3,14 @@
pub mod name_hashes; pub mod name_hashes;
pub mod parsers { pub mod parsers {
pub mod bhd; pub mod bhd;
pub mod bhf;
pub mod bnd; pub mod bnd;
pub mod common; pub mod common;
pub mod dcx; pub mod dcx;
} }
pub mod unpackers { pub mod unpackers {
pub mod bhd; pub mod bhd;
pub mod bhf;
pub mod bnd; pub mod bnd;
pub mod dcx; pub mod dcx;
pub mod errors; pub mod errors;

120
src/parsers/bhf.rs Normal file
View file

@ -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<u8>,
pub version: Vec<u8>,
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<String>,
}
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<BhfFileInfo>,
}
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 }))
}

View file

@ -7,6 +7,7 @@ use nom::sequence::tuple;
use crate::utils::bin::has_flag; use crate::utils::bin::has_flag;
use crate::parsers::common::{sjis_to_string, take_cstring}; use crate::parsers::common::{sjis_to_string, take_cstring};
const FORMAT_BE: u8 = 0b00000001;
const FORMAT_HAS_ID: u8 = 0b00000010; const FORMAT_HAS_ID: u8 = 0b00000010;
const FORMAT_HAS_NAME1: u8 = 0b00000100; const FORMAT_HAS_NAME1: u8 = 0b00000100;
const FORMAT_HAS_NAME2: u8 = 0b00001000; const FORMAT_HAS_NAME2: u8 = 0b00001000;
@ -26,40 +27,47 @@ pub struct BndHeader {
pub unk1C: u32, pub unk1C: u32,
} }
impl BndHeader { pub trait BinderOptions {
/// Return format u8 with varying endianness managed. fn format(&self) -> u8;
pub fn format(&self) -> u8 { format(self.bit_endianness, self.raw_format) } fn use_be(&self) -> bool;
/// Return whether parsing byte order is big endian or not.
pub fn use_be(&self) -> bool { use_be(self.endianness, self.format()) }
/// Return whether files have IDs. /// Return whether files have IDs.
pub fn has_ids(&self) -> bool { fn has_ids(&self) -> bool {
has_flag(self.format(), FORMAT_HAS_ID) has_flag(self.format(), FORMAT_HAS_ID)
} }
/// Return whether files have paths. /// Return whether files have paths.
pub fn has_paths(&self) -> bool { fn has_paths(&self) -> bool {
let format = self.format(); let format = self.format();
has_flag(format, FORMAT_HAS_NAME1) || has_flag(format, FORMAT_HAS_NAME2) has_flag(format, FORMAT_HAS_NAME1) || has_flag(format, FORMAT_HAS_NAME2)
} }
/// Return whether files have uncompressed size. /// 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) has_flag(self.format(), FORMAT_HAS_UNCOMP_SIZE)
} }
} }
fn format(bit_en: u8, raw_format: u8) -> u8 { impl BinderOptions for BndHeader {
if bit_en == 1 || has_flag(raw_format, 0x1) && !has_flag(raw_format, 0x80) { /// 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 raw_format
} else { } else {
raw_format.reverse_bits() raw_format.reverse_bits()
} }
} }
fn use_be(en: u8, format: u8) -> bool { /// Return whether parsing byte order is big endian or not.
en == 1 || has_flag(format, 0x1) pub fn use_be(en: u8, format: u8) -> bool {
en == 1 || has_flag(format, FORMAT_BE)
} }
fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> { fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> {

58
src/unpackers/bhf.rs Normal file
View file

@ -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<path::PathBuf> {
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());
}
}