diff --git a/.gitignore b/.gitignore index 0595486..87991c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *target/ -*.lock \ No newline at end of file +*.lock +/tests/config.sh diff --git a/Cargo.toml b/Cargo.toml index 9a920fc..d85f8ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,6 @@ flate2 = "1.0" hashbrown = "0.13" thiserror = "1.0" base64 = "0.21.0" + +[dev-dependencies] +paste = "1.0.11" diff --git a/src/lib.rs b/src/lib.rs index 50679e6..717d727 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,16 +14,18 @@ pub const MAGIC: u32 = 0x5A6F12E1; Clone, Copy, PartialEq, Eq, PartialOrd, Debug, strum::Display, strum::FromRepr, strum::EnumIter, )] pub enum Version { - Unknown, // unknown (mostly just for padding) - Initial, // initial specification - NoTimestamps, // timestamps removed - CompressionEncryption, // compression and encryption support - IndexEncryption, // index encryption support - RelativeChunkOffsets, // offsets are relative to header - DeleteRecords, // record deletion support - EncryptionKeyGuid, // include key GUID - FNameBasedCompression, // compression names included - FrozenIndex, // frozen index byte included + Unknown, // v0 unknown (mostly just for padding) + Initial, // v1 initial specification + NoTimestamps, // v2 timestamps removed + CompressionEncryption, // v3 compression and encryption support + IndexEncryption, // v4 index encryption support + RelativeChunkOffsets, // v5 offsets are relative to header + DeleteRecords, // v6 record deletion support + EncryptionKeyGuid, // v7 include key GUID + FNameBasedCompression, // v8 compression names included + FrozenIndex, // v9 frozen index byte included + PathHashIndex, // v10 + Fnv64BugFix, // v11 } // strum shouldn't need to be installed by users diff --git a/tests/config.example.sh b/tests/config.example.sh new file mode 100644 index 0000000..af6ce58 --- /dev/null +++ b/tests/config.example.sh @@ -0,0 +1,5 @@ +UNREAL_4_20=ue/4.20/Engine/Binaries/Linux/UnrealPak +UNREAL_4_21=ue/4.21/Engine/Binaries/Linux/UnrealPak +UNREAL_4_23=ue/4.23/Engine/Binaries/Linux/UnrealPak +UNREAL_4_25=ue/4.25/Engine/Binaries/Linux/UnrealPak +UNREAL_4_27=ue/4.27/Engine/Binaries/Linux/UnrealPak diff --git a/tests/crypto.json b/tests/crypto.json new file mode 100644 index 0000000..eabb6a5 --- /dev/null +++ b/tests/crypto.json @@ -0,0 +1,27 @@ +{ + "$types":{ + "UnrealBuildTool.EncryptionAndSigning+CryptoSettings, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null":"1", + "UnrealBuildTool.EncryptionAndSigning+EncryptionKey, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null":"2", + "UnrealBuildTool.EncryptionAndSigning+SigningKeyPair, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null":"3", + "UnrealBuildTool.EncryptionAndSigning+SigningKey, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null":"4" + }, + "$type":"1", + "EncryptionKey":{ + "$type":"2", + "Name":"key", + "Guid":"00000000000000000000000000000000", + "Key":"lNJbw660IOC+kU7cnVQ1oeqrXyhk4J6UAZrCBbcnp94=" + }, + "SigningKey": null, + "bEnablePakSigning":true, + "bEnablePakIndexEncryption":true, + "bEnablePakIniEncryption":true, + "bEnablePakUAssetEncryption":true, + "bEnablePakFullAssetEncryption":false, + "bDataCryptoRequired":true, + "PakEncryptionRequired":true, + "PakSigningRequired":true, + "SecondaryEncryptionKeys":[ + + ] +} diff --git a/tests/generate.sh b/tests/generate.sh new file mode 100755 index 0000000..d706712 --- /dev/null +++ b/tests/generate.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -a +. config.sh +set +a + +unreal-version-for() { + case "$1" in + 5) + echo -n "$UNREAL_4_20" + ;; + 7) + echo -n "$UNREAL_4_21" + ;; + 8) + echo -n "$UNREAL_4_23" + ;; + 9) + echo -n "$UNREAL_4_25" + ;; + 11) + echo -n "$UNREAL_4_27" + ;; + esac +} + +generate() { + rm -r packs && mkdir packs + _version=(5 7 8 9 11) + _compress=("" "-compress") + _encrypt=("" "-encrypt") + _encryptindex=("" "-encryptindex") + echo "\"$(realpath "pack/*")\" \"../mount/point/\"" > input.txt + for version in "${_version[@]}"; do + for compress in "${_compress[@]}"; do + for encrypt in "${_encrypt[@]}"; do + for encryptindex in "${_encryptindex[@]}"; do + name="$version$compress$encrypt$encryptindex" + "$(unreal-version-for "$version")" "$(realpath "packs/pack_v${name//-/_}.pak")" -Create="$(realpath input.txt)" -cryptokeys="$(realpath crypto.json)" ${compress:+"$compress"} ${encrypt:+"$encrypt"} ${encryptindex:+"$encryptindex"} & + done + done + done + done + wait + rm input.txt +} + +if [ $# -eq 0 ]; then + generate +else + "$@" +fi diff --git a/tests/pack/root/directory/nested.txt b/tests/pack/root/directory/nested.txt new file mode 100644 index 0000000..e885033 --- /dev/null +++ b/tests/pack/root/directory/nested.txt @@ -0,0 +1 @@ +Proin urna leo, placerat non tristique sed, commodo sit amet enim. Nam aliquet metus et turpis semper tempus. Aliquam vitae dolor aliquam, elementum augue non, molestie nisi. Maecenas aliquet sagittis elit, id elementum magna dictum sed. Vivamus nulla nulla, aliquet et magna ut, tempus ultrices diam. Donec posuere fringilla feugiat. Etiam imperdiet neque nec mollis ornare. Fusce mollis neque risus, ac molestie ligula sagittis vel. Nam tempus et ante eget egestas. Curabitur porta placerat nisi ut vehicula. Nunc suscipit lacinia leo nec tincidunt. Phasellus blandit arcu non pulvinar mollis. diff --git a/tests/pack/root/test.png b/tests/pack/root/test.png new file mode 100644 index 0000000..1dbc55a Binary files /dev/null and b/tests/pack/root/test.png differ diff --git a/tests/pack/root/test.txt b/tests/pack/root/test.txt new file mode 100644 index 0000000..1b37687 --- /dev/null +++ b/tests/pack/root/test.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/tests/pack/root/zeros.bin b/tests/pack/root/zeros.bin new file mode 100644 index 0000000..e9784eb Binary files /dev/null and b/tests/pack/root/zeros.bin differ diff --git a/tests/packs/pack_v11.pak b/tests/packs/pack_v11.pak new file mode 100644 index 0000000..e68fc9f Binary files /dev/null and b/tests/packs/pack_v11.pak differ diff --git a/tests/packs/pack_v11_compress.pak b/tests/packs/pack_v11_compress.pak new file mode 100644 index 0000000..09a851b Binary files /dev/null and b/tests/packs/pack_v11_compress.pak differ diff --git a/tests/packs/pack_v11_compress_encrypt.pak b/tests/packs/pack_v11_compress_encrypt.pak new file mode 100644 index 0000000..e88a987 Binary files /dev/null and b/tests/packs/pack_v11_compress_encrypt.pak differ diff --git a/tests/packs/pack_v11_compress_encrypt_encryptindex.pak b/tests/packs/pack_v11_compress_encrypt_encryptindex.pak new file mode 100644 index 0000000..69482fb Binary files /dev/null and b/tests/packs/pack_v11_compress_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v11_compress_encryptindex.pak b/tests/packs/pack_v11_compress_encryptindex.pak new file mode 100644 index 0000000..2cffbfe Binary files /dev/null and b/tests/packs/pack_v11_compress_encryptindex.pak differ diff --git a/tests/packs/pack_v11_encrypt.pak b/tests/packs/pack_v11_encrypt.pak new file mode 100644 index 0000000..dd240fb Binary files /dev/null and b/tests/packs/pack_v11_encrypt.pak differ diff --git a/tests/packs/pack_v11_encrypt_encryptindex.pak b/tests/packs/pack_v11_encrypt_encryptindex.pak new file mode 100644 index 0000000..3fb8d6b Binary files /dev/null and b/tests/packs/pack_v11_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v11_encryptindex.pak b/tests/packs/pack_v11_encryptindex.pak new file mode 100644 index 0000000..1967f42 Binary files /dev/null and b/tests/packs/pack_v11_encryptindex.pak differ diff --git a/tests/packs/pack_v5.pak b/tests/packs/pack_v5.pak new file mode 100644 index 0000000..30640d5 Binary files /dev/null and b/tests/packs/pack_v5.pak differ diff --git a/tests/packs/pack_v5_compress.pak b/tests/packs/pack_v5_compress.pak new file mode 100644 index 0000000..7fab8eb Binary files /dev/null and b/tests/packs/pack_v5_compress.pak differ diff --git a/tests/packs/pack_v5_compress_encrypt.pak b/tests/packs/pack_v5_compress_encrypt.pak new file mode 100644 index 0000000..20957c2 Binary files /dev/null and b/tests/packs/pack_v5_compress_encrypt.pak differ diff --git a/tests/packs/pack_v5_compress_encrypt_encryptindex.pak b/tests/packs/pack_v5_compress_encrypt_encryptindex.pak new file mode 100644 index 0000000..8a86e23 Binary files /dev/null and b/tests/packs/pack_v5_compress_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v5_compress_encryptindex.pak b/tests/packs/pack_v5_compress_encryptindex.pak new file mode 100644 index 0000000..a58070c Binary files /dev/null and b/tests/packs/pack_v5_compress_encryptindex.pak differ diff --git a/tests/packs/pack_v5_encrypt.pak b/tests/packs/pack_v5_encrypt.pak new file mode 100644 index 0000000..9dcef18 Binary files /dev/null and b/tests/packs/pack_v5_encrypt.pak differ diff --git a/tests/packs/pack_v5_encrypt_encryptindex.pak b/tests/packs/pack_v5_encrypt_encryptindex.pak new file mode 100644 index 0000000..7ab5d06 Binary files /dev/null and b/tests/packs/pack_v5_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v5_encryptindex.pak b/tests/packs/pack_v5_encryptindex.pak new file mode 100644 index 0000000..9dadf46 Binary files /dev/null and b/tests/packs/pack_v5_encryptindex.pak differ diff --git a/tests/packs/pack_v7.pak b/tests/packs/pack_v7.pak new file mode 100644 index 0000000..aeda3a6 Binary files /dev/null and b/tests/packs/pack_v7.pak differ diff --git a/tests/packs/pack_v7_compress.pak b/tests/packs/pack_v7_compress.pak new file mode 100644 index 0000000..8ffb64a Binary files /dev/null and b/tests/packs/pack_v7_compress.pak differ diff --git a/tests/packs/pack_v7_compress_encrypt.pak b/tests/packs/pack_v7_compress_encrypt.pak new file mode 100644 index 0000000..cd1c249 Binary files /dev/null and b/tests/packs/pack_v7_compress_encrypt.pak differ diff --git a/tests/packs/pack_v7_compress_encrypt_encryptindex.pak b/tests/packs/pack_v7_compress_encrypt_encryptindex.pak new file mode 100644 index 0000000..97d7973 Binary files /dev/null and b/tests/packs/pack_v7_compress_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v7_compress_encryptindex.pak b/tests/packs/pack_v7_compress_encryptindex.pak new file mode 100644 index 0000000..796c559 Binary files /dev/null and b/tests/packs/pack_v7_compress_encryptindex.pak differ diff --git a/tests/packs/pack_v7_encrypt.pak b/tests/packs/pack_v7_encrypt.pak new file mode 100644 index 0000000..5148260 Binary files /dev/null and b/tests/packs/pack_v7_encrypt.pak differ diff --git a/tests/packs/pack_v7_encrypt_encryptindex.pak b/tests/packs/pack_v7_encrypt_encryptindex.pak new file mode 100644 index 0000000..ac98cd4 Binary files /dev/null and b/tests/packs/pack_v7_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v7_encryptindex.pak b/tests/packs/pack_v7_encryptindex.pak new file mode 100644 index 0000000..64d6330 Binary files /dev/null and b/tests/packs/pack_v7_encryptindex.pak differ diff --git a/tests/packs/pack_v8.pak b/tests/packs/pack_v8.pak new file mode 100644 index 0000000..981aca0 Binary files /dev/null and b/tests/packs/pack_v8.pak differ diff --git a/tests/packs/pack_v8_compress.pak b/tests/packs/pack_v8_compress.pak new file mode 100644 index 0000000..47f6443 Binary files /dev/null and b/tests/packs/pack_v8_compress.pak differ diff --git a/tests/packs/pack_v8_compress_encrypt.pak b/tests/packs/pack_v8_compress_encrypt.pak new file mode 100644 index 0000000..755f9a6 Binary files /dev/null and b/tests/packs/pack_v8_compress_encrypt.pak differ diff --git a/tests/packs/pack_v8_compress_encrypt_encryptindex.pak b/tests/packs/pack_v8_compress_encrypt_encryptindex.pak new file mode 100644 index 0000000..e358ada Binary files /dev/null and b/tests/packs/pack_v8_compress_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v8_compress_encryptindex.pak b/tests/packs/pack_v8_compress_encryptindex.pak new file mode 100644 index 0000000..0b5c69f Binary files /dev/null and b/tests/packs/pack_v8_compress_encryptindex.pak differ diff --git a/tests/packs/pack_v8_encrypt.pak b/tests/packs/pack_v8_encrypt.pak new file mode 100644 index 0000000..cead107 Binary files /dev/null and b/tests/packs/pack_v8_encrypt.pak differ diff --git a/tests/packs/pack_v8_encrypt_encryptindex.pak b/tests/packs/pack_v8_encrypt_encryptindex.pak new file mode 100644 index 0000000..1494fcf Binary files /dev/null and b/tests/packs/pack_v8_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v8_encryptindex.pak b/tests/packs/pack_v8_encryptindex.pak new file mode 100644 index 0000000..6c846e9 Binary files /dev/null and b/tests/packs/pack_v8_encryptindex.pak differ diff --git a/tests/packs/pack_v9.pak b/tests/packs/pack_v9.pak new file mode 100644 index 0000000..2d684c0 Binary files /dev/null and b/tests/packs/pack_v9.pak differ diff --git a/tests/packs/pack_v9_compress.pak b/tests/packs/pack_v9_compress.pak new file mode 100644 index 0000000..f062f88 Binary files /dev/null and b/tests/packs/pack_v9_compress.pak differ diff --git a/tests/packs/pack_v9_compress_encrypt.pak b/tests/packs/pack_v9_compress_encrypt.pak new file mode 100644 index 0000000..c2e6aa5 Binary files /dev/null and b/tests/packs/pack_v9_compress_encrypt.pak differ diff --git a/tests/packs/pack_v9_compress_encrypt_encryptindex.pak b/tests/packs/pack_v9_compress_encrypt_encryptindex.pak new file mode 100644 index 0000000..6175846 Binary files /dev/null and b/tests/packs/pack_v9_compress_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v9_compress_encryptindex.pak b/tests/packs/pack_v9_compress_encryptindex.pak new file mode 100644 index 0000000..ce994cd Binary files /dev/null and b/tests/packs/pack_v9_compress_encryptindex.pak differ diff --git a/tests/packs/pack_v9_encrypt.pak b/tests/packs/pack_v9_encrypt.pak new file mode 100644 index 0000000..3e859cd Binary files /dev/null and b/tests/packs/pack_v9_encrypt.pak differ diff --git a/tests/packs/pack_v9_encrypt_encryptindex.pak b/tests/packs/pack_v9_encrypt_encryptindex.pak new file mode 100644 index 0000000..d29b653 Binary files /dev/null and b/tests/packs/pack_v9_encrypt_encryptindex.pak differ diff --git a/tests/packs/pack_v9_encryptindex.pak b/tests/packs/pack_v9_encryptindex.pak new file mode 100644 index 0000000..eb2e4d1 Binary files /dev/null and b/tests/packs/pack_v9_encryptindex.pak differ diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..f4f4a1a --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,95 @@ +fn load_pak( + bytes: &[u8], + key: Option, +) -> Result>, unpak::Error> { + use aes::cipher::KeyInit; + use base64::{engine::general_purpose, Engine as _}; + let key = key + .map(|k| { + general_purpose::STANDARD + .decode(k) + .as_ref() + .map_err(|_| unpak::Error::Base64) + .and_then(|bytes| { + aes::Aes256Dec::new_from_slice(bytes).map_err(|_| unpak::Error::Aes) + }) + }) + .transpose()?; + + for ver in unpak::Version::iter() { + match unpak::Pak::new(std::io::Cursor::new(bytes), ver, key.clone()) { + Ok(pak) => { + return Ok(pak); + } + _ => { + continue; + } + } + } + Err(unpak::Error::Other("version unsupported")) +} + +static AES_KEY: &str = "lNJbw660IOC+kU7cnVQ1oeqrXyhk4J6UAZrCBbcnp94="; + +use paste::paste; + +macro_rules! matrix_test { + ( ($($version:literal $exp_version:expr),* $(,)?), $compress:tt, $encrypt:tt, $encryptindex:tt ) => { + $( compress!($version, $exp_version, $compress, $encrypt, $encryptindex); )* + }; +} + +macro_rules! compress { + ( $version:literal, $exp_version:expr, ($($compress:literal),* $(,)?), $encrypt:tt, $encryptindex:tt ) => { + $( encrypt!($version, $exp_version, $compress, $encrypt, $encryptindex); )* + }; +} + +macro_rules! encrypt { + ( $version:literal, $exp_version:expr, $compress:literal, ($($encrypt:literal),* $(,)?), $encryptindex:tt ) => { + $( encryptindex!($version, $exp_version, $compress, $encrypt, $encryptindex); )* + }; +} + +macro_rules! encryptindex { + ( $version:literal, $exp_version:expr, $compress:literal, $encrypt:literal, ($($encryptindex:literal),* $(,)?) ) => { + $( + paste! { + #[test] + fn [< test_version_ $version $compress $encrypt $encryptindex >]() { + let mut pak = load_pak(include_bytes!(concat!("packs/pack_", $version, $compress, $encrypt, $encryptindex, ".pak")), Some(AES_KEY.to_string())).unwrap(); + assert_eq!(pak.version(), $exp_version); + use std::collections::HashSet; + let files: HashSet = HashSet::from_iter(pak.files()); + assert_eq!(files, HashSet::from_iter(vec!["test.txt", "test.png", "zeros.bin", "directory/nested.txt"].into_iter().map(String::from))); + + for file in files { + let mut buf = vec![]; + let mut writer = std::io::Cursor::new(&mut buf); + pak.read(&file, &mut writer).unwrap(); + match file.as_str() { + "test.txt" => assert_eq!(buf, include_bytes!("pack/root/test.txt"), "test.txt incorrect contents"), + "test.png" => assert_eq!(buf, include_bytes!("pack/root/test.png"), "test.png incorrect contents"), + "zeros.bin" => assert_eq!(buf, include_bytes!("pack/root/zeros.bin"), "zeros.bin incorrect contents"), + "directory/nested.txt" => assert_eq!(buf, include_bytes!("pack/root/directory/nested.txt"), "nested.txt incorrect contents"), + name => panic!("unrecognized file {}", name) + } + } + } + } + )* + }; +} + +matrix_test!( + ( + "v5" unpak::Version::RelativeChunkOffsets, + "v7" unpak::Version::EncryptionKeyGuid, + "v8" unpak::Version::FNameBasedCompression, + "v9" unpak::Version::FrozenIndex, + "v11" unpak::Version::Fnv64BugFix, + ), + ("", "_compress"), + ("", "_encrypt"), + ("", "_encryptindex") +);