From 529bdcac4e06c9b879dd5fd7e15b2903a7ad27df Mon Sep 17 00:00:00 2001 From: Truman Kilen Date: Fri, 20 Jan 2023 12:09:44 -0600 Subject: [PATCH] Refactor version handling to handle subversions and correctly parse v8a --- src/entry.rs | 27 ++++++++++++++----------- src/footer.rs | 18 +++++++++-------- src/lib.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++------- src/pak.rs | 4 ++-- tests/test.rs | 12 +++++------ 5 files changed, 82 insertions(+), 35 deletions(-) diff --git a/src/entry.rs b/src/entry.rs index b73bcc2..7150ef8 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,4 +1,4 @@ -use super::{ext::ReadExt, Compression, Version}; +use super::{ext::ReadExt, Compression, Version, VersionMajor}; use byteorder::{ReadBytesExt, LE}; use std::io; @@ -45,8 +45,11 @@ impl Entry { size += 8; // offset size += 8; // compressed size += 8; // uncompressed - size += 4; // compression - size += match version == Version::Initial { + size += match version != Version::V8A { + true => 4, // 32 bit compression + false => 1, // 8 bit compression + }; + size += match version.version_major() == VersionMajor::Initial { true => 8, // timestamp false => 0, }; @@ -56,7 +59,7 @@ impl Entry { false => 0, }; size += 1; // encrypted - size += match version >= Version::CompressionEncryption { + size += match version.version_major() >= VersionMajor::CompressionEncryption { true => 4, // blocks uncompressed false => 0, }; @@ -68,7 +71,7 @@ impl Entry { let offset = reader.read_u64::()?; let compressed = reader.read_u64::()?; let uncompressed = reader.read_u64::()?; - let compression = match reader.read_u32::()? { + let compression = match if version == Version::V8A { reader.read_u8()? as u32 } else { reader.read_u32::()? } { 0x01 | 0x10 | 0x20 => Compression::Zlib, _ => Compression::None, }; @@ -77,19 +80,19 @@ impl Entry { compressed, uncompressed, compression, - timestamp: match version == Version::Initial { + timestamp: match version.version_major() == VersionMajor::Initial { true => Some(reader.read_u64::()?), false => None, }, hash: Some(reader.read_guid()?), - blocks: match version >= Version::CompressionEncryption + blocks: match version.version_major() >= VersionMajor::CompressionEncryption && compression != Compression::None { true => Some(reader.read_array(Block::new)?), false => None, }, - encrypted: version >= Version::CompressionEncryption && reader.read_bool()?, - block_uncompressed: match version >= Version::CompressionEncryption { + encrypted: version.version_major() >= VersionMajor::CompressionEncryption && reader.read_bool()?, + block_uncompressed: match version.version_major() >= VersionMajor::CompressionEncryption { true => Some(reader.read_u32::()?), false => None, }, @@ -140,7 +143,7 @@ impl Entry { }; let offset_base = - match version >= super::Version::RelativeChunkOffsets { + match version.version_major() >= VersionMajor::RelativeChunkOffsets { true => 0, false => offset, } + Entry::get_serialized_size(version, compression, compression_block_count); @@ -189,7 +192,7 @@ impl Entry { pub fn read( &self, reader: &mut R, - version: super::Version, + version: Version, key: Option<&aes::Aes256Dec>, buf: &mut W, ) -> Result<(), super::Error> { @@ -217,7 +220,7 @@ impl Entry { for block in blocks { io::copy( &mut <$decompressor>::new( - &data[match version >= Version::RelativeChunkOffsets { + &data[match version.version_major() >= VersionMajor::RelativeChunkOffsets { true => { (block.start - (data_offset - self.offset)) as usize ..(block.end - (data_offset - self.offset)) as usize diff --git a/src/footer.rs b/src/footer.rs index 1e9e114..e498e5d 100644 --- a/src/footer.rs +++ b/src/footer.rs @@ -1,4 +1,4 @@ -use super::{ext::ReadExt, Compression, Version}; +use super::{ext::ReadExt, Compression, Version, VersionMajor}; use byteorder::{ReadBytesExt, LE}; use std::str::FromStr; @@ -8,6 +8,7 @@ pub struct Footer { pub encrypted: bool, pub magic: u32, pub version: Version, + pub version_major: VersionMajor, pub index_offset: u64, pub index_size: u64, pub hash: [u8; 20], @@ -18,21 +19,22 @@ pub struct Footer { impl Footer { pub fn new(reader: &mut R, version: Version) -> Result { let footer = Self { - encryption_uuid: match version >= Version::EncryptionKeyGuid { + encryption_uuid: match version.version_major() >= VersionMajor::EncryptionKeyGuid { true => Some(reader.read_u128::()?), false => None, }, - encrypted: version >= Version::IndexEncryption && reader.read_bool()?, + encrypted: version.version_major() >= VersionMajor::IndexEncryption && reader.read_bool()?, magic: reader.read_u32::()?, - version: Version::from_repr(reader.read_u32::()?).unwrap_or(version), + version, + version_major: VersionMajor::from_repr(reader.read_u32::()?).unwrap_or(version.version_major()), index_offset: reader.read_u64::()?, index_size: reader.read_u64::()?, hash: reader.read_guid()?, - frozen: version == Version::FrozenIndex && reader.read_bool()?, + frozen: version.version_major() == VersionMajor::FrozenIndex && reader.read_bool()?, compression: { let mut compression = Vec::with_capacity(match version { - ver if ver < Version::FNameBasedCompression => 0, - Version::FNameBasedCompression => 4, + ver if ver < Version::V8A => 0, + ver if ver < Version::V8B => 4, _ => 5, }); for _ in 0..compression.capacity() { @@ -54,7 +56,7 @@ impl Footer { if super::MAGIC != footer.magic { return Err(super::Error::Magic(footer.magic)); } - if version != footer.version { + if version.version_major() != footer.version_major { return Err(super::Error::Version { used: version, version: footer.version, diff --git a/src/lib.rs b/src/lib.rs index f13c4f8..8b3ea74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,12 +9,31 @@ pub use {error::*, pak::*}; pub const MAGIC: u32 = 0x5A6F12E1; +#[derive( + Clone, Copy, PartialEq, Eq, PartialOrd, Debug, strum::Display, strum::FromRepr, strum::EnumIter, +)] +pub enum Version { + V0, + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8A, + V8B, + V9, + V10, + V11, +} + #[repr(u32)] #[derive( Clone, Copy, PartialEq, Eq, PartialOrd, Debug, strum::Display, strum::FromRepr, strum::EnumIter, )] - -pub enum Version { +/// Version actually written to the pak file +pub enum VersionMajor { Unknown, // v0 unknown (mostly just for padding) Initial, // v1 initial specification NoTimestamps, // v2 timestamps removed @@ -38,24 +57,47 @@ impl Version { pub fn size(self) -> i64 { // (magic + version): u32 + (offset + size): u64 + hash: [u8; 20] let mut size = 4 + 4 + 8 + 8 + 20; - if self >= Version::EncryptionKeyGuid { + if self.version_major() >= VersionMajor::EncryptionKeyGuid { // encryption uuid: u128 size += 16; } - if self >= Version::IndexEncryption { + if self.version_major() >= VersionMajor::IndexEncryption { // encrypted: bool size += 1; } - if self == Version::FrozenIndex { + if self.version_major() == VersionMajor::FrozenIndex { // frozen index: bool size += 1; } - if self >= Version::FNameBasedCompression { + if self >= Version::V8A { // compression names: [[u8; 32]; 5] - size += 32 * 5; + size += 32 * 4; + } + if self >= Version::V8B { + // compression names: [[u8; 32]; 5] + size += 32 * 1; } size } + + /// Losslessly convert full version into major version + pub fn version_major(&self) -> VersionMajor { + match self { + Version::V0 => VersionMajor::Unknown, + Version::V1 => VersionMajor::Initial, + Version::V2 => VersionMajor::NoTimestamps, + Version::V3 => VersionMajor::CompressionEncryption, + Version::V4 => VersionMajor::IndexEncryption, + Version::V5 => VersionMajor::RelativeChunkOffsets, + Version::V6 => VersionMajor::DeleteRecords, + Version::V7 => VersionMajor::EncryptionKeyGuid, + Version::V8A => VersionMajor::FNameBasedCompression, + Version::V8B => VersionMajor::FNameBasedCompression, + Version::V9 => VersionMajor::FrozenIndex, + Version::V10 => VersionMajor::PathHashIndex, + Version::V11 => VersionMajor::Fnv64BugFix, + } + } } #[derive(Default, Clone, Copy, PartialEq, Eq, Debug, strum::Display, strum::EnumString)] diff --git a/src/pak.rs b/src/pak.rs index cd92382..c19939e 100644 --- a/src/pak.rs +++ b/src/pak.rs @@ -1,4 +1,4 @@ -use super::Version; +use super::{Version, VersionMajor}; use hashbrown::HashMap; use std::io::{self, Seek}; @@ -102,7 +102,7 @@ impl PakReader { let mount_point = index.read_string()?; let len = index.read_u32::()? as usize; - let index = if version >= Version::PathHashIndex { + let index = if version.version_major() >= VersionMajor::PathHashIndex { let path_hash_seed = index.read_u64::()?; let path_hash_index = if index.read_u32::()? != 0 { diff --git a/tests/test.rs b/tests/test.rs index 8c47129..34ba6a8 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -65,12 +65,12 @@ macro_rules! encryptindex { matrix_test!( ( - "v5" unpak::Version::RelativeChunkOffsets, - "v7" unpak::Version::EncryptionKeyGuid, - "v8a" unpak::Version::FNameBasedCompression, - "v8b" unpak::Version::FNameBasedCompression, - "v9" unpak::Version::FrozenIndex, - "v11" unpak::Version::Fnv64BugFix, + "v5" unpak::Version::V5, + "v7" unpak::Version::V7, + "v8a" unpak::Version::V8A, + "v8b" unpak::Version::V8B, + "v9" unpak::Version::V9, + "v11" unpak::Version::V11, ), ("", "_compress"), ("", "_encrypt"),