1use primase_foundations::compress::{Decompressor, LZ4_MAX_RATIO, Lz4};
6use std::collections::BTreeMap;
7use std::sync::Arc;
8
9use crate::reader::Reader;
10use crate::typetree::{Node, TypeTree};
11use crate::version::UnityVersion;
12use crate::{Error, Result, invalid};
13
14const MAGIC: u32 = 0x2A4B_5054;
15const HAS_EDITOR_ROOT: u8 = 64;
16const HAS_RELEASE_ROOT: u8 = 128;
17
18#[derive(Clone, Debug)]
19struct Class {
20 name: u16,
21 flags: u8,
22 release_root: Option<u16>,
23}
24
25#[derive(Clone, Debug)]
26struct PackedNode {
27 type_name: u16,
28 name: u16,
29 byte_size: i32,
30 version: i16,
31 meta_flag: u32,
32 children: Vec<u16>,
33}
34
35#[derive(Clone, Debug)]
37pub struct TypeTreePackage {
38 classes: BTreeMap<i32, Vec<(UnityVersion, Option<Class>)>>,
40 nodes: Vec<PackedNode>,
41 strings: Vec<Arc<str>>,
42}
43
44#[cfg(test)]
45impl TypeTreePackage {
46 pub(crate) fn empty() -> Self {
48 TypeTreePackage {
49 classes: BTreeMap::new(),
50 nodes: Vec::new(),
51 strings: Vec::new(),
52 }
53 }
54}
55
56fn varint_string(r: &mut Reader) -> Result<Arc<str>> {
57 let (mut len, mut shift) = (0usize, 0u32);
58 loop {
59 let b = r.u8()?;
60 if shift > 28 {
61 return Err(invalid("TPK: string length varint too long"));
62 }
63 len |= usize::from(b & 0x7F) << shift;
64 shift = shift.saturating_add(7);
65 if b & 0x80 == 0 {
66 break;
67 }
68 }
69 let s = std::str::from_utf8(r.take(len)?).map_err(|_| invalid("TPK: string not UTF-8"))?;
70 Ok(Arc::from(s))
71}
72
73fn skip(r: &mut Reader, n: usize, size: usize, what: &str) -> Result<()> {
75 let bytes = n.checked_mul(size).ok_or_else(|| {
76 invalid(format!(
77 "TPK: {n} {what} entries of {size} bytes at offset {}",
78 r.pos
79 ))
80 })?;
81 r.take(bytes).map(|_| ())
82}
83
84fn count(r: &mut Reader, what: &str) -> Result<usize> {
85 let n = r.i32()?;
86 usize::try_from(n).map_err(|_| invalid(format!("TPK: negative {what} count {n}")))
87}
88
89const MAX_TREE_NODES: usize = 1 << 20;
91
92impl TypeTreePackage {
93 pub fn parse(bytes: &[u8]) -> Result<Self> {
97 let mut r = Reader::new(bytes, false);
98 if r.u32()? != MAGIC {
99 return Err(invalid("not a TPK file"));
100 }
101 let format = r.u8()?;
102 if !(1..=2).contains(&format) {
103 return Err(Error::Unsupported(format!("TPK format version {format}")));
104 }
105 let compression = r.u8()?;
106 let data_type = r.u8()?;
107 let _reserved = r.u8()?;
108 let _reserved2 = r.u32()?;
109 let compressed = r.u32()? as usize;
110 let uncompressed = r.u32()? as usize;
111 if data_type != 0 {
112 return Err(Error::Unsupported(format!(
113 "TPK data type {data_type} (not type trees)"
114 )));
115 }
116 let packed = r.take(compressed)?;
117 let blob: Vec<u8> = match compression {
118 0 => packed.to_vec(),
119 1 => {
120 if uncompressed > packed.len().saturating_mul(LZ4_MAX_RATIO) {
122 return Err(invalid(format!(
123 "TPK: {uncompressed} bytes claimed of {} bytes of LZ4, which inflate to at most {}",
124 packed.len(),
125 packed.len().saturating_mul(LZ4_MAX_RATIO)
126 )));
127 }
128 Lz4.decompress(packed, uncompressed)
129 .map_err(|e| invalid(format!("TPK LZ4: {e}")))?
130 }
131 2 => {
132 return Err(Error::Unsupported(
133 "LZMA-compressed TPK: use an uncompressed or LZ4 package".into(),
134 ));
135 }
136 3 => {
137 return Err(Error::Unsupported(
138 "Brotli-compressed TPK: use an uncompressed or LZ4 package".into(),
139 ));
140 }
141 c => return Err(invalid(format!("TPK compression type {c}"))),
142 };
143 if blob.len() != uncompressed {
144 return Err(invalid(format!(
145 "TPK: {} bytes, header says {uncompressed}",
146 blob.len()
147 )));
148 }
149 Self::parse_blob(&blob, format)
150 }
151
152 fn parse_blob(blob: &[u8], format: u8) -> Result<Self> {
153 let mut r = Reader::new(blob, false);
154 let _created = r.i64()?;
155 let versions = count(&mut r, "version")?;
156 skip(&mut r, versions, 8, "version")?;
157 let mut classes = BTreeMap::new();
158 for _ in 0..count(&mut r, "class")? {
159 let id = r.i32()?;
160 let mut entries = Vec::new();
161 for _ in 0..count(&mut r, "class version")? {
162 let version = UnityVersion(r.u64()?);
163 let class = if r.u8()? != 0 {
164 let name = r.u16()?;
165 let _base = r.u16()?;
166 let flags = r.u8()?;
167 if flags & HAS_EDITOR_ROOT != 0 {
168 r.u16()?;
169 }
170 let release_root = if flags & HAS_RELEASE_ROOT != 0 {
171 Some(r.u16()?)
172 } else {
173 None
174 };
175 Some(Class {
176 name,
177 flags,
178 release_root,
179 })
180 } else {
181 None
182 };
183 entries.push((version, class));
184 }
185 classes.insert(id, entries);
186 }
187 match format {
189 1 => {
190 let n = count(&mut r, "common string version")?;
191 skip(&mut r, n, 9, "common string version")?;
192 let n = count(&mut r, "common string index")?;
193 skip(&mut r, n, 2, "common string index")?;
194 }
195 _ => {
196 for _ in 0..count(&mut r, "common string version")? {
197 r.u64()?;
198 let n = count(&mut r, "common string entry")?;
199 skip(&mut r, n, 4, "common string entry")?;
200 }
201 }
202 }
203 let mut nodes = Vec::new();
204 for _ in 0..count(&mut r, "node")? {
205 let type_name = r.u16()?;
206 let name = r.u16()?;
207 let byte_size = r.i32()?;
208 let version = r.i16()?;
209 let _type_flags = r.u8()?;
210 let meta_flag = r.u32()?;
211 let n = r.u16()?;
212 let children = (0..n).map(|_| r.u16()).collect::<Result<Vec<_>>>()?;
213 nodes.push(PackedNode {
214 type_name,
215 name,
216 byte_size,
217 version,
218 meta_flag,
219 children,
220 });
221 }
222 let strings = (0..count(&mut r, "string")?)
223 .map(|_| varint_string(&mut r))
224 .collect::<Result<Vec<_>>>()?;
225 if r.remaining() != 0 {
226 return Err(invalid(format!(
227 "TPK: {} bytes after the string buffer",
228 r.remaining()
229 )));
230 }
231 let pkg = TypeTreePackage {
232 classes,
233 nodes,
234 strings,
235 };
236 pkg.check()?;
237 Ok(pkg)
238 }
239
240 fn check(&self) -> Result<()> {
242 let (ns, nn) = (self.strings.len(), self.nodes.len());
243 for n in &self.nodes {
244 if usize::from(n.type_name) >= ns || usize::from(n.name) >= ns {
245 return Err(invalid("TPK: node string index out of range"));
246 }
247 if n.children.iter().any(|&c| usize::from(c) >= nn) {
248 return Err(invalid("TPK: child node index out of range"));
249 }
250 }
251 for c in self.classes.values().flatten().filter_map(|(_, c)| c.as_ref()) {
252 if usize::from(c.name) >= ns || c.release_root.is_some_and(|r| usize::from(r) >= nn) {
253 return Err(invalid("TPK: class index out of range"));
254 }
255 }
256 Ok(())
257 }
258
259 fn class(&self, class_id: i32, version: UnityVersion) -> Option<&Class> {
260 self.classes
261 .get(&class_id)?
262 .iter()
263 .take_while(|(v, _)| *v <= version)
264 .last()
265 .and_then(|(_, c)| c.as_ref())
266 }
267
268 pub fn class_name(&self, class_id: i32, version: UnityVersion) -> Option<&str> {
270 self.class(class_id, version)
271 .and_then(|c| self.strings.get(usize::from(c.name)))
272 .map(|s| &**s)
273 }
274
275 pub fn type_tree(&self, class_id: i32, version: UnityVersion) -> Result<TypeTree> {
277 let class = self.class(class_id, version).ok_or_else(|| {
278 invalid(format!(
279 "class {class_id} not in the type tree package for {version}"
280 ))
281 })?;
282 let root = class.release_root.ok_or_else(|| {
283 invalid(format!(
284 "class {class_id} ({}) has no release type tree in {version} (flags {:#x})",
285 self.strings.get(usize::from(class.name)).map_or("?", |s| &**s),
286 class.flags
287 ))
288 })?;
289 let mut nodes: Vec<Node> = Vec::new();
290 fn add(pkg: &TypeTreePackage, id: u16, depth: u32, nodes: &mut Vec<Node>) -> Result<usize> {
292 if depth > 64 {
293 return Err(invalid("TPK: type tree deeper than 64"));
294 }
295 if nodes.len() >= MAX_TREE_NODES {
298 return Err(invalid(format!(
299 "TPK: a type tree of more than {MAX_TREE_NODES} nodes"
300 )));
301 }
302 let p = pkg
303 .nodes
304 .get(usize::from(id))
305 .ok_or_else(|| invalid(format!("TPK: node {id} of {}", pkg.nodes.len())))?;
306 let string = |i: u16| {
307 pkg.strings
308 .get(usize::from(i))
309 .cloned()
310 .ok_or_else(|| invalid(format!("TPK: string {i} of {}", pkg.strings.len())))
311 };
312 let at = nodes.len();
313 nodes.push(Node {
314 ty: string(p.type_name)?,
315 name: string(p.name)?,
316 byte_size: p.byte_size,
317 version: i32::from(p.version),
318 meta_flag: p.meta_flag,
319 children: Vec::new(),
320 });
321 for &c in &p.children {
322 let ci = add(pkg, c, depth.saturating_add(1), nodes)?;
323 nodes
324 .get_mut(at)
325 .ok_or_else(|| invalid("TPK: a type tree node went missing"))?
326 .children
327 .push(ci);
328 }
329 Ok(at)
330 }
331 add(self, root, 0, &mut nodes)?;
332 TypeTree::new(nodes)
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 fn package(compression: u8, packed: &[u8], expected: u32) -> Vec<u8> {
341 let mut bytes = MAGIC.to_le_bytes().to_vec();
342 bytes.extend([2, compression, 0, 0]);
343 bytes.extend(0u32.to_le_bytes());
344 bytes.extend((packed.len() as u32).to_le_bytes());
345 bytes.extend(expected.to_le_bytes());
346 bytes.extend(packed);
347 bytes
348 }
349
350 fn literal_lz4(blob: &[u8]) -> Vec<u8> {
353 let mut out = vec![0xf0];
354 let mut rest = blob.len() - 15;
355 while rest >= 255 {
356 out.push(255);
357 rest -= 255;
358 }
359 out.push(rest as u8);
360 out.extend(blob);
361 out
362 }
363
364 fn one_class_blob() -> Vec<u8> {
367 let mut b = Vec::new();
368 b.extend(0x0102_0304_0506_0708i64.to_le_bytes()); b.extend(1i32.to_le_bytes()); b.extend(0u64.to_le_bytes());
371 b.extend(1i32.to_le_bytes()); b.extend(1i32.to_le_bytes()); b.extend(1i32.to_le_bytes()); b.extend(0u64.to_le_bytes()); b.push(1); b.extend(0u16.to_le_bytes()); b.extend(0u16.to_le_bytes()); b.push(HAS_RELEASE_ROOT);
379 b.extend(0u16.to_le_bytes()); b.extend(0i32.to_le_bytes()); b.extend(1i32.to_le_bytes()); b.extend(0u16.to_le_bytes()); b.extend(0u16.to_le_bytes()); b.extend((-1i32).to_le_bytes()); b.extend(1i16.to_le_bytes()); b.push(0); b.extend(0u32.to_le_bytes()); b.extend(0u16.to_le_bytes()); b.extend(1i32.to_le_bytes()); b.push(4);
391 b.extend(b"Base");
392 b
393 }
394
395 #[test]
398 fn literal_lz4_package_matches_uncompressed_and_rejects_bad_sizes() {
399 let blob = one_class_blob();
400 let n = blob.len() as u32;
401 let packed = literal_lz4(&blob);
402 let raw = TypeTreePackage::parse(&package(0, &blob, n)).unwrap();
403 let compressed = TypeTreePackage::parse(&package(1, &packed, n)).unwrap();
404 for p in [&raw, &compressed] {
405 assert_eq!(p.class_name(1, UnityVersion(1)), Some("Base"));
406 assert_eq!(p.class_name(2, UnityVersion(1)), None);
407 assert_eq!(p.classes[&1][0].1.as_ref().unwrap().release_root, Some(0));
408 assert_eq!(&*p.strings, &[std::sync::Arc::<str>::from("Base")]);
409 let node = &p.nodes[0];
410 assert_eq!(
411 (node.type_name, node.name, node.byte_size, node.version),
412 (0, 0, -1, 1)
413 );
414 assert!(p.type_tree(1, UnityVersion(1)).is_ok());
415 }
416 for expected in [0, n - 1, n + 1] {
417 assert!(TypeTreePackage::parse(&package(1, &packed, expected)).is_err());
418 }
419 assert!(TypeTreePackage::parse(&package(1, &packed[..packed.len() - 1], n)).is_err());
420 assert!(TypeTreePackage::parse(&package(1, &[], 0)).is_err());
421 let e = TypeTreePackage::parse(&package(1, &packed, u32::MAX))
423 .unwrap_err()
424 .to_string();
425 assert!(e.contains("inflate to at most"), "{e}");
426 }
427
428 #[test]
431 fn a_type_tree_that_expands_past_a_million_nodes_is_refused() {
432 let levels = 30u16;
433 let nodes = (0..levels)
434 .map(|i| PackedNode {
435 type_name: 0,
436 name: 0,
437 byte_size: -1,
438 version: 1,
439 meta_flag: 0,
440 children: if i + 1 < levels {
441 vec![i + 1, i + 1]
442 } else {
443 vec![]
444 },
445 })
446 .collect();
447 let class = Class {
448 name: 0,
449 flags: HAS_RELEASE_ROOT,
450 release_root: Some(0),
451 };
452 let pkg = TypeTreePackage {
453 classes: BTreeMap::from([(1, vec![(UnityVersion(0), Some(class))])]),
454 nodes,
455 strings: vec![std::sync::Arc::from("Base")],
456 };
457 let e = pkg.type_tree(1, UnityVersion(1)).unwrap_err().to_string();
458 assert!(e.contains("more than 1048576 nodes"), "{e}");
459 }
460}