fs3ds 1.0.0

a library to access romfs of unencrypted .3ds files
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
use std::error::Error;
use std::fmt;
use std::io;

use std::io::SeekFrom;
use std::io::{Read, Seek};
use std::string::FromUtf16Error;
use std::sync::Arc;
use std::sync::Mutex;

#[derive(Debug)]
pub enum IVFCError {
    ReadError(io::Error, &'static str),
    SeekError(io::Error, &'static str),
    FirstMagicError([u8; 4]),
    SecondMagicError([u8; 4]),
    Level3HeaderLenghtInvalid(u32),
    UTF16LenghtNonMultiple2(&'static str, u32), // what, lenght
    ToUTF16Error(FromUtf16Error, &'static str),
    DirNotFound,
    FileNotFound,
    Poisoned,
}

impl Error for IVFCError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::ReadError(err, _) => Some(err),
            Self::SeekError(err, _) => Some(err),
            Self::ToUTF16Error(err, _) => Some(err),
            _ => None,
        }
    }
}

impl fmt::Display for IVFCError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ReadError(_err, what_failed) => write!(
                f,
                "failed to read the {} due to an error in the source input",
                what_failed
            ),
            Self::SeekError(_err, what_failed) => write!(
                f,
                "failed to seek to the \"{}\" due to an error in the source input",
                what_failed
            ),
            Self::FirstMagicError(first_magic) => write!(
                f,
                "failed to read the first magic. Found {:?}, expected  [73, 86, 70, 67].",
                first_magic
            ),
            Self::SecondMagicError(second_magic) => write!(
                f,
                "failed to read the second magic. Found {:?}, expected [0, 0, 0, 0].",
                second_magic
            ),
            Self::Level3HeaderLenghtInvalid(level_3_header_lenght) => write!(
                f,
                "the lenght of the header of the level 3 is not good. Found the lenght {}, expected 0x28.",
                level_3_header_lenght
            ),
            Self::UTF16LenghtNonMultiple2(what, lenght) => write!(
                f,
                "the lenght of a UTF16 string is not a multiple of 2. This error is found while decoding the {}. The lenght is {}.",
                what, lenght
            ),
            Self::DirNotFound => write!(
                f,
                "the directory is not found in the hiearchy"
            ),
            Self::FileNotFound => write!(
                f,
                "the file is not found in the hiearchy"
            ),
            Self::Poisoned => write!(
                f,
                "the Mutex is poisoned"
            ),
            Self::ToUTF16Error(_, what) => write!(
                f,
                "Impossible to convert \"{}\" to an UTF16 String",
                what
            )
        }
    }
}

#[allow(non_snake_case)]
fn IVFC_read_u32<T: Read>(file: &mut T, what: &'static str) -> Result<u32, IVFCError> {
    let mut buffer = [0; 4];
    match file.read_exact(&mut buffer) {
        Ok(_) => (),
        Err(err) => return Err(IVFCError::ReadError(err, what)),
    };
    Ok(u32::from_le_bytes(buffer))
}

#[allow(non_snake_case)]
fn IVFC_read_u64<T: Read>(file: &mut T, what: &'static str) -> Result<u64, IVFCError> {
    let mut buffer = [0; 8];
    match file.read_exact(&mut buffer) {
        Ok(_) => (),
        Err(err) => return Err(IVFCError::ReadError(err, what)),
    };
    Ok(u64::from_le_bytes(buffer))
}

#[allow(non_snake_case)]
fn IVFC_read_utf_16<T: Read>(
    file: &mut T,
    lenght: u32,
    what: &'static str,
) -> Result<String, IVFCError> {
    if lenght % 2 != 0 {
        return Err(IVFCError::UTF16LenghtNonMultiple2(what, lenght));
    };

    let mut string_numbered = Vec::new();
    for _ in 0..lenght / 2 {
        let mut buffer = [0; 2];
        match file.read_exact(&mut buffer) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::ReadError(err, what)),
        };
        string_numbered.push(u16::from_le_bytes(buffer));
    }
    match String::from_utf16(&string_numbered) {
        Ok(value) => Ok(value),
        Err(err) => Err(IVFCError::ToUTF16Error(err, what)),
    }
}

