use std::io::{BufRead, BufReader};
use std::path::Path;
use std::{borrow::Cow, collections::HashMap};
use std::{fs::File, str::FromStr};
use ltk_hash::{BinHash, WadHash};
#[allow(unused)]
pub trait HashProvider {
fn lookup_entry(&self, hash: BinHash) -> Option<Cow<'_, str>> {
None
}
fn lookup_field(&self, hash: BinHash) -> Option<Cow<'_, str>> {
None
}
fn lookup_hash(&self, hash: BinHash) -> Option<Cow<'_, str>> {
None
}
fn lookup_type(&self, hash: BinHash) -> Option<Cow<'_, str>> {
None
}
fn lookup_wad(&self, hash: WadHash) -> Option<Cow<'_, str>> {
None
}
}
impl HashProvider for () {}
#[derive(Debug, Clone, Default)]
pub struct HashMapProvider {
pub entries: HashMap<BinHash, String>,
pub fields: HashMap<BinHash, String>,
pub hashes: HashMap<BinHash, String>,
pub types: HashMap<BinHash, String>,
pub wads: HashMap<WadHash, String>,
}
impl HashMapProvider {
pub fn new() -> Self {
Self::default()
}
fn load_file<H: ltk_hash::Hash + FromStr>(
path: impl AsRef<Path>,
) -> std::io::Result<HashMap<H, String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut map = HashMap::new();
for line in reader.lines() {
let line = line?;
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((hash_str, value)) = line.split_once(' ') {
if let Ok(hash) = H::from_str(hash_str) {
map.insert(hash, value.to_string());
}
}
}
Ok(map)
}
pub fn load_entries(&mut self, path: impl AsRef<Path>) -> std::io::Result<&mut Self> {
self.entries = Self::load_file(path)?;
Ok(self)
}
pub fn load_fields(&mut self, path: impl AsRef<Path>) -> std::io::Result<&mut Self> {
self.fields = Self::load_file(path)?;
Ok(self)
}
pub fn load_hashes(&mut self, path: impl AsRef<Path>) -> std::io::Result<&mut Self> {
self.hashes = Self::load_file(path)?;
Ok(self)
}
pub fn load_types(&mut self, path: impl AsRef<Path>) -> std::io::Result<&mut Self> {
self.types = Self::load_file(path)?;
Ok(self)
}
pub fn load_wads(&mut self, path: impl AsRef<Path>) -> std::io::Result<&mut Self> {
self.wads = Self::load_file(path)?;
Ok(self)
}
pub fn load_from_directory(&mut self, dir: impl AsRef<Path>) -> &mut Self {
let dir = dir.as_ref();
let _ = self.load_entries(dir.join("hashes.binentries.txt"));
let _ = self.load_fields(dir.join("hashes.binfields.txt"));
let _ = self.load_hashes(dir.join("hashes.binhashes.txt"));
let _ = self.load_types(dir.join("hashes.bintypes.txt"));
let _ = self.load_wads(dir.join("hashes.game.txt"));
self
}
pub fn insert_entry(
&mut self,
hash: impl Into<BinHash>,
value: impl Into<String>,
) -> &mut Self {
self.entries.insert(hash.into(), value.into());
self
}
pub fn insert_field(
&mut self,
hash: impl Into<BinHash>,
value: impl Into<String>,
) -> &mut Self {
self.fields.insert(hash.into(), value.into());
self
}
pub fn insert_hash(&mut self, hash: impl Into<BinHash>, value: impl Into<String>) -> &mut Self {
self.hashes.insert(hash.into(), value.into());
self
}
pub fn insert_type(&mut self, hash: impl Into<BinHash>, value: impl Into<String>) -> &mut Self {
self.types.insert(hash.into(), value.into());
self
}
pub fn insert_wad(&mut self, hash: impl Into<WadHash>, value: impl Into<String>) -> &mut Self {
self.wads.insert(hash.into(), value.into());
self
}
pub fn total_count(&self) -> usize {
self.entries.len() + self.fields.len() + self.hashes.len() + self.types.len()
}
}
impl HashProvider for HashMapProvider {
fn lookup_entry(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.entries.get(&hash).map(|s| s.as_str().into())
}
fn lookup_field(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.fields.get(&hash).map(|s| s.as_str().into())
}
fn lookup_hash(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.hashes.get(&hash).map(|s| s.as_str().into())
}
fn lookup_type(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.types.get(&hash).map(|s| s.as_str().into())
}
fn lookup_wad(&self, hash: WadHash) -> Option<Cow<'_, str>> {
self.wads.get(&hash).map(|s| s.as_str().into())
}
}
impl<T: HashProvider + ?Sized> HashProvider for &T {
fn lookup_entry(&self, hash: BinHash) -> Option<Cow<'_, str>> {
(*self).lookup_entry(hash)
}
fn lookup_field(&self, hash: BinHash) -> Option<Cow<'_, str>> {
(*self).lookup_field(hash)
}
fn lookup_hash(&self, hash: BinHash) -> Option<Cow<'_, str>> {
(*self).lookup_hash(hash)
}
fn lookup_type(&self, hash: BinHash) -> Option<Cow<'_, str>> {
(*self).lookup_type(hash)
}
fn lookup_wad(&self, hash: WadHash) -> Option<Cow<'_, str>> {
(*self).lookup_wad(hash)
}
}
impl HashProvider for Box<dyn HashProvider> {
fn lookup_entry(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.as_ref().lookup_entry(hash)
}
fn lookup_field(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.as_ref().lookup_field(hash)
}
fn lookup_hash(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.as_ref().lookup_hash(hash)
}
fn lookup_type(&self, hash: BinHash) -> Option<Cow<'_, str>> {
self.as_ref().lookup_type(hash)
}
fn lookup_wad(&self, hash: WadHash) -> Option<Cow<'_, str>> {
self.as_ref().lookup_wad(hash)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_provider() {
let provider = ();
assert_eq!(provider.lookup_entry(0x12345678.into()), None);
assert_eq!(provider.lookup_field(0x12345678.into()), None);
assert_eq!(provider.lookup_hash(0x12345678.into()), None);
assert_eq!(provider.lookup_type(0x12345678.into()), None);
assert_eq!(provider.lookup_wad(0x12345678.into()), None);
}
#[test]
fn test_hashmap_provider() {
let mut provider = HashMapProvider::new();
provider.insert_entry(0x12345678, "Characters/Test/Skin0");
provider.insert_field(0xdeadbeef, "skinName");
provider.insert_hash(0xcafebabe, "some/path");
provider.insert_type(0xfeedface, "SkinData");
provider.insert_wad(0xfeedface, "SkinDataWad");
assert_eq!(
provider.lookup_entry(0x12345678.into()),
Some("Characters/Test/Skin0".into())
);
assert_eq!(
provider.lookup_field(0xdeadbeef.into()),
Some("skinName".into())
);
assert_eq!(
provider.lookup_hash(0xcafebabe.into()),
Some("some/path".into())
);
assert_eq!(
provider.lookup_type(0xfeedface.into()),
Some("SkinData".into())
);
assert_eq!(
provider.lookup_wad(0xfeedface.into()),
Some("SkinDataWad".into())
);
assert_eq!(provider.lookup_entry(0x11111111.into()), None);
}
}