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)]
38struct CommonTable {
39 from: UnityVersion,
40 entries: Vec<(u32, u16)>,
41}
42
43#[derive(Clone, Debug)]
45pub struct TypeTreePackage {
46 classes: BTreeMap<i32, Vec<(UnityVersion, Option<Class>)>>,
48 nodes: Vec<PackedNode>,
49 strings: Vec<Arc<str>>,
50 common: Vec<CommonTable>,
52}
53
54#[derive(Clone, Copy, Debug)]
57pub struct CommonStrings<'a> {
58 package: &'a TypeTreePackage,
59 entries: &'a [(u32, u16)],
60}
61
62impl CommonStrings<'_> {
63 pub fn get(&self, offset: u32) -> Option<&str> {
66 let at = self.entries.binary_search_by_key(&offset, |&(o, _)| o).ok()?;
67 let &(_, string) = self.entries.get(at)?;
68 self.package.strings.get(usize::from(string)).map(|s| &**s)
69 }
70
71 pub fn len(&self) -> usize {
73 self.entries.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.entries.is_empty()
79 }
80
81 pub fn iter(&self) -> impl Iterator<Item = (u32, &str)> + '_ {
83 self.entries.iter().filter_map(|&(offset, string)| {
84 self.package
85 .strings
86 .get(usize::from(string))
87 .map(|s| (offset, &**s))
88 })
89 }
90}
91
92#[cfg(test)]
93impl TypeTreePackage {
94 pub(crate) fn empty() -> Self {
96 TypeTreePackage {
97 classes: BTreeMap::new(),
98 nodes: Vec::new(),
99 strings: Vec::new(),
100 common: Vec::new(),
101 }
102 }
103}
104
105fn varint_string(r: &mut Reader) -> Result<Arc<str>> {
106 let (mut len, mut shift) = (0usize, 0u32);
107 loop {
108 let b = r.u8()?;
109 if shift > 28 {
110 return Err(invalid("TPK: string length varint too long"));
111 }
112 len |= usize::from(b & 0x7F) << shift;
113 shift = shift.saturating_add(7);
114 if b & 0x80 == 0 {
115 break;
116 }
117 }
118 let s = std::str::from_utf8(r.take(len)?).map_err(|_| invalid("TPK: string not UTF-8"))?;
119 Ok(Arc::from(s))
120}
121
122fn skip(r: &mut Reader, n: usize, size: usize, what: &str) -> Result<()> {
124 let bytes = n.checked_mul(size).ok_or_else(|| {
125 invalid(format!(
126 "TPK: {n} {what} entries of {size} bytes at offset {}",
127 r.pos
128 ))
129 })?;
130 r.take(bytes).map(|_| ())
131}
132
133fn count(r: &mut Reader, what: &str) -> Result<usize> {
134 let n = r.i32()?;
135 usize::try_from(n).map_err(|_| invalid(format!("TPK: negative {what} count {n}")))
136}
137
138const MAX_TREE_NODES: usize = 1 << 20;
140
141impl TypeTreePackage {
142 pub fn parse(bytes: &[u8]) -> Result<Self> {
146 let mut r = Reader::new(bytes, false);
147 if r.u32()? != MAGIC {
148 return Err(invalid("not a TPK file"));
149 }
150 let format = r.u8()?;
151 if !(1..=2).contains(&format) {
152 return Err(Error::Unsupported(format!("TPK format version {format}")));
153 }
154 let compression = r.u8()?;
155 let data_type = r.u8()?;
156 let _reserved = r.u8()?;
157 let _reserved2 = r.u32()?;
158 let compressed = r.u32()? as usize;
159 let uncompressed = r.u32()? as usize;
160 if data_type != 0 {
161 return Err(Error::Unsupported(format!(
162 "TPK data type {data_type} (not type trees)"
163 )));
164 }
165 let packed = r.take(compressed)?;
166 let blob: Vec<u8> = match compression {
167 0 => packed.to_vec(),
168 1 => {
169 if uncompressed > packed.len().saturating_mul(LZ4_MAX_RATIO) {
171 return Err(invalid(format!(
172 "TPK: {uncompressed} bytes claimed of {} bytes of LZ4, which inflate to at most {}",
173 packed.len(),
174 packed.len().saturating_mul(LZ4_MAX_RATIO)
175 )));
176 }
177 Lz4.decompress(packed, uncompressed)
178 .map_err(|e| invalid(format!("TPK LZ4: {e}")))?
179 }
180 2 => {
181 return Err(Error::Unsupported(
182 "LZMA-compressed TPK: use an uncompressed or LZ4 package".into(),
183 ));
184 }
185 3 => {
186 return Err(Error::Unsupported(
187 "Brotli-compressed TPK: use an uncompressed or LZ4 package".into(),
188 ));
189 }
190 c => return Err(invalid(format!("TPK compression type {c}"))),
191 };
192 if blob.len() != uncompressed {
193 return Err(invalid(format!(
194 "TPK: {} bytes, header says {uncompressed}",
195 blob.len()
196 )));
197 }
198 Self::parse_blob(&blob, format)
199 }
200
201 fn parse_blob(blob: &[u8], format: u8) -> Result<Self> {
202 let mut r = Reader::new(blob, false);
203 let _created = r.i64()?;
204 let versions = count(&mut r, "version")?;
205 skip(&mut r, versions, 8, "version")?;
206 let mut classes = BTreeMap::new();
207 for _ in 0..count(&mut r, "class")? {
208 let id = r.i32()?;
209 let mut entries = Vec::new();
210 for _ in 0..count(&mut r, "class version")? {
211 let version = UnityVersion(r.u64()?);
212 let class = if r.u8()? != 0 {
213 let name = r.u16()?;
214 let _base = r.u16()?;
215 let flags = r.u8()?;
216 if flags & HAS_EDITOR_ROOT != 0 {
217 r.u16()?;
218 }
219 let release_root = if flags & HAS_RELEASE_ROOT != 0 {
220 Some(r.u16()?)
221 } else {
222 None
223 };
224 Some(Class {
225 name,
226 flags,
227 release_root,
228 })
229 } else {
230 None
231 };
232 entries.push((version, class));
233 }
234 classes.insert(id, entries);
235 }
236 let mut common_raw: Vec<(UnityVersion, Vec<(u16, u16)>)> = Vec::new();
240 let mut v1_counts: Vec<(UnityVersion, u8)> = Vec::new();
241 let mut v1_indices: Vec<u16> = Vec::new();
242 match format {
243 1 => {
244 for _ in 0..count(&mut r, "common string version")? {
245 let version = UnityVersion(r.u64()?);
246 v1_counts.push((version, r.u8()?));
247 }
248 for _ in 0..count(&mut r, "common string index")? {
249 v1_indices.push(r.u16()?);
250 }
251 }
252 _ => {
253 for _ in 0..count(&mut r, "common string version")? {
254 let version = UnityVersion(r.u64()?);
255 let n = count(&mut r, "common string entry")?;
256 let mut entries = Vec::new();
257 for _ in 0..n {
258 let offset = r.u16()?;
259 entries.push((offset, r.u16()?));
260 }
261 common_raw.push((version, entries));
262 }
263 }
264 }
265 let mut nodes = Vec::new();
266 for _ in 0..count(&mut r, "node")? {
267 let type_name = r.u16()?;
268 let name = r.u16()?;
269 let byte_size = r.i32()?;
270 let version = r.i16()?;
271 let _type_flags = r.u8()?;
272 let meta_flag = r.u32()?;
273 let n = r.u16()?;
274 let children = (0..n).map(|_| r.u16()).collect::<Result<Vec<_>>>()?;
275 nodes.push(PackedNode {
276 type_name,
277 name,
278 byte_size,
279 version,
280 meta_flag,
281 children,
282 });
283 }
284 let strings = (0..count(&mut r, "string")?)
285 .map(|_| varint_string(&mut r))
286 .collect::<Result<Vec<_>>>()?;
287 if r.remaining() != 0 {
288 return Err(invalid(format!(
289 "TPK: {} bytes after the string buffer",
290 r.remaining()
291 )));
292 }
293 for (version, n) in v1_counts {
294 let mut offset = 0u16;
295 let mut entries = Vec::new();
296 for &index in v1_indices.iter().take(usize::from(n)) {
297 entries.push((offset, index));
298 let len = strings
299 .get(usize::from(index))
300 .ok_or_else(|| invalid("TPK: common string index out of range"))?
301 .len();
302 offset = u16::try_from(len)
303 .ok()
304 .and_then(|l| offset.checked_add(l))
305 .and_then(|o| o.checked_add(1))
306 .ok_or_else(|| invalid("TPK: common string table longer than 64 KiB"))?;
307 }
308 common_raw.push((version, entries));
309 }
310 let common = common_raw
311 .into_iter()
312 .map(|(from, entries)| {
313 let mut entries: Vec<(u32, u16)> =
314 entries.into_iter().map(|(o, s)| (u32::from(o), s)).collect();
315 entries.sort_unstable();
316 if entries
317 .windows(2)
318 .any(|w| w.first().map(|e| e.0) == w.get(1).map(|e| e.0))
319 {
320 return Err(invalid("TPK: two common strings at one offset"));
321 }
322 Ok(CommonTable { from, entries })
323 })
324 .collect::<Result<Vec<_>>>()?;
325 let pkg = TypeTreePackage {
326 classes,
327 nodes,
328 strings,
329 common,
330 };
331 pkg.check()?;
332 Ok(pkg)
333 }
334
335 fn check(&self) -> Result<()> {
337 let (ns, nn) = (self.strings.len(), self.nodes.len());
338 for n in &self.nodes {
339 if usize::from(n.type_name) >= ns || usize::from(n.name) >= ns {
340 return Err(invalid("TPK: node string index out of range"));
341 }
342 if n.children.iter().any(|&c| usize::from(c) >= nn) {
343 return Err(invalid("TPK: child node index out of range"));
344 }
345 }
346 for t in &self.common {
347 if t.entries.iter().any(|&(_, s)| usize::from(s) >= ns) {
348 return Err(invalid("TPK: common string index out of range"));
349 }
350 }
351 for c in self.classes.values().flatten().filter_map(|(_, c)| c.as_ref()) {
352 if usize::from(c.name) >= ns || c.release_root.is_some_and(|r| usize::from(r) >= nn) {
353 return Err(invalid("TPK: class index out of range"));
354 }
355 }
356 Ok(())
357 }
358
359 fn class(&self, class_id: i32, version: UnityVersion) -> Option<&Class> {
360 self.classes
361 .get(&class_id)?
362 .iter()
363 .take_while(|(v, _)| *v <= version)
364 .last()
365 .and_then(|(_, c)| c.as_ref())
366 }
367
368 pub fn common_strings(&self, version: UnityVersion) -> Option<CommonStrings<'_>> {
371 let table = self.common.iter().take_while(|t| t.from <= version).last()?;
372 Some(CommonStrings {
373 package: self,
374 entries: &table.entries,
375 })
376 }
377
378 pub fn class_name(&self, class_id: i32, version: UnityVersion) -> Option<&str> {
380 self.class(class_id, version)
381 .and_then(|c| self.strings.get(usize::from(c.name)))
382 .map(|s| &**s)
383 }
384
385 pub fn type_tree(&self, class_id: i32, version: UnityVersion) -> Result<TypeTree> {
387 let class = self.class(class_id, version).ok_or_else(|| {
388 invalid(format!(
389 "class {class_id} not in the type tree package for {version}"
390 ))
391 })?;
392 let root = class.release_root.ok_or_else(|| {
393 invalid(format!(
394 "class {class_id} ({}) has no release type tree in {version} (flags {:#x})",
395 self.strings.get(usize::from(class.name)).map_or("?", |s| &**s),
396 class.flags
397 ))
398 })?;
399 let mut nodes: Vec<Node> = Vec::new();
400 fn add(pkg: &TypeTreePackage, id: u16, depth: u32, nodes: &mut Vec<Node>) -> Result<usize> {
402 if depth > 64 {
403 return Err(invalid("TPK: type tree deeper than 64"));
404 }
405 if nodes.len() >= MAX_TREE_NODES {
408 return Err(invalid(format!(
409 "TPK: a type tree of more than {MAX_TREE_NODES} nodes"
410 )));
411 }
412 let p = pkg
413 .nodes
414 .get(usize::from(id))
415 .ok_or_else(|| invalid(format!("TPK: node {id} of {}", pkg.nodes.len())))?;
416 let string = |i: u16| {
417 pkg.strings
418 .get(usize::from(i))
419 .cloned()
420 .ok_or_else(|| invalid(format!("TPK: string {i} of {}", pkg.strings.len())))
421 };
422 let at = nodes.len();
423 nodes.push(Node {
424 ty: string(p.type_name)?,
425 name: string(p.name)?,
426 byte_size: p.byte_size,
427 version: i32::from(p.version),
428 meta_flag: p.meta_flag,
429 children: Vec::new(),
430 });
431 for &c in &p.children {
432 let ci = add(pkg, c, depth.saturating_add(1), nodes)?;
433 nodes
434 .get_mut(at)
435 .ok_or_else(|| invalid("TPK: a type tree node went missing"))?
436 .children
437 .push(ci);
438 }
439 Ok(at)
440 }
441 add(self, root, 0, &mut nodes)?;
442 TypeTree::new(nodes)
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 fn package(compression: u8, packed: &[u8], expected: u32) -> Vec<u8> {
451 let mut bytes = MAGIC.to_le_bytes().to_vec();
452 bytes.extend([2, compression, 0, 0]);
453 bytes.extend(0u32.to_le_bytes());
454 bytes.extend((packed.len() as u32).to_le_bytes());
455 bytes.extend(expected.to_le_bytes());
456 bytes.extend(packed);
457 bytes
458 }
459
460 fn literal_lz4(blob: &[u8]) -> Vec<u8> {
463 let mut out = vec![0xf0];
464 let mut rest = blob.len() - 15;
465 while rest >= 255 {
466 out.push(255);
467 rest -= 255;
468 }
469 out.push(rest as u8);
470 out.extend(blob);
471 out
472 }
473
474 fn one_class_blob() -> Vec<u8> {
477 let mut b = Vec::new();
478 b.extend(0x0102_0304_0506_0708i64.to_le_bytes()); b.extend(1i32.to_le_bytes()); b.extend(0u64.to_le_bytes());
481 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);
489 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);
501 b.extend(b"Base");
502 b
503 }
504
505 #[test]
508 fn literal_lz4_package_matches_uncompressed_and_rejects_bad_sizes() {
509 let blob = one_class_blob();
510 let n = blob.len() as u32;
511 let packed = literal_lz4(&blob);
512 let raw = TypeTreePackage::parse(&package(0, &blob, n)).unwrap();
513 let compressed = TypeTreePackage::parse(&package(1, &packed, n)).unwrap();
514 for p in [&raw, &compressed] {
515 assert_eq!(p.class_name(1, UnityVersion(1)), Some("Base"));
516 assert_eq!(p.class_name(2, UnityVersion(1)), None);
517 assert_eq!(p.classes[&1][0].1.as_ref().unwrap().release_root, Some(0));
518 assert_eq!(&*p.strings, &[std::sync::Arc::<str>::from("Base")]);
519 let node = &p.nodes[0];
520 assert_eq!(
521 (node.type_name, node.name, node.byte_size, node.version),
522 (0, 0, -1, 1)
523 );
524 assert!(p.type_tree(1, UnityVersion(1)).is_ok());
525 }
526 for expected in [0, n - 1, n + 1] {
527 assert!(TypeTreePackage::parse(&package(1, &packed, expected)).is_err());
528 }
529 assert!(TypeTreePackage::parse(&package(1, &packed[..packed.len() - 1], n)).is_err());
530 assert!(TypeTreePackage::parse(&package(1, &[], 0)).is_err());
531 let e = TypeTreePackage::parse(&package(1, &packed, u32::MAX))
533 .unwrap_err()
534 .to_string();
535 assert!(e.contains("inflate to at most"), "{e}");
536 }
537
538 #[test]
541 fn a_type_tree_that_expands_past_a_million_nodes_is_refused() {
542 let levels = 30u16;
543 let nodes = (0..levels)
544 .map(|i| PackedNode {
545 type_name: 0,
546 name: 0,
547 byte_size: -1,
548 version: 1,
549 meta_flag: 0,
550 children: if i + 1 < levels {
551 vec![i + 1, i + 1]
552 } else {
553 vec![]
554 },
555 })
556 .collect();
557 let class = Class {
558 name: 0,
559 flags: HAS_RELEASE_ROOT,
560 release_root: Some(0),
561 };
562 let pkg = TypeTreePackage {
563 classes: BTreeMap::from([(1, vec![(UnityVersion(0), Some(class))])]),
564 nodes,
565 strings: vec![std::sync::Arc::from("Base")],
566 common: Vec::new(),
567 };
568 let e = pkg.type_tree(1, UnityVersion(1)).unwrap_err().to_string();
569 assert!(e.contains("more than 1048576 nodes"), "{e}");
570 }
571}