smash_arc/
hash40.rs

1use crate::{HashToIndex, QuickDir};
2use binrw::BinRead;
3use crc32fast::Hasher;
4
5#[repr(transparent)]
6#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
7pub struct Hash40(pub u64);
8
9impl Hash40 {
10    pub fn as_u64(self) -> u64 {
11        self.0
12    }
13
14    pub fn len(self) -> u8 {
15        (self.0 >> 32) as u8
16    }
17
18    pub fn crc32(self) -> u32 {
19        self.0 as u32
20    }
21}
22
23impl From<&Hash40> for Hash40 {
24    fn from(hash: &Hash40) -> Self {
25        *hash
26    }
27}
28
29impl From<u64> for Hash40 {
30    fn from(hash: u64) -> Self {
31        Hash40(hash)
32    }
33}
34
35impl From<&str> for Hash40 {
36    fn from(string: &str) -> Self {
37        hash40(string)
38    }
39}
40
41impl From<&HashToIndex> for Hash40 {
42    fn from(hash_index: &HashToIndex) -> Self {
43        hash_index.hash40()
44    }
45}
46
47impl From<HashToIndex> for Hash40 {
48    fn from(hash_index: HashToIndex) -> Self {
49        hash_index.hash40()
50    }
51}
52
53impl HashToIndex {
54    pub fn hash40(&self) -> Hash40 {
55        Hash40((self.hash() as u64) + ((self.length() as u64) << 32))
56    }
57}
58
59impl QuickDir {
60    pub fn hash40(&self) -> Hash40 {
61        Hash40((self.hash() as u64) + ((self.name_length() as u64) << 32))
62    }
63}
64
65// Find the hash40 of a given string
66pub fn hash40(string: &str) -> Hash40 {
67    hash40_from_bytes(string.as_bytes())
68}
69
70// TODO: Is this worth adding to the public API?
71pub(crate) fn hash40_from_bytes(bytes: &[u8]) -> Hash40 {
72    Hash40(((bytes.len() as u64) << 32) + crc32(bytes) as u64)
73}
74
75fn crc32(bytes: &[u8]) -> u32 {
76    let mut hasher = Hasher::new();
77    hasher.update(bytes);
78    hasher.finalize()
79}
80
81#[cfg(feature = "serialize")]
82pub mod serde {
83    use serde::{
84        de::{Error, Unexpected, Visitor},
85        Deserialize, Deserializer, Serialize, Serializer,
86    };
87
88    use crate::Hash40;
89
90    #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
91    pub struct Hash40String(pub Hash40);
92
93    struct Hash40Visitor;
94
95    impl<'de> Visitor<'de> for Hash40Visitor {
96        type Value = Hash40;
97
98        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
99            formatter.write_str("A string or u64")
100        }
101
102        fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
103        where
104            E: Error,
105        {
106            self.visit_i32(v as i32)
107        }
108
109        fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
110        where
111            E: Error,
112        {
113            self.visit_i32(v as i32)
114        }
115
116        fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
117        where
118            E: Error,
119        {
120            Err(Error::invalid_type(
121                Unexpected::Signed(v as i64),
122                &Hash40Visitor,
123            ))
124        }
125
126        fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
127        where
128            E: Error,
129        {
130            self.visit_u32(v as u32)
131        }
132
133        fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
134        where
135            E: Error,
136        {
137            self.visit_u32(v as u32)
138        }
139
140        fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
141        where
142            E: Error,
143        {
144            Err(Error::invalid_type(
145                Unexpected::Unsigned(v as u64),
146                &Hash40Visitor,
147            ))
148        }
149
150        fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
151        where
152            E: Error,
153        {
154            self.visit_u64(v as u64)
155        }
156
157        fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
158        where
159            E: Error,
160        {
161            Ok(Hash40(v))
162        }
163
164        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
165        where
166            E: Error,
167        {
168            if v.starts_with("0x") {
169                Ok(u64::from_str_radix(v.trim_start_matches("0x"), 16)
170                    .map_or_else(|_| Hash40::from(v), |val| Hash40(val)))
171            } else {
172                Ok(Hash40::from(v))
173            }
174        }
175    }
176
177    impl Serialize for Hash40 {
178        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179        where
180            S: Serializer,
181        {
182            serializer.serialize_u64(self.0)
183        }
184    }
185
186    impl<'de> Deserialize<'de> for Hash40 {
187        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
188        where
189            D: Deserializer<'de>,
190        {
191            deserializer.deserialize_u64(Hash40Visitor)
192        }
193    }
194
195    impl Serialize for Hash40String {
196        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197        where
198            S: Serializer,
199        {
200            Hash40::serialize(&self.0, serializer)
201        }
202    }
203
204    impl<'de> Deserialize<'de> for Hash40String {
205        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206        where
207            D: Deserializer<'de>,
208        {
209            Ok(Self(deserializer.deserialize_any(Hash40Visitor)?))
210        }
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use crate::{
217        hash40::{hash40, hash40_from_bytes},
218        Hash40,
219    };
220
221    #[test]
222    fn hash40_path_string() {
223        assert_eq!(
224            Hash40(0x29954022ed),
225            hash40("fighter/mario/model/body/c00/model.numatb")
226        );
227    }
228
229    #[test]
230    fn hash40_path_bytes() {
231        assert_eq!(
232            Hash40(0x29954022ed),
233            hash40_from_bytes("fighter/mario/model/body/c00/model.numatb".as_bytes())
234        );
235    }
236}