Skip to main content

ltk_modpkg/
utils.rs

1use xxhash_rust::{xxh3, xxh64};
2
3pub fn is_hex_chunk_name(chunk_name: &str) -> bool {
4    // Reject 0x prefix
5    if chunk_name.starts_with("0x") {
6        return false;
7    }
8
9    // Validate the base name (before extension)
10    let base = chunk_name.split('.').next().unwrap_or(chunk_name);
11    if base.len() != 16 {
12        return false;
13    }
14
15    base.chars().all(|c| c.is_ascii_hexdigit())
16}
17
18/// Normalize a chunk path for storage and hashing.
19///
20/// Lowercases the path and converts backslashes to forward slashes so that
21/// the same logical path is represented identically on all platforms.
22/// Call this once before storing or hashing a chunk path.
23pub fn normalize_chunk_path(path: &str) -> String {
24    path.to_lowercase().replace('\\', "/")
25}
26
27/// Hash a layer name using xxhash3.
28pub fn hash_layer_name(name: &str) -> u64 {
29    xxh3::xxh3_64(name.to_lowercase().as_bytes())
30}
31
32/// Hash a chunk name using xxhash64.
33///
34/// Expects a pre-normalized path (lowercase, forward slashes).
35/// Use [`normalize_chunk_path`] before calling this if the input may
36/// contain uppercase characters or backslashes.
37pub fn hash_chunk_name(name: &str) -> u64 {
38    xxh64::xxh64(name.as_bytes(), 0)
39}
40
41/// Hash a wad name using xxhash3.
42pub fn hash_wad_name(name: &str) -> u64 {
43    xxh3::xxh3_64(name.to_lowercase().as_bytes())
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn normalize_chunk_path_lowercases() {
52        assert_eq!(
53            normalize_chunk_path("Graves.wad.client/Data/File.bin"),
54            "graves.wad.client/data/file.bin"
55        );
56    }
57
58    #[test]
59    fn normalize_chunk_path_converts_backslashes() {
60        assert_eq!(
61            normalize_chunk_path("graves.wad.client\\data\\characters\\graves"),
62            "graves.wad.client/data/characters/graves"
63        );
64    }
65
66    #[test]
67    fn normalize_chunk_path_handles_mixed_separators() {
68        assert_eq!(
69            normalize_chunk_path("Graves.wad.client\\Data/Characters\\Graves"),
70            "graves.wad.client/data/characters/graves"
71        );
72    }
73
74    #[test]
75    fn normalize_chunk_path_noop_on_normalized() {
76        let path = "graves.wad.client/data/file.bin";
77        assert_eq!(normalize_chunk_path(path), path);
78    }
79
80    #[test]
81    fn hash_chunk_name_consistent_after_normalization() {
82        let forward = normalize_chunk_path("graves.wad.client/data/file.bin");
83        let back = normalize_chunk_path("graves.wad.client\\data\\file.bin");
84        let mixed = normalize_chunk_path("Graves.wad.client\\Data/File.bin");
85
86        assert_eq!(hash_chunk_name(&forward), hash_chunk_name(&back));
87        assert_eq!(hash_chunk_name(&forward), hash_chunk_name(&mixed));
88    }
89}