unity_asset_binary/asset/
types.rs

1//! Asset type definitions
2//!
3//! This module defines the core data structures for Unity asset processing.
4
5use crate::error::{BinaryError, Result};
6use crate::reader::BinaryReader;
7use crate::typetree::{TypeTree, TypeTreeParser};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Type information for Unity objects
12///
13/// Contains metadata about Unity object types including class information,
14/// type trees, and script references.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SerializedType {
17    /// Unity class ID
18    pub class_id: i32,
19    /// Whether this type is stripped
20    pub is_stripped_type: bool,
21    /// Script type index (for MonoBehaviour)
22    pub script_type_index: Option<i16>,
23    /// Type tree for this type
24    pub type_tree: TypeTree,
25    /// Script ID hash
26    pub script_id: [u8; 16],
27    /// Old type hash
28    pub old_type_hash: [u8; 16],
29    /// Type dependencies
30    pub type_dependencies: Vec<i32>,
31    /// Class name
32    pub class_name: String,
33    /// Namespace
34    pub namespace: String,
35    /// Assembly name
36    pub assembly_name: String,
37}
38
39impl SerializedType {
40    /// Create a new SerializedType
41    pub fn new(class_id: i32) -> Self {
42        Self {
43            class_id,
44            is_stripped_type: false,
45            script_type_index: None,
46            type_tree: TypeTree::new(),
47            script_id: [0; 16],
48            old_type_hash: [0; 16],
49            type_dependencies: Vec::new(),
50            class_name: String::new(),
51            namespace: String::new(),
52            assembly_name: String::new(),
53        }
54    }
55
56    /// Parse SerializedType from binary data
57    pub fn from_reader(
58        reader: &mut BinaryReader,
59        version: u32,
60        enable_type_tree: bool,
61    ) -> Result<Self> {
62        let class_id = reader.read_i32()?;
63        let mut serialized_type = Self::new(class_id);
64
65        if version >= 16 {
66            serialized_type.is_stripped_type = reader.read_bool()?;
67        }
68
69        if version >= 17 {
70            let script_type_index = reader.read_i16()?;
71            serialized_type.script_type_index = Some(script_type_index);
72        }
73
74        if version >= 13 {
75            // Based on unity-rs logic: check conditions for script_id
76            let should_read_script_id = if version < 16 {
77                class_id < 0
78            } else {
79                class_id == 114 // MonoBehaviour
80            };
81
82            if should_read_script_id {
83                // Read script ID
84                let script_id_bytes = reader.read_bytes(16)?;
85                serialized_type.script_id.copy_from_slice(&script_id_bytes);
86            }
87
88            // Always read old type hash for version >= 13
89            let old_type_hash_bytes = reader.read_bytes(16)?;
90            serialized_type
91                .old_type_hash
92                .copy_from_slice(&old_type_hash_bytes);
93        }
94
95        if enable_type_tree {
96            // Use blob format for version >= 12 or version == 10 (like unity-rs)
97            if version >= 12 || version == 10 {
98                serialized_type.type_tree = TypeTreeParser::from_reader_blob(reader, version)?;
99            } else {
100                serialized_type.type_tree = TypeTreeParser::from_reader(reader, version)?;
101            }
102        }
103
104        Ok(serialized_type)
105    }
106
107    /// Check if this is a script type (MonoBehaviour)
108    pub fn is_script_type(&self) -> bool {
109        self.class_id == 114 || self.script_type_index.is_some()
110    }
111
112    /// Check if this type has a TypeTree
113    pub fn has_type_tree(&self) -> bool {
114        !self.type_tree.is_empty()
115    }
116
117    /// Get the type name
118    pub fn type_name(&self) -> String {
119        if !self.class_name.is_empty() {
120            self.class_name.clone()
121        } else {
122            format!("Class_{}", self.class_id)
123        }
124    }
125
126    /// Get full type name including namespace
127    pub fn full_type_name(&self) -> String {
128        if !self.namespace.is_empty() {
129            format!("{}.{}", self.namespace, self.type_name())
130        } else {
131            self.type_name()
132        }
133    }
134
135    /// Validate the serialized type
136    pub fn validate(&self) -> Result<()> {
137        if self.class_id == 0 {
138            return Err(BinaryError::invalid_data("Class ID cannot be zero"));
139        }
140
141        if self.is_script_type() && self.script_id == [0; 16] {
142            return Err(BinaryError::invalid_data(
143                "Script type must have valid script ID",
144            ));
145        }
146
147        Ok(())
148    }
149}
150
151/// External reference to another Unity file
152///
153/// Represents a reference to an asset in another Unity file,
154/// used for cross-file asset dependencies.
155#[derive(Debug, Clone, Serialize, Deserialize, Default)]
156pub struct FileIdentifier {
157    /// GUID of the referenced file
158    pub guid: [u8; 16],
159    /// Type of the reference
160    pub type_: i32,
161    /// Path to the referenced file
162    pub path: String,
163}
164
165impl FileIdentifier {
166    /// Parse FileIdentifier from binary data
167    pub fn from_reader(reader: &mut BinaryReader, _version: u32) -> Result<Self> {
168        let mut guid = [0u8; 16];
169        let guid_bytes = reader.read_bytes(16)?;
170        guid.copy_from_slice(&guid_bytes);
171
172        let type_ = reader.read_i32()?;
173        let path = reader.read_aligned_string()?;
174
175        Ok(Self { guid, type_, path })
176    }
177
178    /// Create a new FileIdentifier
179    pub fn new(guid: [u8; 16], type_: i32, path: String) -> Self {
180        Self { guid, type_, path }
181    }
182
183    /// Check if this is a valid file identifier
184    pub fn is_valid(&self) -> bool {
185        self.guid != [0; 16] || !self.path.is_empty()
186    }
187
188    /// Get GUID as string
189    pub fn guid_string(&self) -> String {
190        format!(
191            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
192            self.guid[0],
193            self.guid[1],
194            self.guid[2],
195            self.guid[3],
196            self.guid[4],
197            self.guid[5],
198            self.guid[6],
199            self.guid[7],
200            self.guid[8],
201            self.guid[9],
202            self.guid[10],
203            self.guid[11],
204            self.guid[12],
205            self.guid[13],
206            self.guid[14],
207            self.guid[15]
208        )
209    }
210}
211
212/// Object information within a SerializedFile
213///
214/// Contains metadata about individual Unity objects including
215/// their location, type, and path ID.
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct ObjectInfo {
218    /// Path ID of the object (unique within file)
219    pub path_id: i64,
220    /// Offset of object data in the file
221    pub byte_start: u64,
222    /// Size of object data
223    pub byte_size: u32,
224    /// Type ID of the object
225    pub type_id: i32,
226    /// Object data
227    pub data: Vec<u8>,
228}
229
230impl ObjectInfo {
231    /// Create a new ObjectInfo
232    pub fn new(path_id: i64, byte_start: u64, byte_size: u32, type_id: i32) -> Self {
233        Self {
234            path_id,
235            byte_start,
236            byte_size,
237            type_id,
238            data: Vec::new(),
239        }
240    }
241
242    /// Check if object data is loaded
243    pub fn has_data(&self) -> bool {
244        !self.data.is_empty()
245    }
246
247    /// Get the end offset of this object
248    pub fn byte_end(&self) -> u64 {
249        self.byte_start + self.byte_size as u64
250    }
251
252    /// Validate object info
253    pub fn validate(&self) -> Result<()> {
254        if self.path_id == 0 {
255            return Err(BinaryError::invalid_data("Path ID cannot be zero"));
256        }
257
258        if self.byte_size == 0 {
259            return Err(BinaryError::invalid_data("Byte size cannot be zero"));
260        }
261
262        if self.type_id == 0 {
263            return Err(BinaryError::invalid_data("Type ID cannot be zero"));
264        }
265
266        Ok(())
267    }
268}
269
270/// Type registry for managing SerializedTypes
271///
272/// Provides efficient lookup and management of type information
273/// within a SerializedFile.
274#[derive(Debug, Clone, Default)]
275pub struct TypeRegistry {
276    types: HashMap<i32, SerializedType>,
277    script_types: HashMap<i16, SerializedType>,
278}
279
280impl TypeRegistry {
281    /// Create a new type registry
282    pub fn new() -> Self {
283        Self {
284            types: HashMap::new(),
285            script_types: HashMap::new(),
286        }
287    }
288
289    /// Add a type to the registry
290    pub fn add_type(&mut self, serialized_type: SerializedType) {
291        let class_id = serialized_type.class_id;
292
293        // Add to script types if applicable
294        if let Some(script_index) = serialized_type.script_type_index {
295            self.script_types
296                .insert(script_index, serialized_type.clone());
297        }
298
299        self.types.insert(class_id, serialized_type);
300    }
301
302    /// Get a type by class ID
303    pub fn get_type(&self, class_id: i32) -> Option<&SerializedType> {
304        self.types.get(&class_id)
305    }
306
307    /// Get a script type by index
308    pub fn get_script_type(&self, script_index: i16) -> Option<&SerializedType> {
309        self.script_types.get(&script_index)
310    }
311
312    /// Get all class IDs
313    pub fn class_ids(&self) -> Vec<i32> {
314        self.types.keys().copied().collect()
315    }
316
317    /// Get all script type indices
318    pub fn script_indices(&self) -> Vec<i16> {
319        self.script_types.keys().copied().collect()
320    }
321
322    /// Check if a class ID is registered
323    pub fn has_type(&self, class_id: i32) -> bool {
324        self.types.contains_key(&class_id)
325    }
326
327    /// Check if a script index is registered
328    pub fn has_script_type(&self, script_index: i16) -> bool {
329        self.script_types.contains_key(&script_index)
330    }
331
332    /// Get the number of registered types
333    pub fn len(&self) -> usize {
334        self.types.len()
335    }
336
337    /// Check if the registry is empty
338    pub fn is_empty(&self) -> bool {
339        self.types.is_empty()
340    }
341
342    /// Clear all types
343    pub fn clear(&mut self) {
344        self.types.clear();
345        self.script_types.clear();
346    }
347
348    /// Get types by predicate
349    pub fn find_types<F>(&self, predicate: F) -> Vec<&SerializedType>
350    where
351        F: Fn(&SerializedType) -> bool,
352    {
353        self.types.values().filter(|t| predicate(t)).collect()
354    }
355
356    /// Get all script types
357    pub fn script_types(&self) -> Vec<&SerializedType> {
358        self.script_types.values().collect()
359    }
360
361    /// Get all non-script types
362    pub fn non_script_types(&self) -> Vec<&SerializedType> {
363        self.types
364            .values()
365            .filter(|t| !t.is_script_type())
366            .collect()
367    }
368}
369
370/// Unity class ID constants
371pub mod class_ids {
372    pub const OBJECT: i32 = 1;
373    pub const COMPONENT: i32 = 2;
374    pub const BEHAVIOUR: i32 = 3;
375    pub const UNITY_ENGINE_OBJECT: i32 = 4;
376    pub const GAME_OBJECT: i32 = 1;
377    pub const TRANSFORM: i32 = 4;
378    pub const MONO_BEHAVIOUR: i32 = 114;
379    pub const TEXTURE_2D: i32 = 28;
380    pub const SPRITE: i32 = 213;
381    pub const MESH: i32 = 43;
382    pub const AUDIO_CLIP: i32 = 83;
383    pub const MATERIAL: i32 = 21;
384    pub const SHADER: i32 = 48;
385    pub const ANIMATION_CLIP: i32 = 74;
386    pub const ANIMATOR_CONTROLLER: i32 = 91;
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn test_serialized_type_creation() {
395        let stype = SerializedType::new(114);
396        assert_eq!(stype.class_id, 114);
397        assert!(stype.is_script_type());
398    }
399
400    #[test]
401    fn test_file_identifier_guid() {
402        let guid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
403        let file_id = FileIdentifier::new(guid, 0, "test.unity".to_string());
404        let guid_str = file_id.guid_string();
405        assert!(guid_str.contains("01020304"));
406    }
407
408    #[test]
409    fn test_type_registry() {
410        let mut registry = TypeRegistry::new();
411        let stype = SerializedType::new(28); // Texture2D
412
413        registry.add_type(stype);
414        assert!(registry.has_type(28));
415        assert_eq!(registry.len(), 1);
416    }
417}