unity_asset_binary/bundle/
mod.rs

1//! Unity AssetBundle processing module
2//!
3//! This module provides comprehensive AssetBundle 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` - Bundle header parsing and validation
10//! - `types` - Core data structures (AssetBundle, BundleFileInfo, etc.)
11//! - `compression` - Compression handling (LZ4, LZMA, Brotli)
12//! - `parser` - Main parsing logic for different bundle formats
13//! - `loader` - Resource loading and management
14//!
15//! # Examples
16//!
17//! ```rust,no_run
18//! use unity_asset_binary::bundle::{BundleLoader, BundleLoadOptions};
19//!
20//! // Simple bundle loading
21//! let bundle = unity_asset_binary::bundle::load_bundle("example.bundle")?;
22//! println!("Loaded bundle with {} assets", bundle.asset_count());
23//!
24//! // Advanced loading with options
25//! let mut loader = BundleLoader::with_options(BundleLoadOptions::fast());
26//! let bundle = loader.load_from_file("example.bundle")?;
27//!
28//! // Find specific assets
29//! let texture_assets = loader.find_assets_by_name("texture");
30//! # Ok::<(), unity_asset_binary::error::BinaryError>(())
31//! ```
32
33pub mod compression;
34pub mod header;
35pub mod loader;
36pub mod parser;
37pub mod types;
38
39// Re-export main types for easy access
40pub use compression::{BundleCompression, CompressionOptions, CompressionStats};
41pub use header::{BundleFormatInfo, BundleHeader};
42pub use loader::{
43    BundleLoader, BundleResourceManager, LoaderStatistics, load_bundle, load_bundle_from_memory,
44    load_bundle_with_options,
45};
46pub use parser::{BundleParser, ParsingComplexity};
47pub use types::{AssetBundle, BundleFileInfo, BundleLoadOptions, BundleStatistics, DirectoryNode};
48
49#[cfg(feature = "async")]
50pub use loader::load_bundle_async;
51
52/// Main bundle processing facade
53///
54/// This struct provides a high-level interface for bundle processing,
55/// combining parsing, loading, and resource management functionality.
56pub struct BundleProcessor {
57    loader: BundleLoader,
58}
59
60impl BundleProcessor {
61    /// Create a new bundle processor
62    pub fn new() -> Self {
63        Self {
64            loader: BundleLoader::new(),
65        }
66    }
67
68    /// Create a new bundle processor with options
69    pub fn with_options(options: BundleLoadOptions) -> Self {
70        Self {
71            loader: BundleLoader::with_options(options),
72        }
73    }
74
75    /// Load and process a bundle from file
76    pub fn process_file<P: AsRef<std::path::Path>>(
77        &mut self,
78        path: P,
79    ) -> crate::error::Result<&AssetBundle> {
80        self.loader.load_from_file(path)
81    }
82
83    /// Load and process a bundle from memory
84    pub fn process_memory(
85        &mut self,
86        name: String,
87        data: Vec<u8>,
88    ) -> crate::error::Result<&AssetBundle> {
89        self.loader.load_from_memory(name, data)
90    }
91
92    /// Get the underlying loader
93    pub fn loader(&self) -> &BundleLoader {
94        &self.loader
95    }
96
97    /// Get mutable access to the underlying loader
98    pub fn loader_mut(&mut self) -> &mut BundleLoader {
99        &mut self.loader
100    }
101
102    /// Extract all assets from a bundle
103    pub fn extract_all_assets(&self, bundle_name: &str) -> Option<Vec<&crate::asset::Asset>> {
104        self.loader
105            .get_bundle(bundle_name)
106            .map(|bundle| bundle.assets.iter().collect())
107    }
108
109    /// Extract assets by type
110    pub fn extract_assets_by_type(
111        &self,
112        bundle_name: &str,
113        _type_id: i32,
114    ) -> Option<Vec<&crate::asset::Asset>> {
115        self.loader
116            .get_bundle(bundle_name)
117            .map(|bundle| bundle.assets.iter().collect()) // TODO: Implement proper type filtering
118    }
119
120    /// Get bundle information
121    pub fn get_bundle_info(&self, bundle_name: &str) -> Option<BundleInfo> {
122        self.loader.get_bundle(bundle_name).map(|bundle| {
123            let stats = bundle.statistics();
124            BundleInfo {
125                name: bundle_name.to_string(),
126                format: bundle.header.signature.clone(),
127                version: bundle.header.version,
128                unity_version: bundle.header.unity_version.clone(),
129                size: bundle.size(),
130                compressed: bundle.is_compressed(),
131                file_count: bundle.file_count(),
132                asset_count: bundle.asset_count(),
133                compression_ratio: stats.compression_ratio,
134            }
135        })
136    }
137
138    /// Validate all loaded bundles
139    pub fn validate_all(&self) -> crate::error::Result<()> {
140        self.loader.validate_all()
141    }
142
143    /// Get processing statistics
144    pub fn statistics(&self) -> LoaderStatistics {
145        self.loader.get_statistics()
146    }
147}
148
149impl Default for BundleProcessor {
150    fn default() -> Self {
151        Self::new()
152    }
153}
154
155/// Bundle information summary
156#[derive(Debug, Clone)]
157pub struct BundleInfo {
158    pub name: String,
159    pub format: String,
160    pub version: u32,
161    pub unity_version: String,
162    pub size: u64,
163    pub compressed: bool,
164    pub file_count: usize,
165    pub asset_count: usize,
166    pub compression_ratio: f64,
167}
168
169/// Convenience functions for common operations
170/// Create a bundle processor with default settings
171pub fn create_processor() -> BundleProcessor {
172    BundleProcessor::default()
173}
174
175/// Quick function to get bundle information
176pub fn get_bundle_info<P: AsRef<std::path::Path>>(path: P) -> crate::error::Result<BundleInfo> {
177    let data = std::fs::read(&path).map_err(|e| {
178        crate::error::BinaryError::generic(format!("Failed to read bundle file: {}", e))
179    })?;
180
181    let complexity = BundleParser::estimate_complexity(&data)?;
182    let bundle = BundleParser::from_bytes(data)?;
183    let stats = bundle.statistics();
184
185    Ok(BundleInfo {
186        name: path
187            .as_ref()
188            .file_name()
189            .and_then(|n| n.to_str())
190            .unwrap_or("unknown")
191            .to_string(),
192        format: complexity.format,
193        version: bundle.header.version,
194        unity_version: bundle.header.unity_version.clone(),
195        size: bundle.size(),
196        compressed: complexity.has_compression,
197        file_count: bundle.file_count(),
198        asset_count: bundle.asset_count(),
199        compression_ratio: stats.compression_ratio,
200    })
201}
202
203/// Quick function to list bundle contents
204pub fn list_bundle_contents<P: AsRef<std::path::Path>>(
205    path: P,
206) -> crate::error::Result<Vec<String>> {
207    let bundle = load_bundle(path)?;
208    Ok(bundle
209        .file_names()
210        .into_iter()
211        .map(|s| s.to_string())
212        .collect())
213}
214
215/// Quick function to extract a specific file from bundle
216pub fn extract_file_from_bundle<P: AsRef<std::path::Path>>(
217    bundle_path: P,
218    file_name: &str,
219) -> crate::error::Result<Vec<u8>> {
220    let bundle = load_bundle(bundle_path)?;
221
222    if let Some(file_info) = bundle.find_file(file_name) {
223        bundle.extract_file_data(file_info)
224    } else {
225        Err(crate::error::BinaryError::generic(format!(
226            "File '{}' not found in bundle",
227            file_name
228        )))
229    }
230}
231
232/// Check if a file is a valid Unity bundle
233pub fn is_valid_bundle<P: AsRef<std::path::Path>>(path: P) -> bool {
234    match std::fs::read(path) {
235        Ok(data) => {
236            if data.len() < 20 {
237                return false;
238            }
239
240            // Check for known bundle signatures
241            let signature = String::from_utf8_lossy(&data[..8]);
242            matches!(signature.as_ref(), "UnityFS\0" | "UnityWeb" | "UnityRaw")
243        }
244        Err(_) => false,
245    }
246}
247
248/// Get supported bundle formats
249pub fn get_supported_formats() -> Vec<&'static str> {
250    vec!["UnityFS", "UnityWeb", "UnityRaw"]
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_processor_creation() {
259        let processor = create_processor();
260        assert_eq!(processor.statistics().bundle_count, 0);
261    }
262
263    #[test]
264    fn test_supported_formats() {
265        let formats = get_supported_formats();
266        assert!(formats.contains(&"UnityFS"));
267        assert!(formats.contains(&"UnityWeb"));
268        assert!(formats.contains(&"UnityRaw"));
269    }
270
271    #[test]
272    fn test_bundle_info_structure() {
273        // Test that BundleInfo can be created
274        let info = BundleInfo {
275            name: "test".to_string(),
276            format: "UnityFS".to_string(),
277            version: 6,
278            unity_version: "2019.4.0f1".to_string(),
279            size: 1024,
280            compressed: true,
281            file_count: 5,
282            asset_count: 10,
283            compression_ratio: 0.7,
284        };
285
286        assert_eq!(info.name, "test");
287        assert_eq!(info.format, "UnityFS");
288        assert!(info.compressed);
289    }
290
291    #[test]
292    fn test_load_options() {
293        let lazy_options = BundleLoadOptions::lazy();
294        assert!(!lazy_options.load_assets);
295        assert!(!lazy_options.decompress_blocks);
296        assert!(lazy_options.validate);
297
298        let fast_options = BundleLoadOptions::fast();
299        assert!(!fast_options.load_assets);
300        assert!(!fast_options.decompress_blocks);
301        assert!(!fast_options.validate);
302
303        let complete_options = BundleLoadOptions::complete();
304        assert!(complete_options.load_assets);
305        assert!(complete_options.decompress_blocks);
306        assert!(complete_options.validate);
307    }
308}