rpgmad_lib/
lib.rs

1//!A decrypter implementation for rpgm-archive-decrypter. Not intended for use in other applications; but can be.
2
3#[cfg(feature = "rayon")]
4use rayon::prelude::*;
5#[cfg(feature = "rayon")]
6use std::sync::{Arc, Mutex};
7use std::{
8    cell::UnsafeCell,
9    fs::{create_dir_all, write},
10    path::{Path, PathBuf},
11};
12
13#[derive(PartialEq)]
14enum Engine {
15    Older,
16    VXAce,
17}
18
19enum SeekFrom {
20    Start,
21    Current,
22}
23
24impl std::fmt::Display for Engine {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        let variant_name: &str = match self {
27            Engine::Older => "XP/VX",
28            Engine::VXAce => "VXAce",
29        };
30
31        write!(f, "{}", variant_name)
32    }
33}
34
35struct VecWalker {
36    data: Vec<u8>,
37    pos: usize,
38    len: usize,
39}
40
41impl VecWalker {
42    pub fn new(data: Vec<u8>) -> Self {
43        let len: usize = data.len();
44        VecWalker { data, pos: 0, len }
45    }
46
47    pub fn advance(&mut self, bytes: usize) -> &[u8] {
48        self.pos += bytes;
49        &self.data[self.pos - bytes..self.pos]
50    }
51
52    pub fn read_chunk(&mut self) -> [u8; 4] {
53        let chunk: &[u8] = self.advance(4);
54        unsafe { *(chunk.as_ptr() as *const [u8; 4]) }
55    }
56
57    pub fn read_byte(&mut self) -> u8 {
58        self.pos += 1;
59        self.data[self.pos - 1]
60    }
61
62    pub fn seek(&mut self, offset: usize, seek_from: SeekFrom) {
63        self.pos = match seek_from {
64            SeekFrom::Start => offset,
65            SeekFrom::Current => self.pos + offset,
66        };
67    }
68}
69
70struct Archive {
71    filename: String,
72    size: i32,
73    offset: usize,
74    key: u32,
75}
76
77pub struct Decrypter {
78    walker: UnsafeCell<VecWalker>,
79    key: u32,
80    engine: Engine,
81}
82
83impl Decrypter {
84    /// Creates a new decrypter for specified archive binary data.
85    pub fn new(bytes: Vec<u8>) -> Self {
86        Self {
87            walker: UnsafeCell::new(VecWalker::new(bytes)),
88            key: 0xDEADCAFE,
89            engine: Engine::Older,
90        }
91    }
92
93    /// Extracts archive to `output_path`. Does nothing if extracted files already exist and `force` is set to `false`.
94    pub fn extract<P: AsRef<Path>>(&mut self, output_path: P, force: bool) -> Result<(), &str> {
95        let walker: &mut VecWalker = unsafe { &mut *self.walker.get() };
96
97        let version: u8 = {
98            let header: &[u8] = walker.advance(6);
99
100            if header != b"RGSSAD" {
101                return Err("Unknown archive header. Expected: RGSSAD.");
102            }
103
104            walker.seek(1, SeekFrom::Current);
105            walker.read_byte()
106        };
107
108        self.engine = if version == 1 {
109            Engine::Older
110        } else if version == 3 {
111            Engine::VXAce
112        } else {
113            return Err("Unknown archive game engine. Archive is possibly corrupted.");
114        };
115
116        let archives: Vec<Archive> = self.read_archive();
117
118        #[cfg(feature = "rayon")]
119        let arc: Arc<Mutex<&mut VecWalker>> = Arc::new(Mutex::new(walker));
120
121        #[cfg(feature = "rayon")]
122        let archives = archives.into_par_iter();
123
124        #[cfg(not(feature = "rayon"))]
125        let archives = archives.into_iter();
126
127        let output_path: &Path = output_path.as_ref();
128
129        archives.for_each(|archive: Archive| {
130            let output_path: PathBuf = output_path.join(archive.filename);
131
132            if output_path.exists() && !force {
133                println!("Output files already exist. Use --force to forcefully overwrite them.");
134                return;
135            }
136
137            #[cfg(feature = "rayon")]
138            let mut walker = arc.lock().unwrap();
139
140            walker.seek(archive.offset, SeekFrom::Start);
141            let data: Vec<u8> = Vec::from(walker.advance(archive.size as usize));
142
143            #[cfg(feature = "rayon")]
144            drop(walker);
145
146            let parent_directory: &Path = unsafe { output_path.parent().unwrap_unchecked() };
147
148            if !parent_directory.exists() {
149                create_dir_all(parent_directory).unwrap();
150            }
151
152            let decrypted: Vec<u8> = Self::decrypt_archive(&data, archive.key);
153            write(output_path, decrypted).unwrap();
154        });
155
156        Ok(())
157    }
158
159    fn decrypt_archive(data: &[u8], mut key: u32) -> Vec<u8> {
160        let mut decrypted: Vec<u8> = Vec::with_capacity(data.len());
161
162        let mut key_bytes: [u8; 4] = key.to_le_bytes();
163        let mut j: usize = 0;
164
165        for item in data {
166            if j == 4 {
167                j = 0;
168                key = key.wrapping_mul(7).wrapping_add(3);
169                key_bytes = key.to_le_bytes();
170            }
171
172            decrypted.push(item ^ key_bytes[j]);
173            j += 1;
174        }
175
176        decrypted
177    }
178
179    fn decrypt_integer(&mut self, value: i32) -> i32 {
180        let result: i32 = value ^ self.key as i32;
181
182        if self.engine == Engine::Older {
183            self.key = self.key.wrapping_mul(7).wrapping_add(3);
184        }
185
186        result
187    }
188
189    fn decrypt_filename(&mut self, filename: &[u8]) -> String {
190        let mut decrypted: Vec<u8> = Vec::with_capacity(filename.len());
191
192        if self.engine == Engine::VXAce {
193            let key_bytes: [u8; 4] = self.key.to_le_bytes();
194            let mut j: usize = 0;
195
196            for item in filename {
197                if j == 4 {
198                    j = 0;
199                }
200
201                decrypted.push(item ^ key_bytes[j]);
202                j += 1;
203            }
204        } else {
205            for item in filename {
206                decrypted.push(item ^ (self.key & 0xff) as u8);
207                self.key = self.key.wrapping_mul(7).wrapping_add(3);
208            }
209        }
210
211        String::from_utf8(decrypted).unwrap()
212    }
213
214    fn read_archive(&mut self) -> Vec<Archive> {
215        let walker: &mut VecWalker = unsafe { &mut *self.walker.get() };
216
217        if self.engine == Engine::VXAce {
218            // 0xDEADCAFE key is not ever used and overwritten.
219            self.key = u32::from_le_bytes(walker.read_chunk())
220                .wrapping_mul(9)
221                .wrapping_add(3);
222        }
223
224        let mut archives: Vec<Archive> = Vec::with_capacity(1024);
225
226        loop {
227            let (filename, size, offset, key) = if self.engine == Engine::VXAce {
228                let offset: usize =
229                    self.decrypt_integer(i32::from_le_bytes(walker.read_chunk())) as usize;
230
231                let size: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
232
233                let key: u32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk())) as u32;
234
235                let length: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
236
237                if offset == 0 {
238                    break;
239                }
240
241                let filename: String = self.decrypt_filename(walker.advance(length as usize));
242
243                (filename, size, offset, key)
244            } else {
245                let length: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
246
247                let filename: String = self.decrypt_filename(walker.advance(length as usize));
248
249                let size: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
250
251                let offset: usize = walker.pos;
252
253                let key: u32 = self.key;
254
255                walker.seek(size as usize, SeekFrom::Current);
256
257                if walker.pos == walker.len {
258                    break;
259                }
260
261                (filename, size, offset, key)
262            };
263
264            archives.push(Archive {
265                filename,
266                size,
267                offset,
268                key,
269            });
270        }
271
272        archives
273    }
274}