banished_formats/
registry.rs1use std::collections::{BTreeMap, BTreeSet};
16
17use crate::pkg::{ref_to_entry, Archive};
18
19fn key(reference: &str) -> String {
23 let r = reference.replace('/', "\\").to_ascii_lowercase();
24 if r.contains(':') {
25 r
26 } else {
27 format!("{r}:resource")
28 }
29}
30
31pub fn references(data: &[u8]) -> Vec<String> {
33 let mut out = Vec::new();
34 let mut i = 0;
35 while i + 5 <= data.len() {
36 if data[i] != 1 {
37 i += 1;
38 continue;
39 }
40 let n = u32::from_le_bytes(data[i + 1..i + 5].try_into().unwrap());
41 let found = if n & 0x8000_0000 != 0 {
42 let len = (n & 0x7fff_ffff) as usize;
43 data.get(i + 5..i + 5 + len).and_then(|b| {
44 let end = b.iter().position(|&c| c == 0).unwrap_or(b.len());
45 let s = std::str::from_utf8(&b[..end]).ok()?;
46 Some((s.to_string(), 5 + len))
47 })
48 } else if (4..512).contains(&n) {
49 let len = n as usize * 2;
50 data.get(i + 5..i + 5 + len).map(|b| {
51 let units: Vec<u16> = b
52 .chunks_exact(2)
53 .map(|c| u16::from_le_bytes([c[0], c[1]]))
54 .take_while(|&u| u != 0)
55 .collect();
56 (String::from_utf16_lossy(&units), 5 + len)
57 })
58 } else {
59 None
60 };
61 match found {
62 Some((s, len)) if is_reference(&s) => {
63 out.push(s);
64 i += len;
65 }
66 _ => i += 1,
67 }
68 }
69 out
70}
71
72type Parsed = BTreeMap<String, Option<(u32, Vec<String>)>>;
77
78fn parse_reachable(archive: &Archive, roots: &[String]) -> Parsed {
79 let mut parsed = Parsed::new();
80 let mut wave: Vec<String> = roots.iter().map(|r| key(r)).collect();
81 while !wave.is_empty() {
82 wave.sort_unstable();
83 wave.dedup();
84 let results = crate::par::map(&wave, |k| {
85 let data = archive.read(&ref_to_entry(k)).ok()?;
86 let header = crate::crs::header(&data).ok()?;
87 Some((header.type_id, references(&data)))
88 });
89 let mut next = Vec::new();
90 for (k, result) in wave.into_iter().zip(results) {
91 if let Some((_, children)) = &result {
92 next.extend(
93 children
94 .iter()
95 .map(|c| key(c))
96 .filter(|c| !parsed.contains_key(c)),
97 );
98 }
99 parsed.insert(k, result);
100 }
101 next.retain(|c| !parsed.contains_key(c));
102 wave = next;
103 }
104 parsed
105}
106
107fn is_reference(s: &str) -> bool {
108 let path = s.split(':').next().unwrap_or("");
109 path.len() > 4
110 && path.to_ascii_lowercase().ends_with(".rsc")
111 && s.chars()
112 .all(|c| c.is_ascii_alphanumeric() || " _-.\\/:()'&".contains(c))
113}
114
115#[derive(Debug, Default, Clone)]
118pub struct Registry {
119 pub loaded: BTreeMap<String, (u32, String)>,
121}
122
123impl Registry {
124 pub fn build(archive: &Archive, roots: &[String]) -> Registry {
126 let parsed = parse_reachable(archive, roots);
127 let mut loaded = BTreeMap::new();
128 let mut seen = BTreeSet::new();
129 let mut stack: Vec<String> = roots.iter().rev().cloned().collect();
130 while let Some(r) = stack.pop() {
131 let k = key(&r);
132 if !seen.insert(k.clone()) {
133 continue;
134 }
135 let Some(Some((type_id, children))) = parsed.get(&k) else {
136 continue;
137 };
138 let written = if r.contains(':') {
139 r.replace('/', "\\")
140 } else {
141 format!("{}:resource", r.replace('/', "\\"))
142 };
143 loaded.insert(k, (*type_id, written));
144 for child in children.iter().rev() {
145 stack.push(child.clone());
146 }
147 }
148 Registry { loaded }
149 }
150
151 pub fn of_type(&self, type_id: u32) -> Vec<&str> {
154 self.loaded
155 .values()
156 .filter(|(t, _)| *t == type_id)
157 .map(|(_, r)| r.as_str())
158 .collect()
159 }
160
161 pub fn contains(&self, reference: &str) -> bool {
163 self.loaded.contains_key(&key(reference))
164 }
165}
166
167pub fn roots(mod_files: &[String]) -> Vec<String> {
170 let mut out = vec![r"Game\GameResources.rsc:resource".to_string()];
171 out.extend(
172 mod_files
173 .iter()
174 .map(|f| format!("{f}Resources.rsc:resource")),
175 );
176 out
177}