1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
use cached::proc_macro::cached; use crc::{crc32, Hasher32}; use lazy_static::lazy_static; use metrohash::MetroHashMap; use std::sync::Mutex; const NAMES: &'static str = include_str!("../data/botw_hashed_names.txt"); const NUMBERED_NAMES: &'static str = include_str!("../data/botw_numbered_names.txt"); lazy_static! { static ref NUMBERED_NAME_LIST: Vec<String> = NUMBERED_NAMES.split("\n").map(|s| s.to_string()).collect(); } #[cached] pub fn get_default_name_table() -> NameTable { NameTable::new(true) } lazy_static::lazy_static! { pub(crate) static ref TABLE: Mutex<NameTable> = Mutex::new(get_default_name_table()); } #[derive(Clone)] pub struct NameTable { table: MetroHashMap<u32, String>, } impl NameTable { pub fn new(include_stock_names: bool) -> NameTable { let mut m: MetroHashMap<u32, String> = MetroHashMap::default(); if include_stock_names { let mut dig = crc32::Digest::new(crc::crc32::IEEE); for name in NAMES.split("\n") { dig.write(name.as_bytes()); m.insert(dig.sum32(), name.to_string()); dig.reset(); } } NameTable { table: m } } pub fn add_name(self: &mut NameTable, name: &str) { let mut digest = crc32::Digest::new(crc32::IEEE); digest.write(name.as_bytes()); self.table.insert(digest.sum32(), name.to_owned()); digest.reset(); } pub fn get_name(&self, crc: u32) -> Option<String> { match self.table.get(&crc) { Some(s) => Some(s.to_string()), None => None, } } } lazy_static::lazy_static! { static ref DIGEST: Mutex<crc32::Digest> = Mutex::new(crc32::Digest::new(crc32::IEEE)); } fn test_names(parent: &str, idx: usize, crc: u32) -> Option<String> { let mut digest = DIGEST.lock().unwrap(); for i in &[idx, idx + 1] { for name in &[ format!("{}{}", parent, i), format!("{}_{}", parent, i), format!("{}{:02}", parent, i), format!("{}_{:02}", parent, i), format!("{}{:03}", parent, i), format!("{}_{:03}", parent, i), ] { digest.write(name.as_bytes()); if digest.sum32() == crc { return Some(name.to_string()); } digest.reset(); } } None } #[cached] pub fn guess_name(crc: u32, parent_crc: u32, idx: usize) -> Option<String> { let table = TABLE.lock().unwrap(); let parent = table.get_name(parent_crc); drop(table); match parent { Some(parent_name) => { let mut matched = test_names(&parent_name, idx, crc); if matched.is_none() { if &parent_name == "Children" { matched = test_names("Child", idx, crc); } if matched.is_none() { for suffix in &["s", "es", "List"] { if parent_name.ends_with(suffix) { matched = test_names( &parent_name[0..parent_name.len() - suffix.len()], idx, crc, ); if matched.is_some() { break; } } } } } match matched { Some(s) => Some(s), None => try_numbered_name(idx, crc), } } None => try_numbered_name(idx, crc), } } #[cached] fn try_numbered_name(idx: usize, crc: u32) -> Option<String> { let mut opt = Option::None; let mut dig = crc32::Digest::new(crc32::IEEE); for name in NUMBERED_NAME_LIST.iter() { for i in 0..idx + 2 { let mut maybe: String = name.to_string(); if name.contains("{") { maybe = rt_format(name, i); } dig.write(maybe.as_bytes()); if dig.sum32() == crc as u32 { opt = Some(maybe); } dig.reset(); } dig.reset(); } opt } fn rt_format(name: &str, i: usize) -> String { if name.contains("{}") { name.replace("{}", &format!("{}", i)) } else if name.contains("{:02}") { name.replace("{:02}", &format!("{:02}", i)) } else if name.contains("{:03}") { name.replace("{:03}", &format!("{:03}", i)) } else if name.contains("{:04}") { name.replace("{:04}", &format!("{:04}", i)) } else { unreachable!() } }