DazedTL/util/wolfdawn-master/crates/wolf-archive/tests/pack_roundtrip.rs
2026-07-03 13:54:07 -05:00

178 lines
6.8 KiB
Rust

//! Plaintext packer gate: `extract(pack(files)) == files`.
//!
//! Proves the VER8 writer produces a container our own extractor reads back with identical
//! paths and bytes, across a synthetic nested tree and a real Wolf `Data/` subtree.
//! cargo test -p wolf-archive --test pack_roundtrip -- --nocapture
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use wolf_archive::{extract_archive, pack_chacha, pack_encrypted, pack_newcrypt, pack_plaintext};
/// Normalize a path to forward slashes for order-independent comparison.
fn norm(p: &str) -> String {
p.replace('\\', "/")
}
fn roundtrip(files: &[(String, Vec<u8>)]) {
let archive = pack_plaintext(files).expect("pack");
let extracted = extract_archive(&archive, b"").expect("extract our own archive");
let want: BTreeMap<String, Vec<u8>> = files.iter().map(|(p, d)| (norm(p), d.clone())).collect();
let got: BTreeMap<String, Vec<u8>> =
extracted.into_iter().map(|(p, d)| (norm(&p), d)).collect();
assert_eq!(
got.keys().collect::<Vec<_>>(),
want.keys().collect::<Vec<_>>(),
"path set differs"
);
for (p, data) in &want {
assert_eq!(got.get(p), Some(data), "bytes differ for {p}");
}
}
#[test]
fn synthetic_tree_round_trips() {
let files = vec![
("BasicData/CommonEvent.dat".to_string(), vec![1, 2, 3, 4, 5]),
("BasicData/Game.dat".to_string(), b"hello game".to_vec()),
("MapData/Map001.mps".to_string(), vec![0xAA; 1000]),
("MapData/Map002.mps".to_string(), vec![]), // empty file
("MapData/sub/Deep.mps".to_string(), vec![7; 3]), // odd length (alignment)
("top.txt".to_string(), b"a top-level file".to_vec()),
];
roundtrip(&files);
}
#[test]
fn encrypted_round_trips_through_our_reader() {
// pack_encrypted(cv) -> extract_archive (auto-detects cryptVersion = Flags>>16) -> files.
// Proves the symmetric re-encryption decodes through our deterministic dispatch.
let files = vec![
(
"BasicData/Game.dat".to_string(),
vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9],
),
("BasicData/CommonEvent.dat".to_string(), vec![0xCD; 777]),
("MapData/Map001.mps".to_string(), b"map data here".to_vec()),
("MapData/sub/Deep.mps".to_string(), vec![9, 8, 7]),
];
let want: BTreeMap<String, Vec<u8>> = files.iter().map(|(p, d)| (norm(p), d.clone())).collect();
for cv in [0x12Cu16, 0x13A] {
let archive = pack_encrypted(&files, cv).expect("encrypt");
// The archive must NOT be plaintext: data should be transformed.
let extracted = extract_archive(&archive, b"").expect("extract encrypted");
let got: BTreeMap<String, Vec<u8>> =
extracted.into_iter().map(|(p, d)| (norm(&p), d)).collect();
assert_eq!(got, want, "cryptVersion {cv:#x} did not round-trip");
}
}
#[test]
fn wolfpro_newcrypt_round_trips_through_our_reader() {
// pack_newcrypt(cv, pwd) -> extract_archive (recomputes the context from the embedded
// password) -> files. Proves the symmetric WolfPro stream + AES-CTR + address scramble
// encode is the exact inverse of our verified decrypt path.
let files = vec![
("BasicData/Game.dat".to_string(), vec![0x11u8; 2048]),
("BasicData/CommonEvent.dat".to_string(), vec![0x42; 5000]),
("MapData/Map001.mps".to_string(), b"hello wolf pro".to_vec()),
("MapData/sub/Deep.mps".to_string(), (0..255u8).collect()),
];
let want: BTreeMap<String, Vec<u8>> = files.iter().map(|(p, d)| (norm(p), d.clone())).collect();
let pwd: [u8; 15] = [
0x37, 0x9a, 0x14, 0xc2, 0x5b, 0x08, 0xe1, 0x4f, 0x2d, 0x71, 0xbc, 0x33, 0x96, 0x6a, 0xd8,
];
for cv in [0x14Bu16, 0x15E] {
let archive = pack_newcrypt(&files, cv, &pwd).expect("newcrypt encode");
let extracted = extract_archive(&archive, b"").expect("decode newcrypt");
let got: BTreeMap<String, Vec<u8>> =
extracted.into_iter().map(|(p, d)| (norm(&p), d)).collect();
assert_eq!(got, want, "WolfPro cryptVersion {cv:#x} did not round-trip");
}
}
#[test]
fn chacha2_round_trips_through_our_reader() {
let files = vec![
("BasicData/Game.dat".to_string(), vec![0x5Au8; 4096]),
(
"MapData/Map001.mps".to_string(),
b"chacha20 round trip".to_vec(),
),
("MapData/sub/Deep.mps".to_string(), (0..200u8).collect()),
];
let want: BTreeMap<String, Vec<u8>> = files.iter().map(|(p, d)| (norm(p), d.clone())).collect();
let archive = pack_chacha(&files).expect("chacha encode");
let extracted = extract_archive(&archive, b"").expect("decode chacha2");
let got: BTreeMap<String, Vec<u8>> =
extracted.into_iter().map(|(p, d)| (norm(&p), d)).collect();
assert_eq!(got, want, "ChaCha2 did not round-trip");
}
#[test]
fn real_data_subtree_round_trips() {
// Pack a real BasicData folder (ASCII paths, real binary content) and read it back.
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../Data/BasicData");
if !base.exists() {
eprintln!("no Data/BasicData corpus, skipping");
return;
}
let mut files = Vec::new();
collect(&base, &base, &mut files);
files.sort();
if files.is_empty() {
eprintln!("no files found, skipping");
return;
}
eprintln!("packing {} real files", files.len());
roundtrip(&files);
}
fn collect(base: &Path, dir: &Path, out: &mut Vec<(String, Vec<u8>)>) {
let Ok(rd) = std::fs::read_dir(dir) else {
return;
};
for e in rd.flatten() {
let p = e.path();
if p.is_dir() {
collect(base, &p, out);
} else if let Ok(bytes) = std::fs::read(&p) {
let rel = p
.strip_prefix(base)
.unwrap()
.to_string_lossy()
.replace('\\', "/");
out.push((format!("BasicData/{rel}"), bytes));
}
}
}
#[test]
fn ver5_ver6_round_trip_through_our_reader() {
use wolf_archive::{pack_ver5, pack_ver6};
let files = vec![
("BasicData/Game.dat".to_string(), vec![0x3Cu8; 1234]),
(
"BasicData/CommonEvent.dat".to_string(),
b"old wolf 2.0x".to_vec(),
),
("MapData/Map001.mps".to_string(), (0..250u8).collect()),
("MapData/sub/Deep.mps".to_string(), vec![5, 4, 3, 2, 1]),
];
let want: BTreeMap<String, Vec<u8>> = files.iter().map(|(p, d)| (norm(p), d.clone())).collect();
for (label, archive) in [
("VER5", pack_ver5(&files).expect("ver5 encode")),
("VER6", pack_ver6(&files).expect("ver6 encode")),
] {
let extracted = extract_archive(&archive, b"").expect("decode legacy archive");
let got: BTreeMap<String, Vec<u8>> =
extracted.into_iter().map(|(p, d)| (norm(&p), d)).collect();
assert_eq!(got, want, "{label} did not round-trip");
}
}