unity_asset_binary/bundle/
mod.rs1pub mod compression;
34pub mod header;
35pub mod loader;
36pub mod parser;
37pub mod types;
38
39pub 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
52pub struct BundleProcessor {
57 loader: BundleLoader,
58}
59
60impl BundleProcessor {
61 pub fn new() -> Self {
63 Self {
64 loader: BundleLoader::new(),
65 }
66 }
67
68 pub fn with_options(options: BundleLoadOptions) -> Self {
70 Self {
71 loader: BundleLoader::with_options(options),
72 }
73 }
74
75 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 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 pub fn loader(&self) -> &BundleLoader {
94 &self.loader
95 }
96
97 pub fn loader_mut(&mut self) -> &mut BundleLoader {
99 &mut self.loader
100 }
101
102 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 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()) }
119
120 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 pub fn validate_all(&self) -> crate::error::Result<()> {
140 self.loader.validate_all()
141 }
142
143 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#[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
169pub fn create_processor() -> BundleProcessor {
172 BundleProcessor::default()
173}
174
175pub 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
203pub 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
215pub 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
232pub 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 let signature = String::from_utf8_lossy(&data[..8]);
242 matches!(signature.as_ref(), "UnityFS\0" | "UnityWeb" | "UnityRaw")
243 }
244 Err(_) => false,
245 }
246}
247
248pub 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 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}