bhf: WIP
This commit is contained in:
parent
a6c07e1ae4
commit
992144801b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
120
src/parsers/bhf.rs
Normal file
120
src/parsers/bhf.rs
Normal 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 }))
|
||||
}
|
|
@ -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> {
|
||||
|
|
58
src/unpackers/bhf.rs
Normal file
58
src/unpackers/bhf.rs
Normal 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());
|
||||
}
|
||||
}
|
Reference in a new issue