1use std::borrow::Cow;
18use std::collections::BTreeMap;
19use std::sync::Arc;
20
21use crate::bundle::Bundle;
22use crate::reader::Reader;
23use crate::tpk::{CommonStrings, TypeTreePackage};
24use crate::typetree::{Node, TypeTree};
25use crate::value::Value;
26use crate::version::UnityVersion;
27use crate::{Error, Result, invalid};
28
29pub const VERSIONS: std::ops::RangeInclusive<u32> = 21..=22;
31
32pub const MONO_BEHAVIOUR: i32 = 114;
34
35#[derive(Clone, Debug, PartialEq)]
37pub struct SerializedType {
38 pub class_id: i32,
40 pub stripped: bool,
42 pub script_type_index: i16,
44 pub script_id: Option<[u8; 16]>,
47 pub old_type_hash: [u8; 16],
49 pub tree: Option<Arc<TypeTree>>,
52 pub dependencies: Vec<i32>,
54 pub reference: Option<ReferenceClass>,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct ReferenceClass {
61 pub class: String,
63 pub namespace: String,
65 pub assembly: String,
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct ObjectInfo {
72 pub path_id: i64,
74 pub offset: u64,
76 pub size: u32,
78 pub type_index: u32,
80 pub class_id: i32,
82}
83
84#[derive(Clone, Debug, PartialEq)]
86pub struct External {
87 pub guid: [u8; 16],
89 pub kind: i32,
91 pub path: String,
94}
95
96#[derive(Clone, Debug, PartialEq)]
98pub struct Decoded {
99 pub value: Value,
101 pub rest: usize,
103}
104
105impl Decoded {
106 pub fn covered(&self, class_id: i32) -> bool {
109 self.rest == 0 || class_id == MONO_BEHAVIOUR
110 }
111}
112
113#[derive(Clone, Debug)]
116pub struct SerializedFile {
117 pub version: u32,
119 pub unity_version: String,
121 pub unity: UnityVersion,
123 pub target_platform: i32,
125 pub big_endian: bool,
127 pub types: Vec<SerializedType>,
129 pub objects: Vec<ObjectInfo>,
131 pub script_types: Vec<(i32, i64)>,
133 pub externals: Vec<External>,
135 pub ref_types: Vec<SerializedType>,
137 pub user_information: String,
139 data: Backing,
140 by_path_id: BTreeMap<i64, usize>,
141}
142
143#[derive(Clone, Debug)]
145enum Backing {
146 Memory(Vec<u8>),
147 Node { bundle: Arc<Bundle>, node: usize },
148}
149
150fn hash(r: &mut Reader) -> Result<[u8; 16]> {
151 Ok(r.take(16)?.try_into().expect("16 bytes"))
152}
153
154fn count(r: &mut Reader, what: &str) -> Result<usize> {
155 let n = r.i32()?;
156 let n = usize::try_from(n).map_err(|_| invalid(format!("negative {what} count {n}")))?;
157 if n > r.remaining() {
158 return Err(invalid(format!("{what} count {n} larger than the metadata")));
159 }
160 Ok(n)
161}
162
163struct TreeContext<'a> {
166 common: Option<CommonStrings<'a>>,
167 version: UnityVersion,
168 strings: BTreeMap<String, Arc<str>>,
169}
170
171impl TreeContext<'_> {
172 fn intern(&mut self, s: &str) -> Arc<str> {
173 if let Some(a) = self.strings.get(s) {
174 return a.clone();
175 }
176 let a: Arc<str> = Arc::from(s);
177 self.strings.insert(s.to_owned(), a.clone());
178 a
179 }
180
181 fn string(&mut self, buffer: &[u8], offset: u32) -> Result<Arc<str>> {
183 if offset & COMMON_STRING != 0 {
184 let at = offset & !COMMON_STRING;
185 let common = self.common.ok_or_else(|| {
186 Error::Unsupported(format!(
187 "a type tree names common string {at}: read the file with the type tree package of Unity {}",
188 self.version
189 ))
190 })?;
191 let s = common.get(at).ok_or_else(|| {
192 invalid(format!(
193 "a type tree names common string {at}, which the table of Unity {} lacks",
194 self.version
195 ))
196 })?;
197 return Ok(self.intern(s));
198 }
199 let rest = usize::try_from(offset).ok().and_then(|o| buffer.get(o..));
200 let bytes = rest
201 .and_then(|r| r.iter().position(|&b| b == 0).and_then(|n| r.get(..n)))
202 .ok_or_else(|| {
203 invalid(format!(
204 "a type tree names string {offset}, outside its {} byte buffer",
205 buffer.len()
206 ))
207 })?;
208 let s = std::str::from_utf8(bytes)
209 .map_err(|_| invalid(format!("type tree string {offset} is not UTF-8")))?;
210 Ok(self.intern(s))
211 }
212}
213
214const COMMON_STRING: u32 = 0x8000_0000;
216
217const TREE_NODE_BYTES: usize = 32;
221
222fn read_tree(r: &mut Reader, context: &mut TreeContext) -> Result<Arc<TypeTree>> {
226 let nodes = r.i32()?;
227 let buffer_size = r.i32()?;
228 let nodes = usize::try_from(nodes)
229 .ok()
230 .filter(|&n| n > 0 && n <= r.remaining() / TREE_NODE_BYTES)
231 .ok_or_else(|| invalid(format!("a type tree of {nodes} nodes in {} bytes", r.remaining())))?;
232 let buffer_size = usize::try_from(buffer_size)
233 .map_err(|_| invalid(format!("a type tree string buffer of {buffer_size} bytes")))?;
234 let raw = r.take(nodes.saturating_mul(TREE_NODE_BYTES))?;
235 let buffer = r.take(buffer_size)?;
236 let mut nodes: Vec<Node> = Vec::with_capacity(nodes);
237 let mut open: Vec<usize> = Vec::new();
239 let mut n = Reader::new(raw, r.big_endian);
240 while n.remaining() > 0 {
241 let version = n.i16()?;
242 let level = usize::from(n.u8()?);
243 let _type_flags = n.u8()?;
244 let (type_offset, name_offset) = (n.u32()?, n.u32()?);
245 let byte_size = n.i32()?;
246 let _index = n.i32()?;
247 let meta_flag = n.u32()?;
248 let _reference_hash = n.u64()?;
249 if level > open.len() || (level == 0 && !nodes.is_empty()) {
250 return Err(invalid(format!(
251 "type tree node {} is at level {level}, below {} open levels",
252 nodes.len(),
253 open.len()
254 )));
255 }
256 open.truncate(level);
257 let at = nodes.len();
258 if let Some(&parent) = open.last() {
259 nodes
260 .get_mut(parent)
261 .ok_or_else(|| invalid("a type tree node went missing"))?
262 .children
263 .push(at);
264 }
265 open.push(at);
266 nodes.push(Node {
267 ty: context.string(buffer, type_offset)?,
268 name: context.string(buffer, name_offset)?,
269 byte_size,
270 version: i32::from(version),
271 meta_flag,
272 children: Vec::new(),
273 });
274 }
275 Ok(Arc::new(TypeTree::new(nodes)?))
276}
277
278fn serialized_type(
280 r: &mut Reader,
281 reference: bool,
282 trees: Option<&mut TreeContext>,
283) -> Result<SerializedType> {
284 let class_id = r.i32()?;
285 let stripped = r.bool()?;
286 let script_type_index = r.i16()?;
287 let script_id = if (reference && script_type_index >= 0) || (!reference && class_id == MONO_BEHAVIOUR) {
288 Some(hash(r)?)
289 } else {
290 None
291 };
292 let old_type_hash = hash(r)?;
293 let (mut tree, mut dependencies, mut reference_class) = (None, Vec::new(), None);
294 if let Some(context) = trees {
295 tree = Some(read_tree(r, context)?);
296 if reference {
297 reference_class = Some(ReferenceClass {
298 class: r.cstr()?,
299 namespace: r.cstr()?,
300 assembly: r.cstr()?,
301 });
302 } else {
303 let n = count(r, "type dependency")?;
304 dependencies = (0..n).map(|_| r.i32()).collect::<Result<Vec<_>>>()?;
305 }
306 }
307 Ok(SerializedType {
308 class_id,
309 stripped,
310 script_type_index,
311 script_id,
312 old_type_hash,
313 tree,
314 dependencies,
315 reference: reference_class,
316 })
317}
318
319struct Header {
321 version: u32,
322 big_endian: bool,
323 metadata_start: usize,
325 metadata_size: usize,
326 file_size: u64,
327 data_offset: u64,
328 head_len: u64,
330}
331
332impl Header {
333 fn of(head: &[u8]) -> Result<Header> {
335 let mut h = Reader::new(head, true);
336 let legacy_metadata = h.u32()?;
337 let legacy_size = h.u32()?;
338 let version = h.u32()?;
339 let legacy_offset = h.u32()?;
340 if !VERSIONS.contains(&version) {
341 return Err(Error::Unsupported(format!(
342 "SerializedFile version {version} (read: {}..={})",
343 VERSIONS.start(),
344 VERSIONS.end()
345 )));
346 }
347 let big_endian = h.bool()?;
348 h.take(3)?;
349 let (metadata_size, file_size, data_offset) = if version == 22 {
350 let metadata_size = h.u32()?;
351 let file_size = h.u64()?;
352 let data_offset = h.u64()?;
353 let _reserved = h.u64()?;
354 (metadata_size, file_size, data_offset)
355 } else {
356 (legacy_metadata, u64::from(legacy_size), u64::from(legacy_offset))
357 };
358 let metadata_size =
359 usize::try_from(metadata_size).map_err(|_| invalid("metadata size overflows its byte range"))?;
360 let head_len =
361 u64::try_from(h.pos.saturating_add(metadata_size)).map_err(|_| invalid("metadata size"))?;
362 Ok(Header {
363 version,
364 big_endian,
365 metadata_start: h.pos,
366 metadata_size,
367 file_size,
368 data_offset,
369 head_len,
370 })
371 }
372}
373
374impl SerializedFile {
375 pub fn looks_like(bytes: &[u8]) -> bool {
377 let be = |at: usize| -> Option<u32> {
378 Some(u32::from_be_bytes(
379 bytes.get(at..at.checked_add(4)?)?.try_into().ok()?,
380 ))
381 };
382 let Ok(len) = u64::try_from(bytes.len()) else {
383 return false;
384 };
385 let check = || -> Option<bool> {
386 Some(match be(8)? {
387 21 => {
388 let end = 20u64.checked_add(u64::from(be(0)?))?;
389 let (size, offset) = (u64::from(be(4)?), u64::from(be(12)?));
390 be(0)? > 0 && size == len && end <= offset && offset <= size
391 }
392 22 => len >= 48 && be(0)? == 0,
393 _ => false,
394 })
395 };
396 check().unwrap_or(false)
397 }
398
399 pub fn container_version(bytes: &[u8]) -> Option<u32> {
404 let be = |at: usize| -> Option<u32> {
405 Some(u32::from_be_bytes(
406 bytes.get(at..at.checked_add(4)?)?.try_into().ok()?,
407 ))
408 };
409 let len = u64::try_from(bytes.len()).ok()?;
410 let version = be(8)?;
411 if VERSIONS.contains(&version) {
412 return Some(version);
413 }
414 let consistent = if version < *VERSIONS.start() {
415 version > 0 && u64::from(be(4)?) == len && u64::from(be(12)?) <= len
417 } else {
418 be(0)? == 0
420 && be(4)? == 0
421 && be(12)? == 0
422 && u64::from_be_bytes(bytes.get(24..32)?.try_into().ok()?) == len
423 };
424 consistent.then_some(version)
425 }
426
427 pub fn parse(data: Vec<u8>) -> Result<Self> {
433 Self::parse_with(data, None)
434 }
435
436 pub fn parse_with(data: Vec<u8>, package: Option<&TypeTreePackage>) -> Result<Self> {
439 let size = u64::try_from(data.len()).map_err(|_| invalid("file past 2^64 bytes"))?;
440 let mut file = Self::parse_head(&data, size, package)?;
441 file.data = Backing::Memory(data);
442 Ok(file)
443 }
444
445 pub fn from_bundle(bundle: &Arc<Bundle>, path: &str, package: Option<&TypeTreePackage>) -> Result<Self> {
448 let (index, node) = bundle
449 .nodes()
450 .iter()
451 .enumerate()
452 .find(|(_, n)| n.path == path)
453 .ok_or_else(|| invalid(format!("{}: no node {path:?}", bundle.path().display())))?;
454 let named = |e: Error| match e {
455 Error::Invalid(m) => invalid(format!("{}: {path}: {m}", bundle.path().display())),
456 Error::Unsupported(m) => Error::Unsupported(format!("{}: {path}: {m}", bundle.path().display())),
457 other => other,
458 };
459 let first = bundle
460 .read(
461 node,
462 0,
463 usize::try_from(node.size.min(Self::HEADER_BYTES)).unwrap_or(0),
464 )
465 .map_err(named)?;
466 let head_len = Header::of(&first).map_err(named)?.head_len;
467 let head = bundle
468 .read(
469 node,
470 0,
471 usize::try_from(head_len.min(node.size)).map_err(|_| invalid("metadata size"))?,
472 )
473 .map_err(named)?;
474 let mut file = Self::parse_head(&head, node.size, package).map_err(named)?;
475 file.data = Backing::Node {
476 bundle: bundle.clone(),
477 node: index,
478 };
479 Ok(file)
480 }
481
482 const HEADER_BYTES: u64 = 48;
484
485 fn parse_head(head: &[u8], file_size: u64, package: Option<&TypeTreePackage>) -> Result<Self> {
488 let Header {
489 version,
490 big_endian,
491 metadata_start,
492 metadata_size,
493 file_size: stated_size,
494 data_offset,
495 ..
496 } = Header::of(head)?;
497 if stated_size != file_size {
498 return Err(invalid(format!(
499 "header file size {stated_size}, file has {file_size} bytes"
500 )));
501 }
502 let meta_end = metadata_start
503 .checked_add(metadata_size)
504 .ok_or_else(|| invalid("metadata size overflows its byte range"))?;
505 if meta_end as u64 > data_offset || data_offset > file_size {
506 return Err(invalid(format!(
507 "metadata ends at {meta_end}, data at {data_offset}, file size {file_size}"
508 )));
509 }
510 let metadata = head.get(..meta_end).ok_or_else(|| {
511 invalid(format!(
512 "metadata ends at {meta_end}, past the {} bytes read",
513 head.len()
514 ))
515 })?;
516 let mut r = Reader::new(metadata, big_endian);
517 r.pos = metadata_start;
518 let unity_version = r.cstr()?;
519 let unity = UnityVersion::parse(&unity_version)?;
520 let target_platform = r.i32()?;
521 let has_trees = r.bool()?;
522 let mut context = TreeContext {
523 common: package.and_then(|p| p.common_strings(unity)),
524 version: unity,
525 strings: BTreeMap::new(),
526 };
527 let types = (0..count(&mut r, "type")?)
528 .map(|_| serialized_type(&mut r, false, has_trees.then_some(&mut context)))
529 .collect::<Result<Vec<_>>>()?;
530 let mut objects = Vec::new();
531 for _ in 0..count(&mut r, "object")? {
532 r.align(4)?;
533 let path_id = r.i64()?;
534 let start = if version == 21 {
535 i64::from(r.u32()?)
536 } else {
537 r.i64()?
538 };
539 let size = r.u32()?;
540 let type_index = r.u32()?;
541 let ty = types.get(type_index as usize).ok_or_else(|| {
542 invalid(format!(
543 "object {path_id}: type index {type_index} of {}",
544 types.len()
545 ))
546 })?;
547 let offset = u64::try_from(start)
548 .ok()
549 .and_then(|s| s.checked_add(data_offset))
550 .filter(|&o| o.checked_add(u64::from(size)).is_some_and(|end| end <= file_size))
551 .ok_or_else(|| invalid(format!("object {path_id}: bytes {start}+{size} outside the file")))?;
552 objects.push(ObjectInfo {
553 path_id,
554 offset,
555 size,
556 type_index,
557 class_id: ty.class_id,
558 });
559 }
560 let mut script_types = Vec::new();
561 for _ in 0..count(&mut r, "script")? {
562 let file = r.i32()?;
563 r.align(4)?;
564 script_types.push((file, r.i64()?));
565 }
566 let mut externals = Vec::new();
567 for _ in 0..count(&mut r, "external")? {
568 let _empty = r.cstr()?;
569 let guid = hash(&mut r)?;
570 let kind = r.i32()?;
571 let path = r.cstr()?;
572 externals.push(External { guid, kind, path });
573 }
574 let ref_types = (0..count(&mut r, "reference type")?)
575 .map(|_| serialized_type(&mut r, true, has_trees.then_some(&mut context)))
576 .collect::<Result<Vec<_>>>()?;
577 let user_information = r.cstr()?;
578 if r.pos != meta_end {
579 return Err(invalid(format!(
580 "metadata read to {}, header says it ends at {meta_end}",
581 r.pos
582 )));
583 }
584 let mut by_path_id = BTreeMap::new();
585 for (i, o) in objects.iter().enumerate() {
586 if by_path_id.insert(o.path_id, i).is_some() {
587 return Err(invalid(format!("path id {} twice", o.path_id)));
588 }
589 }
590 Ok(SerializedFile {
591 version,
592 unity_version,
593 unity,
594 target_platform,
595 big_endian,
596 types,
597 objects,
598 script_types,
599 externals,
600 ref_types,
601 user_information,
602 data: Backing::Memory(Vec::new()),
603 by_path_id,
604 })
605 }
606
607 pub fn object(&self, path_id: i64) -> Option<&ObjectInfo> {
609 self.by_path_id.get(&path_id).and_then(|&i| self.objects.get(i))
610 }
611
612 pub fn tree_of(&self, o: &ObjectInfo) -> Option<&Arc<TypeTree>> {
614 self.types.get(usize::try_from(o.type_index).ok()?)?.tree.as_ref()
615 }
616
617 pub fn bytes(&self, o: &ObjectInfo) -> Result<Cow<'_, [u8]>> {
622 let outside = |len: u64| {
623 invalid(format!(
624 "object {}: bytes {}+{} are outside the file's {len} bytes",
625 o.path_id, o.offset, o.size,
626 ))
627 };
628 match &self.data {
629 Backing::Memory(data) => {
630 let len = u64::try_from(data.len()).unwrap_or(u64::MAX);
631 let start = usize::try_from(o.offset).map_err(|_| outside(len))?;
632 let end = start
633 .checked_add(usize::try_from(o.size).map_err(|_| outside(len))?)
634 .ok_or_else(|| outside(len))?;
635 data.get(start..end)
636 .map(Cow::Borrowed)
637 .ok_or_else(|| outside(len))
638 }
639 Backing::Node { bundle, node } => {
640 let node = bundle
641 .nodes()
642 .get(*node)
643 .ok_or_else(|| invalid("the file's bundle node is gone"))?;
644 if o.offset
645 .checked_add(u64::from(o.size))
646 .is_none_or(|end| end > node.size)
647 {
648 return Err(outside(node.size));
649 }
650 let len = usize::try_from(o.size).map_err(|_| outside(node.size))?;
651 bundle.read(node, o.offset, len).map(Cow::Owned)
652 }
653 }
654 }
655
656 pub fn bundle(&self) -> Option<(&Arc<Bundle>, &crate::bundle::BundleNode)> {
659 match &self.data {
660 Backing::Memory(_) => None,
661 Backing::Node { bundle, node } => bundle.nodes().get(*node).map(|n| (bundle, n)),
662 }
663 }
664
665 pub fn decode_with(&self, o: &ObjectInfo, tree: &TypeTree) -> Result<Decoded> {
668 let bytes = self.bytes(o)?;
669 let (value, used) = tree
670 .decode(&bytes, self.big_endian)
671 .map_err(|e| invalid(format!("object {} (class {}): {e}", o.path_id, o.class_id)))?;
672 let rest = bytes.len().checked_sub(used).ok_or_else(|| {
673 invalid(format!(
674 "object {} (class {}): decoded {used} of {} bytes",
675 o.path_id,
676 o.class_id,
677 bytes.len()
678 ))
679 })?;
680 Ok(Decoded { value, rest })
681 }
682
683 pub fn decode(&self, o: &ObjectInfo, package: &TypeTreePackage) -> Result<Decoded> {
686 let d = match self.tree_of(o) {
687 Some(tree) => self.decode_with(o, tree)?,
688 None => self.decode_with(o, &package.type_tree(o.class_id, self.unity)?)?,
689 };
690 if !d.covered(o.class_id) {
691 return Err(invalid(format!(
692 "object {} (class {}): {} bytes after its type tree",
693 o.path_id, o.class_id, d.rest
694 )));
695 }
696 Ok(d)
697 }
698}