Skip to main content

aamp/
names.rs

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}