1use crate::error::{Error, Result};
7use byteorder::{LittleEndian, ReadBytesExt};
8use bytes::Bytes;
9use std::io::{Cursor, Read};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct AttributeFlags(u32);
14
15impl AttributeFlags {
16 pub const CRC32: u32 = 0x00000001;
18 pub const FILETIME: u32 = 0x00000002;
20 pub const MD5: u32 = 0x00000004;
22 pub const PATCH_BIT: u32 = 0x00000008;
24 pub const ALL: u32 = 0x0000000F;
26
27 pub fn new(value: u32) -> Self {
29 Self(value)
30 }
31
32 pub fn has_crc32(&self) -> bool {
34 self.0 & Self::CRC32 != 0
35 }
36
37 pub fn has_filetime(&self) -> bool {
39 self.0 & Self::FILETIME != 0
40 }
41
42 pub fn has_md5(&self) -> bool {
44 self.0 & Self::MD5 != 0
45 }
46
47 pub fn has_patch_bit(&self) -> bool {
49 self.0 & Self::PATCH_BIT != 0
50 }
51
52 pub fn as_u32(&self) -> u32 {
54 self.0
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct FileAttributes {
61 pub crc32: Option<u32>,
63 pub filetime: Option<u64>,
65 pub md5: Option<[u8; 16]>,
67 pub is_patch: Option<bool>,
69}
70
71impl FileAttributes {
72 pub fn new() -> Self {
74 Self {
75 crc32: None,
76 filetime: None,
77 md5: None,
78 is_patch: None,
79 }
80 }
81}
82
83impl Default for FileAttributes {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct Attributes {
92 pub version: u32,
94 pub flags: AttributeFlags,
96 pub file_attributes: Vec<FileAttributes>,
98 pub crc32: Option<u32>,
100 pub md5: Option<[u8; 16]>,
102 pub filetime: Option<u64>,
104}
105
106impl Attributes {
107 pub const EXPECTED_VERSION: u32 = 100;
109
110 pub fn parse(data: &Bytes, block_count: usize) -> Result<Self> {
112 if data.len() < 8 {
113 return Err(Error::invalid_format(
114 "Attributes file too small for header",
115 ));
116 }
117
118 let mut cursor = Cursor::new(data);
119
120 let version = cursor.read_u32::<LittleEndian>().map_err(Error::Io)?;
122 if version != Self::EXPECTED_VERSION {
123 return Err(Error::invalid_format(format!(
124 "Unsupported attributes version: {} (expected {})",
125 version,
126 Self::EXPECTED_VERSION
127 )));
128 }
129
130 let flags = AttributeFlags::new(cursor.read_u32::<LittleEndian>().map_err(Error::Io)?);
131
132 let mut expected_size = 8; if flags.has_crc32() {
135 expected_size += block_count * 4;
136 }
137 if flags.has_filetime() {
138 expected_size += block_count * 8;
139 }
140 if flags.has_md5() {
141 expected_size += block_count * 16;
142 }
143 if flags.has_patch_bit() {
144 expected_size += block_count.div_ceil(8);
145 }
146
147 let min_required_size = 8 + if flags.has_crc32() { block_count * 4 } else { 0 } +
151 if flags.has_filetime() { block_count * 8 } else { 0 } +
152 if flags.has_md5() { block_count * 16 } else { 0 } +
153 if flags.has_patch_bit() {
154 let ideal_patch_bytes = block_count.div_ceil(8);
156 if ideal_patch_bytes > 0 { ideal_patch_bytes - 1 } else { 0 }
157 } else { 0 };
158
159 if data.len() < min_required_size {
160 return Err(Error::invalid_format(format!(
161 "Attributes file too small: {} bytes (expected at least {}, ideally {})",
162 data.len(),
163 min_required_size,
164 expected_size
165 )));
166 }
167
168 if data.len() != expected_size {
170 log::warn!(
171 "Attributes file size mismatch: actual={}, expected={}, difference={} (tolerating for compatibility)",
172 data.len(),
173 expected_size,
174 expected_size as i32 - data.len() as i32
175 );
176 }
177
178 let mut file_attributes = Vec::with_capacity(block_count);
180
181 let crc32_values = if flags.has_crc32() {
183 let mut values = Vec::with_capacity(block_count);
184 for _ in 0..block_count {
185 values.push(cursor.read_u32::<LittleEndian>().map_err(Error::Io)?);
186 }
187 Some(values)
188 } else {
189 None
190 };
191
192 let filetime_values = if flags.has_filetime() {
194 let mut values = Vec::with_capacity(block_count);
195 for _ in 0..block_count {
196 values.push(cursor.read_u64::<LittleEndian>().map_err(Error::Io)?);
197 }
198 Some(values)
199 } else {
200 None
201 };
202
203 let md5_values = if flags.has_md5() {
205 let mut values = Vec::with_capacity(block_count);
206 for _ in 0..block_count {
207 let mut hash = [0u8; 16];
208 cursor.read_exact(&mut hash).map_err(Error::Io)?;
209 values.push(hash);
210 }
211 Some(values)
212 } else {
213 None
214 };
215
216 let patch_bits = if flags.has_patch_bit() {
218 let ideal_byte_count = block_count.div_ceil(8);
219 let position = cursor.position() as usize;
221 let available_bytes = if data.len() > position {
222 data.len() - position
223 } else {
224 0
225 };
226 let actual_byte_count = available_bytes.min(ideal_byte_count);
227
228 log::debug!(
229 "Patch bits: ideal={ideal_byte_count} bytes, available={available_bytes} bytes, reading={actual_byte_count} bytes"
230 );
231
232 let mut bits = vec![0u8; ideal_byte_count]; if actual_byte_count > 0 {
234 let mut actual_bits = vec![0u8; actual_byte_count];
235 cursor.read_exact(&mut actual_bits).map_err(Error::Io)?;
236 bits[..actual_byte_count].copy_from_slice(&actual_bits);
237 }
239 Some(bits)
240 } else {
241 None
242 };
243
244 for i in 0..block_count {
246 let mut attrs = FileAttributes::new();
247
248 if let Some(ref values) = crc32_values {
249 attrs.crc32 = Some(values[i]);
250 }
251
252 if let Some(ref values) = filetime_values {
253 attrs.filetime = Some(values[i]);
254 }
255
256 if let Some(ref values) = md5_values {
257 attrs.md5 = Some(values[i]);
258 }
259
260 if let Some(ref bits) = patch_bits {
261 let byte_index = i / 8;
262 let bit_index = i % 8;
263 attrs.is_patch = Some((bits[byte_index] & (1 << bit_index)) != 0);
264 }
265
266 file_attributes.push(attrs);
267 }
268
269 Ok(Self {
270 version,
271 flags,
272 file_attributes,
273 crc32: None, md5: None, filetime: None, })
277 }
278
279 pub fn get_file_attributes(&self, block_index: usize) -> Option<&FileAttributes> {
281 self.file_attributes.get(block_index)
282 }
283
284 pub fn to_bytes(&self) -> Result<Vec<u8>> {
286 let block_count = self.file_attributes.len();
287 let mut data = Vec::new();
288
289 data.extend_from_slice(&self.version.to_le_bytes());
291 data.extend_from_slice(&self.flags.as_u32().to_le_bytes());
292
293 if self.flags.has_crc32() {
295 for attrs in &self.file_attributes {
296 let crc = attrs.crc32.unwrap_or(0);
297 data.extend_from_slice(&crc.to_le_bytes());
298 }
299 }
300
301 if self.flags.has_filetime() {
303 for attrs in &self.file_attributes {
304 let time = attrs.filetime.unwrap_or(0);
305 data.extend_from_slice(&time.to_le_bytes());
306 }
307 }
308
309 if self.flags.has_md5() {
311 for attrs in &self.file_attributes {
312 let hash = attrs.md5.unwrap_or([0u8; 16]);
313 data.extend_from_slice(&hash);
314 }
315 }
316
317 if self.flags.has_patch_bit() {
319 let byte_count = block_count.div_ceil(8);
320 let mut bits = vec![0u8; byte_count];
321
322 for (i, attrs) in self.file_attributes.iter().enumerate() {
323 if attrs.is_patch.unwrap_or(false) {
324 let byte_index = i / 8;
325 let bit_index = i % 8;
326 bits[byte_index] |= 1 << bit_index;
327 }
328 }
329
330 data.extend_from_slice(&bits);
331 }
332
333 Ok(data)
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_attribute_flags() {
343 let flags = AttributeFlags::new(AttributeFlags::ALL);
344 assert!(flags.has_crc32());
345 assert!(flags.has_filetime());
346 assert!(flags.has_md5());
347 assert!(flags.has_patch_bit());
348
349 let flags = AttributeFlags::new(AttributeFlags::CRC32 | AttributeFlags::MD5);
350 assert!(flags.has_crc32());
351 assert!(!flags.has_filetime());
352 assert!(flags.has_md5());
353 assert!(!flags.has_patch_bit());
354 }
355
356 #[test]
357 fn test_parse_empty_attributes() {
358 let mut data = Vec::new();
360 data.extend_from_slice(&100u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); let bytes = Bytes::from(data);
364 let attrs = Attributes::parse(&bytes, 0).unwrap();
365
366 assert_eq!(attrs.version, 100);
367 assert_eq!(attrs.flags.as_u32(), 0);
368 assert_eq!(attrs.file_attributes.len(), 0);
369 }
370
371 #[test]
372 fn test_parse_crc32_only() {
373 let mut data = Vec::new();
374 data.extend_from_slice(&100u32.to_le_bytes()); data.extend_from_slice(&AttributeFlags::CRC32.to_le_bytes()); data.extend_from_slice(&0x12345678u32.to_le_bytes());
379 data.extend_from_slice(&0x9ABCDEF0u32.to_le_bytes());
380
381 let bytes = Bytes::from(data);
382 let attrs = Attributes::parse(&bytes, 2).unwrap();
383
384 assert_eq!(attrs.version, 100);
385 assert!(attrs.flags.has_crc32());
386 assert!(!attrs.flags.has_filetime());
387 assert!(!attrs.flags.has_md5());
388 assert!(!attrs.flags.has_patch_bit());
389
390 assert_eq!(attrs.file_attributes.len(), 2);
391 assert_eq!(attrs.file_attributes[0].crc32, Some(0x12345678));
392 assert_eq!(attrs.file_attributes[1].crc32, Some(0x9ABCDEF0));
393 }
394
395 #[test]
396 fn test_roundtrip() {
397 let mut file_attrs = Vec::new();
399
400 let mut attr1 = FileAttributes::new();
401 attr1.crc32 = Some(0x12345678);
402 attr1.filetime = Some(0x01234567_89ABCDEF);
403 attr1.md5 = Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
404 attr1.is_patch = Some(false);
405 file_attrs.push(attr1);
406
407 let mut attr2 = FileAttributes::new();
408 attr2.crc32 = Some(0x9ABCDEF0);
409 attr2.filetime = Some(0xFEDCBA98_76543210);
410 attr2.md5 = Some([16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
411 attr2.is_patch = Some(true);
412 file_attrs.push(attr2);
413
414 let original = Attributes {
415 version: 100,
416 flags: AttributeFlags::new(AttributeFlags::ALL),
417 file_attributes: file_attrs,
418 crc32: None, md5: None, filetime: None, };
422
423 let bytes = original.to_bytes().unwrap();
425 let parsed = Attributes::parse(&Bytes::from(bytes), 2).unwrap();
426
427 assert_eq!(parsed.version, original.version);
428 assert_eq!(parsed.flags.as_u32(), original.flags.as_u32());
429 assert_eq!(parsed.file_attributes.len(), original.file_attributes.len());
430
431 for i in 0..2 {
432 assert_eq!(parsed.file_attributes[i], original.file_attributes[i]);
433 }
434 }
435}