unpak/
lib.rs

1#![allow(dead_code)]
2mod entry;
3mod error;
4mod ext;
5mod footer;
6mod pak;
7
8pub use {error::*, pak::*};
9
10/// the magic used to identify a pak
11pub const MAGIC: u32 = 0x5A6F12E1;
12
13/// different compressions that a pak can use
14#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, strum::EnumString)]
15pub enum Compression {
16    #[default]
17    None,
18    Zlib,
19    Gzip,
20    Oodle,
21}
22
23/// the possible versions that a pak file can be
24#[repr(u32)]
25#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug, strum::Display, strum::EnumIter)]
26pub enum Version {
27    /// initial specification
28    Initial,
29    /// timestamps removed    
30    NoTimestamps,
31    /// compression and encryption support
32    CompressionEncryption,
33    /// index encryption support
34    IndexEncryption,
35    /// offsets now relative to header
36    RelativeChunkOffsets,
37    /// record deletion support
38    DeleteRecords,
39    /// include key UUID
40    EncryptionKeyUuid,
41    /// include compression names
42    FNameBasedCompression,
43    /// adds another compression name
44    FNameBasedCompression2,
45    /// include frozen index byte
46    FrozenIndex,
47    /// index format overhauled
48    PathHashIndex,
49    /// idk what this changed
50    Fnv64BugFix,
51}
52
53impl Version {
54    /// gets an iterator over the versions
55    pub fn iter() -> VersionIter {
56        <Version as strum::IntoEnumIterator>::iter()
57    }
58
59    fn as_u32(self) -> u32 {
60        match self {
61            Version::Initial => 1,
62            Version::NoTimestamps => 2,
63            Version::CompressionEncryption => 3,
64            Version::IndexEncryption => 4,
65            Version::RelativeChunkOffsets => 5,
66            Version::DeleteRecords => 6,
67            Version::EncryptionKeyUuid => 7,
68            Version::FNameBasedCompression => 8,
69            Version::FNameBasedCompression2 => 8,
70            Version::FrozenIndex => 9,
71            Version::PathHashIndex => 10,
72            Version::Fnv64BugFix => 11,
73        }
74    }
75
76    fn footer_size(self) -> i64 {
77        // (magic + version): u32 + (offset + size): u64 + hash: [u8; 20]
78        let mut size = 4 + 4 + 8 + 8 + 20;
79        if self >= Version::EncryptionKeyUuid {
80            // encryption uuid: u128
81            size += 16;
82        }
83        if self >= Version::IndexEncryption {
84            // encrypted: bool
85            size += 1;
86        }
87        if self == Version::FrozenIndex {
88            // frozen index: bool
89            size += 1;
90        }
91        if self >= Version::FNameBasedCompression {
92            // compression names: [[u8; 32]; 4]
93            size += 32 * 4;
94        }
95        if self >= Version::FNameBasedCompression2 {
96            // extra compression name: [u8; 32]
97            size += 32
98        }
99        size
100    }
101}
102
103#[cfg(feature = "encryption")]
104fn decrypt(key: Option<&aes::Aes256Dec>, bytes: &mut [u8]) -> Result<(), Error> {
105    match key {
106        Some(key) => {
107            use aes::cipher::BlockDecrypt;
108            for chunk in bytes.chunks_mut(16) {
109                key.decrypt_block(aes::Block::from_mut_slice(chunk))
110            }
111            Ok(())
112        }
113        None => Err(Error::Encrypted),
114    }
115}