unity_asset_binary/asset/
mod.rs

1//! Unity asset processing module
2//!
3//! This module provides comprehensive Unity asset processing capabilities,
4//! organized following UnityPy and unity-rs best practices.
5//!
6//! # Architecture
7//!
8//! The module is organized into several sub-modules:
9//! - `header` - SerializedFile header parsing and validation
10//! - `types` - Core data structures (SerializedType, FileIdentifier, etc.)
11//! - `parser` - Main parsing logic for SerializedFile structures
12//!
13//! # Examples
14//!
15//! ```rust,no_run
16//! use unity_asset_binary::asset::{SerializedFileParser, SerializedFile};
17//!
18//! // Parse SerializedFile from binary data
19//! let data = std::fs::read("example.assets")?;
20//! let serialized_file = SerializedFileParser::from_bytes(data)?;
21//!
22//! // Access objects and types
23//! println!("Object count: {}", serialized_file.object_count());
24//! println!("Type count: {}", serialized_file.type_count());
25//!
26//! // Find specific objects
27//! let textures = serialized_file.objects_of_type(28); // Texture2D
28//! # Ok::<(), unity_asset_binary::error::BinaryError>(())
29//! ```
30
31pub mod header;
32pub mod parser;
33pub mod types;
34
35// Re-export main types for easy access
36pub use header::{HeaderFormatInfo, HeaderValidation, SerializedFileHeader, validate_header};
37pub use parser::{FileStatistics, ParsingStats, SerializedFile, SerializedFileParser};
38pub use types::{FileIdentifier, ObjectInfo, SerializedType, TypeRegistry, class_ids};
39
40// Legacy compatibility - Asset is an alias for SerializedFile
41pub type Asset = SerializedFile;
42
43/// Main asset processing facade
44///
45/// This struct provides a high-level interface for asset processing,
46/// combining parsing and type management functionality.
47pub struct AssetProcessor {
48    file: Option<SerializedFile>,
49}
50
51impl AssetProcessor {
52    /// Create a new asset processor
53    pub fn new() -> Self {
54        Self { file: None }
55    }
56
57    /// Parse SerializedFile from binary data
58    pub fn parse_from_bytes(&mut self, data: Vec<u8>) -> crate::error::Result<()> {
59        let file = SerializedFileParser::from_bytes(data)?;
60        self.file = Some(file);
61        Ok(())
62    }
63
64    /// Parse SerializedFile from file path
65    pub fn parse_from_file<P: AsRef<std::path::Path>>(
66        &mut self,
67        path: P,
68    ) -> crate::error::Result<()> {
69        let data = std::fs::read(path).map_err(|e| {
70            crate::error::BinaryError::generic(format!("Failed to read file: {}", e))
71        })?;
72        self.parse_from_bytes(data)
73    }
74
75    /// Parse SerializedFile asynchronously
76    #[cfg(feature = "async")]
77    pub async fn parse_from_bytes_async(&mut self, data: Vec<u8>) -> crate::error::Result<()> {
78        let file = SerializedFileParser::from_bytes_async(data).await?;
79        self.file = Some(file);
80        Ok(())
81    }
82
83    /// Get the loaded SerializedFile
84    pub fn file(&self) -> Option<&SerializedFile> {
85        self.file.as_ref()
86    }
87
88    /// Get mutable access to the loaded SerializedFile
89    pub fn file_mut(&mut self) -> Option<&mut SerializedFile> {
90        self.file.as_mut()
91    }
92
93    /// Get objects of a specific type
94    pub fn objects_of_type(&self, type_id: i32) -> Vec<&ObjectInfo> {
95        self.file
96            .as_ref()
97            .map(|f| f.objects_of_type(type_id))
98            .unwrap_or_default()
99    }
100
101    /// Find object by path ID
102    pub fn find_object(&self, path_id: i64) -> Option<&ObjectInfo> {
103        self.file.as_ref().and_then(|f| f.find_object(path_id))
104    }
105
106    /// Find type by class ID
107    pub fn find_type(&self, class_id: i32) -> Option<&SerializedType> {
108        self.file.as_ref().and_then(|f| f.find_type(class_id))
109    }
110
111    /// Get file statistics
112    pub fn statistics(&self) -> Option<FileStatistics> {
113        self.file.as_ref().map(|f| f.statistics())
114    }
115
116    /// Validate the loaded file
117    pub fn validate(&self) -> crate::error::Result<()> {
118        self.file
119            .as_ref()
120            .ok_or_else(|| crate::error::BinaryError::generic("No file loaded"))?
121            .validate()
122    }
123
124    /// Create a type registry from the loaded file
125    pub fn create_type_registry(&self) -> Option<TypeRegistry> {
126        self.file.as_ref().map(|f| f.create_type_registry())
127    }
128
129    /// Clear the loaded file
130    pub fn clear(&mut self) {
131        self.file = None;
132    }
133
134    /// Check if a file is loaded
135    pub fn has_file(&self) -> bool {
136        self.file.is_some()
137    }
138
139    /// Get Unity version
140    pub fn unity_version(&self) -> Option<&str> {
141        self.file.as_ref().map(|f| f.unity_version.as_str())
142    }
143
144    /// Get file format version
145    pub fn format_version(&self) -> Option<u32> {
146        self.file.as_ref().map(|f| f.header.version)
147    }
148
149    /// Get target platform
150    pub fn target_platform(&self) -> Option<i32> {
151        self.file.as_ref().map(|f| f.target_platform)
152    }
153}
154
155impl Default for AssetProcessor {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161/// Convenience functions for common operations
162/// Create an asset processor with default settings
163pub fn create_processor() -> AssetProcessor {
164    AssetProcessor::default()
165}
166
167/// Parse SerializedFile from binary data
168pub fn parse_serialized_file(data: Vec<u8>) -> crate::error::Result<SerializedFile> {
169    SerializedFileParser::from_bytes(data)
170}
171
172/// Parse SerializedFile from file path
173pub fn parse_serialized_file_from_path<P: AsRef<std::path::Path>>(
174    path: P,
175) -> crate::error::Result<SerializedFile> {
176    let data = std::fs::read(path)
177        .map_err(|e| crate::error::BinaryError::generic(format!("Failed to read file: {}", e)))?;
178    SerializedFileParser::from_bytes(data)
179}
180
181/// Parse SerializedFile asynchronously
182#[cfg(feature = "async")]
183pub async fn parse_serialized_file_async(data: Vec<u8>) -> crate::error::Result<SerializedFile> {
184    SerializedFileParser::from_bytes_async(data).await
185}
186
187/// Get file information without full parsing
188pub fn get_file_info<P: AsRef<std::path::Path>>(path: P) -> crate::error::Result<AssetFileInfo> {
189    let data = std::fs::read(&path)
190        .map_err(|e| crate::error::BinaryError::generic(format!("Failed to read file: {}", e)))?;
191
192    // Parse just the header and basic metadata
193    let mut reader = crate::reader::BinaryReader::new(&data, crate::reader::ByteOrder::Big);
194    let header = SerializedFileHeader::from_reader(&mut reader)?;
195
196    reader.set_byte_order(header.byte_order());
197
198    // Read Unity version if available
199    let unity_version = if header.version >= 7 {
200        reader.read_cstring().unwrap_or_default()
201    } else {
202        String::new()
203    };
204
205    // Read target platform if available
206    let target_platform = if header.version >= 8 {
207        reader.read_i32().unwrap_or(0)
208    } else {
209        0
210    };
211
212    Ok(AssetFileInfo {
213        path: path.as_ref().to_string_lossy().to_string(),
214        format_version: header.version,
215        unity_version,
216        target_platform,
217        file_size: header.file_size,
218        is_big_endian: header.endian != 0,
219        supports_type_tree: header.supports_type_trees(),
220    })
221}
222
223/// Check if a file is a valid Unity SerializedFile
224pub fn is_valid_serialized_file<P: AsRef<std::path::Path>>(path: P) -> bool {
225    match std::fs::read(path) {
226        Ok(data) => {
227            if data.len() < 20 {
228                return false;
229            }
230
231            let mut reader = crate::reader::BinaryReader::new(&data, crate::reader::ByteOrder::Big);
232            match SerializedFileHeader::from_reader(&mut reader) {
233                Ok(header) => header.is_valid(),
234                Err(_) => false,
235            }
236        }
237        Err(_) => false,
238    }
239}
240
241/// Asset file information summary
242#[derive(Debug, Clone)]
243pub struct AssetFileInfo {
244    pub path: String,
245    pub format_version: u32,
246    pub unity_version: String,
247    pub target_platform: i32,
248    pub file_size: u64,
249    pub is_big_endian: bool,
250    pub supports_type_tree: bool,
251}
252
253/// Get supported Unity versions
254pub fn get_supported_versions() -> Vec<u32> {
255    (5..=50).collect() // Support Unity 5.x to 2023.x (approximately)
256}
257
258/// Check if a Unity version is supported
259pub fn is_version_supported(version: u32) -> bool {
260    (5..=50).contains(&version)
261}
262
263/// Get recommended parsing options for a Unity version
264pub fn get_parsing_options(version: u32) -> ParsingOptions {
265    ParsingOptions {
266        enable_type_tree: version >= 13,
267        use_big_ids: version >= 14,
268        supports_script_types: version >= 11,
269        supports_ref_types: version >= 20,
270        uses_extended_format: version >= 22,
271    }
272}
273
274/// Parsing options for different Unity versions
275#[derive(Debug, Clone)]
276pub struct ParsingOptions {
277    pub enable_type_tree: bool,
278    pub use_big_ids: bool,
279    pub supports_script_types: bool,
280    pub supports_ref_types: bool,
281    pub uses_extended_format: bool,
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn test_processor_creation() {
290        let processor = create_processor();
291        assert!(!processor.has_file());
292    }
293
294    #[test]
295    fn test_version_support() {
296        assert!(is_version_supported(19));
297        assert!(is_version_supported(5));
298        assert!(!is_version_supported(100));
299    }
300
301    #[test]
302    fn test_parsing_options() {
303        let options = get_parsing_options(19);
304        assert!(options.enable_type_tree);
305        assert!(options.use_big_ids);
306        assert!(options.supports_script_types);
307
308        let old_options = get_parsing_options(10);
309        assert!(!old_options.enable_type_tree);
310        assert!(!old_options.use_big_ids);
311    }
312
313    #[test]
314    fn test_supported_versions() {
315        let versions = get_supported_versions();
316        assert!(versions.contains(&19));
317        assert!(versions.contains(&5));
318        assert!(!versions.is_empty());
319    }
320}