From c0a1c5154c01720924f91ca824a8b6d61d74a56c Mon Sep 17 00:00:00 2001 From: spuds <71292624+bananaturtlesandwich@users.noreply.github.com> Date: Tue, 3 Jan 2023 14:20:35 +0000 Subject: [PATCH] implementation of v1 complete (I think) --- examples/read.rs | 2 +- src/entry.rs | 60 ++++++++++++++++++++++++++++++++++++++ src/error.rs | 4 +++ src/ext.rs | 35 +++++++++++++++++++++- src/footer.rs | 23 ++++++++------- src/index.rs | 34 +++++++++++++++++++++ src/lib.rs | 15 ++++++---- src/{pakfile.rs => pak.rs} | 6 ++-- src/pakentry.rs | 17 ----------- 9 files changed, 159 insertions(+), 37 deletions(-) create mode 100644 src/entry.rs create mode 100644 src/index.rs rename src/{pakfile.rs => pak.rs} (93%) delete mode 100644 src/pakentry.rs diff --git a/examples/read.rs b/examples/read.rs index 4069000..9b41199 100644 --- a/examples/read.rs +++ b/examples/read.rs @@ -1,6 +1,6 @@ fn main() -> Result<(), un_pak::Error> { for version in un_pak::Version::iter().rev() { - match un_pak::PakFile::new(version, std::io::Cursor::new(include_bytes!("rando_p.pak"))) { + match un_pak::Pak::new(version, std::io::Cursor::new(include_bytes!("rando_p.pak"))) { Ok(_) => { println!("parsed successfully!"); return Ok(()); diff --git a/src/entry.rs b/src/entry.rs new file mode 100644 index 0000000..4f66fa8 --- /dev/null +++ b/src/entry.rs @@ -0,0 +1,60 @@ +use byteorder::{ReadBytesExt, LE}; + +use super::{Compression, ReadExt, Version}; + +pub struct Entry { + pub name: String, + pub offset: u64, + pub compressed: u64, + pub uncompressed: u64, + pub compression_method: Compression, + pub timestamp: Option, + pub hash: [u8; 20], + pub compression_blocks: Option>, +} + +impl Entry { + pub fn new( + reader: &mut R, + version: &super::Version, + ) -> Result { + let name = reader.read_string()?; + let offset = reader.read_u64::()?; + let compressed = reader.read_u64::()?; + let uncompressed = reader.read_u64::()?; + let compression_method = match reader.read_u32::()? { + 0x01 => Compression::Zlib, + 0x10 => Compression::ZlibBiasMemory, + 0x20 => Compression::ZlibBiasMemory, + _ => Compression::None, + }; + Ok(Self { + name, + offset, + compressed, + uncompressed, + compression_method, + timestamp: (version == &Version::Initial).then_some(reader.read_u64::()?), + hash: reader.read_guid()?, + compression_blocks: (version >= &Version::CompressionEncryption + && compression_method != Compression::None) + .then_some(reader.read_array(Block::new)?), + }) + } +} + +pub struct Block { + /// start offset relative to the start of the entry header + pub offset: u64, + /// size of the compressed block + pub size: u64, +} + +impl Block { + pub fn new(reader: &mut R) -> Result { + Ok(Self { + offset: reader.read_u64::()?, + size: reader.read_u64::()?, + }) + } +} diff --git a/src/error.rs b/src/error.rs index e1e7991..6772c50 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,4 +6,8 @@ pub enum Error { IoError(#[from] std::io::Error), #[error("error converting to enum: {0}")] StrumError(#[from] strum::ParseError), + #[error("error converting to utf8: {0}")] + Utf8Error(#[from] std::string::FromUtf8Error), + #[error("error converting to utf16: {0}")] + Utf16Error(#[from] std::string::FromUtf16Error), } diff --git a/src/ext.rs b/src/ext.rs index 4b88be6..5f9732c 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -1,8 +1,15 @@ -use byteorder::ReadBytesExt; +use byteorder::{ReadBytesExt, LE}; + +type R = dyn std::io::Read; pub trait ReadExt { fn read_bool(&mut self) -> Result; fn read_guid(&mut self) -> Result<[u8; 20], super::Error>; + fn read_array( + &mut self, + func: impl FnMut(&mut Self) -> Result, + ) -> Result, super::Error>; + fn read_string(&mut self) -> Result; fn read_len(&mut self, len: usize) -> Result, super::Error>; } @@ -17,6 +24,32 @@ impl ReadExt for R { Ok(guid) } + fn read_array( + &mut self, + mut func: impl FnMut(&mut Self) -> Result, + ) -> Result, super::Error> { + let mut buf = Vec::with_capacity(self.read_u32::()? as usize); + for _ in 0..buf.capacity() { + buf.push(func(self)?); + } + Ok(buf) + } + + fn read_string(&mut self) -> Result { + Ok(match self.read_i32::()? { + size if size.is_positive() => String::from_utf8(self.read_len(size as usize)?)?, + size if size.is_negative() => { + let size = 2 * -size; + let mut buf = Vec::with_capacity(size as usize / 2); + for _ in 0..buf.capacity() { + buf.push(self.read_u16::()?); + } + String::from_utf16(&buf)? + } + _ => String::new(), + }) + } + fn read_len(&mut self, len: usize) -> Result, super::Error> { let mut buf = Vec::with_capacity(len); self.read_exact(&mut buf)?; diff --git a/src/footer.rs b/src/footer.rs index 9dffd7f..f7f60cb 100644 --- a/src/footer.rs +++ b/src/footer.rs @@ -1,4 +1,4 @@ -use std::{io, str::FromStr}; +use std::str::FromStr; use byteorder::{ReadBytesExt, LE}; @@ -17,7 +17,7 @@ pub struct Footer { } impl Footer { - pub fn new(reader: &mut R, version: &Version) -> Result { + pub fn new(reader: &mut R, version: &Version) -> Result { let footer = Footer { encryption_guid: (version >= &Version::EncryptionKeyGuid) .then_some(reader.read_guid()?), @@ -36,14 +36,17 @@ impl Footer { 5 }); for _ in 0..compression.capacity() { - compression.push(Compression::from_str( - &reader - .read_len(32)? - .iter() - // filter out whitespace and convert to char - .filter_map(|&ch| (ch != 0).then_some(ch as char)) - .collect::(), - )?) + compression.push( + Compression::from_str( + &reader + .read_len(32)? + .iter() + // filter out whitespace and convert to char + .filter_map(|&ch| (ch != 0).then_some(ch as char)) + .collect::(), + ) + .unwrap_or_default(), + ) } compression }), diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..1f7e6fd --- /dev/null +++ b/src/index.rs @@ -0,0 +1,34 @@ +use super::{ReadExt, Version}; + +pub enum Index { + WithoutPathHash(IndexV1), + WithPathHash(IndexV2), +} + +impl Index { + pub fn new(reader: &mut R, version: &Version) -> Result { + Ok(match version < &Version::PathHashIndex { + true => Index::WithoutPathHash(IndexV1::new(reader, version)?), + false => Index::WithPathHash(todo!()), + }) + } +} + +pub struct IndexV1 { + pub mount_point: String, + pub entries: Vec, +} + +impl IndexV1 { + pub fn new( + reader: &mut R, + version: &super::Version, + ) -> Result { + Ok(Self { + mount_point: reader.read_string()?, + entries: reader.read_array(|reader| super::Entry::new(reader, version))?, + }) + } +} + +pub struct IndexV2 {} diff --git a/src/lib.rs b/src/lib.rs index 8ef5ded..36928d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,12 @@ #![allow(dead_code)] +mod entry; mod error; mod ext; mod footer; -mod pakentry; -mod pakfile; +mod index; +mod pak; -pub use {error::*, ext::*, footer::*, pakentry::*, pakfile::*}; +pub use {entry::*, error::*, ext::*, footer::*, index::*, pak::*}; pub const MAGIC: u32 = 0x5A6F12E1; @@ -37,16 +38,20 @@ pub enum Version { PathHashIndex, // more compression methods } -// i don't want people to need to install strum +// strum shouldn't need to be installed impl Version { pub fn iter() -> VersionIter { ::iter() } } -#[derive(Copy, Clone, Debug, strum::Display, strum::EnumString)] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, strum::Display, strum::EnumString)] pub enum Compression { + #[default] + None, Zlib, + ZlibBiasMemory, + ZlibBiasSpeed, Gzip, Oodle, } diff --git a/src/pakfile.rs b/src/pak.rs similarity index 93% rename from src/pakfile.rs rename to src/pak.rs index 1add19d..d6cda17 100644 --- a/src/pakfile.rs +++ b/src/pak.rs @@ -2,13 +2,13 @@ use std::io; use super::Version; -pub struct PakFile { +pub struct Pak { pub version: Version, pub footer: super::Footer, - pub entries: hashbrown::HashMap, + pub entries: hashbrown::HashMap, } -impl PakFile { +impl Pak { pub fn new( version: super::Version, mut reader: R, diff --git a/src/pakentry.rs b/src/pakentry.rs deleted file mode 100644 index 604182d..0000000 --- a/src/pakentry.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub struct PakEntry { - pub offset: u64, - pub compressed: u64, - pub decompressed: u64, - pub compression_method: super::Compression, - pub hash: [u8; 20], - pub compression_blocks: Vec, - pub flags: Vec, - pub block_size: u32, -} - -pub struct Block { - /// start offset relative to the start of the entry header - pub offset: u64, - /// size of the compressed block - pub size: u64, -}