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))?; 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))?; 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#[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, pub unk2_10: u32, 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], 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#[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#[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 pub file_start_index: u32,
318 pub file_count: u32,
319 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 pub info_to_data_index: InfoToDataIdx,
330 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}