diff --git a/Cargo.lock b/Cargo.lock index 5f36abb..553991b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,14 @@ dependencies = [ "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]] name = "flate2" version = "1.0.14" @@ -173,6 +181,7 @@ name = "rusted_iron_ring" version = "0.1.0" dependencies = [ "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)", "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)", @@ -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 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 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 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" diff --git a/Cargo.toml b/Cargo.toml index e9f9c79..40cc458 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,8 @@ name = "ironring" [dependencies] clap = "2.33" +encoding_rs = "0.8" +flate2 = "1.0" nom = "5" num-bigint = "0.2" num-traits = "0.2" -flate2 = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 30a5b22..9b2c894 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod name_hashes; pub mod parsers { pub mod bhd; pub mod bnd; + pub mod common; pub mod dcx; } pub mod unpackers { diff --git a/src/parsers/bnd.rs b/src/parsers/bnd.rs index 9ade9a8..12ae3f0 100644 --- a/src/parsers/bnd.rs +++ b/src/parsers/bnd.rs @@ -1,10 +1,18 @@ +use std::str; + use nom::IResult; -use nom::bytes::complete::{tag, take, take_until}; +use nom::bytes::complete::{tag, take}; use nom::multi::count; use nom::number::complete::*; use nom::sequence::tuple; 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)] pub struct BndHeader { @@ -18,38 +26,49 @@ pub struct BndHeader { 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() - } + /// 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) } - pub fn use_be(en: u8, format: u8) -> bool { - en == 1 || has_flag(format, 0x1) + /// 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) { + raw_format + } else { + raw_format.reverse_bits() + } +} + +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 format = format(bit_endianness, raw_format); + let u32_parser = if use_be(endianness, format) { be_u32 } else { le_u32 }; let (i, (num_files, ofs_data, unk18, unk1C)) = tuple((u32_parser, u32_parser, u32_parser, u32_parser))(i)?; Ok(( @@ -65,9 +84,6 @@ fn parse_header(i: &[u8]) -> IResult<&[u8], BndHeader> { ofs_data, unk18, unk1C, - - format, - use_be, } )) } @@ -84,19 +100,16 @@ pub struct BndFileInfo { pub ofs_path: u32, pub uncompressed_size: u32, - pub path: String, + pub path: Option, } 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, 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) }; + 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, @@ -110,7 +123,7 @@ fn parse_file_info<'a>(i: &'a[u8], header: &BndHeader) -> IResult<&'a[u8], BndFi id, ofs_path, uncompressed_size, - path: String::new(), + path: None, } )) } @@ -124,13 +137,19 @@ pub struct Bnd { 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.])?; + let (i, 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); + } } } - println!("{:?}", header); - println!("{:?}", file_infos); - Ok((i, Bnd { header, file_infos })) + Ok((full_file, Bnd { header, file_infos })) } diff --git a/src/parsers/common.rs b/src/parsers/common.rs new file mode 100644 index 0000000..3ccaf9c --- /dev/null +++ b/src/parsers/common.rs @@ -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 { + let (cow, _, has_errors) = SHIFT_JIS.decode(i); + if has_errors { + return None + } + Some(cow.to_string()) +} diff --git a/src/unpackers/bnd.rs b/src/unpackers/bnd.rs index 025e952..68c9137 100644 --- a/src/unpackers/bnd.rs +++ b/src/unpackers/bnd.rs @@ -1,24 +1,26 @@ use std::fs; use std::io::Read; +use std::path; use nom::Err::{Error as NomError, Failure as NomFailure}; use crate::parsers::bnd; use crate::unpackers::errors::{self as unpackers_errors, UnpackError}; +use crate::utils::fs as fs_utils; /// Extract BND file contents to disk. /// /// 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(()) } /// Extract BND contents to disk. pub fn extract_bnd(bnd: bnd::Bnd, output_path: &str) -> Result<(), UnpackError> { - //TODO - //let mut output_file = fs::File::create(output_path)?; + let output_path = path::Path::new(output_path); + fs_utils::ensure_dir_exists(output_path)?; //output_file.write_all(&decomp_data)?; Ok(()) }