unity_asset_binary/bundle/
parser.rs1use super::compression::BundleCompression;
7use super::header::BundleHeader;
8use super::types::{AssetBundle, BundleFileInfo, BundleLoadOptions, DirectoryNode};
9use crate::compression::CompressionType;
10use crate::error::{BinaryError, Result};
11use crate::reader::{BinaryReader, ByteOrder};
12
13pub struct BundleParser;
18
19impl BundleParser {
20 pub fn from_bytes(data: Vec<u8>) -> Result<AssetBundle> {
22 Self::from_bytes_with_options(data, BundleLoadOptions::default())
23 }
24
25 pub fn from_bytes_with_options(
27 data: Vec<u8>,
28 options: BundleLoadOptions,
29 ) -> Result<AssetBundle> {
30 let data_clone = data.clone();
31 let mut reader = BinaryReader::new(&data, ByteOrder::Big);
32
33 let header = BundleHeader::from_reader(&mut reader)?;
35
36 if options.validate {
37 header.validate()?;
38 }
39
40 let mut bundle = AssetBundle::new(header, data_clone);
41
42 match bundle.header.signature.as_str() {
44 "UnityFS" => {
45 Self::parse_unity_fs(&mut bundle, &mut reader, &options)?;
46 }
47 "UnityWeb" | "UnityRaw" => {
48 Self::parse_legacy(&mut bundle, &mut reader, &options)?;
49 }
50 _ => {
51 return Err(BinaryError::unsupported(format!(
52 "Unsupported bundle format: {}",
53 bundle.header.signature
54 )));
55 }
56 }
57
58 if options.validate {
59 bundle.validate()?;
60 }
61
62 Ok(bundle)
63 }
64
65 fn parse_unity_fs(
67 bundle: &mut AssetBundle,
68 reader: &mut BinaryReader,
69 options: &BundleLoadOptions,
70 ) -> Result<()> {
71 Self::read_blocks_info(bundle, reader)?;
73
74 if options.decompress_blocks || options.load_assets {
76 let blocks_data = Self::read_blocks(bundle, reader)?;
77 Self::parse_files(bundle, &blocks_data)?;
78
79 if options.load_assets {
81 Self::load_assets(bundle)?;
82 }
83 } else {
84 Self::parse_directory_lazy(bundle, reader)?;
86 }
87
88 Ok(())
89 }
90
91 fn parse_legacy(
93 bundle: &mut AssetBundle,
94 reader: &mut BinaryReader,
95 options: &BundleLoadOptions,
96 ) -> Result<()> {
97 let header_size = bundle.header.header_size() as usize;
99
100 reader.set_position(header_size as u64)?;
102
103 let compressed_size = reader.read_u32()?;
105 let _uncompressed_size = reader.read_u32()?;
106
107 let skip_bytes = if bundle.header.version >= 2 { 4 } else { 0 };
109 if skip_bytes > 0 {
110 reader.read_bytes(skip_bytes)?;
111 }
112
113 reader.set_position(header_size as u64)?;
115
116 let compressed_data = reader.read_bytes(compressed_size as usize)?;
118 let directory_data = if bundle.header.signature == "UnityWeb" {
119 crate::compression::decompress(
121 &compressed_data,
122 CompressionType::Lzma,
123 compressed_data.len() * 4, )?
125 } else {
126 compressed_data
128 };
129
130 Self::parse_legacy_directory(bundle, &directory_data, header_size)?;
132
133 if options.load_assets {
135 Self::load_assets(bundle)?;
136 }
137
138 Ok(())
139 }
140
141 fn read_blocks_info(bundle: &mut AssetBundle, reader: &mut BinaryReader) -> Result<()> {
143 if bundle.header.version >= 7 {
145 reader.align()?;
146 }
147
148 let blocks_info_data =
151 reader.read_bytes(bundle.header.compressed_blocks_info_size as usize)?;
152
153 let uncompressed_data =
155 BundleCompression::decompress_blocks_info(&bundle.header, &blocks_info_data)?;
156
157 bundle.blocks = BundleCompression::parse_compression_blocks(&uncompressed_data)?;
159
160 BundleCompression::validate_blocks(&bundle.blocks)?;
162
163 Self::parse_directory_from_blocks_info(bundle, &uncompressed_data)?;
165
166 Ok(())
167 }
168
169 fn read_blocks(bundle: &AssetBundle, reader: &mut BinaryReader) -> Result<Vec<u8>> {
171 BundleCompression::decompress_data_blocks(&bundle.header, &bundle.blocks, reader)
172 }
173
174 fn parse_files(bundle: &mut AssetBundle, blocks_data: &[u8]) -> Result<()> {
176 *bundle.data_mut() = blocks_data.to_vec();
178
179 for node in &bundle.nodes {
181 let file_info = BundleFileInfo::new(node.name.clone(), node.offset, node.size);
182 bundle.files.push(file_info);
183 }
184
185 Ok(())
186 }
187
188 fn parse_directory_lazy(_bundle: &mut AssetBundle, _reader: &mut BinaryReader) -> Result<()> {
190 Ok(())
198 }
199
200 fn parse_directory_from_blocks_info(
202 bundle: &mut AssetBundle,
203 blocks_info_data: &[u8],
204 ) -> Result<()> {
205 let mut reader = BinaryReader::new(blocks_info_data, ByteOrder::Big);
206
207 reader.read_bytes(16)?;
209
210 let block_count = reader.read_i32()? as usize;
212 for _ in 0..block_count {
213 reader.read_u32()?; reader.read_u32()?; reader.read_u16()?; }
217
218 let node_count = reader.read_i32()? as usize;
220
221 for _i in 0..node_count {
223 let offset = reader.read_i64()? as u64; let size = reader.read_i64()? as u64; let flags = reader.read_u32()?;
226 let name = reader.read_cstring()?;
227
228 let node = DirectoryNode::new(name, offset, size, flags);
229 bundle.nodes.push(node);
230 }
231
232 Ok(())
233 }
234
235 #[allow(dead_code)]
237 fn parse_directory_from_data(bundle: &mut AssetBundle, data: &[u8]) -> Result<()> {
238 let mut reader = BinaryReader::new(data, ByteOrder::Big);
239
240 reader.set_position(0)?;
243
244 let node_count = reader.read_i32()? as usize;
246
247 for _ in 0..node_count {
249 let offset = reader.read_u64()?;
250 let size = reader.read_u64()?;
251 let flags = reader.read_u32()?;
252 let name = reader.read_cstring()?;
253
254 let node = DirectoryNode::new(name, offset, size, flags);
255 bundle.nodes.push(node);
256 }
257
258 Ok(())
259 }
260
261 fn parse_legacy_directory(
263 bundle: &mut AssetBundle,
264 directory_data: &[u8],
265 header_size: usize,
266 ) -> Result<()> {
267 let mut dir_reader = BinaryReader::new(directory_data, ByteOrder::Big);
268 dir_reader.set_position(header_size as u64)?; let file_count = dir_reader.read_i32()? as usize;
272
273 for _ in 0..file_count {
275 let name = dir_reader.read_cstring()?;
276 let offset = dir_reader.read_u32()? as u64;
277 let size = dir_reader.read_u32()? as u64;
278
279 let file_info = BundleFileInfo::new(name.clone(), offset, size);
280 bundle.files.push(file_info);
281
282 let node = DirectoryNode::new(name, offset, size, 1); bundle.nodes.push(node);
285 }
286
287 Ok(())
288 }
289
290 fn load_assets(bundle: &mut AssetBundle) -> Result<()> {
292 let bundle_data = bundle.data().to_vec();
294 let mut data_reader = BinaryReader::new(&bundle_data, ByteOrder::Big);
295
296 let nodes = bundle.nodes.clone();
298
299 for node in &nodes {
300 if node.is_file() {
301 if node.name.ends_with(".resS") || node.name.ends_with(".resource") {
303 continue;
304 }
305
306 data_reader.set_position(node.offset)?;
308
309 let file_data = data_reader.read_bytes(node.size as usize)?;
311
312 match crate::asset::SerializedFileParser::from_bytes(file_data) {
314 Ok(serialized_file) => {
315 bundle.assets.push(serialized_file);
317 }
318 Err(_e) => {
319 continue;
322 }
323 }
324 }
325 }
326
327 Ok(())
328 }
329
330 pub fn estimate_complexity(data: &[u8]) -> Result<ParsingComplexity> {
332 let mut reader = BinaryReader::new(data, ByteOrder::Big);
333 let header = BundleHeader::from_reader(&mut reader)?;
334
335 let complexity = match header.signature.as_str() {
336 "UnityFS" => {
337 let compression_type = header.compression_type()?;
338 let has_compression = compression_type != CompressionType::None;
339
340 ParsingComplexity {
341 format: "UnityFS".to_string(),
342 estimated_time: if has_compression { "Medium" } else { "Fast" }.to_string(),
343 memory_usage: header.size,
344 has_compression,
345 block_count: 0, }
347 }
348 "UnityWeb" | "UnityRaw" => ParsingComplexity {
349 format: header.signature.clone(),
350 estimated_time: "Fast".to_string(),
351 memory_usage: header.size,
352 has_compression: header.signature == "UnityWeb",
353 block_count: 1,
354 },
355 _ => {
356 return Err(BinaryError::unsupported(format!(
357 "Unknown bundle format: {}",
358 header.signature
359 )));
360 }
361 };
362
363 Ok(complexity)
364 }
365}
366
367#[derive(Debug, Clone)]
369pub struct ParsingComplexity {
370 pub format: String,
371 pub estimated_time: String,
372 pub memory_usage: u64,
373 pub has_compression: bool,
374 pub block_count: usize,
375}
376
377#[cfg(test)]
378mod tests {
379 #[test]
380 fn test_parser_creation() {
381 let _dummy = 1 + 1;
384 assert_eq!(_dummy, 2);
385 }
386}