unity_asset/
lib.rs

1//! Unity Asset Parser
2//!
3//! A comprehensive Rust library for parsing Unity asset files, supporting both YAML and binary formats.
4//!
5//! This crate provides high-performance, memory-safe parsing of Unity files
6//! while maintaining exact compatibility with Unity's formats.
7//!
8//! # Features
9//!
10//! - **YAML Processing**: Complete Unity YAML format support with multi-document parsing
11//! - **Binary Assets**: AssetBundle and SerializedFile parsing with compression support
12//! - **Async Support**: Optional async/await API for concurrent processing (enable with `async` feature)
13//! - **Type Safety**: Rust's type system prevents common parsing vulnerabilities
14//! - **Performance**: Zero-cost abstractions and memory-efficient parsing
15//!
16//! # Examples
17//!
18//! ## Basic YAML Processing
19//!
20//! ```rust,no_run
21//! use unity_asset::{YamlDocument, UnityDocument};
22//!
23//! // Load a Unity YAML file
24//! let doc = YamlDocument::load_yaml("ProjectSettings.asset", false)?;
25//!
26//! // Access and filter objects
27//! let settings = doc.get(Some("PlayerSettings"), None)?;
28//! println!("Product name: {:?}", settings.get("productName"));
29//!
30//! # Ok::<(), unity_asset::UnityAssetError>(())
31//! ```
32//!
33//! ## Binary Asset Processing
34//!
35//! ```rust,no_run
36//! use unity_asset::load_bundle_from_memory;
37//!
38//! // Load and parse AssetBundle
39//! let data = std::fs::read("game.bundle")?;
40//! let bundle = load_bundle_from_memory(data)?;
41//!
42//! // Process assets
43//! for asset in &bundle.assets {
44//!     println!("Found asset with {} objects", asset.object_count());
45//! }
46//!
47//! # Ok::<(), Box<dyn std::error::Error>>(())
48//! ```
49//!
50//! ## Async Processing (requires `async` feature)
51//!
52//! ```rust,no_run
53//! # #[cfg(feature = "async")]
54//! # {
55//! use unity_asset::{YamlDocument, AsyncUnityDocument};
56//!
57//! #[tokio::main]
58//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
59//!     // Load file asynchronously
60//!     let doc = YamlDocument::load_yaml_async("ProjectSettings.asset", false).await?;
61//!
62//!     // Same API as sync version
63//!     let settings = doc.get(Some("PlayerSettings"), None)?;
64//!     println!("Product name: {:?}", settings.get("productName"));
65//!
66//!     Ok(())
67//! }
68//! # }
69//! ```
70
71// Re-export from core crate
72pub use unity_asset_core::{
73    DocumentFormat, Result, UnityAssetError, UnityClass, UnityClassRegistry, UnityDocument,
74    UnityValue, constants::*,
75};
76
77// Re-export from YAML crate
78pub use unity_asset_yaml::YamlDocument;
79
80// Re-export from binary crate
81pub use unity_asset_binary::{
82    AssetBundle, SerializedFile, load_bundle, load_bundle_from_memory, load_bundle_with_options,
83};
84
85// Re-export async traits when async feature is enabled
86#[cfg(feature = "async")]
87pub use unity_asset_core::document::AsyncUnityDocument;
88
89/// Environment for managing multiple Unity assets
90pub mod environment {
91    use crate::{Result, UnityClass, YamlDocument};
92    use std::collections::HashMap;
93    use std::path::{Path, PathBuf};
94    use unity_asset_core::{UnityAssetError, UnityDocument};
95
96    /// Unified environment for managing Unity assets
97    pub struct Environment {
98        /// Loaded YAML documents
99        yaml_documents: HashMap<PathBuf, YamlDocument>,
100        /// Base path for relative file resolution
101        #[allow(dead_code)]
102        base_path: PathBuf,
103    }
104
105    impl Environment {
106        /// Create a new environment
107        pub fn new() -> Self {
108            Self {
109                yaml_documents: HashMap::new(),
110                base_path: std::env::current_dir().unwrap_or_default(),
111            }
112        }
113
114        /// Load assets from a path (file or directory)
115        pub fn load<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
116            let path = path.as_ref();
117
118            if path.is_file() {
119                self.load_file(path)?;
120            } else if path.is_dir() {
121                self.load_directory(path)?;
122            }
123
124            Ok(())
125        }
126
127        /// Load a single file
128        pub fn load_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
129            let path = path.as_ref();
130
131            // Check file extension to determine type
132            if let Some(ext) = path.extension() {
133                match ext.to_str() {
134                    Some("asset") | Some("prefab") | Some("unity") | Some("meta") => {
135                        let doc = YamlDocument::load_yaml(path, false)?;
136                        self.yaml_documents.insert(path.to_path_buf(), doc);
137                    }
138                    _ => {
139                        // For now, skip unknown file types
140                        // Future: Add binary asset support (.bundle, .assets, etc.)
141                    }
142                }
143            }
144
145            Ok(())
146        }
147
148        /// Load all supported files from a directory
149        pub fn load_directory<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
150            let path = path.as_ref();
151
152            if !path.exists() {
153                return Err(UnityAssetError::format(format!(
154                    "Directory does not exist: {:?}",
155                    path
156                )));
157            }
158
159            if !path.is_dir() {
160                return Err(UnityAssetError::format(format!(
161                    "Path is not a directory: {:?}",
162                    path
163                )));
164            }
165
166            // Recursively traverse directory
167            self.traverse_directory(path)?;
168
169            Ok(())
170        }
171
172        /// Recursively traverse directory and load Unity files
173        fn traverse_directory(&mut self, dir: &Path) -> Result<()> {
174            let entries = std::fs::read_dir(dir).map_err(|e| {
175                UnityAssetError::format(format!("Failed to read directory {:?}: {}", dir, e))
176            })?;
177
178            for entry in entries {
179                let entry = entry.map_err(|e| {
180                    UnityAssetError::format(format!("Failed to read directory entry: {}", e))
181                })?;
182                let path = entry.path();
183
184                if path.is_dir() {
185                    // Skip common Unity directories that don't contain assets
186                    if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
187                        match dir_name {
188                            "Library" | "Temp" | "Logs" | ".git" | ".vs" | "obj" | "bin" => {
189                                continue; // Skip these directories
190                            }
191                            _ => {
192                                // Recursively process subdirectory
193                                self.traverse_directory(&path)?;
194                            }
195                        }
196                    }
197                } else if path.is_file() {
198                    // Try to load the file
199                    if let Err(e) = self.load_file(&path) {
200                        // Log error but continue processing other files
201                        eprintln!("Warning: Failed to load {:?}: {}", path, e);
202                    }
203                }
204            }
205
206            Ok(())
207        }
208
209        /// Get all Unity objects from all loaded documents
210        pub fn objects(&self) -> impl Iterator<Item = &UnityClass> {
211            self.yaml_documents.values().flat_map(|doc| doc.entries())
212        }
213
214        /// Filter objects by class name
215        pub fn filter_by_class(&self, class_name: &str) -> Vec<&UnityClass> {
216            self.objects()
217                .filter(|obj| obj.class_name == class_name)
218                .collect()
219        }
220
221        /// Get loaded YAML documents
222        pub fn yaml_documents(&self) -> &HashMap<PathBuf, YamlDocument> {
223            &self.yaml_documents
224        }
225    }
226
227    impl Default for Environment {
228        fn default() -> Self {
229            Self::new()
230        }
231    }
232}