rpgmad_lib/
lib.rs

1#![warn(clippy::all, clippy::pedantic)]
2#![allow(clippy::needless_doctest_main)]
3#![allow(clippy::cast_possible_truncation)]
4#![allow(clippy::cast_possible_wrap)]
5#![allow(clippy::cast_sign_loss)]
6#![allow(clippy::deref_addrof)]
7#![doc = include_str!("../README.md")]
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11use std::{borrow::Cow, io::SeekFrom};
12use strum_macros::{Display, EnumIs};
13use thiserror::Error;
14
15macro_rules! sizeof {
16    ($t:ty) => {{ size_of::<$t>() }};
17}
18
19const ARCHIVE_HEADER: &[u8; 7] = b"RGSSAD\0";
20
21const OLDER_DECRYPTION_KEY: u32 = 0xDEAD_CAFE;
22const ENCRYPTION_KEY: u32 = 0;
23
24// Archives probably may contains more entries, but even the largest games will have less or around 16384.
25// We allocate the memory in a vector, so if this is not enough vector will reallocate to twice the size anyways.
26const MAX_ENTRY_AMOUNT: usize = 16384;
27
28pub const XP_RGSSAD_EXT: &str = "rgssad";
29pub const VX_RGSS2A_EXT: &str = "rgss2a";
30pub const VXACE_RGSS3A_EXT: &str = "rgss3a";
31
32#[derive(Debug, Error)]
33#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
34pub enum ExtractError {
35    #[error(
36        "Invalid archive file header: {0:?}. Expected: RGSSAD␀ ([82, 71, 83, 83, 65, 68, 0])"
37    )]
38    InvalidHeader([u8; 7]),
39    #[error(
40        "Invalid game engine byte: {0}. Expected `1` for XP/VX or `3` for VX Ace."
41    )]
42    InvalidEngine(u8),
43}
44
45#[derive(PartialEq, Debug, Display, EnumIs, Clone, Copy)]
46pub enum Engine {
47    #[strum(to_string = "XP/VX")]
48    Older = 1,
49    #[strum(to_string = "VXAce")]
50    VXAce = 3,
51}
52
53/// Struct representing decrypted file.
54///
55/// # Fields
56/// - `path` - Represents path to the decrypted file. For example, graphics files are stored in Graphics/DIR, e.g. Graphics/Actors/Actor1.png.
57///
58/// 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.
59///
60/// - `data` - Represents content of the file.
61pub struct ArchiveEntry {
62    pub path: Cow<'static, [u8]>,
63    pub data: Vec<u8>,
64}
65
66/// A struct responsible for decrypting and extracting files from encrypted game archives.
67pub struct Decrypter<'a> {
68    engine: Engine,
69    key: u32,
70    key_bytes: [u8; sizeof!(u32)],
71
72    // Decryption members
73    data: &'a [u8],
74    pos: usize,
75    len: usize,
76}
77
78impl<'a> Decrypter<'a> {
79    /// Creates a new [`Decrypter`] with empty buffer.
80    #[must_use]
81    pub fn new() -> Self {
82        Self {
83            engine: Engine::Older,
84            key: OLDER_DECRYPTION_KEY,
85            key_bytes: OLDER_DECRYPTION_KEY.to_le_bytes(),
86
87            data: &[],
88            pos: 0,
89            len: 0,
90        }
91    }
92
93    #[inline]
94    fn update_key(&mut self, new_key: u32) {
95        self.key = new_key;
96        self.key_bytes = new_key.to_le_bytes();
97    }
98
99    #[inline]
100    fn update_key_older(&mut self) {
101        self.update_key(self.key.wrapping_mul(7).wrapping_add(3));
102    }
103
104    #[inline]
105    fn update_key_vxace(&mut self) {
106        self.update_key(self.key.wrapping_mul(9).wrapping_add(3));
107    }
108
109    #[inline]
110    fn read_bytes(&mut self, count: usize) -> &[u8] {
111        self.pos += count;
112        &self.data[self.pos - count..self.pos]
113    }
114
115    #[inline]
116    fn read_u32(&mut self) -> u32 {
117        let chunk = self.read_bytes(sizeof!(u32));
118        u32::from_le_bytes(unsafe {
119            *chunk.as_ptr().cast::<[u8; sizeof!(u32)]>()
120        })
121    }
122
123    #[inline]
124    fn read_byte(&mut self) -> u8 {
125        self.pos += 1;
126        self.data[self.pos - 1]
127    }
128
129    #[inline]
130    fn seek_byte(&mut self, from: SeekFrom) {
131        self.pos = match from {
132            SeekFrom::Start(offset) => offset as usize,
133            SeekFrom::Current(offset) => self.pos + offset as usize,
134            SeekFrom::End(_) => unreachable!(),
135        };
136    }
137
138    #[inline]
139    /// Decrypts u32 if `u32` is encrypted, encrypts u32 if `u32` is decrypted.
140    fn xor_u32_vxace(&mut self, u32: u32) -> u32 {
141        u32 ^ self.key
142    }
143
144    #[inline]
145    /// Decrypts u32 if `u32` is encrypted, encrypts u32 if `u32` is decrypted.
146    fn xor_u32_older(&mut self, u32: u32) -> u32 {
147        let decrypted = u32 ^ self.key;
148
149        if self.engine.is_older() {
150            self.update_key_older();
151        }
152
153        decrypted
154    }
155
156    #[inline]
157    /// Decrypts path if `path_data` is encrypted, encrypts path if `path_data` is decrypted.
158    fn xor_path_vxace(&mut self, path_data: &[u8], output: &mut Vec<u8>) {
159        for (idx, byte) in path_data.iter().enumerate() {
160            // Compiler is smart and can optimize this modulo into `& 0b11`.
161            // Since modulo is more self-descriptive, let it be here.
162            output.push(byte ^ self.key_bytes[idx % 4]);
163        }
164    }
165
166    #[inline]
167    /// Decrypts path if `path_data` is encrypted, encrypts path if `path_data` is decrypted.
168    fn xor_path_older(&mut self, path_data: &[u8], output: &mut Vec<u8>) {
169        for byte in path_data {
170            output.push(byte ^ self.key as u8);
171            self.update_key_older();
172        }
173    }
174
175    #[inline]
176    /// Decrypts data if `data` is encrypted, encrypts data if `data` is decrypted.
177    fn xor_data(mut key: u32, data: &[u8], output: &mut Vec<u8>) {
178        let mut key_bytes = key.to_le_bytes();
179        let mut key_byte_pos = 0;
180
181        // Decrypting data
182        for data_byte in data {
183            if key_byte_pos == 4 {
184                key_byte_pos = 0;
185                key = key.wrapping_mul(7).wrapping_add(3);
186                key_bytes = key.to_le_bytes();
187            }
188
189            output.push(data_byte ^ key_bytes[key_byte_pos]);
190            key_byte_pos += 1;
191        }
192    }
193
194    #[inline]
195    fn parse_header(&mut self) -> Result<(), ExtractError> {
196        let header = self.read_bytes(ARCHIVE_HEADER.len());
197
198        if header != ARCHIVE_HEADER {
199            return Err(ExtractError::InvalidHeader(unsafe {
200                *header.as_ptr().cast::<[u8; 7]>()
201            }));
202        }
203
204        let engine_type = self.read_byte();
205
206        self.engine = match engine_type {
207            1 => Engine::Older,
208            3 => Engine::VXAce,
209            _ => {
210                return Err(ExtractError::InvalidEngine(engine_type));
211            }
212        };
213
214        Ok(())
215    }
216
217    #[inline]
218    fn decrypt_entries(&mut self) -> Vec<ArchiveEntry> {
219        if self.engine.is_vx_ace() {
220            // Default key is not ever used and overwritten.
221            let key = self.read_u32();
222            self.update_key(key);
223            self.update_key_vxace();
224        }
225
226        let mut entries = Vec::with_capacity(MAX_ENTRY_AMOUNT);
227        let mut u32: u32;
228
229        if self.engine.is_vx_ace() {
230            loop {
231                u32 = self.read_u32();
232                let data_offset = u64::from(self.xor_u32_vxace(u32));
233
234                // End of data
235                if data_offset == 0 {
236                    break;
237                }
238
239                u32 = self.read_u32();
240                let data_size = self.xor_u32_vxace(u32);
241
242                u32 = self.read_u32();
243                let entry_key = self.xor_u32_vxace(u32);
244
245                u32 = self.read_u32();
246                let path_size = self.xor_u32_vxace(u32) as usize;
247                let mut decrypted_path = Vec::with_capacity(path_size);
248
249                let path_data =
250                    unsafe { &*(self.read_bytes(path_size) as *const [u8]) };
251
252                self.xor_path_vxace(path_data, &mut decrypted_path);
253
254                // Store current position
255                let prev_pos = self.pos;
256
257                // Read data
258                self.seek_byte(SeekFrom::Start(data_offset));
259
260                let entry_data = self.read_bytes(data_size as usize);
261                let mut decrypted_data = Vec::with_capacity(data_size as usize);
262                Self::xor_data(entry_key, entry_data, &mut decrypted_data);
263
264                entries.push(ArchiveEntry {
265                    path: Cow::Owned(decrypted_path),
266                    data: decrypted_data,
267                });
268
269                // Restore position
270                self.seek_byte(SeekFrom::Start(prev_pos as u64));
271            }
272        } else {
273            loop {
274                // End of data
275                if self.pos == self.len {
276                    break;
277                }
278
279                u32 = self.read_u32();
280                let path_size = self.xor_u32_older(u32) as usize;
281                let mut decrypted_path = Vec::with_capacity(path_size);
282
283                let path_data =
284                    unsafe { &*(self.read_bytes(path_size) as *const [u8]) };
285
286                self.xor_path_older(path_data, &mut decrypted_path);
287
288                u32 = self.read_u32();
289                let data_size = self.xor_u32_older(u32);
290                let data_offset = self.pos as u64;
291                let entry_key = self.key;
292
293                // Skip data block
294                self.seek_byte(SeekFrom::Current(i64::from(data_size)));
295
296                // Store current position
297                let prev_pos = self.pos;
298
299                // Seek back to the data and read it
300                self.seek_byte(SeekFrom::Start(data_offset));
301
302                let entry_data = self.read_bytes(data_size as usize);
303                let mut decrypted_data = Vec::with_capacity(data_size as usize);
304                Self::xor_data(entry_key, entry_data, &mut decrypted_data);
305
306                entries.push(ArchiveEntry {
307                    path: Cow::Owned(decrypted_path),
308                    data: decrypted_data,
309                });
310
311                // Restore position
312                self.seek_byte(SeekFrom::Start(prev_pos as u64));
313            }
314        }
315
316        entries
317    }
318
319    fn encrypt_entries(
320        &mut self,
321        entries: &[ArchiveEntry],
322        archive_buffer: &mut Vec<u8>,
323    ) {
324        if self.engine.is_vx_ace() {
325            self.update_key(ENCRYPTION_KEY);
326
327            archive_buffer.extend_from_slice(&self.key_bytes);
328            self.update_key_vxace();
329        }
330
331        // We'll store the position of the file offsets to later modify it
332        let mut data_offsets_indices = Vec::with_capacity(entries.len());
333
334        if self.engine.is_vx_ace() {
335            // First we write metadata: content size, key, path size and path itself
336            for entry in entries {
337                // Placeholder offset, we'll modify it later
338                data_offsets_indices.push(archive_buffer.len());
339                archive_buffer.extend_from_slice(&0u32.to_le_bytes());
340
341                let data_size = entry.data.len() as u32;
342                let encoded_data_size = self.xor_u32_vxace(data_size);
343                archive_buffer
344                    .extend_from_slice(&encoded_data_size.to_le_bytes());
345
346                // self.key ^ self.key = 0
347                archive_buffer.extend_from_slice(&ENCRYPTION_KEY.to_le_bytes());
348
349                let path_size = entry.path.len() as u32;
350                let encoded_path_size = self.xor_u32_vxace(path_size);
351                archive_buffer
352                    .extend_from_slice(&encoded_path_size.to_le_bytes());
353
354                self.xor_path_vxace(&entry.path, archive_buffer);
355            }
356
357            // Write the key, when decrypthing it will be xor'd against itself which will produce 0, and decryption will stop.
358            archive_buffer.extend_from_slice(&self.key.to_le_bytes());
359
360            // Write the actual contents and modify the offsets with the offsets of the contents
361            for (idx, entry) in entries.iter().enumerate() {
362                let data_offset = archive_buffer.len() as u32;
363                let encrypted_data_offset = self.xor_u32_vxace(data_offset);
364                let offset_slice_mut = &mut archive_buffer[data_offsets_indices
365                    [idx]
366                    ..data_offsets_indices[idx] + sizeof!(u32)];
367
368                offset_slice_mut
369                    .copy_from_slice(&encrypted_data_offset.to_le_bytes());
370                Self::xor_data(self.key, &entry.data, archive_buffer);
371            }
372        } else {
373            for entry in entries {
374                let path_size = entry.path.len() as u32;
375
376                let encoded_path_size = self.xor_u32_older(path_size);
377                archive_buffer
378                    .extend_from_slice(&encoded_path_size.to_le_bytes());
379
380                self.xor_path_older(&entry.path, archive_buffer);
381
382                let data_size = entry.data.len() as u32;
383                let encoded_data_size = self.xor_u32_older(data_size);
384                archive_buffer
385                    .extend_from_slice(&encoded_data_size.to_le_bytes());
386
387                Self::xor_data(self.key, &entry.data, archive_buffer);
388            }
389        }
390    }
391
392    fn reset(&mut self, data: &'a [u8]) {
393        self.data = data;
394        self.len = data.len();
395        self.pos = 0;
396
397        self.engine = Engine::Older;
398        self.key = OLDER_DECRYPTION_KEY;
399        self.key_bytes = OLDER_DECRYPTION_KEY.to_le_bytes();
400    }
401
402    /// Returns [`Vec`] of decrypted [`ArchiveEntry`] entries.
403    ///
404    /// # Parameters
405    /// - `archive_data`: The content of the archive file.
406    ///
407    /// # Returns
408    /// - [`Vec<ArchiveEntry>`] if files were successfully decrypted.
409    /// - [`ExtractError`] otherwise.
410    ///
411    /// # Errors
412    ///
413    /// - [`ExtractError::InvalidHeader`] for invalid header.
414    /// - [`ExtractError::InvalidEngine`] for invalid header engine type byte.
415    ///
416    /// # Example
417    /// ```no_run
418    /// use rpgmad_lib::Decrypter;
419    /// use std::{path::PathBuf, fs::{read, write, create_dir_all}};
420    ///
421    /// let data = read("C:/Game/Game.rgss3a").unwrap();
422    /// let decrypted_entries = Decrypter::new().decrypt(&data).unwrap();
423    ///
424    /// for entry in decrypted_entries {
425    ///     let path = String::from_utf8_lossy(&entry.path);
426    ///     let output_path = PathBuf::from("C:/Game").join(path.as_ref());
427    ///
428    ///     if let Some(parent) = output_path.parent() {
429    ///         create_dir_all(parent).unwrap();
430    ///     }
431    ///
432    ///     write(output_path, entry.data).unwrap();
433    /// }
434    /// ```
435    #[inline]
436    pub fn decrypt(
437        &mut self,
438        archive_data: &'a [u8],
439    ) -> Result<Vec<ArchiveEntry>, ExtractError> {
440        self.reset(archive_data);
441        self.parse_header()?;
442        Ok(self.decrypt_entries())
443    }
444
445    /// Returns encrypted archive data as [`Vec<u8>`].
446    ///
447    /// # Parameters
448    /// - `entries`: Archive entries to encrypt.
449    /// - `engine`: Target archive engine.
450    ///
451    /// # Returns
452    /// - [`Vec<u8>`] representing encrypted archive data.
453    ///
454    /// # Example
455    /// ```no_run
456    /// use rpgmad_lib::{Decrypter, Engine, ArchiveEntry};
457    /// use std::{fs::{read, write}, borrow::Cow};
458    ///
459    /// let archive_entries = [ArchiveEntry {
460    ///     path: Cow::Borrowed(b"Graphics/Tilesets/Tileset1.png"),
461    ///     data: read("Graphics/Tilesets/Tileset1.png").unwrap()
462    /// }];
463    /// let archive_data = Decrypter::new().encrypt(&archive_entries, Engine::VXAce);
464    /// write("./Game.rgss3a", archive_data).unwrap();
465    /// ```
466    #[must_use]
467    #[inline]
468    pub fn encrypt(
469        &mut self,
470        archive_entries: &[ArchiveEntry],
471        engine: Engine,
472    ) -> Vec<u8> {
473        let mut buf_size: usize = ARCHIVE_HEADER.len();
474
475        // Engine byte
476        buf_size += 1;
477
478        for entry in archive_entries {
479            if engine.is_vx_ace() {
480                // Offset
481                buf_size += sizeof!(u32);
482
483                // Data size
484                buf_size += sizeof!(u32);
485
486                // Key
487                buf_size += sizeof!(u32);
488
489                // Path size
490                buf_size += sizeof!(u32);
491            } else {
492                // Path size
493                buf_size += sizeof!(u32);
494
495                // Data size
496                buf_size += sizeof!(u32);
497            }
498
499            buf_size += entry.data.len();
500            buf_size += entry.path.len();
501        }
502
503        if engine.is_vx_ace() {
504            // Stop offset int
505            buf_size += sizeof!(u32);
506        }
507
508        let mut archive_buffer = Vec::with_capacity(buf_size);
509
510        archive_buffer.extend_from_slice(ARCHIVE_HEADER);
511        archive_buffer.push(engine as u8);
512
513        self.engine = engine;
514        self.encrypt_entries(archive_entries, &mut archive_buffer);
515
516        archive_buffer
517    }
518}
519
520impl Default for Decrypter<'_> {
521    /// Returns a new [`Decrypter`] with default parameters.
522    ///
523    /// Equivalent to calling [`Decrypter::new`].
524    fn default() -> Self {
525        Self::new()
526    }
527}
528
529/// A convenience function to decrypt an archive in a single call.
530///
531/// This is a wrapper around [`Decrypter::decrypt`] with automatic initialization.
532///
533/// # Parameters
534/// - `archive_data`: The content of the archive file.
535///
536/// # Returns
537/// - [`Vec<DecryptedFile>`] if files were successfully decrypted.
538/// - [`ExtractError`] otherwise.
539///
540/// # Errors
541/// - [`ExtractError::InvalidHeader`] for invalid header.
542/// - [`ExtractError::InvalidEngine`] for invalid header engine type byte.
543///
544/// # Example
545/// ```no_run
546/// use rpgmad_lib::decrypt_archive;
547/// use std::{path::PathBuf, fs::{read, write, create_dir_all}};
548///
549/// let data = read("C:/Game/Game.rgss3a").unwrap();
550/// let decrypted_entries = decrypt_archive(&data).unwrap();
551///
552/// for entry in decrypted_entries {
553///     let path = String::from_utf8_lossy(&entry.path);
554///     let output_path = PathBuf::from("C:/Game").join(path.as_ref());
555///
556///     if let Some(parent) = output_path.parent() {
557///         create_dir_all(parent).unwrap();
558///     }
559///
560///     write(output_path, entry.data).unwrap();
561/// }
562/// ```
563pub fn decrypt_archive(
564    archive_data: &[u8],
565) -> Result<Vec<ArchiveEntry>, ExtractError> {
566    Decrypter::new().decrypt(archive_data)
567}
568
569/// A convenience function to encrypt an archive in a single call.
570///
571/// This is a wrapper around [`Decrypter::encrypt`] with automatic initialization.
572///
573/// # Parameters
574/// - `archive_entries`: Entries to pack into the archive.
575/// - `engine`: [`Engine`] that defines output archive format.
576///
577/// # Returns
578/// - [`Vec<u8>`] representing the archive.
579///
580/// # Example
581/// ```no_run
582/// use rpgmad_lib::{encrypt_archive, ArchiveEntry, Engine};
583/// use std::{fs::{read, write}, borrow::Cow};
584///
585/// let archive_entries = [ArchiveEntry {
586///     path: Cow::Borrowed(b"Graphics/Tilesets/Tileset1.png"),
587///     data: read("Graphics/Tilesets/Tileset1.png").unwrap()
588/// }];
589/// let archive_data = encrypt_archive(&archive_entries, Engine::VXAce);
590/// write("./Game.rgss3a", archive_data).unwrap();
591/// ```
592#[must_use]
593pub fn encrypt_archive(
594    archive_entries: &[ArchiveEntry],
595    engine: Engine,
596) -> Vec<u8> {
597    Decrypter::new().encrypt(archive_entries, engine)
598}