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: u32,
20 pub version: u32,
22 pub data_offset: u32,
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()?;
35 let version = reader.read_u32()?;
36 let mut data_offset = reader.read_u32()?;
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 reader.set_position((file_size - metadata_size) as u64)?;
50 endian = reader.read_u8()?;
51 reader.set_position(current_pos)?;
52 }
53
54 if version >= 22 {
56 metadata_size = reader.read_u32()?;
57 file_size = reader.read_i64()? as u32;
58 data_offset = reader.read_i64()? as u32;
59 reader.read_i64()?; }
61
62 Ok(Self {
63 metadata_size,
64 file_size,
65 version,
66 data_offset,
67 endian,
68 reserved,
69 })
70 }
71
72 pub fn byte_order(&self) -> ByteOrder {
74 if self.endian == 0 {
75 ByteOrder::Little
76 } else {
77 ByteOrder::Big
78 }
79 }
80
81 pub fn is_valid(&self) -> bool {
83 self.version > 0
85 && self.version < 100
86 && self.data_offset > 0
87 && self.file_size > self.data_offset
88 }
89
90 pub fn format_info(&self) -> HeaderFormatInfo {
92 HeaderFormatInfo {
93 version: self.version,
94 is_big_endian: self.endian != 0,
95 has_extended_format: self.version >= 22,
96 supports_large_files: self.version >= 22,
97 metadata_size: self.metadata_size,
98 data_offset: self.data_offset,
99 }
100 }
101
102 pub fn validate(&self) -> Result<()> {
104 if !self.is_valid() {
105 return Err(BinaryError::invalid_data("Invalid SerializedFile header"));
106 }
107
108 if self.metadata_size == 0 {
109 return Err(BinaryError::invalid_data("Metadata size cannot be zero"));
110 }
111
112 if self.data_offset < self.metadata_size {
113 return Err(BinaryError::invalid_data(
114 "Data offset cannot be less than metadata size",
115 ));
116 }
117
118 if self.file_size < self.data_offset {
119 return Err(BinaryError::invalid_data(
120 "File size cannot be less than data offset",
121 ));
122 }
123
124 Ok(())
125 }
126
127 pub fn header_size(&self) -> u32 {
129 if self.version >= 22 {
130 4 + 4 + 4 + 4 + 1 + 3 + 4 + 8 + 8 + 8 } else if self.version >= 9 {
133 4 + 4 + 4 + 4 + 1 + 3 } else {
136 4 + 4 + 4 + 4 }
139 }
140
141 pub fn supports_type_trees(&self) -> bool {
143 self.version >= 7
144 }
145
146 pub fn supports_script_types(&self) -> bool {
148 self.version >= 11
149 }
150
151 pub fn uses_new_object_format(&self) -> bool {
153 self.version >= 14
154 }
155}
156
157impl Default for SerializedFileHeader {
158 fn default() -> Self {
159 Self {
160 metadata_size: 0,
161 file_size: 0,
162 version: 19, data_offset: 0,
164 endian: 0, reserved: [0; 3],
166 }
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct HeaderFormatInfo {
173 pub version: u32,
174 pub is_big_endian: bool,
175 pub has_extended_format: bool,
176 pub supports_large_files: bool,
177 pub metadata_size: u32,
178 pub data_offset: u32,
179}
180
181#[derive(Debug, Clone)]
183pub struct HeaderValidation {
184 pub is_valid: bool,
185 pub errors: Vec<String>,
186 pub warnings: Vec<String>,
187}
188
189impl HeaderValidation {
190 pub fn new() -> Self {
191 Self {
192 is_valid: true,
193 errors: Vec::new(),
194 warnings: Vec::new(),
195 }
196 }
197
198 pub fn add_error(&mut self, error: String) {
199 self.is_valid = false;
200 self.errors.push(error);
201 }
202
203 pub fn add_warning(&mut self, warning: String) {
204 self.warnings.push(warning);
205 }
206}
207
208impl Default for HeaderValidation {
209 fn default() -> Self {
210 Self::new()
211 }
212}
213
214pub fn validate_header(header: &SerializedFileHeader) -> HeaderValidation {
216 let mut validation = HeaderValidation::new();
217
218 if let Err(e) = header.validate() {
220 validation.add_error(e.to_string());
221 return validation;
222 }
223
224 if header.version < 7 {
226 validation.add_warning("Very old Unity version, limited feature support".to_string());
227 }
228
229 if header.version > 50 {
230 validation.add_warning("Very new Unity version, may have compatibility issues".to_string());
231 }
232
233 if header.endian != 0 {
235 validation.add_warning("Big-endian format detected, ensure proper handling".to_string());
236 }
237
238 if header.file_size > 1024 * 1024 * 1024 {
240 validation.add_warning("Large file size (>1GB), may impact performance".to_string());
241 }
242
243 validation
244}
245
246pub mod versions {
248 pub const MIN_SUPPORTED: u32 = 5;
249 pub const FIRST_WITH_TYPETREE: u32 = 7;
250 pub const FIRST_WITH_ENDIAN_FLAG: u32 = 9;
251 pub const FIRST_WITH_SCRIPT_TYPES: u32 = 11;
252 pub const FIRST_WITH_NEW_OBJECTS: u32 = 14;
253 pub const FIRST_WITH_EXTENDED_FORMAT: u32 = 22;
254 pub const CURRENT_RECOMMENDED: u32 = 19;
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_header_validation() {
263 let header = SerializedFileHeader {
264 version: 19,
265 file_size: 1000,
266 data_offset: 100,
267 metadata_size: 50,
268 ..Default::default()
269 };
270
271 assert!(header.is_valid());
272 assert!(header.validate().is_ok());
273 }
274
275 #[test]
276 fn test_byte_order() {
277 #[allow(clippy::field_reassign_with_default)]
278 {
279 let mut header = SerializedFileHeader::default();
280
281 header.endian = 0;
282 assert_eq!(header.byte_order(), ByteOrder::Little);
283
284 header.endian = 1;
285 assert_eq!(header.byte_order(), ByteOrder::Big);
286 }
287 }
288
289 #[test]
290 #[allow(clippy::field_reassign_with_default)]
291 fn test_version_features() {
292 let mut header = SerializedFileHeader::default();
293
294 header.version = 6;
295 assert!(!header.supports_type_trees());
296
297 header.version = 7;
298 assert!(header.supports_type_trees());
299
300 header.version = 11;
301 assert!(header.supports_script_types());
302
303 header.version = 22;
304 assert!(header.uses_new_object_format());
305 }
306}