bnd: WIP parsing again, handle shift JIS, etc

This commit is contained in:
dece 2020-04-26 22:31:16 +02:00
parent 0f2b68a50f
commit 1f239ce90d
6 changed files with 94 additions and 46 deletions

10
Cargo.lock generated
View file

@ -68,6 +68,14 @@ dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "encoding_rs"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.14" version = "1.0.14"
@ -173,6 +181,7 @@ name = "rusted_iron_ring"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -259,6 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28"
"checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" "checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
"checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" "checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e"
"checksum lexical-core 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f" "checksum lexical-core 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f"

View file

@ -13,7 +13,8 @@ name = "ironring"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
encoding_rs = "0.8"
flate2 = "1.0"
nom = "5" nom = "5"
num-bigint = "0.2" num-bigint = "0.2"
num-traits = "0.2" num-traits = "0.2"
flate2 = "1.0"

View file

@ -4,6 +4,7 @@ pub mod name_hashes;
pub mod parsers { pub mod parsers {
pub mod bhd; pub mod bhd;
pub mod bnd; pub mod bnd;
pub mod common;
pub mod dcx; pub mod dcx;
} }
pub mod unpackers { pub mod unpackers {

View file

@ -1,10 +1,18 @@
use std::str;
use nom::IResult; use nom::IResult;
use nom::bytes::complete::{tag, take, take_until}; use nom::bytes::complete::{tag, take};
use nom::multi::count; use nom::multi::count;
use nom::number::complete::*; use nom::number::complete::*;
use nom::sequence::tuple; 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};
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;
#[derive(Debug)] #[derive(Debug)]
pub struct BndHeader { pub struct BndHeader {
@ -18,38 +26,49 @@ pub struct BndHeader {
pub ofs_data: u32, pub ofs_data: u32,
pub unk18: u32, pub unk18: u32,
pub unk1C: 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 { impl BndHeader {
pub fn format(bit_en: u8, raw_format: u8) -> u8 { /// 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()) }
/// Return whether files have IDs.
pub fn has_ids(&self) -> bool {
has_flag(self.format(), FORMAT_HAS_ID)
}
/// Return whether files have paths.
pub 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 {
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) { if bit_en == 1 || has_flag(raw_format, 0x1) && !has_flag(raw_format, 0x80) {
raw_format raw_format
} else { } else {
raw_format.reverse_bits() raw_format.reverse_bits()
} }
} }
pub fn use_be(en: u8, format: u8) -> bool { fn use_be(en: u8, format: u8) -> bool {
en == 1 || has_flag(format, 0x1) en == 1 || has_flag(format, 0x1)
}
} }
fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> { fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> {
let (i, (magic, version, raw_format, endianness, bit_endianness, flags0F)) = 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)?; tuple((tag(b"BND3"), take(8usize), le_u8, le_u8, le_u8, le_u8))(i)?;
let format = BndHeader::format(bit_endianness, raw_format); let format = format(bit_endianness, raw_format);
let use_be = BndHeader::use_be(endianness, format); let u32_parser = if use_be(endianness, format) { be_u32 } else { le_u32 };
let u32_parser = if use_be { be_u32 } else { le_u32 };
let (i, (num_files, ofs_data, unk18, unk1C)) = let (i, (num_files, ofs_data, unk18, unk1C)) =
tuple((u32_parser, u32_parser, u32_parser, u32_parser))(i)?; tuple((u32_parser, u32_parser, u32_parser, u32_parser))(i)?;
Ok(( Ok((
@ -65,9 +84,6 @@ fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> {
ofs_data, ofs_data,
unk18, unk18,
unk1C, unk1C,
format,
use_be,
} }
)) ))
} }
@ -84,19 +100,16 @@ pub struct BndFileInfo {
pub ofs_path: u32, pub ofs_path: u32,
pub uncompressed_size: u32, pub uncompressed_size: u32,
pub path: String, pub path: Option<String>,
} }
fn parse_file_info<'a>(i: &'a[u8], header: &BndHeader) -> IResult<&'a[u8], BndFileInfo> { 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 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, (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 (i, id) = if header.has_ids() { u32_parser(i)? } else { (i, 0) };
let has_name = has_flag(header.format, FORMAT_HAS_NAME1) || let (i, ofs_path) = if header.has_paths() { u32_parser(i)? } else { (i, 0) };
has_flag(header.format, FORMAT_HAS_NAME2); let (i, uncompressed_size) = if header.has_uncomp_size() { u32_parser(i)? } else { (i, 0) };
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(( Ok((
i, i,
@ -110,7 +123,7 @@ fn parse_file_info<'a>(i: &'a[u8], header: &BndHeader) -> IResult<&'a[u8], BndFi
id, id,
ofs_path, ofs_path,
uncompressed_size, uncompressed_size,
path: String::new(), path: None,
} }
)) ))
} }
@ -124,13 +137,19 @@ pub struct Bnd {
pub fn parse(i: &[u8]) -> IResult<&[u8], Bnd> { pub fn parse(i: &[u8]) -> IResult<&[u8], Bnd> {
let full_file = i; let full_file = i;
let (i, header) = parse_header(i)?; let (i, header) = parse_header(i)?;
let (i, file_infos) = count(|i| parse_file_info(i, &header), header.num_files as usize)(i)?; let (i, mut file_infos) = count(
if has_flag(header.format, FORMAT_HAS_NAME1) || has_flag(header.format, FORMAT_HAS_NAME2) { |i| parse_file_info(i, &header),
for info in &file_infos { header.num_files as usize
let (_, path) = take_until(b"\0")(i[info.])?; )(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);
} }
} }
println!("{:?}", header); }
println!("{:?}", file_infos); Ok((full_file, Bnd { header, file_infos }))
Ok((i, Bnd { header, file_infos }))
} }

15
src/parsers/common.rs Normal file
View file

@ -0,0 +1,15 @@
use encoding_rs::SHIFT_JIS;
use nom::IResult;
use nom::bytes::complete::take_while;
pub fn take_cstring(i: &[u8]) -> IResult<&[u8], &[u8]> {
take_while(|c| c != b'\0')(i)
}
pub fn sjis_to_string(i: &[u8]) -> Option<String> {
let (cow, _, has_errors) = SHIFT_JIS.decode(i);
if has_errors {
return None
}
Some(cow.to_string())
}

View file

@ -1,24 +1,26 @@
use std::fs; use std::fs;
use std::io::Read; use std::io::Read;
use std::path;
use nom::Err::{Error as NomError, Failure as NomFailure}; 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};
use crate::utils::fs as fs_utils;
/// Extract BND file contents to disk. /// Extract BND file contents to disk.
/// ///
/// 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(())
} }
/// Extract BND contents to disk. /// Extract BND contents to disk.
pub fn extract_bnd(bnd: bnd::Bnd, output_path: &str) -> Result<(), UnpackError> { pub fn extract_bnd(bnd: bnd::Bnd, output_path: &str) -> Result<(), UnpackError> {
//TODO let output_path = path::Path::new(output_path);
//let mut output_file = fs::File::create(output_path)?; fs_utils::ensure_dir_exists(output_path)?;
//output_file.write_all(&decomp_data)?; //output_file.write_all(&decomp_data)?;
Ok(()) Ok(())
} }