1use std::collections::HashMap;
16
17use crate::error::{CascError, Result};
18use crate::util::io::{read_le_i32, read_le_u32, read_le_u64};
19
20use super::flags::{ContentFlags, LocaleFlags};
21
22const MFST_MAGIC_BE: u32 = 0x5453464D; const MFST_MAGIC_LE: u32 = 0x4D465354; #[derive(Debug, Clone)]
34pub struct RootEntry {
35 pub ckey: [u8; 16],
37 pub content_flags: ContentFlags,
39 pub locale_flags: LocaleFlags,
41 pub name_hash: Option<u64>,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum RootFormat {
49 Legacy,
51 MfstV1,
53 MfstV2,
55}
56
57pub struct RootFile {
59 format: RootFormat,
60 entries: HashMap<u32, Vec<RootEntry>>,
62 total_entries: usize,
63}
64
65impl RootFile {
66 pub fn parse(data: &[u8]) -> Result<Self> {
68 let (format, block_start) = detect_format(data)?;
69
70 let mut entries: HashMap<u32, Vec<RootEntry>> = HashMap::new();
71 let mut total_entries: usize = 0;
72 let mut pos = block_start;
73
74 while pos < data.len() {
75 let (block_entries, new_pos) = parse_block(data, pos, format)?;
76 total_entries += block_entries.len();
77 for (fdid, entry) in block_entries {
78 entries.entry(fdid).or_default().push(entry);
79 }
80 pos = new_pos;
81 }
82
83 Ok(Self {
84 format,
85 entries,
86 total_entries,
87 })
88 }
89
90 pub fn find_by_fdid(&self, fdid: u32, locale: LocaleFlags) -> Option<&RootEntry> {
92 self.entries
93 .get(&fdid)?
94 .iter()
95 .find(|e| e.locale_flags.matches(locale))
96 }
97
98 pub fn iter_all(&self) -> impl Iterator<Item = (u32, &RootEntry)> {
100 self.entries
101 .iter()
102 .flat_map(|(fdid, entries)| entries.iter().map(move |entry| (*fdid, entry)))
103 }
104
105 pub fn format(&self) -> RootFormat {
107 self.format
108 }
109
110 pub fn len(&self) -> usize {
112 self.total_entries
113 }
114
115 pub fn is_empty(&self) -> bool {
117 self.total_entries == 0
118 }
119
120 pub fn fdid_count(&self) -> usize {
122 self.entries.len()
123 }
124}
125
126fn detect_format(data: &[u8]) -> Result<(RootFormat, usize)> {
128 if data.len() < 4 {
129 if data.is_empty() {
131 return Err(CascError::InvalidFormat("root file is empty".to_string()));
132 }
133 return Ok((RootFormat::Legacy, 0));
134 }
135
136 let magic = read_le_u32(&data[0..4]);
137 if magic != MFST_MAGIC_LE && magic != MFST_MAGIC_BE {
138 return Ok((RootFormat::Legacy, 0));
140 }
141
142 if data.len() < 12 {
144 return Err(CascError::InvalidFormat(
145 "MFST header too short".to_string(),
146 ));
147 }
148
149 let field_at_4 = read_le_u32(&data[4..8]);
150
151 if (12..=1024).contains(&field_at_4) && data.len() >= field_at_4 as usize {
159 let header_size = field_at_4 as usize;
161 let version = read_le_u32(&data[8..12]);
162 let format = match version {
163 1 => RootFormat::MfstV1,
164 2 => RootFormat::MfstV2,
165 _ => {
166 return Err(CascError::UnsupportedVersion(version));
167 }
168 };
169 Ok((format, header_size))
170 } else {
171 Ok((RootFormat::MfstV1, 12))
174 }
175}
176
177fn parse_block(
180 data: &[u8],
181 pos: usize,
182 format: RootFormat,
183) -> Result<(Vec<(u32, RootEntry)>, usize)> {
184 let (num_records, content_flags, locale_flags, mut pos) =
185 parse_block_header(data, pos, format)?;
186
187 if num_records == 0 {
188 return Ok((Vec::new(), pos));
189 }
190
191 let num = num_records as usize;
192
193 let deltas_size = num * 4;
195 if pos + deltas_size > data.len() {
196 return Err(CascError::InvalidFormat(
197 "root block: not enough data for FileDataID deltas".to_string(),
198 ));
199 }
200
201 let mut fdids = Vec::with_capacity(num);
202 let mut current_fdid: i64 = 0;
203 for i in 0..num {
204 let delta = read_le_i32(&data[pos + i * 4..]) as i64;
205 if i == 0 {
206 current_fdid = delta;
208 } else {
209 current_fdid = current_fdid + 1 + delta;
210 }
211 fdids.push(current_fdid as u32);
212 }
213 pos += deltas_size;
214
215 let ckeys_size = num * 16;
217 if pos + ckeys_size > data.len() {
218 return Err(CascError::InvalidFormat(
219 "root block: not enough data for content keys".to_string(),
220 ));
221 }
222
223 let mut ckeys = Vec::with_capacity(num);
224 for i in 0..num {
225 let mut ckey = [0u8; 16];
226 ckey.copy_from_slice(&data[pos + i * 16..pos + i * 16 + 16]);
227 ckeys.push(ckey);
228 }
229 pos += ckeys_size;
230
231 let has_name_hashes = !content_flags.has_no_name_hash();
233 let mut name_hashes: Vec<Option<u64>> = Vec::with_capacity(num);
234
235 if has_name_hashes {
236 let hashes_size = num * 8;
237 if pos + hashes_size > data.len() {
238 return Err(CascError::InvalidFormat(
239 "root block: not enough data for name hashes".to_string(),
240 ));
241 }
242 for i in 0..num {
243 name_hashes.push(Some(read_le_u64(&data[pos + i * 8..])));
244 }
245 pos += hashes_size;
246 } else {
247 name_hashes.resize(num, None);
248 }
249
250 let mut result = Vec::with_capacity(num);
252 for i in 0..num {
253 result.push((
254 fdids[i],
255 RootEntry {
256 ckey: ckeys[i],
257 content_flags,
258 locale_flags,
259 name_hash: name_hashes[i],
260 },
261 ));
262 }
263
264 Ok((result, pos))
265}
266
267fn parse_block_header(
269 data: &[u8],
270 pos: usize,
271 format: RootFormat,
272) -> Result<(u32, ContentFlags, LocaleFlags, usize)> {
273 match format {
274 RootFormat::Legacy | RootFormat::MfstV1 => {
275 if pos + 12 > data.len() {
277 return Err(CascError::InvalidFormat(
278 "root block header v1: not enough data".to_string(),
279 ));
280 }
281 let num_records = read_le_u32(&data[pos..]);
282 let content_flags = ContentFlags(read_le_u32(&data[pos + 4..]));
283 let locale_flags = LocaleFlags(read_le_u32(&data[pos + 8..]));
284 Ok((num_records, content_flags, locale_flags, pos + 12))
285 }
286 RootFormat::MfstV2 => {
287 if pos + 17 > data.len() {
289 return Err(CascError::InvalidFormat(
290 "root block header v2: not enough data".to_string(),
291 ));
292 }
293 let num_records = read_le_u32(&data[pos..]);
294 let locale_flags = LocaleFlags(read_le_u32(&data[pos + 4..]));
295 let unk1 = read_le_u32(&data[pos + 8..]);
296 let unk2 = read_le_u32(&data[pos + 12..]);
297 let unk3 = data[pos + 16];
298 let content_flags = ContentFlags(unk1 | unk2 | ((unk3 as u32) << 17));
300 Ok((num_records, content_flags, locale_flags, pos + 17))
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::root::flags::{ContentFlags, LocaleFlags};
309
310 type RootBlockEntry = (i32, [u8; 16], Option<u64>);
311
312 fn build_root_v1(blocks: &[(u32, u32, Vec<RootBlockEntry>)]) -> Vec<u8> {
315 let total_count: u32 = blocks.iter().map(|(_, _, e)| e.len() as u32).sum();
316 let named_count: u32 = blocks
317 .iter()
318 .filter(|(cf, _, _)| (cf & 0x10000000) == 0)
319 .map(|(_, _, e)| e.len() as u32)
320 .sum();
321
322 let mut data = Vec::new();
323
324 data.extend_from_slice(&MFST_MAGIC_BE.to_le_bytes()); data.extend_from_slice(&24u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&total_count.to_le_bytes()); data.extend_from_slice(&named_count.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); assert_eq!(data.len(), 24);
332
333 for (content_flags, locale_flags, entries) in blocks {
335 let num_records = entries.len() as u32;
336 data.extend_from_slice(&num_records.to_le_bytes());
338 data.extend_from_slice(&content_flags.to_le_bytes());
339 data.extend_from_slice(&locale_flags.to_le_bytes());
340
341 for (delta, _, _) in entries {
343 data.extend_from_slice(&delta.to_le_bytes());
344 }
345
346 for (_, ckey, _) in entries {
348 data.extend_from_slice(ckey);
349 }
350
351 if (content_flags & 0x10000000) == 0 {
353 for (_, _, name_hash) in entries {
354 let hash = name_hash.unwrap_or(0);
355 data.extend_from_slice(&hash.to_le_bytes());
356 }
357 }
358 }
359
360 data
361 }
362
363 #[test]
364 fn detect_mfst_format() {
365 let data = build_root_v1(&[]);
366 let root = RootFile::parse(&data).unwrap();
367 assert_eq!(root.format(), RootFormat::MfstV1);
368 }
369
370 #[test]
371 fn parse_single_block_single_entry() {
372 let ckey = [0xAA; 16];
373 let blocks = vec![(0x8u32, 0x2u32, vec![(100i32, ckey, Some(0xDEADBEEF_u64))])]; let data = build_root_v1(&blocks);
375 let root = RootFile::parse(&data).unwrap();
376
377 assert_eq!(root.len(), 1);
378 let entry = root.find_by_fdid(100, LocaleFlags::EN_US).unwrap();
379 assert_eq!(entry.ckey, ckey);
380 assert_eq!(entry.name_hash, Some(0xDEADBEEF));
381 }
382
383 #[test]
384 fn parse_fdid_deltas_sequential() {
385 let blocks = vec![(
386 0x10000008u32,
387 0x2u32,
388 vec![
389 (100i32, [0x01; 16], None), (0i32, [0x02; 16], None), (0i32, [0x03; 16], None), (2i32, [0x04; 16], None), ],
394 )];
395 let data = build_root_v1(&blocks);
396 let root = RootFile::parse(&data).unwrap();
397
398 assert_eq!(root.len(), 4);
399 assert!(root.find_by_fdid(100, LocaleFlags::ALL).is_some());
400 assert!(root.find_by_fdid(101, LocaleFlags::ALL).is_some());
401 assert!(root.find_by_fdid(102, LocaleFlags::ALL).is_some());
402 assert!(root.find_by_fdid(103, LocaleFlags::ALL).is_none()); assert!(root.find_by_fdid(104, LocaleFlags::ALL).is_none()); assert!(root.find_by_fdid(105, LocaleFlags::ALL).is_some());
405 }
406
407 #[test]
408 fn parse_block_with_name_hashes() {
409 let blocks = vec![(
410 0x8u32,
411 0x2u32,
412 vec![(50i32, [0xBB; 16], Some(0x1234567890ABCDEF_u64))],
413 )]; let data = build_root_v1(&blocks);
415 let root = RootFile::parse(&data).unwrap();
416
417 let entry = root.find_by_fdid(50, LocaleFlags::ALL).unwrap();
418 assert_eq!(entry.name_hash, Some(0x1234567890ABCDEF));
419 }
420
421 #[test]
422 fn parse_block_without_name_hashes() {
423 let blocks = vec![(0x10000008u32, 0x2u32, vec![(50i32, [0xCC; 16], None)])]; let data = build_root_v1(&blocks);
425 let root = RootFile::parse(&data).unwrap();
426
427 let entry = root.find_by_fdid(50, LocaleFlags::ALL).unwrap();
428 assert_eq!(entry.name_hash, None);
429 }
430
431 #[test]
432 fn parse_multiple_blocks_different_locales() {
433 let blocks = vec![
434 (0x8u32, 0x2u32, vec![(100i32, [0x01; 16], Some(0))]), (0x8u32, 0x20u32, vec![(100i32, [0x02; 16], Some(0))]), ];
437 let data = build_root_v1(&blocks);
438 let root = RootFile::parse(&data).unwrap();
439
440 let en = root.find_by_fdid(100, LocaleFlags::EN_US).unwrap();
442 assert_eq!(en.ckey, [0x01; 16]);
443
444 let de = root.find_by_fdid(100, LocaleFlags::DE_DE).unwrap();
445 assert_eq!(de.ckey, [0x02; 16]);
446 }
447
448 #[test]
449 fn parse_locale_filter() {
450 let blocks = vec![(0x8u32, 0x20u32, vec![(200i32, [0xFF; 16], Some(0))])]; let data = build_root_v1(&blocks);
452 let root = RootFile::parse(&data).unwrap();
453
454 assert!(root.find_by_fdid(200, LocaleFlags::EN_US).is_none()); assert!(root.find_by_fdid(200, LocaleFlags::DE_DE).is_some()); assert!(root.find_by_fdid(200, LocaleFlags::ALL).is_some()); }
458
459 #[test]
460 fn iter_all_entries() {
461 let blocks = vec![(
462 0x10000008u32,
463 0x2u32,
464 vec![(10i32, [0x01; 16], None), (0i32, [0x02; 16], None)],
465 )];
466 let data = build_root_v1(&blocks);
467 let root = RootFile::parse(&data).unwrap();
468
469 let all: Vec<_> = root.iter_all().collect();
470 assert_eq!(all.len(), 2);
471 }
472
473 #[test]
474 fn parse_empty_root() {
475 let data = build_root_v1(&[]);
476 let root = RootFile::parse(&data).unwrap();
477 assert!(root.is_empty());
478 assert_eq!(root.fdid_count(), 0);
479 }
480
481 #[test]
482 fn detect_legacy_format() {
483 let mut data = Vec::new();
486 data.extend_from_slice(&1u32.to_le_bytes());
488 data.extend_from_slice(&0x10000008u32.to_le_bytes());
489 data.extend_from_slice(&0x2u32.to_le_bytes());
490 data.extend_from_slice(&42i32.to_le_bytes());
492 data.extend_from_slice(&[0xDD; 16]);
494 let root = RootFile::parse(&data).unwrap();
497 assert_eq!(root.format(), RootFormat::Legacy);
498 assert_eq!(root.len(), 1);
499 assert!(root.find_by_fdid(42, LocaleFlags::ALL).is_some());
500 }
501
502 #[test]
503 fn detect_pre_1017_mfst() {
504 let mut data = Vec::new();
506 data.extend_from_slice(&MFST_MAGIC_BE.to_le_bytes()); data.extend_from_slice(&500000u32.to_le_bytes()); data.extend_from_slice(&400000u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0x10000008u32.to_le_bytes()); data.extend_from_slice(&0x2u32.to_le_bytes()); data.extend_from_slice(&7i32.to_le_bytes()); data.extend_from_slice(&[0xEE; 16]); let root = RootFile::parse(&data).unwrap();
519 assert_eq!(root.format(), RootFormat::MfstV1);
520 assert_eq!(root.len(), 1);
521 assert!(root.find_by_fdid(7, LocaleFlags::ALL).is_some());
522 }
523
524 #[test]
525 fn mfst_v2_block_header() {
526 let mut data = Vec::new();
528 data.extend_from_slice(&MFST_MAGIC_BE.to_le_bytes());
530 data.extend_from_slice(&24u32.to_le_bytes()); data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0x2u32.to_le_bytes()); data.extend_from_slice(&0x8u32.to_le_bytes()); data.extend_from_slice(&0x10000000u32.to_le_bytes()); data.push(0); data.extend_from_slice(&99i32.to_le_bytes());
545 data.extend_from_slice(&[0xAB; 16]);
547 let root = RootFile::parse(&data).unwrap();
550 assert_eq!(root.format(), RootFormat::MfstV2);
551 assert_eq!(root.len(), 1);
552
553 let entry = root.find_by_fdid(99, LocaleFlags::EN_US).unwrap();
554 assert_eq!(entry.ckey, [0xAB; 16]);
555 assert!(entry.content_flags.has(ContentFlags::LOAD_ON_WINDOWS));
557 assert!(entry.content_flags.has_no_name_hash());
558 assert_eq!(entry.name_hash, None);
559 }
560
561 #[test]
562 fn mfst_extended_header_size() {
563 let mut data = Vec::new();
566 data.extend_from_slice(&MFST_MAGIC_BE.to_le_bytes());
568 data.extend_from_slice(&32u32.to_le_bytes()); data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&[0u8; 12]); assert_eq!(data.len(), 32);
574
575 data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0x2u32.to_le_bytes()); data.extend_from_slice(&0x8u32.to_le_bytes()); data.extend_from_slice(&0x10000000u32.to_le_bytes()); data.push(0); data.extend_from_slice(&77i32.to_le_bytes()); data.extend_from_slice(&[0xCD; 16]); let root = RootFile::parse(&data).unwrap();
586 assert_eq!(root.format(), RootFormat::MfstV2);
587 assert_eq!(root.len(), 1);
588 let entry = root.find_by_fdid(77, LocaleFlags::EN_US).unwrap();
589 assert_eq!(entry.ckey, [0xCD; 16]);
590 }
591
592 #[test]
593 fn mfst_unsupported_version_errors() {
594 let mut data = Vec::new();
595 data.extend_from_slice(&MFST_MAGIC_BE.to_le_bytes());
596 data.extend_from_slice(&24u32.to_le_bytes()); data.extend_from_slice(&99u32.to_le_bytes()); data.extend_from_slice(&[0u8; 12]);
599
600 let err = match RootFile::parse(&data) {
601 Err(e) => e,
602 Ok(_) => panic!("expected UnsupportedVersion error"),
603 };
604 assert!(matches!(err, CascError::UnsupportedVersion(99)));
605 }
606
607 #[test]
608 fn parse_error_on_empty_data() {
609 let result = RootFile::parse(&[]);
610 assert!(result.is_err());
611 }
612
613 #[test]
614 fn parse_error_on_truncated_block() {
615 let mut data = Vec::new();
616 data.extend_from_slice(&MFST_MAGIC_BE.to_le_bytes());
618 data.extend_from_slice(&24u32.to_le_bytes());
619 data.extend_from_slice(&1u32.to_le_bytes());
620 data.extend_from_slice(&1u32.to_le_bytes());
621 data.extend_from_slice(&1u32.to_le_bytes());
622 data.extend_from_slice(&0u32.to_le_bytes());
623 data.extend_from_slice(&1000u32.to_le_bytes());
625 data.extend_from_slice(&0x8u32.to_le_bytes());
626 data.extend_from_slice(&0x2u32.to_le_bytes());
627
628 let result = RootFile::parse(&data);
629 assert!(result.is_err());
630 }
631
632 #[test]
633 fn fdid_count_vs_len() {
634 let blocks = vec![
636 (0x8u32, 0x2u32, vec![(50i32, [0x01; 16], Some(0))]),
637 (0x8u32, 0x20u32, vec![(50i32, [0x02; 16], Some(0))]),
638 ];
639 let data = build_root_v1(&blocks);
640 let root = RootFile::parse(&data).unwrap();
641
642 assert_eq!(root.len(), 2);
643 assert_eq!(root.fdid_count(), 1);
644 }
645}