bsa3_hash/
lib.rs

1// Copyright (c) 2019, 2021 FaultyRAM
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. This file may not be copied,
6// modified, or distributed except according to those terms.
7
8//! The hash function used in BSA files for *The Elder Scrolls III: Morrowind*.
9//!
10//! # Example
11//!
12//! ```no_run
13//! assert_eq!(
14//!     bsa3_hash::calculate(r"meshes\m\probe_journeyman_01.nif".as_bytes()),
15//!     (0x0002_0336, 0xBB50_0695)
16//! );
17//! ```
18
19#![no_std]
20#![deny(
21    warnings,
22    future_incompatible,
23    unused,
24    unsafe_code,
25    clippy::all,
26    clippy::cargo,
27    clippy::pedantic,
28    rustdoc::all
29)]
30#![allow(clippy::cast_lossless, clippy::must_use_candidate)]
31
32#[inline]
33/// Computes the hash of a given byte sequence, expressed as a tuple of two 32-bit integers.
34///
35/// # Example
36///
37/// ```no_run
38/// println!("{:?}", bsa3_hash::calculate(b"foo"));
39/// ```
40pub fn calculate(input: &[u8]) -> (u32, u32) {
41    const MASK: u32 = 0b1_1111;
42    let (left, right) = input.split_at(input.len() >> 1);
43    let (mut a, mut b, mut shift) = (0, 0, 0);
44    for &n in left {
45        a ^= u32::from(n) << shift;
46        shift = (shift + 8) & MASK;
47    }
48    shift = 0;
49    for &n in right {
50        let temp = u32::from(n) << shift;
51        b = (b ^ temp).rotate_right(temp & MASK);
52        shift = (shift + 8) & MASK;
53    }
54    (a, b)
55}
56
57#[cfg(test)]
58mod tests {
59    include!("../bsa_lists/morrowind.rs");
60    include!("../bsa_lists/tribunal.rs");
61    include!("../bsa_lists/bloodmoon.rs");
62
63    #[inline]
64    fn test_hashes(list: &[(&str, u32, u32)]) {
65        for &(filename, left_hash, right_hash) in list {
66            assert_eq!(
67                crate::calculate(filename.as_bytes()),
68                (left_hash, right_hash)
69            );
70        }
71    }
72
73    #[test]
74    fn morrowind_bsa() {
75        test_hashes(MORROWIND_BSA);
76    }
77
78    #[test]
79    fn tribunal_bsa() {
80        test_hashes(TRIBUNAL_BSA);
81    }
82
83    #[test]
84    fn bloodmoon_bsa() {
85        test_hashes(BLOODMOON_BSA);
86    }
87}