bhf: WIP
This commit is contained in:
parent
a6c07e1ae4
commit
992144801b
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
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::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
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