unity_asset_binary/bundle/
types.rs1use super::header::BundleHeader;
6use crate::asset::Asset;
7use crate::compression::CompressionBlock;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
14pub struct BundleFileInfo {
15 pub offset: u64,
17 pub size: u64,
19 pub name: String,
21}
22
23impl BundleFileInfo {
24 pub fn new(name: String, offset: u64, size: u64) -> Self {
26 Self { name, offset, size }
27 }
28
29 pub fn is_valid(&self) -> bool {
31 !self.name.is_empty() && self.size > 0
32 }
33
34 pub fn end_offset(&self) -> u64 {
36 self.offset + self.size
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, Default)]
45pub struct DirectoryNode {
46 pub name: String,
48 pub offset: u64,
50 pub size: u64,
52 pub flags: u32,
54}
55
56impl DirectoryNode {
57 pub fn new(name: String, offset: u64, size: u64, flags: u32) -> Self {
59 Self {
60 name,
61 offset,
62 size,
63 flags,
64 }
65 }
66
67 pub fn is_file(&self) -> bool {
69 (self.flags & 0x4) != 0
71 }
72
73 pub fn is_directory(&self) -> bool {
75 !self.is_file()
76 }
77
78 pub fn is_compressed(&self) -> bool {
80 (self.flags & 0x2) != 0
81 }
82
83 pub fn end_offset(&self) -> u64 {
85 self.offset + self.size
86 }
87}
88
89#[derive(Debug)]
94pub struct AssetBundle {
95 pub header: BundleHeader,
97 pub blocks: Vec<CompressionBlock>,
99 pub nodes: Vec<DirectoryNode>,
101 pub files: Vec<BundleFileInfo>,
103 pub assets: Vec<Asset>,
105 data: Vec<u8>,
107}
108
109impl AssetBundle {
110 pub fn new(header: BundleHeader, data: Vec<u8>) -> Self {
112 Self {
113 header,
114 blocks: Vec::new(),
115 nodes: Vec::new(),
116 files: Vec::new(),
117 assets: Vec::new(),
118 data,
119 }
120 }
121
122 pub fn data(&self) -> &[u8] {
124 &self.data
125 }
126
127 pub fn data_mut(&mut self) -> &mut Vec<u8> {
129 &mut self.data
130 }
131
132 pub fn size(&self) -> u64 {
134 self.data.len() as u64
135 }
136
137 pub fn is_compressed(&self) -> bool {
139 !self.blocks.is_empty()
140 && self.blocks.iter().any(|block| {
141 block
142 .compression_type()
143 .unwrap_or(crate::compression::CompressionType::None)
144 != crate::compression::CompressionType::None
145 })
146 }
147
148 pub fn file_count(&self) -> usize {
150 self.files.len()
151 }
152
153 pub fn asset_count(&self) -> usize {
155 self.assets.len()
156 }
157
158 pub fn find_file(&self, name: &str) -> Option<&BundleFileInfo> {
160 self.files.iter().find(|file| file.name == name)
161 }
162
163 pub fn find_node(&self, name: &str) -> Option<&DirectoryNode> {
165 self.nodes.iter().find(|node| node.name == name)
166 }
167
168 pub fn file_names(&self) -> Vec<&str> {
170 self.files.iter().map(|file| file.name.as_str()).collect()
171 }
172
173 pub fn node_names(&self) -> Vec<&str> {
175 self.nodes.iter().map(|node| node.name.as_str()).collect()
176 }
177
178 pub fn extract_file_data(&self, file: &BundleFileInfo) -> crate::error::Result<Vec<u8>> {
180 if file.offset + file.size > self.data.len() as u64 {
181 return Err(crate::error::BinaryError::invalid_data(
182 "File offset/size exceeds bundle data",
183 ));
184 }
185
186 let start = file.offset as usize;
187 let end = (file.offset + file.size) as usize;
188 Ok(self.data[start..end].to_vec())
189 }
190
191 pub fn extract_node_data(&self, node: &DirectoryNode) -> crate::error::Result<Vec<u8>> {
193 if node.offset + node.size > self.data.len() as u64 {
194 return Err(crate::error::BinaryError::invalid_data(
195 "Node offset/size exceeds bundle data",
196 ));
197 }
198
199 let start = node.offset as usize;
200 let end = (node.offset + node.size) as usize;
201 Ok(self.data[start..end].to_vec())
202 }
203
204 pub fn statistics(&self) -> BundleStatistics {
206 let total_compressed_size: u64 = self.blocks.iter().map(|b| b.compressed_size as u64).sum();
207 let total_uncompressed_size: u64 =
208 self.blocks.iter().map(|b| b.uncompressed_size as u64).sum();
209
210 BundleStatistics {
211 total_size: self.size(),
212 header_size: self.header.header_size(),
213 compressed_size: total_compressed_size,
214 uncompressed_size: total_uncompressed_size,
215 compression_ratio: if total_uncompressed_size > 0 {
216 total_compressed_size as f64 / total_uncompressed_size as f64
217 } else {
218 1.0
219 },
220 file_count: self.file_count(),
221 asset_count: self.asset_count(),
222 block_count: self.blocks.len(),
223 node_count: self.nodes.len(),
224 }
225 }
226
227 pub fn validate(&self) -> crate::error::Result<()> {
229 self.header.validate()?;
231
232 for file in &self.files {
234 if file.end_offset() > self.size() {
235 return Err(crate::error::BinaryError::invalid_data(format!(
236 "File '{}' exceeds bundle size",
237 file.name
238 )));
239 }
240 }
241
242 for node in &self.nodes {
244 if node.end_offset() > self.size() {
245 return Err(crate::error::BinaryError::invalid_data(format!(
246 "Node '{}' exceeds bundle size",
247 node.name
248 )));
249 }
250 }
251
252 Ok(())
253 }
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct BundleStatistics {
259 pub total_size: u64,
260 pub header_size: u64,
261 pub compressed_size: u64,
262 pub uncompressed_size: u64,
263 pub compression_ratio: f64,
264 pub file_count: usize,
265 pub asset_count: usize,
266 pub block_count: usize,
267 pub node_count: usize,
268}
269
270#[derive(Debug, Clone)]
272pub struct BundleLoadOptions {
273 pub load_assets: bool,
275 pub decompress_blocks: bool,
277 pub validate: bool,
279 pub max_memory: Option<usize>,
281}
282
283impl Default for BundleLoadOptions {
284 fn default() -> Self {
285 Self {
286 load_assets: true,
287 decompress_blocks: false, validate: true,
289 max_memory: Some(1024 * 1024 * 1024), }
291 }
292}
293
294impl BundleLoadOptions {
295 pub fn fast() -> Self {
297 Self {
298 load_assets: false,
299 decompress_blocks: false,
300 validate: false,
301 max_memory: None,
302 }
303 }
304
305 pub fn complete() -> Self {
307 Self {
308 load_assets: true,
309 decompress_blocks: true,
310 validate: true,
311 max_memory: Some(2048 * 1024 * 1024), }
313 }
314}