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}