festy/
file.rs

1use std::io::Cursor;
2use byteorder::{LittleEndian, ReadBytesExt};
3use crate::{
4    crc32::crc32,
5    huffman,
6};
7
8/// Decompresses a file in the specified format.
9///
10/// This function takes in a vector of bytes representing the file buffer,
11/// which is the entire contents of the file in memory. It then iterates over
12/// the file buffer to find the sector type, and returns an error if it is not found.
13///
14/// The function then reads the expected length and CRC value of the decompressed data
15/// from the file buffer, and uses a Huffman decoding function to decrypt the Huffman set
16/// in the file buffer. It checks that the decrypted length matches the expected length,
17/// and returns an error if these values do not match.
18///
19/// The function then calculates the CRC value of the file buffer and the decrypted data,
20/// and compares it to the expected value. If these values do not match, the function
21/// returns an error. Finally, if all checks pass, the function returns the concatenation
22/// of the file buffer and the decrypted data as a `Vec<u8>`.
23///
24/// # Arguments
25///
26/// * `file_buffer` - A `Vec<u8>` representing the file buffer.
27///
28/// # Returns
29///
30/// * If the sector type is not found, or if the decrypted length and expected length do not
31///   match, or if the calculated CRC value and expected CRC value do not match, the function
32///   returns an error as a `String`.
33/// * Otherwise, the function returns the concatenation of the file buffer and the decrypted
34///   data as a `Vec<u8>`.
35pub fn decompress(file_buffer: Vec<u8>) -> Result<Vec<u8>, String> {
36    // Create a new cursor that reads from the file buffer
37    let mut cursor = Cursor::new(&file_buffer);
38
39    // Initialize the offset and sector type variables
40    let mut offset = 0;
41    let mut sector_type = 0;
42
43    // Iterate over the file buffer until the end of the file is reached,
44    // or the maximum offset is reached, or the sector type is found
45    while offset <= 1024 && offset+3 < file_buffer.len() {
46        // Read a 4-byte word from the file buffer using the cursor
47        let word = cursor.read_u32::<LittleEndian>().unwrap();
48
49        // If the current word is 0x434f4d50, then set the sector type
50        if word == 0x434f4d50 {
51            sector_type = word;
52            break;
53        }
54
55        // Increment the offset and move the cursor to the new position
56        offset += 64;
57        cursor.set_position(offset as u64);
58    }
59
60    // If the sector type was not found, return an error
61    if sector_type == 0 {
62        return Err("not a compressed 3ds file".to_owned())
63    }
64
65    // Move the cursor to the expected length and read it
66    cursor.set_position(offset as u64 + 8);
67    let expected_length = cursor.read_u32::<LittleEndian>().unwrap() as usize;
68
69    // Read the expected CRC value
70    let expected_crc = cursor.read_u32::<LittleEndian>().unwrap();
71
72    // Get the starting position of the Huffman set
73    let huff_set_start = cursor.position() as usize;
74
75    // Check if the next byte is 0x28, and return an error if it is not
76    if cursor.read_u8().unwrap() != 0x28 {
77        return Err("unkown compresssion format".to_owned())
78    }
79
80    // Decrypt the Huffman set
81    let decrypted = huffman::decode(&file_buffer[huff_set_start..]).unwrap();
82
83    // Check if the decrypted length matches the expected length, and return an error if it does not
84    if decrypted.len() != expected_length {
85        return Err("unexpected length, something defeinitely went wrong".to_owned())
86    }
87
88    // Get the header from the file buffer
89    let header = &file_buffer[..offset];
90
91    // Calculate the CRC value of the header and the decrypted data
92    let crc = !crc32(crc32(!0, header), &decrypted);
93
94    // Check if the calculated CRC value matches the expected value, and return an error if it does not
95    if crc != expected_crc {
96        return Err("unexpected crc".to_owned())
97    }
98
99    // Return the concatenation of the header and decrypted data
100    return Ok([header, &decrypted].concat());
101}