rpgmad_lib/
lib.rs

1/*!
2# rpgm-archive-decrypter-lib
3
4Library for decrypting RPG Maker `rgss` archives.
5
6Used in [rpgm-archive-decrypter](https://github.com/savannstm/rpgm-archive-decrypter).
7
8## Example
9```no_run
10use rpgmad_lib::{Decrypter, decrypt_archive};
11use std::path::PathBuf;
12
13let archive_content: Vec<u8> = std::fs::read("C:/Game/Game.rgss3a").unwrap();
14
15// Using Decrypter struct
16let mut decrypter = Decrypter::new();
17let decrypted_files = decrypter.decrypt(&archive_content).unwrap();
18
19// Using function
20let decrypted_files = decrypt_archive(&archive_content).unwrap();
21
22for file in decrypted_files {
23    let path = String::from_utf8_lossy(&file.path);
24    let output_path = PathBuf::from("C:/Game").join(path.as_ref());
25
26    if let Some(parent) = output_path.parent() {
27        std::fs::create_dir_all(parent).unwrap();
28    }
29
30    std::fs::write(output_path, file.content).unwrap();
31}
32```
33
34## License
35
36Project is licensed under WTFPL.
37*/
38
39#[cfg(feature = "serde")]
40use serde::{Deserialize, Serialize};
41use std::io::SeekFrom;
42use strum_macros::{Display, EnumIs};
43use thiserror::Error;
44
45const ARCHIVE_HEADER: &[u8; 6] = b"RGSSAD";
46const OLDER_DEFAULT_KEY: u32 = 0xDEADCAFE;
47
48#[derive(Debug, Error)]
49#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
50pub enum ExtractError {
51    #[error(
52        "Invalid archive file header: {0:?}. Expected: RGSSAD ([82, 71, 83, 83, 65, 68])"
53    )]
54    InvalidHeader([u8; 6]),
55    #[error(
56        "Invalid game engine byte: {0}. Expected `1` for XP/VX or `3` for VX Ace."
57    )]
58    InvalidEngine(u8),
59}
60
61#[derive(Debug, Display, EnumIs)]
62enum EngineType {
63    #[strum(to_string = "XP/VX")]
64    Older,
65    #[strum(to_string = "VXAce")]
66    VXAce,
67}
68
69/// Struct representing decrypted file.
70///
71/// # Fields
72/// - `path` - Represents path to the decrypted file. For example, graphics files are stored in Graphics/DIR, e.g. Graphics/Actors/Actor1.png.
73///
74/// Note, that `path` is represented by `Vec<u8>` because it may contain non-UTF-8 sequences, e.g. Japanese Shift JIS text. In that case, it's up to you how to handle the path.
75///
76/// - `data` - Represents data of the file.
77pub struct DecryptedFile {
78    pub path: Vec<u8>,
79    pub content: Vec<u8>,
80}
81
82/// A struct responsible for decrypting and extracting files from encrypted game archives.
83pub struct Decrypter<'a> {
84    data: &'a [u8],
85    pos: usize,
86    len: usize,
87
88    engine_type: EngineType,
89    key: u32,
90    key_bytes: [u8; 4],
91}
92
93impl<'a> Decrypter<'a> {
94    /// Creates a new `Decrypter` with empty buffer.
95    pub fn new() -> Self {
96        Self {
97            data: &[],
98            pos: 0,
99            len: 0,
100
101            engine_type: EngineType::Older,
102            key: OLDER_DEFAULT_KEY,
103            key_bytes: OLDER_DEFAULT_KEY.to_le_bytes(),
104        }
105    }
106
107    #[inline]
108    fn update_key(&mut self, new_key: u32) {
109        self.key = new_key;
110        self.key_bytes = new_key.to_le_bytes();
111    }
112
113    #[inline]
114    fn read_bytes(&mut self, bytes: usize) -> &[u8] {
115        self.pos += bytes;
116        &self.data[self.pos - bytes..self.pos]
117    }
118
119    #[inline]
120    fn read_int(&mut self) -> i32 {
121        let chunk = self.read_bytes(4);
122        i32::from_le_bytes(unsafe { *(chunk.as_ptr() as *const [u8; 4]) })
123    }
124
125    #[inline]
126    fn read_byte(&mut self) -> u8 {
127        self.pos += 1;
128        self.data[self.pos - 1]
129    }
130
131    #[inline]
132    fn seek_byte(&mut self, from: SeekFrom) {
133        self.pos = match from {
134            SeekFrom::Start(offset) => offset as usize,
135            SeekFrom::Current(offset) => self.pos + offset as usize,
136            _ => unreachable!(),
137        };
138    }
139
140    #[inline]
141    fn decrypt_int(&mut self) -> i32 {
142        let int = self.read_int();
143        let result = int ^ self.key as i32;
144
145        if self.engine_type.is_older() {
146            self.update_key(self.key.wrapping_mul(7).wrapping_add(3));
147        }
148
149        result
150    }
151
152    #[inline]
153    fn decrypt_path(&mut self, path: &mut Vec<u8>, path_size: usize) {
154        let filename_bytes =
155            unsafe { &*(self.read_bytes(path_size) as *const [u8]) };
156
157        if self.engine_type.is_vx_ace() {
158            let mut key_byte_pos = 0;
159
160            for byte in filename_bytes {
161                if key_byte_pos == 4 {
162                    key_byte_pos = 0;
163                }
164
165                path.push(byte ^ self.key_bytes[key_byte_pos]);
166                key_byte_pos += 1;
167            }
168        } else {
169            for byte in filename_bytes {
170                path.push(byte ^ self.key as u8);
171                self.update_key(self.key.wrapping_mul(7).wrapping_add(3));
172            }
173        }
174    }
175
176    #[inline]
177    fn parse_header(&mut self) -> Result<(), ExtractError> {
178        let header = self.read_bytes(6);
179
180        if header != ARCHIVE_HEADER {
181            return Err(ExtractError::InvalidHeader(unsafe {
182                *(header.as_ptr() as *const [u8; 6])
183            }));
184        }
185
186        self.seek_byte(SeekFrom::Current(1));
187        let engine_type = self.read_byte();
188
189        self.engine_type = match engine_type {
190            1 => EngineType::Older,
191            3 => EngineType::VXAce,
192            _ => {
193                return Err(ExtractError::InvalidEngine(engine_type));
194            }
195        };
196
197        Ok(())
198    }
199
200    #[inline]
201    fn decrypt_entries(&mut self) -> Vec<DecryptedFile> {
202        if self.engine_type.is_vx_ace() {
203            // Default key is not ever used and overwritten.
204            let key = self.read_int() as u32;
205            self.update_key(key.wrapping_mul(9).wrapping_add(3));
206        }
207
208        let mut entries = Vec::with_capacity(16384);
209        let mut prev_pos: usize;
210
211        loop {
212            let (size, offset, mut key);
213            let mut path;
214
215            match self.engine_type {
216                EngineType::VXAce => {
217                    offset = self.decrypt_int() as u64;
218
219                    if offset == 0 {
220                        break;
221                    }
222
223                    size = self.decrypt_int();
224                    key = self.decrypt_int() as u32;
225
226                    let path_size = self.decrypt_int() as usize;
227                    path = Vec::with_capacity(path_size);
228                    self.decrypt_path(&mut path, path_size);
229                }
230                EngineType::Older => {
231                    if self.pos == self.len {
232                        break;
233                    }
234
235                    let path_size = self.decrypt_int() as usize;
236                    path = Vec::with_capacity(path_size);
237                    self.decrypt_path(&mut path, path_size);
238
239                    size = self.decrypt_int();
240                    offset = self.pos as u64;
241                    key = self.key;
242
243                    self.seek_byte(SeekFrom::Current(size as i64));
244                }
245            }
246
247            prev_pos = self.pos;
248
249            let mut key_bytes = key.to_le_bytes();
250            let mut key_byte_pos = 0;
251
252            self.seek_byte(SeekFrom::Start(offset));
253
254            let content = self.read_bytes(size as usize);
255            let mut decrypted = Vec::with_capacity(content.len());
256
257            for byte in content {
258                if key_byte_pos == 4 {
259                    key_byte_pos = 0;
260                    key = key.wrapping_mul(7).wrapping_add(3);
261                    key_bytes = key.to_le_bytes();
262                }
263
264                decrypted.push(byte ^ key_bytes[key_byte_pos]);
265                key_byte_pos += 1;
266            }
267
268            entries.push(DecryptedFile {
269                content: decrypted,
270                path,
271            });
272
273            self.seek_byte(SeekFrom::Start(prev_pos as u64));
274        }
275
276        entries
277    }
278
279    fn reset(&mut self, data: &'a [u8]) {
280        *self = Self::new();
281        self.data = data;
282        self.len = data.len();
283    }
284
285    /// Returns `Vec` of decrypted files.
286    ///
287    /// # Parameters
288    /// - `data`: The content of the archive file.
289    ///
290    /// # Returns
291    /// - `Ok(Vec<DecryptedFile>)` if files were successfully decrypted.
292    /// - `Err(ExtractError::InvalidHeader)` for invalid header.
293    /// - `Err(ExtractError::InvalidEngine)` for invalid header engine type byte.
294    ///
295    /// # Example
296    /// ```no_run
297    /// use rpgmad_lib::Decrypter;
298    /// use std::path::PathBuf;
299    ///
300    /// let data: Vec<u8> = std::fs::read("C:/Game/Game.rgss3a").unwrap();
301    /// let decrypted_files = Decrypter::new().decrypt(&data).unwrap();
302    ///
303    /// for file in decrypted_files {
304    ///     let path = String::from_utf8_lossy(&file.path);
305    ///     let output_path = PathBuf::from("C:/Game").join(path.as_ref());
306    ///
307    ///     if let Some(parent) = output_path.parent() {
308    ///         std::fs::create_dir_all(parent).unwrap();
309    ///     }
310    ///
311    ///     std::fs::write(output_path, file.content).unwrap();
312    /// }
313    /// ```
314    #[inline]
315    pub fn decrypt(
316        &mut self,
317        data: &'a [u8],
318    ) -> Result<Vec<DecryptedFile>, ExtractError> {
319        self.reset(data);
320        self.parse_header()?;
321        Ok(self.decrypt_entries())
322    }
323}
324
325impl<'a> Default for Decrypter<'a> {
326    /// Returns a new `Decrypter` with default parameters.
327    ///
328    /// Equivalent to calling `Decrypter::new()`.
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334/// A convenience function to decrypt an archive in a single call.
335///
336/// This is a wrapper around `Decrypter::decrypt` with automatic initialization.
337///
338/// # Parameters
339/// - `data`: The content of the archive file.
340///
341/// # Returns
342/// - `Ok(Vec<DecryptedFile>)` if files were successfully decrypted.
343/// - `Err(ExtractError::InvalidHeader)` for invalid header.
344/// - `Err(ExtractError::InvalidEngine)` for invalid header engine type byte.
345///
346/// # Example
347/// ```no_run
348/// use rpgmad_lib::decrypt_archive;
349/// use std::path::PathBuf;
350///
351///
352/// let data: Vec<u8> = std::fs::read("C:/Game/Game.rgss3a").unwrap();
353/// let decrypted_files = decrypt_archive(&data).unwrap();
354///
355/// for file in decrypted_files {
356///     let path = String::from_utf8_lossy(&file.path);
357///     let output_path = PathBuf::from("C:/Game").join(path.as_ref());
358///
359///     if let Some(parent) = output_path.parent() {
360///         std::fs::create_dir_all(parent).unwrap();
361///     }
362///
363///     std::fs::write(output_path, file.content).unwrap();
364/// }
365/// ```
366pub fn decrypt_archive(
367    data: &[u8],
368) -> Result<Vec<DecryptedFile>, ExtractError> {
369    Decrypter::new().decrypt(data)
370}