use super::*;
fn record(compressed_size: u32, uncompressed_size: u32) -> WadSubchunk {
WadSubchunk {
compressed_size,
uncompressed_size,
checksum: 0,
}
}
fn toc_bytes(records: &[WadSubchunk]) -> Vec<u8> {
let mut data = Vec::new();
for sub in records {
data.extend_from_slice(&sub.compressed_size.to_le_bytes());
data.extend_from_slice(&sub.uncompressed_size.to_le_bytes());
data.extend_from_slice(&sub.checksum.to_le_bytes());
}
data
}
fn multi_chunk(start_frame: u32, frame_count: u8, sizes: (usize, usize)) -> WadChunk {
WadChunk {
path_hash: crate::WadHash(start_frame as u64 + 1),
data_offset: 0,
compressed_size: sizes.0,
uncompressed_size: sizes.1,
compression_type: WadChunkCompression::ZstdMulti,
is_duplicated: false,
frame_count,
start_frame,
checksum: 0,
}
}
#[test]
fn parses_records_and_rejects_odd_lengths() {
let records = [record(10, 20), record(5, 5)];
let toc = SubchunkToc::from_bytes(&toc_bytes(&records)).unwrap();
assert_eq!(toc.records(), records);
assert!(toc.records()[1].is_raw());
SubchunkToc::from_bytes(&[0u8; 17]).unwrap_err();
}
#[test]
fn subchunks_of_slices_by_frame_and_bounds() {
let toc =
SubchunkToc::from_bytes(&toc_bytes(&[record(1, 2), record(3, 4), record(5, 6)])).unwrap();
let chunk = multi_chunk(1, 2, (8, 10));
assert_eq!(
toc.subchunks_of(&chunk),
Some(&[record(3, 4), record(5, 6)][..])
);
assert_eq!(toc.subchunks_of(&multi_chunk(2, 2, (0, 0))), None);
let mut zstd = chunk;
zstd.compression_type = WadChunkCompression::Zstd;
assert_eq!(toc.subchunks_of(&zstd), None);
}
#[test]
fn covers_wants_the_sums_to_match() {
let toc = SubchunkToc::from_bytes(&toc_bytes(&[record(1, 2), record(3, 4)])).unwrap();
let good = WadChunks::from_iter([multi_chunk(0, 2, (4, 6))]);
assert!(toc.covers(&good));
let bad = WadChunks::from_iter([multi_chunk(0, 2, (4, 7))]);
assert!(!toc.covers(&bad));
let out_of_range = WadChunks::from_iter([multi_chunk(1, 2, (8, 10))]);
assert!(!toc.covers(&out_of_range));
}
#[test]
fn subchunked_decode_runs_on_either_backend() {
let content: Vec<u8> = b"league subchunk ".repeat(4);
let frame: &[u8] = &[
0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x68, 0xc5, 0x00, 0x00, 0x88, 0x6c, 0x65, 0x61, 0x67, 0x75,
0x65, 0x20, 0x73, 0x75, 0x62, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x20, 0x6c, 0x01, 0x00, 0x99,
0x68, 0x2e, 0x01,
];
let raw_run = b"raw (\xb5/\xfda fake frame start";
let subchunks = [
record(raw_run.len() as u32, raw_run.len() as u32),
record(frame.len() as u32, content.len() as u32),
];
let raw: Vec<u8> = [raw_run.as_slice(), frame].concat();
let expected: Vec<u8> = [raw_run.as_slice(), &content].concat();
let data = crate::decompress_subchunked(&raw, &subchunks, expected.len()).unwrap();
assert_eq!(&data[..], &expected[..]);
let kept = crate::ChunkDecoder::new()
.decompress_subchunked(&raw, &subchunks, expected.len())
.unwrap();
assert_eq!(&kept[..], &expected[..]);
let head = crate::ChunkDecoder::new()
.decompress_subchunked_prefix(&raw, &subchunks, expected.len())
.unwrap();
assert_eq!(&head[..], &expected[..]);
}
#[cfg(feature = "zstd")]
fn subchunked_wad() -> (Vec<u8>, [WadChunk; 2], Vec<u8>, Vec<u8>) {
use byteorder::{WriteBytesExt as _, LE};
use std::io::Cursor;
use xxhash_rust::xxh3::xxh3_64;
let raw_run = b"raw (\xb5/\xfda fake frame start".to_vec();
let content = b"compressed content".repeat(20);
let content_frame =
zstd::encode_all(Cursor::new(&content[..]), 3).expect("encoding cannot fail");
let chunk_data: Vec<u8> = [raw_run.as_slice(), &content_frame].concat();
let mut records = [
record(raw_run.len() as u32, raw_run.len() as u32),
record(content_frame.len() as u32, content.len() as u32),
];
records[0].checksum = xxh3_64(&raw_run);
records[1].checksum = xxh3_64(&content_frame);
let toc_data = toc_bytes(&records);
let data_start = 2 + 1 + 1 + 256 + 8 + 4 + 2 * 32;
let chunks = [
WadChunk {
path_hash: crate::WadHash(1),
data_offset: data_start,
compressed_size: chunk_data.len(),
uncompressed_size: raw_run.len() + content.len(),
compression_type: WadChunkCompression::ZstdMulti,
is_duplicated: false,
frame_count: 2,
start_frame: 0,
checksum: 0,
},
WadChunk {
path_hash: crate::WadHash(2),
data_offset: data_start + chunk_data.len(),
compressed_size: toc_data.len(),
uncompressed_size: toc_data.len(),
compression_type: WadChunkCompression::None,
is_duplicated: false,
frame_count: 0,
start_frame: 0,
checksum: 0,
},
];
let mut file = Vec::new();
file.write_u16::<LE>(0x5752).unwrap();
file.push(3);
file.push(4);
file.extend_from_slice(&[0u8; 256]);
file.write_u64::<LE>(0).unwrap();
file.write_i32::<LE>(chunks.len() as i32).unwrap();
for chunk in &chunks {
chunk.write_v3_4(&mut file).unwrap();
}
assert_eq!(file.len(), data_start);
file.extend_from_slice(&chunk_data);
file.extend_from_slice(&toc_data);
(file, chunks, raw_run, content)
}
#[test]
#[cfg(feature = "zstd")]
fn mount_detects_the_table_and_decodes_by_it() {
use crate::Wad;
use std::io::Cursor;
let (file, mut chunks, raw_run, content) = subchunked_wad();
let expected: Vec<u8> = [raw_run.as_slice(), &content].concat();
let mut wad = Wad::mount(Cursor::new(file)).unwrap();
let toc = wad.subchunk_toc().expect("the table is found by shape");
assert_eq!(toc.records().len(), 2);
let data = wad.load_chunk_decompressed(&chunks[0]).unwrap();
assert_eq!(&data[..], &expected[..]);
chunks[1].uncompressed_size += 16;
let no_toc = detect_subchunk_toc(&WadChunks::from_iter(chunks), |_| {
Err(WadError::Other(String::from("not read")))
});
assert!(no_toc.is_none());
}
#[test]
#[cfg(feature = "zstd")]
fn load_subchunks_reads_a_verified_prefix() {
use crate::Wad;
use std::io::Cursor;
let (file, chunks, raw_run, content) = subchunked_wad();
let chunk = chunks[0];
let mut wad = Wad::mount(Cursor::new(file.clone())).unwrap();
let first = wad.load_subchunks(&chunk, 1).unwrap();
assert_eq!(&first[..], &raw_run[..]);
let all = wad.load_subchunks(&chunk, 2).unwrap();
assert_eq!(&all[..], &[raw_run.as_slice(), &content].concat()[..]);
wad.load_subchunks(&chunk, 0).unwrap_err();
wad.load_subchunks(&chunk, 3).unwrap_err();
wad.load_subchunks(&chunks[1], 1).unwrap_err();
let mut corrupt = file;
corrupt[chunk.data_offset] ^= 1;
let mut wad = Wad::mount(Cursor::new(corrupt)).unwrap();
let error = wad.load_subchunks(&chunk, 1).unwrap_err();
assert!(error.to_string().contains("checksum"), "{error}");
}