From 0f2b68a50fd833574c3b3310ad8de0b65360ca48 Mon Sep 17 00:00:00 2001 From: dece Date: Sat, 25 Apr 2020 22:55:17 +0200 Subject: [PATCH] bnd: WIP parsing --- src/bin/ironring.rs | 7 ++- src/lib.rs | 3 + src/parsers/bnd.rs | 136 +++++++++++++++++++++++++++++++++++++++++++ src/parsers/dcx.rs | 4 +- src/unpackers/bnd.rs | 21 ++++++- src/utils/bin.rs | 19 ++++++ src/utils/fs.rs | 6 +- 7 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 src/utils/bin.rs diff --git a/src/bin/ironring.rs b/src/bin/ironring.rs index 7ad8dca..373ad0c 100644 --- a/src/bin/ironring.rs +++ b/src/bin/ironring.rs @@ -199,7 +199,10 @@ fn cmd_dcx(args: &ArgMatches) -> i32 { } fn cmd_bnd(args: &ArgMatches) -> i32 { - let _file_path: &str = args.value_of("file").unwrap(); + let file_path: &str = args.value_of("file").unwrap(); let _output_path: &str = args.value_of("output").unwrap(); - 0 + match unpackers::bnd::load_bnd_file(file_path) { + Err(e) => { eprintln!("Failed to extract BND: {:?}", e); return 1 } + _ => { 0 } + } } diff --git a/src/lib.rs b/src/lib.rs index 99646db..30a5b22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + pub mod name_hashes; pub mod parsers { pub mod bhd; @@ -11,5 +13,6 @@ pub mod unpackers { pub mod errors; } pub mod utils { + pub mod bin; pub mod fs; } diff --git a/src/parsers/bnd.rs b/src/parsers/bnd.rs index e69de29..9ade9a8 100644 --- a/src/parsers/bnd.rs +++ b/src/parsers/bnd.rs @@ -0,0 +1,136 @@ +use nom::IResult; +use nom::bytes::complete::{tag, take, take_until}; +use nom::multi::count; +use nom::number::complete::*; +use nom::sequence::tuple; + +use crate::utils::bin::has_flag; + +#[derive(Debug)] +pub struct BndHeader { + pub magic: Vec, + pub version: Vec, + pub raw_format: u8, + pub endianness: u8, + pub bit_endianness: u8, + pub flags0F: u8, + pub num_files: u32, + pub ofs_data: u32, + pub unk18: u32, + pub unk1C: u32, + + // Data computed once to avoid clutter. TODO do it better + format: u8, // Format with correct bit order. + use_be: bool, // Use big-endian to parse this BND. + has_paths: bool, // Files have paths. +} + +const FORMAT_HAS_ID: u8 = 0b00000010; +const FORMAT_HAS_NAME1: u8 = 0b00000100; +const FORMAT_HAS_NAME2: u8 = 0b00001000; +const FORMAT_HAS_UNCOMP_SIZE: u8 = 0b00100000; + +impl BndHeader { + pub fn format(bit_en: u8, raw_format: u8) -> u8 { + if bit_en == 1 || has_flag(raw_format, 0x1) && !has_flag(raw_format, 0x80) { + raw_format + } else { + raw_format.reverse_bits() + } + } + + pub fn use_be(en: u8, format: u8) -> bool { + en == 1 || has_flag(format, 0x1) + } +} + +fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> { + let (i, (magic, version, raw_format, endianness, bit_endianness, flags0F)) = + tuple((tag(b"BND3"), take(8usize), le_u8, le_u8, le_u8, le_u8))(i)?; + let format = BndHeader::format(bit_endianness, raw_format); + let use_be = BndHeader::use_be(endianness, format); + let u32_parser = if use_be { be_u32 } else { le_u32 }; + let (i, (num_files, ofs_data, unk18, unk1C)) = + tuple((u32_parser, u32_parser, u32_parser, u32_parser))(i)?; + Ok(( + i, + BndHeader { + magic: magic.to_vec(), + version: version.to_vec(), + raw_format, + endianness, + bit_endianness, + flags0F, + num_files, + ofs_data, + unk18, + unk1C, + + format, + use_be, + } + )) +} + +#[derive(Debug)] +pub struct BndFileInfo { + 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: String, +} + +fn parse_file_info<'a>(i: &'a[u8], header: &BndHeader) -> IResult<&'a[u8], BndFileInfo> { + let u32_parser = if header.use_be { be_u32 } else { le_u32 }; + let (i, (flags, size, ofs_data)) = tuple((count(le_u8, 4), u32_parser, u32_parser))(i)?; + + let (i, id) = if has_flag(header.format, FORMAT_HAS_ID) { u32_parser(i)? } else { (i, 0) }; + let has_name = has_flag(header.format, FORMAT_HAS_NAME1) || + has_flag(header.format, FORMAT_HAS_NAME2); + let (i, ofs_path) = if has_name { u32_parser(i)? } else { (i, 0) }; + let has_uncomp_size = has_flag(header.format, FORMAT_HAS_UNCOMP_SIZE); + let (i, uncompressed_size) = if has_uncomp_size { u32_parser(i)? } else { (i, 0) }; + + Ok(( + i, + BndFileInfo { + unk00: flags[0], + unk01: flags[1], + unk02: flags[2], + unk03: flags[3], + size, + ofs_data, + id, + ofs_path, + uncompressed_size, + path: String::new(), + } + )) +} + +#[derive(Debug)] +pub struct Bnd { + pub header: BndHeader, + pub file_infos: Vec, +} + +pub fn parse(i: &[u8]) -> IResult<&[u8], Bnd> { + let full_file = i; + let (i, header) = parse_header(i)?; + let (i, file_infos) = count(|i| parse_file_info(i, &header), header.num_files as usize)(i)?; + if has_flag(header.format, FORMAT_HAS_NAME1) || has_flag(header.format, FORMAT_HAS_NAME2) { + for info in &file_infos { + let (_, path) = take_until(b"\0")(i[info.])?; + } + } + println!("{:?}", header); + println!("{:?}", file_infos); + Ok((i, Bnd { header, file_infos })) +} diff --git a/src/parsers/dcx.rs b/src/parsers/dcx.rs index 310b234..1b6edd8 100644 --- a/src/parsers/dcx.rs +++ b/src/parsers/dcx.rs @@ -34,7 +34,6 @@ fn parse_sizes(i: &[u8]) -> IResult<&[u8], DcxSizes> { Ok((i, DcxSizes { magic: magic.to_vec(), uncompressed_size, compressed_size })) } -#[allow(non_snake_case)] #[derive(Debug)] pub struct DcxParams { pub magic: Vec, @@ -50,7 +49,6 @@ pub struct DcxParams { pub unk1C: u32, } -#[allow(non_snake_case)] fn parse_params(i: &[u8]) -> IResult<&[u8], DcxParams> { let (i, (magic, method, ofs_dca, flags, unk10, unk14, unk18, unk1C)) = tuple(( @@ -76,7 +74,7 @@ fn parse_params(i: &[u8]) -> IResult<&[u8], DcxParams> { unk10, unk14, unk18, - unk1C + unk1C, } )) } diff --git a/src/unpackers/bnd.rs b/src/unpackers/bnd.rs index c620f8e..025e952 100644 --- a/src/unpackers/bnd.rs +++ b/src/unpackers/bnd.rs @@ -1,3 +1,8 @@ +use std::fs; +use std::io::Read; + +use nom::Err::{Error as NomError, Failure as NomFailure}; + use crate::parsers::bnd; use crate::unpackers::errors::{self as unpackers_errors, UnpackError}; @@ -5,8 +10,8 @@ use crate::unpackers::errors::{self as unpackers_errors, UnpackError}; /// /// Wraps around `extract_bnd` to load the BND from disk. pub fn extract_bnd_file(bnd_path: &str, output_path: &str) -> Result<(), UnpackError> { - let bnd = load_bnd_file(bnd_path)?; - extract_bnd(bnd, output_path)?; +// let bnd = load_bnd_file(bnd_path)?; +// extract_bnd(bnd, output_path)?; Ok(()) } @@ -31,5 +36,15 @@ pub fn load_bnd_file(bnd_path: &str) -> Result { /// Load a BND file from a bytes slice. pub fn load_bnd(bnd_data: &[u8]) -> Result { - Ok(()) + let (_, bnd) = match bnd::parse(bnd_data) { + 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)) + } + e => { + return Err(UnpackError::Unknown(format!("Unknown error: {:?}", e))) + } + }; + Ok(bnd) } diff --git a/src/utils/bin.rs b/src/utils/bin.rs new file mode 100644 index 0000000..bf515a8 --- /dev/null +++ b/src/utils/bin.rs @@ -0,0 +1,19 @@ +/// Return whether i has this flag set. +/// +/// The flag value can be a combination of several flags, the function +/// will return i has all the flags combined. +pub fn has_flag(i: u8, flag: u8) -> bool { + i & flag == flag +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_has_flag() { + assert!(has_flag(0x0, 0x0)); + assert!(has_flag(0x80, 0x80)); + assert!(!has_flag(0x80, 0x40)); + } +} diff --git a/src/utils/fs.rs b/src/utils/fs.rs index ba21aba..952a762 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -29,10 +29,10 @@ mod tests { use super::*; #[test] - fn test_strip_ext() { + fn test_strip_extension() { let pb = path::PathBuf::from("file.ext"); - assert_eq!(strip_ext(&pb).unwrap(), path::PathBuf::from("file")); + assert_eq!(strip_extension(&pb).unwrap(), path::PathBuf::from("file")); let pb = path::PathBuf::from("file"); - assert!(strip_ext(&pb).is_none()); + assert!(strip_extension(&pb).is_none()); } }