#[derive(Clone, Debug)]
pub enum DirectoryOrFile {
    Dir(DirectoryMetadata),
    File(FileMetadata),
}

#[derive(Debug, Clone)]
pub struct DirectoryMetadata {
    pub offset_parent: Option<u32>,
    pub offset_next_sibling: Option<u32>,
    pub offset_first_subdir: Option<u32>,
    pub offset_first_file: Option<u32>,
    pub name: Option<String>,
}

impl DirectoryMetadata {
    pub fn new<T: Read + Seek>(
        file: &mut T,
        is_root: bool,
    ) -> Result<DirectoryMetadata, IVFCError> {
        let offset_parent = Some(IVFC_read_u32(
            file,
            "offset of the parent directory in a directory metadata",
        )?);
        let offset_next_sibling = match IVFC_read_u32(
            file,
            "offset of the next sibling directory in a directory metadata",
        )? {
            0xFFFF_FFFF => None,
            value => Some(value),
        };
        let offset_first_subdir = match IVFC_read_u32(
            file,
            "offset of the first subdirectory in a directory metadata",
        )? {
            0xFFFF_FFFF => None,
            value => Some(value),
        };
        let offset_first_file =
            match IVFC_read_u32(file, "offset of the first file in a directory metadata")? {
                0xFFFF_FFFF => None,
                value => Some(value),
            };
        let _ = IVFC_read_u32(
            file,
            "offset of the next directory in the same hash table in a directory metadata",
        )?;

        let name;
        if !is_root {
            let name_lenght = IVFC_read_u32(file, "lenght of the name of a directory")?;
            //let physical_name_lenght = (((name_lenght as f32)/4.0).ceil()*4.0+0.01) as u32;
            name = Some(IVFC_read_utf_16(file, name_lenght, "directory name")?);
        } else {
            name = None;
        };
        Ok(DirectoryMetadata {
            offset_parent,
            offset_next_sibling,
            offset_first_subdir,
            offset_first_file,
            name,
        })
    }
}

#[derive(Debug, Clone)]
pub struct FileMetadata {
    pub offset_parent: u32,
    pub offset_sibling: Option<u32>,
    pub offset_file_data: u64,
    pub lenght_file_data: u64,
    pub name: String,
}

impl FileMetadata {
    fn new<T: Read + Seek>(file: &mut T) -> Result<FileMetadata, IVFCError> {
        let offset_parent = IVFC_read_u32(file, "an offset of the parent of a file metadata")?;
        let offset_sibling = match IVFC_read_u32(file, "an offset the sibling of a file metadata")?
        {
            0xFFFF_FFFF => None,
            offset => Some(offset),
        };
        let offset_file_data = IVFC_read_u64(file, "the offset of a file in a file metadata")?;
        let lenght_file_data = IVFC_read_u64(file, "the lenght of a file in a file metadata")?;
        let _ = IVFC_read_u32(
            file,
            "the offset of the next file in it's Hash Table Bucket in a file metadata",
        )?;
        let name_lenght = IVFC_read_u32(file, "the lenght of a name of a file")?;
        let name = IVFC_read_utf_16(file, name_lenght, "file name")?;
        Ok(FileMetadata {
            offset_parent,
            offset_sibling,
            offset_file_data,
            lenght_file_data,
            name,
        })
    }
}

#[derive(Debug)]
pub struct IVFCReader<T: Read + Seek> {
    pub file: Arc<Mutex<T>>,
    pub dir_metadata_part_offset: u32,
    pub file_metadata_part_offset: u32,
    pub first_dir_metadata: DirectoryMetadata,
    pub file_data_offset: u32,
}

