1use std::collections::BTreeMap;
16
17use crate::reader::Reader;
18use crate::tpk::TypeTreePackage;
19use crate::typetree::TypeTree;
20use crate::value::Value;
21use crate::version::UnityVersion;
22use crate::{Error, Result, invalid};
23
24pub const VERSIONS: std::ops::RangeInclusive<u32> = 21..=22;
26
27pub const MONO_BEHAVIOUR: i32 = 114;
29
30#[derive(Clone, Debug, PartialEq)]
32pub struct SerializedType {
33 pub class_id: i32,
35 pub stripped: bool,
37 pub script_type_index: i16,
39 pub script_id: Option<[u8; 16]>,
42 pub old_type_hash: [u8; 16],
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct ObjectInfo {
49 pub path_id: i64,
51 pub offset: u64,
53 pub size: u32,
55 pub type_index: u32,
57 pub class_id: i32,
59}
60
61#[derive(Clone, Debug, PartialEq)]
63pub struct External {
64 pub guid: [u8; 16],
66 pub kind: i32,
68 pub path: String,
71}
72
73#[derive(Clone, Debug, PartialEq)]
75pub struct Decoded {
76 pub value: Value,
78 pub rest: usize,
80}
81
82impl Decoded {
83 pub fn covered(&self, class_id: i32) -> bool {
86 self.rest == 0 || class_id == MONO_BEHAVIOUR
87 }
88}
89
90#[derive(Clone, Debug)]
93pub struct SerializedFile {
94 pub version: u32,
96 pub unity_version: String,
98 pub unity: UnityVersion,
100 pub target_platform: i32,
102 pub big_endian: bool,
104 pub types: Vec<SerializedType>,
106 pub objects: Vec<ObjectInfo>,
108 pub script_types: Vec<(i32, i64)>,
110 pub externals: Vec<External>,
112 pub ref_types: Vec<SerializedType>,
114 pub user_information: String,
116 data: Vec<u8>,
117 by_path_id: BTreeMap<i64, usize>,
118}
119
120fn hash(r: &mut Reader) -> Result<[u8; 16]> {
121 Ok(r.take(16)?.try_into().expect("16 bytes"))
122}
123
124fn count(r: &mut Reader, what: &str) -> Result<usize> {
125 let n = r.i32()?;
126 let n = usize::try_from(n).map_err(|_| invalid(format!("negative {what} count {n}")))?;
127 if n > r.remaining() {
128 return Err(invalid(format!("{what} count {n} larger than the metadata")));
129 }
130 Ok(n)
131}
132
133fn serialized_type(r: &mut Reader, reference: bool) -> Result<SerializedType> {
134 let class_id = r.i32()?;
135 let stripped = r.bool()?;
136 let script_type_index = r.i16()?;
137 let script_id = if (reference && script_type_index >= 0) || (!reference && class_id == MONO_BEHAVIOUR) {
138 Some(hash(r)?)
139 } else {
140 None
141 };
142 let old_type_hash = hash(r)?;
143 Ok(SerializedType {
144 class_id,
145 stripped,
146 script_type_index,
147 script_id,
148 old_type_hash,
149 })
150}
151
152impl SerializedFile {
153 pub fn looks_like(bytes: &[u8]) -> bool {
155 let be = |at: usize| -> Option<u32> {
156 Some(u32::from_be_bytes(
157 bytes.get(at..at.checked_add(4)?)?.try_into().ok()?,
158 ))
159 };
160 let Ok(len) = u64::try_from(bytes.len()) else {
161 return false;
162 };
163 let check = || -> Option<bool> {
164 Some(match be(8)? {
165 21 => {
166 let end = 20u64.checked_add(u64::from(be(0)?))?;
167 let (size, offset) = (u64::from(be(4)?), u64::from(be(12)?));
168 be(0)? > 0 && size == len && end <= offset && offset <= size
169 }
170 22 => len >= 48 && be(0)? == 0,
171 _ => false,
172 })
173 };
174 check().unwrap_or(false)
175 }
176
177 pub fn container_version(bytes: &[u8]) -> Option<u32> {
182 let be = |at: usize| -> Option<u32> {
183 Some(u32::from_be_bytes(
184 bytes.get(at..at.checked_add(4)?)?.try_into().ok()?,
185 ))
186 };
187 let len = u64::try_from(bytes.len()).ok()?;
188 let version = be(8)?;
189 if VERSIONS.contains(&version) {
190 return Some(version);
191 }
192 let consistent = if version < *VERSIONS.start() {
193 version > 0 && u64::from(be(4)?) == len && u64::from(be(12)?) <= len
195 } else {
196 be(0)? == 0
198 && be(4)? == 0
199 && be(12)? == 0
200 && u64::from_be_bytes(bytes.get(24..32)?.try_into().ok()?) == len
201 };
202 consistent.then_some(version)
203 }
204
205 pub fn parse(data: Vec<u8>) -> Result<Self> {
210 let mut h = Reader::new(&data, true);
211 let legacy_metadata = h.u32()?;
212 let legacy_size = h.u32()?;
213 let version = h.u32()?;
214 let legacy_offset = h.u32()?;
215 if !VERSIONS.contains(&version) {
216 return Err(Error::Unsupported(format!(
217 "SerializedFile version {version} (read: {}..={})",
218 VERSIONS.start(),
219 VERSIONS.end()
220 )));
221 }
222 let big_endian = h.bool()?;
223 h.take(3)?;
224 let (metadata_size, file_size, data_offset) = if version == 22 {
225 let metadata_size = h.u32()?;
226 let file_size = h.u64()?;
227 let data_offset = h.u64()?;
228 let _reserved = h.u64()?;
229 (metadata_size, file_size, data_offset)
230 } else {
231 (legacy_metadata, u64::from(legacy_size), u64::from(legacy_offset))
232 };
233 if file_size != data.len() as u64 {
234 return Err(invalid(format!(
235 "header file size {file_size}, file has {} bytes",
236 data.len()
237 )));
238 }
239 let meta_end = h
240 .pos
241 .checked_add(metadata_size as usize)
242 .ok_or_else(|| invalid("metadata size overflows its byte range"))?;
243 if meta_end as u64 > data_offset || data_offset > file_size {
244 return Err(invalid(format!(
245 "metadata ends at {meta_end}, data at {data_offset}, file size {file_size}"
246 )));
247 }
248 let metadata = data.get(..meta_end).ok_or_else(|| {
249 invalid(format!(
250 "metadata ends at {meta_end}, past the file's {} bytes",
251 data.len()
252 ))
253 })?;
254 let mut r = Reader::new(metadata, big_endian);
255 r.pos = h.pos;
256 let unity_version = r.cstr()?;
257 let unity = UnityVersion::parse(&unity_version)?;
258 let target_platform = r.i32()?;
259 if r.bool()? {
260 return Err(Error::Unsupported(
261 "embedded type trees (decode stripped player files with a type tree package)".into(),
262 ));
263 }
264 let types = (0..count(&mut r, "type")?)
265 .map(|_| serialized_type(&mut r, false))
266 .collect::<Result<Vec<_>>>()?;
267 let mut objects = Vec::new();
268 for _ in 0..count(&mut r, "object")? {
269 r.align(4)?;
270 let path_id = r.i64()?;
271 let start = if version == 21 {
272 i64::from(r.u32()?)
273 } else {
274 r.i64()?
275 };
276 let size = r.u32()?;
277 let type_index = r.u32()?;
278 let ty = types.get(type_index as usize).ok_or_else(|| {
279 invalid(format!(
280 "object {path_id}: type index {type_index} of {}",
281 types.len()
282 ))
283 })?;
284 let offset = u64::try_from(start)
285 .ok()
286 .and_then(|s| s.checked_add(data_offset))
287 .filter(|&o| o.checked_add(u64::from(size)).is_some_and(|end| end <= file_size))
288 .ok_or_else(|| invalid(format!("object {path_id}: bytes {start}+{size} outside the file")))?;
289 objects.push(ObjectInfo {
290 path_id,
291 offset,
292 size,
293 type_index,
294 class_id: ty.class_id,
295 });
296 }
297 let mut script_types = Vec::new();
298 for _ in 0..count(&mut r, "script")? {
299 let file = r.i32()?;
300 r.align(4)?;
301 script_types.push((file, r.i64()?));
302 }
303 let mut externals = Vec::new();
304 for _ in 0..count(&mut r, "external")? {
305 let _empty = r.cstr()?;
306 let guid = hash(&mut r)?;
307 let kind = r.i32()?;
308 let path = r.cstr()?;
309 externals.push(External { guid, kind, path });
310 }
311 let ref_types = (0..count(&mut r, "reference type")?)
312 .map(|_| serialized_type(&mut r, true))
313 .collect::<Result<Vec<_>>>()?;
314 let user_information = r.cstr()?;
315 if r.pos != meta_end {
316 return Err(invalid(format!(
317 "metadata read to {}, header says it ends at {meta_end}",
318 r.pos
319 )));
320 }
321 let mut by_path_id = BTreeMap::new();
322 for (i, o) in objects.iter().enumerate() {
323 if by_path_id.insert(o.path_id, i).is_some() {
324 return Err(invalid(format!("path id {} twice", o.path_id)));
325 }
326 }
327 Ok(SerializedFile {
328 version,
329 unity_version,
330 unity,
331 target_platform,
332 big_endian,
333 types,
334 objects,
335 script_types,
336 externals,
337 ref_types,
338 user_information,
339 data,
340 by_path_id,
341 })
342 }
343
344 pub fn object(&self, path_id: i64) -> Option<&ObjectInfo> {
346 self.by_path_id.get(&path_id).and_then(|&i| self.objects.get(i))
347 }
348
349 pub fn bytes(&self, o: &ObjectInfo) -> Result<&[u8]> {
352 let outside = || {
353 invalid(format!(
354 "object {}: bytes {}+{} are outside the file's {} bytes",
355 o.path_id,
356 o.offset,
357 o.size,
358 self.data.len()
359 ))
360 };
361 let start = usize::try_from(o.offset).map_err(|_| outside())?;
362 let end = start
363 .checked_add(usize::try_from(o.size).map_err(|_| outside())?)
364 .ok_or_else(outside)?;
365 self.data.get(start..end).ok_or_else(outside)
366 }
367
368 pub fn decode_with(&self, o: &ObjectInfo, tree: &TypeTree) -> Result<Decoded> {
371 let bytes = self.bytes(o)?;
372 let (value, used) = tree
373 .decode(bytes, self.big_endian)
374 .map_err(|e| invalid(format!("object {} (class {}): {e}", o.path_id, o.class_id)))?;
375 let rest = bytes.len().checked_sub(used).ok_or_else(|| {
376 invalid(format!(
377 "object {} (class {}): decoded {used} of {} bytes",
378 o.path_id,
379 o.class_id,
380 bytes.len()
381 ))
382 })?;
383 Ok(Decoded { value, rest })
384 }
385
386 pub fn decode(&self, o: &ObjectInfo, package: &TypeTreePackage) -> Result<Decoded> {
389 let tree = package.type_tree(o.class_id, self.unity)?;
390 let d = self.decode_with(o, &tree)?;
391 if !d.covered(o.class_id) {
392 return Err(invalid(format!(
393 "object {} (class {}): {} bytes after its type tree",
394 o.path_id, o.class_id, d.rest
395 )));
396 }
397 Ok(d)
398 }
399}