diff --git a/crates/cli/src/bin/entryfilelist-test.rs b/crates/cli/src/bin/entryfilelist-test.rs new file mode 100644 index 0000000..15953c8 --- /dev/null +++ b/crates/cli/src/bin/entryfilelist-test.rs @@ -0,0 +1,56 @@ +use std::{error::Error, path::PathBuf}; + +use clap::Parser; +use fstools_formats::entryfilelist::EntryfilelistContainer; +use fstools_dvdbnd::{FileKeyProvider, DvdBnd}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(long)] + erpath: PathBuf, + + #[arg(long)] + dictionary: String, +} + +fn main() -> Result<(), Box> { + let args = Args::parse(); + + let er_path = args.erpath; + + let keys = FileKeyProvider::new("keys"); + let archives = [ + er_path.join("Data0"), + er_path.join("Data1"), + er_path.join("Data2"), + er_path.join("Data3"), + er_path.join("sd/sd"), + ]; + + let vfs = DvdBnd::create(archives.clone(), &keys).expect("unable to create vfs"); + + let dictionary = std::fs::read_to_string(args.dictionary)?; + let lines = dictionary + .lines() + .map(std::path::PathBuf::from) + .collect::>(); + + lines.iter() + .filter(|l| l.to_str().unwrap().ends_with("entryfilelist")) + .for_each(|l| { + println!("Parsing: {}", l.to_str().unwrap()); + + let reader = vfs.open(l).expect("Could not open dvdbnd entry"); + let container = EntryfilelistContainer::parse(reader.data()) + .expect("Could not parse entryfilelist"); + + let entryfilelist = container.decompress().unwrap(); + + for string in entryfilelist.strings.iter() { + println!(" - Referenced asset: {:?}", string); + } + }); + + Ok(()) +} diff --git a/crates/formats/src/entryfilelist.rs b/crates/formats/src/entryfilelist.rs new file mode 100644 index 0000000..2ba492b --- /dev/null +++ b/crates/formats/src/entryfilelist.rs @@ -0,0 +1,167 @@ +use std::{char, io::{self, Cursor, ErrorKind, Read, Seek}}; + +use byteorder::{ReadBytesExt, LE}; +use flate2::read::ZlibDecoder; +use thiserror::Error; +use zerocopy::{FromBytes, FromZeroes, Ref, U32}; + +use crate::io_ext::{ReadFormatsExt, ReadWidestringError, SeekExt}; + +#[derive(Debug, Error)] +pub enum EntryfilelistError { + #[error("Could not read string")] + String(#[from] ReadWidestringError), + + #[error("Could not create reference to value")] + UnalignedValue, + + #[error("Zlib error")] + Zlib, + + #[error("Io error")] + Io(#[from] io::Error), +} + +#[allow(unused)] +pub struct EntryfilelistContainer<'a> { + bytes: &'a [u8], + + header: &'a ContainerHeader, + + compressed: &'a [u8], +} + +impl<'a> EntryfilelistContainer<'a> { + pub fn parse(bytes: &'a [u8]) -> Result { + let (header, compressed) = Ref::<_, ContainerHeader>::new_from_prefix(bytes) + .ok_or(EntryfilelistError::UnalignedValue)?; + + Ok(Self { + bytes, + header: header.into_ref(), + compressed, + }) + } + + fn hint_size(&self) -> usize { + self.header.decompressed_size.get() as usize + } + + pub fn decompress(&self) -> Result { + let mut buf: Vec = Vec::with_capacity(self.hint_size()); + let mut decoder = ZlibDecoder::new(self.compressed); + + decoder.read_to_end(&mut buf) + .map_err(|_| EntryfilelistError::Zlib)?; + + Ok(Entryfilelist::parse(Cursor::new(buf))?) + } +} + +impl<'a> std::fmt::Debug for EntryfilelistContainer<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Entryfilelist") + .field("unk04", &self.header.unk04.get()) + .field("compressed_size", &self.header.compressed_size.get()) + .field("decompressed_size", &self.header.decompressed_size.get()) + .finish() + } +} + +#[derive(FromZeroes, FromBytes, Debug)] +#[repr(packed)] +#[allow(unused)] +pub struct ContainerHeader { + magic: [u8; 4], + + unk04: U32, + + compressed_size: U32, + + decompressed_size: U32, +} + +#[derive(Debug)] +#[allow(unused)] +pub struct Entryfilelist { + pub unk1: Vec, + pub unk2: Vec, + pub strings: Vec, +} + +impl Entryfilelist { + pub fn parse(mut reader: R) -> Result { + let _unk0 = reader.read_u32::()?; + let unk1_count = reader.read_u32::()?; + let unk2_count = reader.read_u32::()?; + let _unkc = reader.read_u32::()?; + + let unk1 = (0..unk1_count) + .map(|_| Unk1::parse(&mut reader)) + .collect::>()?; + reader.seek_until_alignment(0x10)?; + + let unk2 = (0..unk2_count) + .map(|_| reader.read_u64::()) + .collect::>()?; + reader.seek_until_alignment(0x10)?; + + let _unk = reader.read_u16::()?; + + let strings = (0..unk2_count) + .map(|_| Self::read_string(&mut reader)) + .collect::>()?; + + Ok(Self { + unk1, + unk2, + strings, + }) + } + + pub fn read_string(mut reader: R) -> Result { + let mut string = String::new(); + + loop { + // We always know read the right amount of strings so we + // shouldn't encounter EOF + let c = reader.read_u16::()?; + // Read until NULL terminator + if c == 0x0 { + break; + } + + string.push( + char::from_u32(c as u32) + .ok_or(io::Error::from(ErrorKind::InvalidData))? + ); + } + + Ok(string) + } +} + +#[derive(FromZeroes, FromBytes, Debug)] +#[repr(packed)] +#[allow(unused)] +pub struct Header { + unk0: U32, + count_unk1: U32, + count_unk2: U32, + unkc: U32, +} + +#[derive(Debug)] +pub struct Unk1 { + pub step: u16, + pub index: u16, +} + +impl Unk1 { + pub fn parse(mut reader: R) -> Result { + Ok(Self { + step: reader.read_u16::()?, + index: reader.read_u16::()?, + }) + } +} diff --git a/crates/formats/src/io_ext/mod.rs b/crates/formats/src/io_ext/mod.rs index ffc35d3..40509a6 100644 --- a/crates/formats/src/io_ext/mod.rs +++ b/crates/formats/src/io_ext/mod.rs @@ -1,7 +1,9 @@ /// Extensions for Rust standard library IO traits. mod read; +mod seek; mod widestring; pub mod zerocopy; pub use read::*; +pub use seek::*; pub use widestring::*; diff --git a/crates/formats/src/io_ext/seek.rs b/crates/formats/src/io_ext/seek.rs new file mode 100644 index 0000000..8a1afad --- /dev/null +++ b/crates/formats/src/io_ext/seek.rs @@ -0,0 +1,22 @@ +use std::io::{self, ErrorKind, Seek, SeekFrom}; + +pub trait SeekExt: Seek { + fn seek_until_alignment(&mut self, alignment: usize) -> io::Result; +} + +impl SeekExt for T { + fn seek_until_alignment(&mut self, alignment: usize) -> io::Result { + let current = self.seek(io::SeekFrom::Current(0))? as usize; + let difference = if current % alignment == 0 { + 0 + } else { + alignment - current % alignment + }; + + let difference_offset = i64::try_from(difference) + .map_err(|_| io::Error::from(ErrorKind::InvalidData))?; + self.seek(SeekFrom::Current(difference_offset))?; + + Ok(difference) + } +} diff --git a/crates/formats/src/lib.rs b/crates/formats/src/lib.rs index bedbbb3..fc95be9 100644 --- a/crates/formats/src/lib.rs +++ b/crates/formats/src/lib.rs @@ -6,3 +6,4 @@ pub mod io_ext; pub mod matbin; pub mod msb; pub mod tpf; +pub mod entryfilelist;