smash_arc/
filesystem.rs

1use crate::{FileDataIdx, FileInfoIdx, FileInfoIndiceIdx, FilePathIdx, Hash40, InfoToDataIdx};
2use modular_bitfield::prelude::*;
3use std::convert::TryFrom;
4
5use binrw::{binread, io::*, BinRead, BinResult, Endian};
6
7#[derive(BinRead, Debug, Clone, Copy)]
8#[br(magic = 0x10_u32)]
9pub struct CompTableHeader {
10    pub decomp_size: u32,
11    pub comp_size: u32,
12    pub section_size: u32,
13}
14
15pub(crate) struct CompressedFileSystem(pub FileSystem);
16
17impl BinRead for CompressedFileSystem {
18    type Args<'a> = ();
19
20    fn read_options<R>(reader: &mut R, endian: Endian, args: Self::Args<'_>) -> BinResult<Self>
21    where
22        R: Read + Seek,
23    {
24        let current_offset = reader.stream_position().unwrap();
25        let header = CompTableHeader::read_options(reader, endian, args)?;
26
27        let mut compressed = vec![0; header.comp_size as usize];
28        reader.read_exact(&mut compressed)?;
29
30        reader.seek(SeekFrom::Start(current_offset + header.section_size as u64))?; // seek to end of section to continue reading properly
31
32        let compressed = Cursor::new(compressed);
33        let mut decompressed = Cursor::new(crate::zstd_backend::decode_all(compressed)?);
34
35        FileSystem::read_options(&mut decompressed, endian, ()).map(CompressedFileSystem)
36    }
37}
38
39pub(crate) struct CompressedSearchFileSystem(pub SearchFileSystem);
40
41impl BinRead for CompressedSearchFileSystem {
42    type Args<'a> = ();
43
44    fn read_options<R>(reader: &mut R, endian: Endian, args: Self::Args<'_>) -> BinResult<Self>
45    where
46        R: Read + Seek,
47    {
48        let current_offset = reader.stream_position().unwrap();
49        let header = CompTableHeader::read_options(reader, endian, args)?;
50
51        let mut compressed = vec![0; header.comp_size as usize];
52        reader.read_exact(&mut compressed)?;
53
54        reader.seek(SeekFrom::Start(current_offset + header.section_size as u64))?; // seek to end of section to continue reading properly
55
56        let compressed = Cursor::new(compressed);
57        let mut decompressed = Cursor::new(crate::zstd_backend::decode_all(compressed)?);
58
59        SearchFileSystem::read_options(&mut decompressed, endian, ())
60            .map(CompressedSearchFileSystem)
61    }
62
63}
64
65/// The filesystem itself. Includes all the linking between paths, file data, directories, and
66/// mass-loading groups.
67#[binread]
68#[derive(Debug)]
69pub struct FileSystem {
70    pub fs_header: FileSystemHeader,
71
72    #[br(align_before = 0x100)]
73    pub stream_header: StreamHeader,
74
75    #[br(count = stream_header.quick_dir_count)]
76    pub quick_dirs: Vec<QuickDir>,
77
78    #[br(count = stream_header.stream_hash_count)]
79    pub stream_hash_to_entries: Vec<HashToIndex>,
80
81    #[br(count = stream_header.stream_hash_count)]
82    pub stream_entries: Vec<StreamEntry>,
83
84    #[br(count = stream_header.stream_file_index_count)]
85    pub stream_file_indices: Vec<u32>,
86
87    #[br(count = stream_header.stream_offset_entry_count)]
88    pub stream_datas: Vec<StreamData>,
89
90    #[br(temp)]
91    pub hash_index_group_count: u32,
92
93    #[br(temp)]
94    pub bucket_count: u32,
95
96    #[br(count = bucket_count)]
97    pub file_info_buckets: Vec<FileInfoBucket>,
98
99    #[br(count = hash_index_group_count)]
100    pub file_hash_to_path_index: Vec<HashToIndex>,
101
102    #[br(count = fs_header.file_info_path_count)]
103    pub file_paths: Vec<FilePath>,
104
105    #[br(count = fs_header.file_info_index_count)]
106    pub file_info_indices: Vec<FileInfoIndex>,
107
108    #[br(count = fs_header.folder_count)]
109    pub dir_hash_to_info_index: Vec<HashToIndex>,
110
111    #[br(count = fs_header.folder_count)]
112    pub dir_infos: Vec<DirInfo>,
113
114    #[br(count = fs_header.folder_offset_count_1 + fs_header.folder_offset_count_2 + fs_header.extra_folder)]
115    pub folder_offsets: Vec<DirectoryOffset>,
116
117    #[br(count = fs_header.hash_folder_count)]
118    pub folder_child_hashes: Vec<HashToIndex>,
119
120    #[br(count = fs_header.file_info_count + fs_header.file_data_count_2 + fs_header.extra_count)]
121    pub file_infos: Vec<FileInfo>,
122
123    #[br(count = fs_header.file_info_sub_index_count + fs_header.file_data_count_2 + fs_header.extra_count_2)]
124    pub file_info_to_datas: Vec<FileInfoToFileData>,
125
126    #[br(count = fs_header.file_data_count + fs_header.file_data_count_2 + fs_header.extra_sub_count)]
127    pub file_datas: Vec<FileData>,
128}
129
130#[binread]
131#[derive(Debug)]
132pub struct SearchFileSystem {
133    pub header: SearchFileSystemHeader,
134
135    #[br(count = header.folder_count)]
136    pub folder_lookup: Vec<HashToIndex>,
137
138    #[br(count = header.folder_count)]
139    pub folders: Vec<FolderPathListEntry>,
140
141    #[br(count = header.path_index_count)]
142    pub path_index_lookup: Vec<HashToIndex>,
143
144    #[br(count = header.path_index_count)]
145    pub path_indices: Vec<u32>,
146
147    #[br(count = header.path_count)]
148    pub paths: Vec<PathListEntry>,
149}
150
151#[repr(C)]
152#[derive(BinRead, Debug, Clone, Copy)]
153pub struct FileSystemHeader {
154    pub table_filesize: u32,
155    pub file_info_path_count: u32,
156    pub file_info_index_count: u32,
157    pub folder_count: u32,
158
159    pub folder_offset_count_1: u32,
160
161    pub hash_folder_count: u32,
162    pub file_info_count: u32,
163    pub file_info_sub_index_count: u32,
164    pub file_data_count: u32,
165
166    pub folder_offset_count_2: u32,
167    pub file_data_count_2: u32,
168    pub padding: u32,
169
170    pub unk1_10: u32, // always 0x10
171    pub unk2_10: u32, // always 0x10
172
173    pub regional_count_1: u8,
174    pub regional_count_2: u8,
175    pub padding2: u16,
176
177    pub version: u32,
178    pub extra_folder: u32,
179    pub extra_count: u32,
180
181    pub unk: [u32; 2], // in the loaded arc this is a pointer to the parsed search data
182
183    pub extra_count_2: u32,
184    pub extra_sub_count: u32,
185}
186
187#[repr(C)]
188#[derive(BinRead, Debug, Copy, Clone)]
189pub struct SearchFileSystemHeader {
190    pub size: u64,
191    pub folder_count: u32,
192    pub path_index_count: u32,
193    pub path_count: u32,
194}
195
196#[repr(C)]
197#[derive(BinRead, Debug)]
198pub struct StreamHeader {
199    pub quick_dir_count: u32,
200    pub stream_hash_count: u32,
201    pub stream_file_index_count: u32,
202    pub stream_offset_entry_count: u32,
203}
204
205#[bitfield]
206#[derive(BinRead, Debug, Clone, Copy)]
207#[br(map = Self::from_bytes)]
208pub struct QuickDir {
209    pub hash: u32,
210    pub name_length: u8,
211    pub count: B24,
212    pub index: u32,
213}
214
215#[repr(C)]
216#[derive(BinRead, Debug, Clone, Copy)]
217pub struct StreamEntry {
218    pub path: HashToIndex,
219    pub flags: StreamEntryFlags,
220}
221
222#[bitfield]
223#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Hash)]
224#[br(map = Self::from_bytes)]
225pub struct StreamEntryFlags {
226    pub is_regional: bool,
227    pub is_localized: bool,
228    pub unused: B30,
229}
230
231#[bitfield]
232#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
233#[br(map = Self::from_bytes)]
234pub struct HashToIndex {
235    pub hash: u32,
236    pub length: u8,
237    pub index: B24,
238}
239
240impl PartialEq<Hash40> for HashToIndex {
241    fn eq(&self, other: &Hash40) -> bool {
242        self.hash40() == *other
243    }
244}
245
246#[repr(C)]
247#[derive(BinRead, Debug, Clone, Copy)]
248pub struct FileInfoBucket {
249    pub start: u32,
250    pub count: u32,
251}
252#[repr(C)]
253#[derive(BinRead, Debug, Clone, Copy)]
254pub struct FilePath {
255    pub path: HashToIndex,
256    pub ext: HashToIndex,
257    pub parent: HashToIndex,
258    pub file_name: HashToIndex,
259}
260#[repr(C)]
261#[derive(BinRead, Debug, Clone, Copy)]
262pub struct FileInfoIndex {
263    pub dir_offset_index: u32,
264    pub file_info_index: FileInfoIdx,
265}
266
267/// Also known as MassLoadingGroup
268#[repr(C)]
269#[cfg_attr(feature = "smash-runtime", repr(packed))]
270#[derive(BinRead, Debug, Clone, Copy)]
271pub struct DirInfo {
272    pub path: HashToIndex,
273    pub name: Hash40,
274    pub parent: Hash40,
275    pub extra_dis_re: u32,
276    pub extra_dis_re_length: u32,
277    pub file_info_start_index: u32,
278    pub file_count: u32,
279    pub child_dir_start_index: u32,
280    pub child_dir_count: u32,
281    pub flags: DirInfoFlags,
282}
283
284#[repr(C)]
285#[derive(Debug, Clone)]
286pub enum RedirectionType {
287    Symlink(DirInfo),
288    Shared(DirectoryOffset),
289}
290
291#[bitfield]
292#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Hash)]
293#[br(map = Self::from_bytes)]
294pub struct DirInfoFlags {
295    pub unk1: B26,
296    pub redirected: bool,
297    pub unk2: bool,
298    pub is_symlink: bool,
299    pub unk3: B3,
300}
301
302#[repr(C)]
303#[derive(BinRead, Debug, Clone, Copy)]
304pub struct StreamData {
305    pub size: u64,
306    pub offset: u64,
307}
308
309/// Also known as MassLoadingData
310#[repr(packed)]
311#[derive(BinRead, Debug, Clone, Copy)]
312pub struct DirectoryOffset {
313    pub offset: u64,
314    pub decomp_size: u32,
315    pub size: u32,
316    /// FileData index if using DirInfo.path.index(), FileInfo if redirected from a DirectoryOffset.directory_index
317    pub file_start_index: u32,
318    pub file_count: u32,
319    /// This can be a DirInfo OR a DirectoryOffset index, depending on the flags of the matching DirInfo. Considering checking the tests in lookups.rs for an example.
320    pub directory_index: u32,
321}
322
323#[repr(C)]
324#[derive(BinRead, Debug, Clone, Copy)]
325pub struct FileInfo {
326    pub file_path_index: FilePathIdx,
327    pub file_info_indice_index: FileInfoIndiceIdx,
328    // SubIndexIndex
329    pub info_to_data_index: InfoToDataIdx,
330    // Flags
331    pub flags: FileInfoFlags,
332}
333
334#[bitfield]
335#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Hash)]
336#[br(map = Self::from_bytes)]
337pub struct FileInfoFlags {
338    pub unused: B4,
339    pub is_redirect: bool,
340    pub unused2: B7,
341    pub unknown1: bool,
342    pub padding3: B2,
343    pub is_regional: bool,
344    pub is_localized: bool,
345    pub unused3: B3,
346    pub unknown2: bool,
347    pub unknown3: bool,
348    pub unused4: B10,
349}
350
351#[repr(C)]
352#[derive(BinRead, Debug, Clone, Copy)]
353pub struct FileInfoToFileData {
354    pub folder_offset_index: u32,
355    pub file_data_index: FileDataIdx,
356    pub file_info_index_and_load_type: FileInfoToFileDataBitfield,
357}
358
359#[bitfield]
360#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Hash)]
361#[br(map = Self::from_bytes)]
362pub struct FileInfoToFileDataBitfield {
363    pub file_info_idx: B24,
364    pub load_type: u8,
365}
366
367#[repr(C)]
368#[derive(BinRead, Debug, Clone, Copy)]
369pub struct FileData {
370    pub offset_in_folder: u32,
371    pub comp_size: u32,
372    pub decomp_size: u32,
373    pub flags: FileDataFlags,
374}
375
376#[bitfield]
377#[derive(BinRead, Debug, Clone, Copy, PartialEq, Eq, Hash)]
378#[br(map = Self::from_bytes)]
379pub struct FileDataFlags {
380    pub compressed: bool,
381    pub use_zstd: bool,
382    pub unk: B30,
383}
384
385#[repr(C)]
386#[derive(BinRead, Debug, Copy, Clone)]
387pub struct SearchListEntry {
388    pub path: HashToIndex,
389    pub parent: HashToIndex,
390    pub file_name: HashToIndex,
391    pub ext: HashToIndex,
392}
393
394#[repr(transparent)]
395#[derive(BinRead, Debug, Copy, Clone)]
396pub struct PathListEntry(pub SearchListEntry);
397
398#[repr(transparent)]
399#[derive(BinRead, Debug, Copy, Clone)]
400pub struct FolderPathListEntry(pub SearchListEntry);
401
402macro_rules! impl_fs_index {
403    ($to_index:ty, $index_with:ty) => {
404        impl std::ops::Index<$index_with> for [$to_index] {
405            type Output = $to_index;
406            fn index(&self, index: $index_with) -> &Self::Output {
407                self.get(usize::from(index)).unwrap()
408            }
409        }
410
411        impl std::ops::IndexMut<$index_with> for [$to_index] {
412            fn index_mut(&mut self, index: $index_with) -> &mut Self::Output {
413                self.get_mut(usize::from(index)).unwrap()
414            }
415        }
416    };
417}
418
419impl_fs_index!(FilePath, FilePathIdx);
420impl_fs_index!(FileInfoIndex, FileInfoIndiceIdx);
421impl_fs_index!(FileInfo, FileInfoIdx);
422impl_fs_index!(FileInfoToFileData, InfoToDataIdx);
423impl_fs_index!(FileData, FileDataIdx);
424
425use std::ops::{Deref, DerefMut};
426
427impl Deref for PathListEntry {
428    type Target = SearchListEntry;
429
430    fn deref(&self) -> &Self::Target {
431        &self.0
432    }
433}
434
435impl DerefMut for PathListEntry {
436    fn deref_mut(&mut self) -> &mut Self::Target {
437        &mut self.0
438    }
439}
440
441impl Deref for FolderPathListEntry {
442    type Target = SearchListEntry;
443
444    fn deref(&self) -> &Self::Target {
445        &self.0
446    }
447}
448
449impl DerefMut for FolderPathListEntry {
450    fn deref_mut(&mut self) -> &mut Self::Target {
451        &mut self.0
452    }
453}
454
455impl PathListEntry {
456    pub fn is_directory(&self) -> bool {
457        self.parent.index() & 0x40_0000 != 0
458    }
459
460    pub fn as_folder_entry(&self) -> FolderPathListEntry {
461        let mut file_path = self.path;
462        file_path.set_index(0xFF_FFFF);
463        let mut parent = self.parent;
464        parent.set_index(0x40_0000);
465        let mut ext = self.ext;
466        ext.set_hash(0xFFFF_FFFF);
467        ext.set_length(0x00);
468        ext.set_index(0x00);
469
470        FolderPathListEntry(SearchListEntry {
471            path: file_path,
472            parent,
473            ext,
474            file_name: self.file_name,
475        })
476    }
477}
478
479impl FolderPathListEntry {
480    pub fn get_child_count(&self) -> usize {
481        self.parent.index() as usize
482    }
483
484    pub fn get_first_child_index(&self) -> usize {
485        (self.ext.hash40().as_u64() & 0xFF_FFFF) as usize
486    }
487
488    pub fn as_path_entry(&self) -> PathListEntry {
489        let mut file_path = self.path;
490        file_path.set_index(0xFF_FFFF);
491        let mut parent = self.parent;
492        parent.set_index(0x40_0000);
493        let mut ext = self.ext;
494        ext.set_hash(0x0);
495        ext.set_length(0x0);
496        ext.set_index(0x0);
497        PathListEntry(SearchListEntry {
498            path: file_path,
499            parent,
500            ext,
501            file_name: self.file_name,
502        })
503    }
504
505    pub fn set_first_child_index(&mut self, idx: u32) {
506        self.ext.set_hash(idx & 0xFF_FFFF)
507    }
508}