1#[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 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 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 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}