Refactor version handling to handle subversions and correctly parse v8a

This commit is contained in:
Truman Kilen 2023-01-20 12:09:44 -06:00
parent af3ef60f30
commit 529bdcac4e
5 changed files with 82 additions and 35 deletions

View file

@ -1,4 +1,4 @@
use super::{ext::ReadExt, Compression, Version}; use super::{ext::ReadExt, Compression, Version, VersionMajor};
use byteorder::{ReadBytesExt, LE}; use byteorder::{ReadBytesExt, LE};
use std::io; use std::io;
@ -45,8 +45,11 @@ impl Entry {
size += 8; // offset size += 8; // offset
size += 8; // compressed size += 8; // compressed
size += 8; // uncompressed size += 8; // uncompressed
size += 4; // compression size += match version != Version::V8A {
size += match version == Version::Initial { true => 4, // 32 bit compression
false => 1, // 8 bit compression
};
size += match version.version_major() == VersionMajor::Initial {
true => 8, // timestamp true => 8, // timestamp
false => 0, false => 0,
}; };
@ -56,7 +59,7 @@ impl Entry {
false => 0, false => 0,
}; };
size += 1; // encrypted size += 1; // encrypted
size += match version >= Version::CompressionEncryption { size += match version.version_major() >= VersionMajor::CompressionEncryption {
true => 4, // blocks uncompressed true => 4, // blocks uncompressed
false => 0, false => 0,
}; };
@ -68,7 +71,7 @@ impl Entry {
let offset = reader.read_u64::<LE>()?; let offset = reader.read_u64::<LE>()?;
let compressed = reader.read_u64::<LE>()?; let compressed = reader.read_u64::<LE>()?;
let uncompressed = reader.read_u64::<LE>()?; let uncompressed = reader.read_u64::<LE>()?;
let compression = match reader.read_u32::<LE>()? { let compression = match if version == Version::V8A { reader.read_u8()? as u32 } else { reader.read_u32::<LE>()? } {
0x01 | 0x10 | 0x20 => Compression::Zlib, 0x01 | 0x10 | 0x20 => Compression::Zlib,
_ => Compression::None, _ => Compression::None,
}; };
@ -77,19 +80,19 @@ impl Entry {
compressed, compressed,
uncompressed, uncompressed,
compression, compression,
timestamp: match version == Version::Initial { timestamp: match version.version_major() == VersionMajor::Initial {
true => Some(reader.read_u64::<LE>()?), true => Some(reader.read_u64::<LE>()?),
false => None, false => None,
}, },
hash: Some(reader.read_guid()?), hash: Some(reader.read_guid()?),
blocks: match version >= Version::CompressionEncryption blocks: match version.version_major() >= VersionMajor::CompressionEncryption
&& compression != Compression::None && compression != Compression::None
{ {
true => Some(reader.read_array(Block::new)?), true => Some(reader.read_array(Block::new)?),
false => None, false => None,
}, },
encrypted: version >= Version::CompressionEncryption && reader.read_bool()?, encrypted: version.version_major() >= VersionMajor::CompressionEncryption && reader.read_bool()?,
block_uncompressed: match version >= Version::CompressionEncryption { block_uncompressed: match version.version_major() >= VersionMajor::CompressionEncryption {
true => Some(reader.read_u32::<LE>()?), true => Some(reader.read_u32::<LE>()?),
false => None, false => None,
}, },
@ -140,7 +143,7 @@ impl Entry {
}; };
let offset_base = let offset_base =
match version >= super::Version::RelativeChunkOffsets { match version.version_major() >= VersionMajor::RelativeChunkOffsets {
true => 0, true => 0,
false => offset, false => offset,
} + Entry::get_serialized_size(version, compression, compression_block_count); } + Entry::get_serialized_size(version, compression, compression_block_count);
@ -189,7 +192,7 @@ impl Entry {
pub fn read<R: io::Read + io::Seek, W: io::Write>( pub fn read<R: io::Read + io::Seek, W: io::Write>(
&self, &self,
reader: &mut R, reader: &mut R,
version: super::Version, version: Version,
key: Option<&aes::Aes256Dec>, key: Option<&aes::Aes256Dec>,
buf: &mut W, buf: &mut W,
) -> Result<(), super::Error> { ) -> Result<(), super::Error> {
@ -217,7 +220,7 @@ impl Entry {
for block in blocks { for block in blocks {
io::copy( io::copy(
&mut <$decompressor>::new( &mut <$decompressor>::new(
&data[match version >= Version::RelativeChunkOffsets { &data[match version.version_major() >= VersionMajor::RelativeChunkOffsets {
true => { true => {
(block.start - (data_offset - self.offset)) as usize (block.start - (data_offset - self.offset)) as usize
..(block.end - (data_offset - self.offset)) as usize ..(block.end - (data_offset - self.offset)) as usize

View file

@ -1,4 +1,4 @@
use super::{ext::ReadExt, Compression, Version}; use super::{ext::ReadExt, Compression, Version, VersionMajor};
use byteorder::{ReadBytesExt, LE}; use byteorder::{ReadBytesExt, LE};
use std::str::FromStr; use std::str::FromStr;
@ -8,6 +8,7 @@ pub struct Footer {
pub encrypted: bool, pub encrypted: bool,
pub magic: u32, pub magic: u32,
pub version: Version, pub version: Version,
pub version_major: VersionMajor,
pub index_offset: u64, pub index_offset: u64,
pub index_size: u64, pub index_size: u64,
pub hash: [u8; 20], pub hash: [u8; 20],
@ -18,21 +19,22 @@ pub struct Footer {
impl Footer { impl Footer {
pub fn new<R: std::io::Read>(reader: &mut R, version: Version) -> Result<Self, super::Error> { pub fn new<R: std::io::Read>(reader: &mut R, version: Version) -> Result<Self, super::Error> {
let footer = Self { let footer = Self {
encryption_uuid: match version >= Version::EncryptionKeyGuid { encryption_uuid: match version.version_major() >= VersionMajor::EncryptionKeyGuid {
true => Some(reader.read_u128::<LE>()?), true => Some(reader.read_u128::<LE>()?),
false => None, false => None,
}, },
encrypted: version >= Version::IndexEncryption && reader.read_bool()?, encrypted: version.version_major() >= VersionMajor::IndexEncryption && reader.read_bool()?,
magic: reader.read_u32::<LE>()?, magic: reader.read_u32::<LE>()?,
version: Version::from_repr(reader.read_u32::<LE>()?).unwrap_or(version), version,
version_major: VersionMajor::from_repr(reader.read_u32::<LE>()?).unwrap_or(version.version_major()),
index_offset: reader.read_u64::<LE>()?, index_offset: reader.read_u64::<LE>()?,
index_size: reader.read_u64::<LE>()?, index_size: reader.read_u64::<LE>()?,
hash: reader.read_guid()?, hash: reader.read_guid()?,
frozen: version == Version::FrozenIndex && reader.read_bool()?, frozen: version.version_major() == VersionMajor::FrozenIndex && reader.read_bool()?,
compression: { compression: {
let mut compression = Vec::with_capacity(match version { let mut compression = Vec::with_capacity(match version {
ver if ver < Version::FNameBasedCompression => 0, ver if ver < Version::V8A => 0,
Version::FNameBasedCompression => 4, ver if ver < Version::V8B => 4,
_ => 5, _ => 5,
}); });
for _ in 0..compression.capacity() { for _ in 0..compression.capacity() {
@ -54,7 +56,7 @@ impl Footer {
if super::MAGIC != footer.magic { if super::MAGIC != footer.magic {
return Err(super::Error::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 { return Err(super::Error::Version {
used: version, used: version,
version: footer.version, version: footer.version,

View file

@ -9,12 +9,31 @@ pub use {error::*, pak::*};
pub const MAGIC: u32 = 0x5A6F12E1; 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)] #[repr(u32)]
#[derive( #[derive(
Clone, Copy, PartialEq, Eq, PartialOrd, Debug, strum::Display, strum::FromRepr, strum::EnumIter, Clone, Copy, PartialEq, Eq, PartialOrd, Debug, strum::Display, strum::FromRepr, strum::EnumIter,
)] )]
/// Version actually written to the pak file
pub enum Version { pub enum VersionMajor {
Unknown, // v0 unknown (mostly just for padding) Unknown, // v0 unknown (mostly just for padding)
Initial, // v1 initial specification Initial, // v1 initial specification
NoTimestamps, // v2 timestamps removed NoTimestamps, // v2 timestamps removed
@ -38,24 +57,47 @@ impl Version {
pub fn size(self) -> i64 { pub fn size(self) -> i64 {
// (magic + version): u32 + (offset + size): u64 + hash: [u8; 20] // (magic + version): u32 + (offset + size): u64 + hash: [u8; 20]
let mut size = 4 + 4 + 8 + 8 + 20; let mut size = 4 + 4 + 8 + 8 + 20;
if self >= Version::EncryptionKeyGuid { if self.version_major() >= VersionMajor::EncryptionKeyGuid {
// encryption uuid: u128 // encryption uuid: u128
size += 16; size += 16;
} }
if self >= Version::IndexEncryption { if self.version_major() >= VersionMajor::IndexEncryption {
// encrypted: bool // encrypted: bool
size += 1; size += 1;
} }
if self == Version::FrozenIndex { if self.version_major() == VersionMajor::FrozenIndex {
// frozen index: bool // frozen index: bool
size += 1; size += 1;
} }
if self >= Version::FNameBasedCompression { if self >= Version::V8A {
// compression names: [[u8; 32]; 5] // compression names: [[u8; 32]; 5]
size += 32 * 5; size += 32 * 4;
}
if self >= Version::V8B {
// compression names: [[u8; 32]; 5]
size += 32 * 1;
} }
size 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)] #[derive(Default, Clone, Copy, PartialEq, Eq, Debug, strum::Display, strum::EnumString)]

View file

@ -1,4 +1,4 @@
use super::Version; use super::{Version, VersionMajor};
use hashbrown::HashMap; use hashbrown::HashMap;
use std::io::{self, Seek}; use std::io::{self, Seek};
@ -102,7 +102,7 @@ impl<R: io::Read + io::Seek> PakReader<R> {
let mount_point = index.read_string()?; let mount_point = index.read_string()?;
let len = index.read_u32::<LE>()? as usize; let len = index.read_u32::<LE>()? as usize;
let index = if version >= Version::PathHashIndex { let index = if version.version_major() >= VersionMajor::PathHashIndex {
let path_hash_seed = index.read_u64::<LE>()?; let path_hash_seed = index.read_u64::<LE>()?;
let path_hash_index = if index.read_u32::<LE>()? != 0 { let path_hash_index = if index.read_u32::<LE>()? != 0 {

View file

@ -65,12 +65,12 @@ macro_rules! encryptindex {
matrix_test!( matrix_test!(
( (
"v5" unpak::Version::RelativeChunkOffsets, "v5" unpak::Version::V5,
"v7" unpak::Version::EncryptionKeyGuid, "v7" unpak::Version::V7,
"v8a" unpak::Version::FNameBasedCompression, "v8a" unpak::Version::V8A,
"v8b" unpak::Version::FNameBasedCompression, "v8b" unpak::Version::V8B,
"v9" unpak::Version::FrozenIndex, "v9" unpak::Version::V9,
"v11" unpak::Version::Fnv64BugFix, "v11" unpak::Version::V11,
), ),
("", "_compress"), ("", "_compress"),
("", "_encrypt"), ("", "_encrypt"),