unity_asset_binary/typetree/
registry.rs1use crate::typetree::TypeTree;
9use crate::{error::BinaryError, error::Result};
10use serde::Deserialize;
11use std::collections::HashMap;
12use std::io::Read;
13use std::path::Path;
14use std::sync::Arc;
15
16pub trait TypeTreeRegistry: Send + Sync + std::fmt::Debug {
17 fn resolve(&self, unity_version: &str, class_id: i32) -> Option<Arc<TypeTree>>;
18}
19
20#[derive(Debug, Default, Clone)]
22pub struct CompositeTypeTreeRegistry {
23 registries: Vec<Arc<dyn TypeTreeRegistry>>,
24}
25
26impl CompositeTypeTreeRegistry {
27 pub fn new(registries: Vec<Arc<dyn TypeTreeRegistry>>) -> Self {
28 Self { registries }
29 }
30
31 pub fn push(&mut self, registry: Arc<dyn TypeTreeRegistry>) {
32 self.registries.push(registry);
33 }
34
35 pub fn extend(&mut self, registries: impl IntoIterator<Item = Arc<dyn TypeTreeRegistry>>) {
36 self.registries.extend(registries);
37 }
38
39 pub fn is_empty(&self) -> bool {
40 self.registries.is_empty()
41 }
42}
43
44impl TypeTreeRegistry for CompositeTypeTreeRegistry {
45 fn resolve(&self, unity_version: &str, class_id: i32) -> Option<Arc<TypeTree>> {
46 for r in &self.registries {
47 if let Some(t) = r.resolve(unity_version, class_id) {
48 return Some(t);
49 }
50 }
51 None
52 }
53}
54
55#[derive(Debug, Clone)]
56enum VersionSelector {
57 Any,
58 Exact(String),
59 Prefix(String),
60}
61
62#[derive(Debug, Clone)]
63struct RegistryEntry {
64 selector: VersionSelector,
65 tree: Arc<TypeTree>,
66}
67
68#[derive(Debug, Default, Clone)]
70pub struct InMemoryTypeTreeRegistry {
71 by_class_id: HashMap<i32, Vec<RegistryEntry>>,
72}
73
74impl InMemoryTypeTreeRegistry {
75 pub fn insert_any(&mut self, class_id: i32, tree: TypeTree) {
76 self.insert_internal(class_id, VersionSelector::Any, tree);
77 }
78
79 pub fn insert_exact(&mut self, unity_version: String, class_id: i32, tree: TypeTree) {
80 self.insert_internal(class_id, VersionSelector::Exact(unity_version), tree);
81 }
82
83 pub fn insert_prefix(&mut self, unity_version_prefix: String, class_id: i32, tree: TypeTree) {
84 self.insert_internal(
85 class_id,
86 VersionSelector::Prefix(unity_version_prefix),
87 tree,
88 );
89 }
90
91 fn insert_internal(&mut self, class_id: i32, selector: VersionSelector, tree: TypeTree) {
92 self.by_class_id
93 .entry(class_id)
94 .or_default()
95 .push(RegistryEntry {
96 selector,
97 tree: Arc::new(tree),
98 });
99 }
100}
101
102impl TypeTreeRegistry for InMemoryTypeTreeRegistry {
103 fn resolve(&self, unity_version: &str, class_id: i32) -> Option<Arc<TypeTree>> {
104 let entries = self.by_class_id.get(&class_id)?;
105
106 for e in entries {
108 if matches!(&e.selector, VersionSelector::Exact(v) if v == unity_version) {
109 return Some(e.tree.clone());
110 }
111 }
112
113 let mut best: Option<(&RegistryEntry, usize)> = None;
115 for e in entries {
116 let VersionSelector::Prefix(prefix) = &e.selector else {
117 continue;
118 };
119 if unity_version.starts_with(prefix) {
120 let len = prefix.len();
121 match best {
122 Some((_prev, prev_len)) if prev_len >= len => {}
123 _ => best = Some((e, len)),
124 }
125 }
126 }
127 if let Some((e, _)) = best {
128 return Some(e.tree.clone());
129 }
130
131 for e in entries {
133 if matches!(e.selector, VersionSelector::Any) {
134 return Some(e.tree.clone());
135 }
136 }
137
138 None
139 }
140}
141
142#[derive(Debug, Deserialize)]
143struct JsonRegistryFile {
144 schema: u32,
145 entries: Vec<JsonRegistryEntry>,
146}
147
148#[derive(Debug, Deserialize)]
149struct JsonRegistryEntry {
150 #[serde(default)]
151 unity_version: Option<String>,
152 class_id: i32,
153 type_tree: TypeTree,
154}
155
156#[derive(Debug, Default, Clone)]
163pub struct JsonTypeTreeRegistry {
164 inner: InMemoryTypeTreeRegistry,
165}
166
167impl JsonTypeTreeRegistry {
168 pub fn from_reader(mut reader: impl Read) -> Result<Self> {
169 let mut buf = String::new();
170 reader
171 .read_to_string(&mut buf)
172 .map_err(|e| BinaryError::generic(format!("Failed to read registry JSON: {}", e)))?;
173 let parsed: JsonRegistryFile = serde_json::from_str(&buf)
174 .map_err(|e| BinaryError::invalid_data(format!("Invalid registry JSON: {}", e)))?;
175 if parsed.schema != 1 {
176 return Err(BinaryError::invalid_data(format!(
177 "Unsupported registry schema: {}",
178 parsed.schema
179 )));
180 }
181
182 let mut inner = InMemoryTypeTreeRegistry::default();
183 for e in parsed.entries {
184 match e.unity_version {
185 None => inner.insert_any(e.class_id, e.type_tree),
186 Some(v) => {
187 if v.is_empty() {
188 inner.insert_any(e.class_id, e.type_tree);
189 } else if let Some(prefix) = v.strip_suffix('*') {
190 inner.insert_prefix(prefix.to_string(), e.class_id, e.type_tree);
191 } else {
192 inner.insert_exact(v, e.class_id, e.type_tree);
193 }
194 }
195 }
196 }
197
198 Ok(Self { inner })
199 }
200
201 pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
202 let mut f = std::fs::File::open(path.as_ref()).map_err(|e| {
203 BinaryError::generic(format!(
204 "Failed to open registry JSON {:?}: {}",
205 path.as_ref(),
206 e
207 ))
208 })?;
209 Self::from_reader(&mut f)
210 }
211}
212
213impl TypeTreeRegistry for JsonTypeTreeRegistry {
214 fn resolve(&self, unity_version: &str, class_id: i32) -> Option<Arc<TypeTree>> {
215 self.inner.resolve(unity_version, class_id)
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 fn dummy_tree(tag: u32) -> TypeTree {
224 let mut t = TypeTree::new();
225 t.version = tag;
226 t.platform = tag;
227 t
228 }
229
230 #[test]
231 fn in_memory_registry_version_precedence() {
232 let class_id = 28;
233
234 let mut reg = InMemoryTypeTreeRegistry::default();
235 reg.insert_any(class_id, dummy_tree(1));
236 reg.insert_prefix("2020.3.".to_string(), class_id, dummy_tree(2));
237 reg.insert_exact("2020.3.48f1".to_string(), class_id, dummy_tree(3));
238
239 let exact = reg.resolve("2020.3.48f1", class_id).unwrap();
240 assert_eq!(exact.version, 3);
241
242 let prefix = reg.resolve("2020.3.9f1", class_id).unwrap();
243 assert_eq!(prefix.version, 2);
244
245 let any = reg.resolve("2019.4.40f1", class_id).unwrap();
246 assert_eq!(any.version, 1);
247 }
248
249 #[test]
250 fn in_memory_registry_longest_prefix_wins() {
251 let class_id = 28;
252
253 let mut reg = InMemoryTypeTreeRegistry::default();
254 reg.insert_prefix("2020.".to_string(), class_id, dummy_tree(1));
255 reg.insert_prefix("2020.3.".to_string(), class_id, dummy_tree(2));
256
257 let t = reg.resolve("2020.3.48f1", class_id).unwrap();
258 assert_eq!(t.version, 2);
259 }
260
261 #[test]
262 fn composite_registry_first_match_wins() {
263 let class_id = 28;
264
265 let mut a = InMemoryTypeTreeRegistry::default();
266 a.insert_any(class_id, dummy_tree(1));
267 let mut b = InMemoryTypeTreeRegistry::default();
268 b.insert_any(class_id, dummy_tree(2));
269
270 let composite_ab = CompositeTypeTreeRegistry::new(vec![Arc::new(a), Arc::new(b)]);
271 let t = composite_ab.resolve("2020.3.48f1", class_id).unwrap();
272 assert_eq!(t.version, 1);
273
274 let mut a2 = InMemoryTypeTreeRegistry::default();
275 a2.insert_any(class_id, dummy_tree(1));
276 let mut b2 = InMemoryTypeTreeRegistry::default();
277 b2.insert_any(class_id, dummy_tree(2));
278
279 let composite_ba = CompositeTypeTreeRegistry::new(vec![Arc::new(b2), Arc::new(a2)]);
280 let t = composite_ba.resolve("2020.3.48f1", class_id).unwrap();
281 assert_eq!(t.version, 2);
282 }
283
284 #[test]
285 fn json_registry_supports_wildcard_and_exact() {
286 let json = r#"
287 {
288 "schema": 1,
289 "entries": [
290 { "unity_version": "2020.3.*", "class_id": 28, "type_tree": { "nodes": [], "string_buffer": [], "version": 2, "platform": 2, "has_type_dependencies": false } },
291 { "unity_version": "2020.3.48f1", "class_id": 28, "type_tree": { "nodes": [], "string_buffer": [], "version": 3, "platform": 3, "has_type_dependencies": false } },
292 { "class_id": 28, "type_tree": { "nodes": [], "string_buffer": [], "version": 1, "platform": 1, "has_type_dependencies": false } }
293 ]
294 }
295 "#;
296
297 let reg = JsonTypeTreeRegistry::from_reader(json.as_bytes()).unwrap();
298
299 let exact = reg.resolve("2020.3.48f1", 28).unwrap();
300 assert_eq!(exact.version, 3);
301
302 let prefix = reg.resolve("2020.3.9f1", 28).unwrap();
303 assert_eq!(prefix.version, 2);
304
305 let any = reg.resolve("2019.4.40f1", 28).unwrap();
306 assert_eq!(any.version, 1);
307 }
308}