1use std::fmt;
10use std::fs::File;
11use std::io::{BufReader, BufRead, BufWriter, Write};
12use std::collections::HashMap;
13use std::path::Path;
14use std::hash::Hash;
15use num_traits::Num;
16use thiserror::Error;
17use cdragon_utils::GuardedFile;
18
19#[cfg(feature = "bin")]
20pub mod bin;
21#[cfg(feature = "rst")]
22pub mod rst;
23#[cfg(feature = "wad")]
24pub mod wad;
25
26type Result<T, E = HashError> = std::result::Result<T, E>;
27
28
29#[allow(missing_docs)]
33#[derive(Error, Debug)]
34pub enum HashError {
35 #[error(transparent)]
36 Io(#[from] std::io::Error),
37 #[error("invalid hash line: {0:?}")]
38 InvalidHashLine(String),
39 #[error("invalid hash value: {0:?}")]
40 InvalidHashValue(String),
41}
42
43
44#[derive(Default)]
49pub struct HashMapper<T, const NBITS: usize> where T: Hash {
50 map: HashMap<T, String>,
51}
52
53impl<T, const NBITS: usize> HashMapper<T, NBITS> where T: Hash {
54 const NCHARS: usize = NBITS.div_ceil(4);
56}
57
58impl<T, const N: usize> HashMapper<T, N> where T: Eq + Hash + Copy {
59 pub fn new() -> Self {
61 Self { map: HashMap::<T, String>::new() }
62 }
63
64 pub fn get(&self, hash: T) -> Option<&str> {
66 self.map.get(&hash).map(|v| v.as_ref())
67 }
68
69 pub fn seek(&self, hash: T) -> HashOrStr<T, &str> {
80 match self.map.get(&hash) {
81 Some(s) => HashOrStr::Str(s.as_ref()),
82 None => HashOrStr::Hash(hash),
83 }
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.map.is_empty()
89 }
90
91 pub fn is_known(&self, hash: T) -> bool {
93 self.map.contains_key(&hash)
94 }
95
96 pub fn insert(&mut self, hash: T, value: String) {
100 self.map.insert(hash, value);
101 }
102}
103
104impl<T, const N: usize> HashMapper<T, N> where T: Num + Eq + Hash + Copy {
105 pub fn from_reader<R: BufRead>(reader: R) -> Result<Self> {
107 let mut this = Self::new();
108 this.load_reader(reader)?;
109 Ok(this)
110 }
111
112 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
114 let mut this = Self::new();
115 this.load_path(&path)?;
116 Ok(this)
117 }
118
119 pub fn load_reader<R: BufRead>(&mut self, reader: R) -> Result<(), HashError> {
121 for line in reader.lines() {
122 let l = line?;
123 if l.len() < Self::NCHARS + 2 {
124 return Err(HashError::InvalidHashLine(l));
125 }
126 let hash = T::from_str_radix(&l[..Self::NCHARS], 16).map_err(|_e| {
127 HashError::InvalidHashValue(l[..Self::NCHARS].to_string())
128 })?;
129 self.map.insert(hash, l[Self::NCHARS+1..].to_string());
130 }
131 Ok(())
132 }
133
134 pub fn load_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
136 let file = File::open(&path)?;
137 self.load_reader(BufReader::new(file))?;
138 Ok(())
139 }
140}
141
142impl<T, const N: usize> HashMapper<T, N> where T: Eq + Hash + Copy + fmt::LowerHex {
143 pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
145 let mut entries: Vec<_> = self.map.iter().collect();
146 entries.sort_by_key(|kv| kv.1);
147 for (h, s) in entries {
148 writeln!(writer, "{:0w$x} {}", h, s, w = Self::NCHARS)?;
149 }
150 Ok(())
151 }
152
153 pub fn write_path<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
157 GuardedFile::for_scope(path, |file| {
158 self.write(&mut BufWriter::new(file))
159 })
160 }
161}
162
163impl<T, const N: usize> std::fmt::Debug for HashMapper<T, N> where T: Hash {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 f.debug_struct("HashMapper")
166 .field("BIT_SIZE", &N)
167 .field("len", &self.map.len())
168 .finish()
169 }
170}
171
172
173pub trait HashDef: Sized {
177 type Hash: Sized;
179 const HASHER: fn(&str) -> Self::Hash;
181
182 fn new(hash: Self::Hash) -> Self;
184
185 #[inline]
187 fn hashed(s: &str) -> Self {
188 Self::new(Self::HASHER(s))
189 }
190
191 fn is_null(&self) -> bool;
193}
194
195
196#[derive(Debug)]
201pub enum HashOrStr<H, S>
202where H: Copy, S: AsRef<str> {
203 Hash(H),
205 Str(S),
207}
208
209impl<H, S> fmt::Display for HashOrStr<H, S>
210where H: Copy + fmt::LowerHex, S: AsRef<str> {
211 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212 match self {
213 Self::Hash(h) => write!(f, "{{{:0w$x}}}", h, w = std::mem::size_of::<H>() * 2),
214 Self::Str(s) => write!(f, "{}", s.as_ref()),
215 }
216 }
217}
218
219
220#[macro_export]
230macro_rules! define_hash_type {
231 (
232 $(#[$meta:meta])*
233 $name:ident($T:ty) => $hasher:expr
234 ) => {
235 $(#[$meta])*
236 #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
237 pub struct $name {
238 pub hash: $T,
240 }
241
242 impl $crate::HashDef for $name {
243 type Hash = $T;
244 const HASHER: fn(&str) -> Self::Hash = $hasher;
245
246 #[inline]
247 fn new(hash: Self::Hash) -> Self {
248 Self { hash }
249 }
250
251 #[inline]
252 fn is_null(&self) -> bool {
253 self.hash == 0
254 }
255 }
256
257 impl From<$T> for $name {
258 fn from(v: $T) -> Self {
259 Self { hash: v }
260 }
261 }
262
263 impl std::fmt::Debug for $name {
264 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
265 write!(f, concat!(stringify!($name), "({:x})"), self)
266 }
267 }
268
269 impl std::fmt::LowerHex for $name {
270 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
271 write!(f, "{:0w$x}", self.hash, w = std::mem::size_of::<$T>() * 2)
272 }
273 }
274 }
275}
276
277
278#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
282pub enum HashKind {
283 WadGame,
285 WadLcu,
287 BinEntryPath,
289 BinClassName,
291 BinFieldName,
293 BinHashValue,
295 Rst,
297}
298
299impl HashKind {
300 pub fn mapping_path(&self) -> &'static str {
308 match self {
309 Self::WadGame => "hashes.game.txt",
310 Self::WadLcu => "hashes.lcu.txt",
311 Self::BinEntryPath => "hashes.binentries.txt",
312 Self::BinClassName => "hashes.bintypes.txt",
313 Self::BinFieldName => "hashes.binfields.txt",
314 Self::BinHashValue => "hashes.binhashes.txt",
315 Self::Rst => "hashes.rst.txt",
316 }
317 }
318
319 pub fn from_wad_path<P: AsRef<Path>>(path: P) -> Option<Self> {
329 let path = path.as_ref().to_str()?;
330 if path.ends_with(".wad.client") {
331 Some(Self::WadGame)
332 } else if path.ends_with(".wad") {
333 Some(Self::WadLcu)
334 } else {
335 None
336 }
337 }
338}
339