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