impl<T: Read + Seek> IVFCReader<T> {
    pub fn new(mut file: T) -> Result<IVFCReader<T>, IVFCError> {
        // magic "IVFC"
        let mut magic_1 = [0; 4];
        match file.read_exact(&mut magic_1) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::ReadError(err, "magic \"IVFC\"")),
        };

        if magic_1 != [73, 86, 70, 67] {
            return Err(IVFCError::FirstMagicError(magic_1));
        };

        // magic 0x10000
        let mut magic_2 = [0; 4];
        match file.read_exact(&mut magic_2) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::ReadError(err, "magic 0x00010000")),
        };

        if magic_2 != [0, 0, 1, 0] {
            return Err(IVFCError::SecondMagicError(magic_2));
        };
        // seek to the table 3

        let offset_table_3 = 4096;

        match file.seek(SeekFrom::Start(offset_table_3 as u64)) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::SeekError(err, "level 3")),
        };

        // check we are in the good section

        let level_3_header_lenght = IVFC_read_u32(&mut file, "level 3 header lenght")?;

        if level_3_header_lenght != 0x28 {
            return Err(IVFCError::Level3HeaderLenghtInvalid(level_3_header_lenght));
        };

        // read header information

        let _relative_offset_dir_hashdata =
            IVFC_read_u32(&mut file, "offset of the directory hashdata")?;
        let _dir_hashdata_lenght = IVFC_read_u32(&mut file, "lenght of the directory hashdata")?;

        let relative_offset_dir_metadata =
            IVFC_read_u32(&mut file, "offset of the directory metadata")?;
        let _dir_metadata_lenght = IVFC_read_u32(&mut file, "lenght of the directory metadata")?;
        let dir_metadata_part_offset = offset_table_3 + relative_offset_dir_metadata;

        let _relative_offset_file_hashdata =
            IVFC_read_u32(&mut file, "offset of the file hashdata")?;
        let _file_hashdata_lenght = IVFC_read_u32(&mut file, "lenght of the file hashdata")?;

        let relative_offset_file_metadata =
            IVFC_read_u32(&mut file, "offset of the file metadata")?;

        let file_metadata_part_offset = offset_table_3 + relative_offset_file_metadata;

        let _lenght_file_metadata = IVFC_read_u32(&mut file, "lenght of the file metadata")?;
        let file_data_offset = IVFC_read_u32(&mut file, "file data offset")? + offset_table_3;

        // Seek to root directory
        match file.seek(SeekFrom::Start((dir_metadata_part_offset) as u64)) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::SeekError(err, "first directory metadata")),
        };

        // parse it
        let first_dir_metadata = DirectoryMetadata::new(&mut file, true)?;

        Ok(IVFCReader {
            file: Arc::new(Mutex::new(file)),
            dir_metadata_part_offset,
            file_metadata_part_offset,
            first_dir_metadata,
            file_data_offset,
        })
    }

    /// Return a child by it's name. It may either be a folder or a file
    pub fn get_child(
        &self,
        dir: &DirectoryMetadata,
        path: &str,
    ) -> Result<DirectoryOrFile, IVFCError> {
        let mut file = match self.file.lock() {
            Ok(guard) => guard,
            Err(_err) => return Err(IVFCError::Poisoned),
        };
        // check for folder
        match file.seek(SeekFrom::Start(match dir.offset_first_subdir {
            Some(value) => (value + self.dir_metadata_part_offset) as u64,
            None => return Err(IVFCError::DirNotFound),
        })) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::SeekError(err, "a directory metadata")),
        };
        let mut actual_subdir = DirectoryMetadata::new(&mut *file, false)?;
        loop {
            if actual_subdir.name.as_ref().unwrap() == path {
                return Ok(DirectoryOrFile::Dir(actual_subdir));
            };
            //get the next one
            let offset_to_seek = match actual_subdir.offset_next_sibling {
                Some(value) => (value + self.dir_metadata_part_offset) as u64,
                None => break,
            };
            match file.seek(SeekFrom::Start(offset_to_seek)) {
                Ok(_) => (),
                Err(err) => return Err(IVFCError::SeekError(err, "a directory metadata")),
            };
            actual_subdir = DirectoryMetadata::new(&mut *file, false)?;
        }
        //check for file
        // get the first sub-file
        match file.seek(SeekFrom::Start(match dir.offset_first_file {
            Some(value) => (value + self.file_metadata_part_offset) as u64,
            None => return Err(IVFCError::FileNotFound),
        })) {
            Ok(_) => (),
            Err(err) => return Err(IVFCError::SeekError(err, "a file metadata")),
        };
        let mut actual_file = FileMetadata::new(&mut *file)?;
        loop {
            if actual_file.name == path {
                return Ok(DirectoryOrFile::File(actual_file));
            };
            let offset_to_seek = match actual_file.offset_sibling {
                Some(value) => (value + self.file_metadata_part_offset) as u64,
                None => break,
            };
            match file.seek(SeekFrom::Start(offset_to_seek)) {
                Ok(_) => (),
                Err(err) => return Err(IVFCError::SeekError(err, "a file metadata")),
            };
            actual_file = FileMetadata::new(&mut *file)?;
        }
        Err(IVFCError::FileNotFound)
    }

    pub fn list_file_child(
        &self,
        dir: &DirectoryMetadata,
        childs: &mut Vec<String>,
    ) -> Result<(), IVFCError> {
        let mut file = match self.file.lock() {
            Ok(file) => file,
            Err(_) => return Err(IVFCError::Poisoned),
        };

        let first_child_offset = match dir.offset_first_file {
            Some(value) => value as u64,
            None => return Ok(()),
        } + self.file_metadata_part_offset as u64;

        match file.seek(SeekFrom::Start(first_child_offset)) {
            Ok(_) => (),
            Err(err) => {
                return Err(IVFCError::SeekError(
                    err,
                    "a file metadata for listing child files",
                ))
            }
        };

        let mut actual_file_metadata = FileMetadata::new(&mut *file)?;

        loop {
            childs.push(actual_file_metadata.name.clone());

            let sibling_file_offset = match actual_file_metadata.offset_sibling {
                Some(value) => value as u64,
                None => return Ok(()),
            } + self.file_metadata_part_offset as u64;

            match file.seek(SeekFrom::Start(sibling_file_offset)) {
                Ok(_) => (),
                Err(err) => {
                    return Err(IVFCError::SeekError(
                        err,
                        "a file metadata for listing child files on not the first loop",
                    ))
                }
            };

            actual_file_metadata = FileMetadata::new(&mut *file)?;
        }
    }

    pub fn list_dir_child(
        &self,
        dir: &DirectoryMetadata,
        childs: &mut Vec<String>,
    ) -> Result<(), IVFCError> {
        let mut file = match self.file.lock() {
            Ok(file) => file,
            Err(_) => return Err(IVFCError::Poisoned),
        };

        let first_dir_offset = match dir.offset_first_subdir {
            Some(value) => value as u64,
            None => return Ok(()),
        } + self.dir_metadata_part_offset as u64;

        match file.seek(SeekFrom::Start(first_dir_offset)) {
            Ok(_) => (),
            Err(err) => {
                return Err(IVFCError::SeekError(
                    err,
                    "a file directory for listing child files",
                ))
            }
        };

        let mut actual_dir_metadata = DirectoryMetadata::new(&mut *file, false)?;

        loop {
            childs.push(actual_dir_metadata.name.unwrap().clone());

            let sibling_dir_offset = match actual_dir_metadata.offset_next_sibling {
                Some(value) => value as u64,
                None => return Ok(()),
            } + self.dir_metadata_part_offset as u64;

            match file.seek(SeekFrom::Start(sibling_dir_offset)) {
                Ok(_) => (),
                Err(err) => {
                    return Err(IVFCError::SeekError(
                        err,
                        "a directory metadata for listing child directory on not the first loop",
                    ))
                }
            };

            actual_dir_metadata = DirectoryMetadata::new(&mut *file, false)?;
        }
    }

    pub fn list_child(&self, dir: &DirectoryMetadata) -> Result<Vec<String>, IVFCError> {
        let mut childs = Vec::new();
        self.list_file_child(dir, &mut childs)?;
        self.list_dir_child(dir, &mut childs)?;
        Ok(childs)
    }

    pub fn get_file_real_offset(&self, file: &FileMetadata) -> u64 {
        file.offset_file_data + self.file_data_offset as u64
    }
}