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
65pub fn hash40(string: &str) -> Hash40 {
67 hash40_from_bytes(string.as_bytes())
68}
69
70pub(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}