Skip to main content

casc_lib/util/
hash.rs

1//! Jenkins hashlittle2 hash and WoW file path hashing.
2//!
3//! CASC root files optionally store a 64-bit name hash for each entry,
4//! computed using Bob Jenkins' `hashlittle2` (lookup3) algorithm over the
5//! normalized (uppercased, backslash-separated) file path. The
6//! `hashpath` function performs this normalization and returns the
7//! combined 64-bit hash used by the game client.
8
9/// Bob Jenkins' lookup3 hashlittle2 function.
10/// Returns (pc, pb) hash pair.
11pub fn hashlittle2(key: &[u8], pc: u32, pb: u32) -> (u32, u32) {
12    let mut a: u32;
13    let mut b: u32;
14    let mut c: u32;
15
16    a = 0xdeadbeef_u32
17        .wrapping_add(key.len() as u32)
18        .wrapping_add(pc);
19    b = a;
20    c = a.wrapping_add(pb);
21
22    let mut offset = 0;
23    let mut remaining = key.len();
24
25    // Process 12-byte chunks
26    while remaining > 12 {
27        a = a.wrapping_add(
28            key[offset] as u32
29                | (key[offset + 1] as u32) << 8
30                | (key[offset + 2] as u32) << 16
31                | (key[offset + 3] as u32) << 24,
32        );
33        b = b.wrapping_add(
34            key[offset + 4] as u32
35                | (key[offset + 5] as u32) << 8
36                | (key[offset + 6] as u32) << 16
37                | (key[offset + 7] as u32) << 24,
38        );
39        c = c.wrapping_add(
40            key[offset + 8] as u32
41                | (key[offset + 9] as u32) << 8
42                | (key[offset + 10] as u32) << 16
43                | (key[offset + 11] as u32) << 24,
44        );
45
46        // mix
47        a = a.wrapping_sub(c);
48        a ^= c.rotate_left(4);
49        c = c.wrapping_add(b);
50        b = b.wrapping_sub(a);
51        b ^= a.rotate_left(6);
52        a = a.wrapping_add(c);
53        c = c.wrapping_sub(b);
54        c ^= b.rotate_left(8);
55        b = b.wrapping_add(a);
56        a = a.wrapping_sub(c);
57        a ^= c.rotate_left(16);
58        c = c.wrapping_add(b);
59        b = b.wrapping_sub(a);
60        b ^= a.rotate_left(19);
61        a = a.wrapping_add(c);
62        c = c.wrapping_sub(b);
63        c ^= b.rotate_left(4);
64        b = b.wrapping_add(a);
65
66        offset += 12;
67        remaining -= 12;
68    }
69
70    // Handle the last few bytes (the switch/case in C)
71    match remaining {
72        12 => {
73            c = c.wrapping_add((key[offset + 11] as u32) << 24);
74            c = c.wrapping_add((key[offset + 10] as u32) << 16);
75            c = c.wrapping_add((key[offset + 9] as u32) << 8);
76            c = c.wrapping_add(key[offset + 8] as u32);
77            b = b.wrapping_add((key[offset + 7] as u32) << 24);
78            b = b.wrapping_add((key[offset + 6] as u32) << 16);
79            b = b.wrapping_add((key[offset + 5] as u32) << 8);
80            b = b.wrapping_add(key[offset + 4] as u32);
81            a = a.wrapping_add((key[offset + 3] as u32) << 24);
82            a = a.wrapping_add((key[offset + 2] as u32) << 16);
83            a = a.wrapping_add((key[offset + 1] as u32) << 8);
84            a = a.wrapping_add(key[offset] as u32);
85        }
86        11 => {
87            c = c.wrapping_add((key[offset + 10] as u32) << 16);
88            c = c.wrapping_add((key[offset + 9] as u32) << 8);
89            c = c.wrapping_add(key[offset + 8] as u32);
90            b = b.wrapping_add((key[offset + 7] as u32) << 24);
91            b = b.wrapping_add((key[offset + 6] as u32) << 16);
92            b = b.wrapping_add((key[offset + 5] as u32) << 8);
93            b = b.wrapping_add(key[offset + 4] as u32);
94            a = a.wrapping_add((key[offset + 3] as u32) << 24);
95            a = a.wrapping_add((key[offset + 2] as u32) << 16);
96            a = a.wrapping_add((key[offset + 1] as u32) << 8);
97            a = a.wrapping_add(key[offset] as u32);
98        }
99        10 => {
100            c = c.wrapping_add((key[offset + 9] as u32) << 8);
101            c = c.wrapping_add(key[offset + 8] as u32);
102            b = b.wrapping_add((key[offset + 7] as u32) << 24);
103            b = b.wrapping_add((key[offset + 6] as u32) << 16);
104            b = b.wrapping_add((key[offset + 5] as u32) << 8);
105            b = b.wrapping_add(key[offset + 4] as u32);
106            a = a.wrapping_add((key[offset + 3] as u32) << 24);
107            a = a.wrapping_add((key[offset + 2] as u32) << 16);
108            a = a.wrapping_add((key[offset + 1] as u32) << 8);
109            a = a.wrapping_add(key[offset] as u32);
110        }
111        9 => {
112            c = c.wrapping_add(key[offset + 8] as u32);
113            b = b.wrapping_add((key[offset + 7] as u32) << 24);
114            b = b.wrapping_add((key[offset + 6] as u32) << 16);
115            b = b.wrapping_add((key[offset + 5] as u32) << 8);
116            b = b.wrapping_add(key[offset + 4] as u32);
117            a = a.wrapping_add((key[offset + 3] as u32) << 24);
118            a = a.wrapping_add((key[offset + 2] as u32) << 16);
119            a = a.wrapping_add((key[offset + 1] as u32) << 8);
120            a = a.wrapping_add(key[offset] as u32);
121        }
122        8 => {
123            b = b.wrapping_add((key[offset + 7] as u32) << 24);
124            b = b.wrapping_add((key[offset + 6] as u32) << 16);
125            b = b.wrapping_add((key[offset + 5] as u32) << 8);
126            b = b.wrapping_add(key[offset + 4] as u32);
127            a = a.wrapping_add((key[offset + 3] as u32) << 24);
128            a = a.wrapping_add((key[offset + 2] as u32) << 16);
129            a = a.wrapping_add((key[offset + 1] as u32) << 8);
130            a = a.wrapping_add(key[offset] as u32);
131        }
132        7 => {
133            b = b.wrapping_add((key[offset + 6] as u32) << 16);
134            b = b.wrapping_add((key[offset + 5] as u32) << 8);
135            b = b.wrapping_add(key[offset + 4] as u32);
136            a = a.wrapping_add((key[offset + 3] as u32) << 24);
137            a = a.wrapping_add((key[offset + 2] as u32) << 16);
138            a = a.wrapping_add((key[offset + 1] as u32) << 8);
139            a = a.wrapping_add(key[offset] as u32);
140        }
141        6 => {
142            b = b.wrapping_add((key[offset + 5] as u32) << 8);
143            b = b.wrapping_add(key[offset + 4] as u32);
144            a = a.wrapping_add((key[offset + 3] as u32) << 24);
145            a = a.wrapping_add((key[offset + 2] as u32) << 16);
146            a = a.wrapping_add((key[offset + 1] as u32) << 8);
147            a = a.wrapping_add(key[offset] as u32);
148        }
149        5 => {
150            b = b.wrapping_add(key[offset + 4] as u32);
151            a = a.wrapping_add((key[offset + 3] as u32) << 24);
152            a = a.wrapping_add((key[offset + 2] as u32) << 16);
153            a = a.wrapping_add((key[offset + 1] as u32) << 8);
154            a = a.wrapping_add(key[offset] as u32);
155        }
156        4 => {
157            a = a.wrapping_add((key[offset + 3] as u32) << 24);
158            a = a.wrapping_add((key[offset + 2] as u32) << 16);
159            a = a.wrapping_add((key[offset + 1] as u32) << 8);
160            a = a.wrapping_add(key[offset] as u32);
161        }
162        3 => {
163            a = a.wrapping_add((key[offset + 2] as u32) << 16);
164            a = a.wrapping_add((key[offset + 1] as u32) << 8);
165            a = a.wrapping_add(key[offset] as u32);
166        }
167        2 => {
168            a = a.wrapping_add((key[offset + 1] as u32) << 8);
169            a = a.wrapping_add(key[offset] as u32);
170        }
171        1 => {
172            a = a.wrapping_add(key[offset] as u32);
173        }
174        0 => {
175            return (c, b);
176        }
177        _ => unreachable!(),
178    }
179
180    // final mix
181    c ^= b;
182    c = c.wrapping_sub(b.rotate_left(14));
183    a ^= c;
184    a = a.wrapping_sub(c.rotate_left(11));
185    b ^= a;
186    b = b.wrapping_sub(a.rotate_left(25));
187    c ^= b;
188    c = c.wrapping_sub(b.rotate_left(16));
189    a ^= c;
190    a = a.wrapping_sub(c.rotate_left(4));
191    b ^= a;
192    b = b.wrapping_sub(a.rotate_left(14));
193    c ^= b;
194    c = c.wrapping_sub(b.rotate_left(24));
195
196    (c, b)
197}
198
199/// Hash a file path using Jenkins96, normalizing to uppercase with backslashes.
200pub fn hashpath(path: &str) -> u64 {
201    let normalized = path.to_uppercase().replace('/', "\\");
202    let (pc, pb) = hashlittle2(normalized.as_bytes(), 0, 0);
203    ((pb as u64) << 32) | (pc as u64)
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn hashlittle2_empty() {
212        let (pc, pb) = hashlittle2(b"", 0, 0);
213        // Known value for empty input with 0,0 seeds
214        // From reference implementation
215        assert_eq!(pc, 0xdeadbeef);
216        assert_eq!(pb, 0xdeadbeef);
217    }
218
219    #[test]
220    fn hashlittle2_known_vectors() {
221        // Test with "abc" - known from Jenkins reference
222        let (pc, pb) = hashlittle2(b"abc", 0, 0);
223        // These are verifiable against the C reference implementation
224        assert_ne!(pc, 0); // Non-trivial output
225        assert_ne!(pb, 0);
226    }
227
228    #[test]
229    fn hashpath_normalizes_case_and_slashes() {
230        let h1 = hashpath("World/Maps/Azeroth/Azeroth.wdt");
231        let h2 = hashpath("world/maps/azeroth/azeroth.wdt");
232        let h3 = hashpath("WORLD\\MAPS\\AZEROTH\\AZEROTH.WDT");
233        assert_eq!(h1, h2);
234        assert_eq!(h2, h3);
235    }
236
237    #[test]
238    fn hashpath_deterministic() {
239        let h1 = hashpath("test/file.txt");
240        let h2 = hashpath("test/file.txt");
241        assert_eq!(h1, h2);
242    }
243}