use tact_parser::install::{InstallManifest, Platform};
fn create_test_install_manifest(
tags: Vec<(&str, u16, Vec<bool>)>,
entries: Vec<(&str, Vec<u8>, u32)>,
) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&[0x49, 0x4E]);
data.push(1);
data.push(16);
data.extend_from_slice(&(tags.len() as u16).to_be_bytes());
data.extend_from_slice(&(entries.len() as u32).to_be_bytes());
let bytes_per_tag = entries.len().div_ceil(8);
for (name, tag_type, mask) in &tags {
data.extend_from_slice(name.as_bytes());
data.push(0);
data.extend_from_slice(&(*tag_type).to_be_bytes());
let mut mask_bytes = vec![0u8; bytes_per_tag];
for (i, &has_tag) in mask.iter().enumerate() {
if has_tag {
mask_bytes[i / 8] |= 1 << (i % 8);
}
}
data.extend_from_slice(&mask_bytes);
}
for (path, ckey, size) in &entries {
data.extend_from_slice(path.as_bytes());
data.push(0);
data.extend_from_slice(ckey);
data.extend_from_slice(&size.to_be_bytes());
}
data
}
#[test]
fn test_multi_platform_install() {
let tags = vec![
("Windows", 0x01, vec![true, false, true, true]), ("OSX", 0x02, vec![false, true, true, false]), ("enUS", 0x04, vec![true, true, true, true]), ];
let entries = vec![
("game.exe", vec![1u8; 16], 1000), ("game.app", vec![2u8; 16], 2000), ("data/shared.dat", vec![3u8; 16], 3000), ("win64.dll", vec![4u8; 16], 4000), ];
let data = create_test_install_manifest(tags, entries);
let manifest = InstallManifest::parse(&data).unwrap();
let windows_files = manifest.get_files_for_platform(Platform::Windows);
assert_eq!(windows_files.len(), 3);
assert!(windows_files.iter().any(|f| f.path == "game.exe"));
assert!(windows_files.iter().any(|f| f.path == "data/shared.dat"));
assert!(windows_files.iter().any(|f| f.path == "win64.dll"));
let mac_files = manifest.get_files_for_platform(Platform::Mac);
assert_eq!(mac_files.len(), 2);
assert!(mac_files.iter().any(|f| f.path == "game.app"));
assert!(mac_files.iter().any(|f| f.path == "data/shared.dat"));
assert_eq!(
manifest.calculate_size_for_platform(Platform::Windows),
8000
); assert_eq!(manifest.calculate_size_for_platform(Platform::Mac), 5000); assert_eq!(manifest.calculate_size_for_platform(Platform::All), 10000);
let windows_enus = manifest.get_files_for_tags(&["Windows", "enUS"]);
assert_eq!(windows_enus.len(), 3);
let all_tags = manifest.get_all_tags();
assert!(all_tags.contains(&"Windows"));
assert!(all_tags.contains(&"OSX"));
assert!(all_tags.contains(&"enUS"));
}
#[test]
fn test_locale_specific_files() {
let tags = vec![
("enUS", 0x01, vec![true, false, false, true]),
("deDE", 0x02, vec![false, true, false, true]),
("frFR", 0x04, vec![false, false, true, true]),
("Common", 0x08, vec![false, false, false, true]),
];
let entries = vec![
("locale/enUS/strings.db", vec![1u8; 16], 100),
("locale/deDE/strings.db", vec![2u8; 16], 110),
("locale/frFR/strings.db", vec![3u8; 16], 120),
("data/common.dat", vec![4u8; 16], 5000),
];
let data = create_test_install_manifest(tags, entries);
let manifest = InstallManifest::parse(&data).unwrap();
let enus_files = manifest.get_files_for_tags(&["enUS"]);
assert_eq!(enus_files.len(), 2);
assert!(
enus_files
.iter()
.any(|f| f.path == "locale/enUS/strings.db")
);
assert!(enus_files.iter().any(|f| f.path == "data/common.dat"));
let dede_files = manifest.get_files_for_tags(&["deDE"]);
assert_eq!(dede_files.len(), 2);
assert!(
dede_files
.iter()
.any(|f| f.path == "locale/deDE/strings.db")
);
assert_eq!(manifest.calculate_size_for_tags(&["enUS"]), 5100);
assert_eq!(manifest.calculate_size_for_tags(&["deDE"]), 5110);
assert_eq!(manifest.calculate_size_for_tags(&["frFR"]), 5120);
let common_only = manifest.get_files_for_tags(&["Common"]);
assert_eq!(common_only.len(), 1);
assert_eq!(common_only[0].path, "data/common.dat");
}
#[test]
fn test_get_file_by_path() {
let tags = vec![("All", 0x01, vec![true, true, true])];
let entries = vec![
("file1.dat", vec![1u8; 16], 1000),
("dir/file2.dat", vec![2u8; 16], 2000),
("dir/subdir/file3.dat", vec![3u8; 16], 3000),
];
let data = create_test_install_manifest(tags, entries);
let manifest = InstallManifest::parse(&data).unwrap();
let file1 = manifest.get_file_by_path("file1.dat").unwrap();
assert_eq!(file1.size, 1000);
assert_eq!(file1.ckey, vec![1u8; 16]);
let file2 = manifest.get_file_by_path("dir/file2.dat").unwrap();
assert_eq!(file2.size, 2000);
let file3 = manifest.get_file_by_path("dir/subdir/file3.dat").unwrap();
assert_eq!(file3.size, 3000);
assert!(manifest.get_file_by_path("nonexistent.dat").is_none());
}
#[test]
fn test_complex_tag_combinations() {
let tags = vec![
("A", 0x01, vec![true, true, false, false]),
("B", 0x02, vec![true, false, true, false]),
("C", 0x04, vec![true, false, false, true]),
("D", 0x08, vec![false, true, true, true]),
];
let entries = vec![
("file_abc.dat", vec![1u8; 16], 1000), ("file_ad.dat", vec![2u8; 16], 2000), ("file_bd.dat", vec![3u8; 16], 3000), ("file_cd.dat", vec![4u8; 16], 4000), ];
let data = create_test_install_manifest(tags, entries);
let manifest = InstallManifest::parse(&data).unwrap();
assert_eq!(manifest.get_files_for_tags(&["A"]).len(), 2);
assert_eq!(manifest.get_files_for_tags(&["B"]).len(), 2);
assert_eq!(manifest.get_files_for_tags(&["C"]).len(), 2);
assert_eq!(manifest.get_files_for_tags(&["D"]).len(), 3);
assert_eq!(manifest.get_files_for_tags(&["A", "B"]).len(), 1); assert_eq!(manifest.get_files_for_tags(&["A", "C"]).len(), 1); assert_eq!(manifest.get_files_for_tags(&["B", "D"]).len(), 1); assert_eq!(manifest.get_files_for_tags(&["C", "D"]).len(), 1);
assert_eq!(manifest.get_files_for_tags(&["A", "B", "D"]).len(), 0);
}
#[test]
fn test_large_manifest() {
let num_files = 100;
let mut tag_mask = vec![false; num_files];
for i in (0..num_files).step_by(2) {
tag_mask[i] = true;
}
let tags = vec![("EvenFiles", 0x01, tag_mask.clone())];
let mut owned_entries = Vec::new();
for i in 0..num_files {
let path = format!("file_{i:03}.dat");
let ckey = vec![i as u8; 16];
let size = (i * 100) as u32;
owned_entries.push((path, ckey, size));
}
let mut data = Vec::new();
data.extend_from_slice(&[0x49, 0x4E]);
data.push(1);
data.push(16);
data.extend_from_slice(&(tags.len() as u16).to_be_bytes());
data.extend_from_slice(&(num_files as u32).to_be_bytes());
let bytes_per_tag = num_files.div_ceil(8);
for (name, tag_type, mask) in &tags {
data.extend_from_slice(name.as_bytes());
data.push(0);
let tag_type_u16: u16 = *tag_type;
data.extend_from_slice(&tag_type_u16.to_be_bytes());
let mut mask_bytes = vec![0u8; bytes_per_tag];
for (i, &has_tag) in mask.iter().enumerate() {
if has_tag {
mask_bytes[i / 8] |= 1 << (i % 8);
}
}
data.extend_from_slice(&mask_bytes);
}
for (path, ckey, size) in &owned_entries {
data.extend_from_slice(path.as_bytes());
data.push(0);
data.extend_from_slice(ckey);
data.extend_from_slice(&size.to_be_bytes());
}
let manifest = InstallManifest::parse(&data).unwrap();
assert_eq!(manifest.entries.len(), num_files);
let even_files = manifest.get_files_for_tags(&["EvenFiles"]);
assert_eq!(even_files.len(), num_files / 2);
for i in 0..num_files {
let path = format!("file_{i:03}.dat");
let file = manifest.get_file_by_path(&path).unwrap();
assert_eq!(file.size, (i * 100) as u32);
if i % 2 == 0 {
assert!(file.tags.contains(&"EvenFiles".to_string()));
} else {
assert!(!file.tags.contains(&"EvenFiles".to_string()));
}
}
}