use super::keys::{ASCII_TO_UPPER, ENCRYPTION_TABLE};
pub fn hash_string(filename: &str, hash_type: u32) -> u32 {
let mut seed1: u32 = 0x7FED7FED;
let mut seed2: u32 = 0xEEEEEEEE;
for &byte in filename.as_bytes() {
let mut ch = byte;
if ch == b'/' {
ch = b'\\';
}
ch = ASCII_TO_UPPER[ch as usize];
let table_idx = hash_type.wrapping_add(ch as u32) as usize;
seed1 = ENCRYPTION_TABLE[table_idx] ^ (seed1.wrapping_add(seed2));
seed2 = (ch as u32)
.wrapping_add(seed1)
.wrapping_add(seed2)
.wrapping_add(seed2 << 5)
.wrapping_add(3);
}
seed1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::types::hash_type;
#[test]
fn test_hash_string_test_vectors() {
assert_eq!(
hash_string("(listfile)", hash_type::TABLE_OFFSET),
0x5F3DE859
);
assert_eq!(hash_string("(hash table)", hash_type::FILE_KEY), 0xC3AF3770);
assert_eq!(
hash_string("(block table)", hash_type::FILE_KEY),
0xEC83B3A3
);
}
#[test]
fn test_path_separator_normalization() {
let hash1 = hash_string("path/to/file.txt", hash_type::TABLE_OFFSET);
let hash2 = hash_string("path\\to\\file.txt", hash_type::TABLE_OFFSET);
assert_eq!(hash1, hash2);
assert_eq!(
hash_string("path\\to\\file", hash_type::TABLE_OFFSET),
hash_string("path/to/file", hash_type::TABLE_OFFSET)
);
assert_eq!(
hash_string("path\\to\\file", hash_type::TABLE_OFFSET),
0x534CC8EE
);
assert_eq!(
hash_string("interface\\glue\\mainmenu.blp", hash_type::TABLE_OFFSET),
hash_string("interface/glue/mainmenu.blp", hash_type::TABLE_OFFSET)
);
assert_eq!(
hash_string("interface\\glue\\mainmenu.blp", hash_type::TABLE_OFFSET),
0x2BBE7C09
);
}
#[test]
fn test_case_insensitivity() {
let hash1 = hash_string("File.txt", hash_type::TABLE_OFFSET);
let hash2 = hash_string("FILE.TXT", hash_type::TABLE_OFFSET);
assert_eq!(hash1, hash2);
assert_eq!(
hash_string("file.txt", hash_type::TABLE_OFFSET),
hash_string("FILE.TXT", hash_type::TABLE_OFFSET)
);
assert_eq!(hash_string("file.txt", hash_type::TABLE_OFFSET), 0x3EA98D7A);
assert_eq!(
hash_string("path\\to\\FILE", hash_type::TABLE_OFFSET),
hash_string("PATH\\TO\\file", hash_type::TABLE_OFFSET)
);
assert_eq!(
hash_string("path\\to\\FILE", hash_type::TABLE_OFFSET),
0x534CC8EE
);
}
#[test]
fn test_hash_table_lookup_process() {
let filename = "(listfile)";
let hash_a = hash_string(filename, hash_type::NAME_A);
let hash_b = hash_string(filename, hash_type::NAME_B);
let hash_offset = hash_string(filename, hash_type::TABLE_OFFSET);
let hash_table_size = 0x1000u32;
let index = hash_offset & (hash_table_size - 1);
println!("Hash A: 0x{hash_a:08X}");
println!("Hash B: 0x{hash_b:08X}");
println!("Hash offset: 0x{hash_offset:08X}");
println!("Table index: 0x{index:04X}");
assert_eq!(hash_offset, 0x5F3DE859);
assert_eq!(index, 0x0859);
assert_ne!(hash_a, 0); assert_ne!(hash_b, 0);
}
#[test]
fn test_encryption_key_calculation() {
let filename = "(hash table)";
let key = hash_string(filename, hash_type::FILE_KEY);
assert_eq!(key, 0xC3AF3770);
let filename = "(block table)";
let key = hash_string(filename, hash_type::FILE_KEY);
assert_eq!(key, 0xEC83B3A3);
}
}