unity_asset_binary/asset/
header.rs1use crate::error::{BinaryError, Result};
7use crate::reader::{BinaryReader, ByteOrder};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SerializedFileHeader {
16 pub metadata_size: u32,
18 pub file_size: u64,
20 pub version: u32,
22 pub data_offset: u64,
24 pub endian: u8,
26 pub reserved: [u8; 3],
28}
29
30impl SerializedFileHeader {
31 pub fn from_reader(reader: &mut BinaryReader) -> Result<Self> {
33 let mut metadata_size = reader.read_u32()?;
34 let mut file_size = reader.read_u32()? as u64;
35 let version = reader.read_u32()?;
36 let mut data_offset = reader.read_u32()? as u64;
37
38 let endian;
39 let mut reserved = [0u8; 3];
40
41 if version >= 9 {
43 endian = reader.read_u8()?;
44 let reserved_bytes = reader.read_bytes(3)?;
45 reserved.copy_from_slice(&reserved_bytes);
46 } else {
47 let current_pos = reader.position();
49 let endian_pos = file_size.checked_sub(metadata_size as u64).ok_or_else(|| {
50 BinaryError::invalid_data("Invalid header: file_size < metadata_size")
51 })?;
52 reader.set_position(endian_pos)?;
53 endian = reader.read_u8()?;
54 reader.set_position(current_pos)?;
55 }
56
57 if version >= 22 {
59 metadata_size = reader.read_u32()?;
60 file_size = i64_to_u64_checked(reader.read_i64()?, "file_size")?;
61 data_offset = i64_to_u64_checked(reader.read_i64()?, "data_offset")?;
62 reader.read_i64()?; }
64
65 Ok(Self {
66 metadata_size,
67 file_size,
68 version,
69 data_offset,
70 endian,
71 reserved,
72 })
73 }
74
75 pub fn byte_order(&self) -> ByteOrder {
77 if self.endian == 0 {
78 ByteOrder::Little
79 } else {
80 ByteOrder::Big
81 }
82 }
83
84 pub fn is_valid(&self) -> bool {
86 self.version > 0
88 && self.version < 100
89 && self.data_offset > 0
90 && self.file_size > self.data_offset
91 }
92
93 pub fn format_info(&self) -> HeaderFormatInfo {
95 HeaderFormatInfo {
96 version: self.version,
97 is_big_endian: self.endian != 0,
98 has_extended_format: self.version >= 22,
99 supports_large_files: self.version >= 22,
100 metadata_size: self.metadata_size,
101 data_offset: self.data_offset,
102 }
103 }
104
105 pub fn validate(&self) -> Result<()> {
107 if !self.is_valid() {
108 return Err(BinaryError::invalid_data("Invalid SerializedFile header"));
109 }
110
111 if self.metadata_size == 0 {
112 return Err(BinaryError::invalid_data("Metadata size cannot be zero"));
113 }
114
115 if self.data_offset < self.metadata_size as u64 {
116 return Err(BinaryError::invalid_data(
117 "Data offset cannot be less than metadata size",
118 ));
119 }
120
121 if self.file_size < self.data_offset {
122 return Err(BinaryError::invalid_data(
123 "File size cannot be less than data offset",
124 ));
125 }
126
127 Ok(())
128 }
129
130 pub fn header_size(&self) -> u32 {
132 if self.version >= 22 {
133 4 + 4 + 4 + 4 + 1 + 3 + 4 + 8 + 8 + 8 } else if self.version >= 9 {
136 4 + 4 + 4 + 4 + 1 + 3 } else {
139 4 + 4 + 4 + 4 }
142 }
143
144 pub fn supports_type_trees(&self) -> bool {
146 self.version >= 7
147 }
148
149 pub fn supports_script_types(&self) -> bool {
151 self.version >= 11
152 }
153
154 pub fn uses_new_object_format(&self) -> bool {
156 self.version >= 14
157 }
158}
159
160impl Default for SerializedFileHeader {
161 fn default() -> Self {
162 Self {
163 metadata_size: 0,
164 file_size: 0,
165 version: 19, data_offset: 0,
167 endian: 0, reserved: [0; 3],
169 }
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct HeaderFormatInfo {
176 pub version: u32,
177 pub is_big_endian: bool,
178 pub has_extended_format: bool,
179 pub supports_large_files: bool,
180 pub metadata_size: u32,
181 pub data_offset: u64,
182}
183
184fn i64_to_u64_checked(value: i64, name: &'static str) -> Result<u64> {
185 if value < 0 {
186 return Err(BinaryError::invalid_data(format!(
187 "Invalid {}: negative value {}",
188 name, value
189 )));
190 }
191 Ok(value as u64)
192}
193
194#[derive(Debug, Clone)]
196pub struct HeaderValidation {
197 pub is_valid: bool,
198 pub errors: Vec<String>,
199 pub warnings: Vec<String>,
200}
201
202impl HeaderValidation {
203 pub fn new() -> Self {
204 Self {
205 is_valid: true,
206 errors: Vec::new(),
207 warnings: Vec::new(),
208 }
209 }
210
211 pub fn add_error(&mut self, error: String) {
212 self.is_valid = false;
213 self.errors.push(error);
214 }
215
216 pub fn add_warning(&mut self, warning: String) {
217 self.warnings.push(warning);
218 }
219}
220
221impl Default for HeaderValidation {
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227pub fn validate_header(header: &SerializedFileHeader) -> HeaderValidation {
229 let mut validation = HeaderValidation::new();
230
231 if let Err(e) = header.validate() {
233 validation.add_error(e.to_string());
234 return validation;
235 }
236
237 if header.version < 7 {
239 validation.add_warning("Very old Unity version, limited feature support".to_string());
240 }
241
242 if header.version > 50 {
243 validation.add_warning("Very new Unity version, may have compatibility issues".to_string());
244 }
245
246 if header.endian != 0 {
248 validation.add_warning("Big-endian format detected, ensure proper handling".to_string());
249 }
250
251 if header.file_size > 1024_u64 * 1024 * 1024 {
253 validation.add_warning("Large file size (>1GB), may impact performance".to_string());
254 }
255
256 validation
257}
258
259pub mod versions {
261 pub const MIN_SUPPORTED: u32 = 5;
262 pub const FIRST_WITH_TYPETREE: u32 = 7;
263 pub const FIRST_WITH_ENDIAN_FLAG: u32 = 9;
264 pub const FIRST_WITH_SCRIPT_TYPES: u32 = 11;
265 pub const FIRST_WITH_NEW_OBJECTS: u32 = 14;
266 pub const FIRST_WITH_EXTENDED_FORMAT: u32 = 22;
267 pub const CURRENT_RECOMMENDED: u32 = 19;
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_header_validation() {
276 let header = SerializedFileHeader {
277 version: 19,
278 file_size: 1000,
279 data_offset: 100,
280 metadata_size: 50,
281 ..Default::default()
282 };
283
284 assert!(header.is_valid());
285 assert!(header.validate().is_ok());
286 }
287
288 #[test]
289 fn test_byte_order() {
290 #[allow(clippy::field_reassign_with_default)]
291 {
292 let mut header = SerializedFileHeader::default();
293
294 header.endian = 0;
295 assert_eq!(header.byte_order(), ByteOrder::Little);
296
297 header.endian = 1;
298 assert_eq!(header.byte_order(), ByteOrder::Big);
299 }
300 }
301
302 #[test]
303 #[allow(clippy::field_reassign_with_default)]
304 fn test_version_features() {
305 let mut header = SerializedFileHeader::default();
306
307 header.version = 6;
308 assert!(!header.supports_type_trees());
309
310 header.version = 7;
311 assert!(header.supports_type_trees());
312
313 header.version = 11;
314 assert!(header.supports_script_types());
315
316 header.version = 22;
317 assert!(header.uses_new_object_format());
318 }
319}