bnd: WIP parsing
This commit is contained in:
parent
761662e76f
commit
0f2b68a50f
|
@ -199,7 +199,10 @@ fn cmd_dcx(args: &ArgMatches) -> i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_bnd(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();
|
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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
pub mod name_hashes;
|
pub mod name_hashes;
|
||||||
pub mod parsers {
|
pub mod parsers {
|
||||||
pub mod bhd;
|
pub mod bhd;
|
||||||
|
@ -11,5 +13,6 @@ pub mod unpackers {
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
}
|
}
|
||||||
pub mod utils {
|
pub mod utils {
|
||||||
|
pub mod bin;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<u8>,
|
||||||
|
pub version: Vec<u8>,
|
||||||
|
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<BndFileInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }))
|
||||||
|
}
|
|
@ -34,7 +34,6 @@ fn parse_sizes(i: &[u8]) -> IResult<&[u8], DcxSizes> {
|
||||||
Ok((i, DcxSizes { magic: magic.to_vec(), uncompressed_size, compressed_size }))
|
Ok((i, DcxSizes { magic: magic.to_vec(), uncompressed_size, compressed_size }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DcxParams {
|
pub struct DcxParams {
|
||||||
pub magic: Vec<u8>,
|
pub magic: Vec<u8>,
|
||||||
|
@ -50,7 +49,6 @@ pub struct DcxParams {
|
||||||
pub unk1C: u32,
|
pub unk1C: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn parse_params(i: &[u8]) -> IResult<&[u8], DcxParams> {
|
fn parse_params(i: &[u8]) -> IResult<&[u8], DcxParams> {
|
||||||
let (i, (magic, method, ofs_dca, flags, unk10, unk14, unk18, unk1C)) =
|
let (i, (magic, method, ofs_dca, flags, unk10, unk14, unk18, unk1C)) =
|
||||||
tuple((
|
tuple((
|
||||||
|
@ -76,7 +74,7 @@ fn parse_params(i: &[u8]) -> IResult<&[u8], DcxParams> {
|
||||||
unk10,
|
unk10,
|
||||||
unk14,
|
unk14,
|
||||||
unk18,
|
unk18,
|
||||||
unk1C
|
unk1C,
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::parsers::bnd;
|
||||||
use crate::unpackers::errors::{self as unpackers_errors, UnpackError};
|
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.
|
/// Wraps around `extract_bnd` to load the BND from disk.
|
||||||
pub fn extract_bnd_file(bnd_path: &str, output_path: &str) -> Result<(), UnpackError> {
|
pub fn extract_bnd_file(bnd_path: &str, output_path: &str) -> Result<(), UnpackError> {
|
||||||
let bnd = load_bnd_file(bnd_path)?;
|
// let bnd = load_bnd_file(bnd_path)?;
|
||||||
extract_bnd(bnd, output_path)?;
|
// extract_bnd(bnd, output_path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,5 +36,15 @@ pub fn load_bnd_file(bnd_path: &str) -> Result<bnd::Bnd, UnpackError> {
|
||||||
|
|
||||||
/// Load a BND file from a bytes slice.
|
/// Load a BND file from a bytes slice.
|
||||||
pub fn load_bnd(bnd_data: &[u8]) -> Result<bnd::Bnd, UnpackError> {
|
pub fn load_bnd(bnd_data: &[u8]) -> Result<bnd::Bnd, UnpackError> {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
19
src/utils/bin.rs
Normal file
19
src/utils/bin.rs
Normal file
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,10 +29,10 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_strip_ext() {
|
fn test_strip_extension() {
|
||||||
let pb = path::PathBuf::from("file.ext");
|
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");
|
let pb = path::PathBuf::from("file");
|
||||||
assert!(strip_ext(&pb).is_none());
|
assert!(strip_extension(&pb).is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue