Skip to content

Commit f5c5573

Browse files
committed
Separated decrypter logic to external library rpgmad-lib, slightly changed the code
1 parent 31290e9 commit f5c5573

File tree

1 file changed

+14
-277
lines changed

1 file changed

+14
-277
lines changed

src/main.rs

Lines changed: 14 additions & 277 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,10 @@
11
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
2-
use rayon::prelude::*;
2+
use rpgmad_lib::Decrypter;
33
use std::{
4-
cell::UnsafeCell,
5-
fs::{create_dir_all, read, write},
4+
fs::read,
65
path::{Path, PathBuf},
7-
sync::{Arc, Mutex},
86
time::Instant,
97
};
10-
#[derive(PartialEq, Clone, Copy)]
11-
#[allow(clippy::upper_case_acronyms)]
12-
enum EngineType {
13-
XPVX,
14-
VXAce,
15-
}
16-
17-
impl std::fmt::Display for EngineType {
18-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19-
let variant_name: &str = match self {
20-
EngineType::XPVX => "XP/VX",
21-
EngineType::VXAce => "VXAce",
22-
};
23-
24-
write!(f, "{}", variant_name)
25-
}
26-
}
27-
28-
#[derive(Default)]
29-
struct VecWalker {
30-
data: Vec<u8>,
31-
pos: usize,
32-
len: usize,
33-
}
34-
35-
enum SeekFrom {
36-
Start,
37-
Current,
38-
}
39-
40-
impl VecWalker {
41-
pub fn new(data: Vec<u8>) -> Self {
42-
let len: usize = data.len();
43-
VecWalker { data, pos: 0, len }
44-
}
45-
46-
pub fn advance(&mut self, bytes: usize) -> &[u8] {
47-
let start: usize = self.pos;
48-
self.pos += bytes;
49-
&self.data[start..self.pos]
50-
}
51-
52-
pub fn read_chunk(&mut self) -> [u8; 4] {
53-
let read: &[u8] = self.advance(4);
54-
unsafe { *(read.as_ptr() as *const [u8; 4]) }
55-
}
56-
57-
pub fn read_byte(&mut self) -> u8 {
58-
let byte: u8 = self.data[self.pos];
59-
self.pos += 1;
60-
byte
61-
}
62-
63-
pub fn seek(&mut self, offset: usize, seek_from: SeekFrom) {
64-
self.pos = match seek_from {
65-
SeekFrom::Start => offset,
66-
SeekFrom::Current => self.pos + offset,
67-
};
68-
}
69-
}
708

719
struct Localization<'a> {
7210
// Arg descriptions
@@ -79,238 +17,37 @@ struct Localization<'a> {
7917
// Messages
8018
input_path_missing_msg: &'a str,
8119
output_path_missing_msg: &'a str,
82-
unknown_engine_type_msg: &'a str,
83-
unknown_archive_header_msg: &'a str,
84-
output_files_already_exists_msg: &'a str,
85-
}
86-
87-
struct Archive {
88-
name: String,
89-
size: i32,
90-
offset: usize,
91-
key: u32,
92-
}
93-
94-
struct Decrypter<'a> {
95-
walker: UnsafeCell<VecWalker>,
96-
key: u32,
97-
engine: EngineType,
98-
localization: Localization<'a>,
99-
}
100-
101-
impl<'a> Decrypter<'a> {
102-
fn new(bytes: Vec<u8>, localization: Localization<'a>) -> Self {
103-
Self {
104-
walker: UnsafeCell::new(VecWalker::new(bytes)),
105-
key: 0xDEADCAFE,
106-
engine: EngineType::XPVX,
107-
localization,
108-
}
109-
}
110-
111-
fn extract(&mut self, output_path: &Path, force: bool) {
112-
let version: u8 = self.get_archive_version();
113-
114-
if version == 1 {
115-
self.engine = EngineType::XPVX
116-
} else if version == 3 {
117-
self.engine = EngineType::VXAce
118-
} else {
119-
panic!("{}", self.localization.unknown_engine_type_msg)
120-
}
121-
122-
let archives: Vec<Archive> = self.read_archive();
123-
124-
let walker: &mut VecWalker = unsafe { &mut *self.walker.get() };
125-
let arc: Arc<Mutex<&mut VecWalker>> = Arc::new(Mutex::new(walker));
126-
127-
archives.into_par_iter().for_each(|archive: Archive| {
128-
let output_path: PathBuf = output_path.join(archive.name);
129-
130-
if output_path.exists() && !force {
131-
println!("{}", self.localization.output_files_already_exists_msg);
132-
return;
133-
}
134-
135-
let mut walker = arc.lock().unwrap();
136-
137-
walker.seek(archive.offset, SeekFrom::Start);
138-
139-
let mut data: Vec<u8> = Vec::with_capacity(archive.size as usize);
140-
data.extend_from_slice(walker.advance(archive.size as usize));
141-
142-
drop(walker);
143-
144-
create_dir_all(unsafe { output_path.parent().unwrap_unchecked() }).unwrap();
145-
write(output_path, Self::decrypt_archive(&data, archive.key)).unwrap();
146-
});
147-
}
148-
149-
fn decrypt_archive(data: &[u8], mut key: u32) -> Vec<u8> {
150-
let mut decrypted: Vec<u8> = Vec::with_capacity(data.len());
151-
152-
let mut key_bytes: [u8; 4] = key.to_le_bytes();
153-
let mut j: usize = 0;
154-
155-
for item in data {
156-
if j == 4 {
157-
j = 0;
158-
key = key.wrapping_mul(7).wrapping_add(3);
159-
key_bytes = key.to_le_bytes();
160-
}
161-
162-
decrypted.push(item ^ key_bytes[j]);
163-
j += 1;
164-
}
165-
166-
decrypted
167-
}
168-
169-
fn get_archive_version(&mut self) -> u8 {
170-
let walker: &mut VecWalker = unsafe { &mut *self.walker.get() };
171-
let header: &[u8] = walker.advance(6);
172-
173-
if header != b"RGSSAD" {
174-
panic!("{}", self.localization.unknown_archive_header_msg);
175-
}
176-
177-
walker.seek(1, SeekFrom::Current);
178-
let version: u8 = walker.read_byte();
179-
180-
walker.seek(0, SeekFrom::Start);
181-
182-
version
183-
}
184-
185-
fn decrypt_integer(&mut self, value: i32) -> i32 {
186-
let result: i32 = value ^ self.key as i32;
187-
188-
if self.engine == EngineType::XPVX {
189-
self.key = self.key.wrapping_mul(7).wrapping_add(3);
190-
}
191-
192-
result
193-
}
194-
195-
fn decrypt_filename(&mut self, filename: &[u8]) -> String {
196-
let mut decrypted: Vec<u8> = Vec::with_capacity(filename.len());
197-
198-
if self.engine == EngineType::VXAce {
199-
let key_bytes: [u8; 4] = self.key.to_le_bytes();
200-
let mut j: usize = 0;
201-
202-
for item in filename {
203-
if j == 4 {
204-
j = 0;
205-
}
206-
207-
decrypted.push(item ^ key_bytes[j]);
208-
j += 1;
209-
}
210-
} else {
211-
for item in filename {
212-
decrypted.push(item ^ (self.key & 0xff) as u8);
213-
self.key = self.key.wrapping_mul(7).wrapping_add(3);
214-
}
215-
}
216-
217-
String::from_utf8(decrypted).unwrap()
218-
}
219-
220-
fn read_archive(&mut self) -> Vec<Archive> {
221-
let walker: &mut VecWalker = unsafe { &mut *self.walker.get() };
222-
walker.seek(8, SeekFrom::Start);
223-
224-
if self.engine == EngineType::VXAce {
225-
self.key = u32::from_le_bytes(walker.read_chunk())
226-
.wrapping_mul(9)
227-
.wrapping_add(3);
228-
}
229-
230-
let mut archives: Vec<Archive> = Vec::with_capacity(1024);
231-
232-
loop {
233-
let (name, size, offset, key) = if self.engine == EngineType::VXAce {
234-
let offset: usize =
235-
self.decrypt_integer(i32::from_le_bytes(walker.read_chunk())) as usize;
236-
237-
let size: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
238-
239-
let key: u32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk())) as u32;
240-
241-
let length: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
242-
243-
if offset == 0 {
244-
break;
245-
}
246-
247-
let name: String = self.decrypt_filename(walker.advance(length as usize));
248-
249-
(name, size, offset, key)
250-
} else {
251-
let length: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
252-
253-
let name: String = self.decrypt_filename(walker.advance(length as usize));
254-
255-
let size: i32 = self.decrypt_integer(i32::from_le_bytes(walker.read_chunk()));
256-
257-
let offset: usize = walker.pos;
258-
259-
let key: u32 = self.key;
260-
261-
walker.seek(size as usize, SeekFrom::Current);
262-
263-
if walker.pos == walker.len {
264-
break;
265-
}
266-
267-
(name, size, offset, key)
268-
};
269-
270-
archives.push(Archive {
271-
name,
272-
size,
273-
offset,
274-
key,
275-
});
276-
}
277-
278-
archives
279-
}
28020
}
28121

28222
fn main() {
28323
let start_time: Instant = Instant::now();
28424

28525
const LOCALIZATION: Localization = Localization {
286-
input_path_arg_desc: "Path to the RGSSAD file.",
26+
input_path_arg_desc: "Path to the .rgss file.",
28727
output_path_arg_desc: "Path to put output files.",
28828
force_arg_desc: "Forcefully overwrite existing Data, Graphics and other files.",
28929
help_arg_desc: "Prints the help message.",
29030
about: "A tool to extract encrypted .rgss RPG Maker archives.",
29131

29232
input_path_missing_msg: "Input file does not exist.",
29333
output_path_missing_msg: "Output path does not exist.",
294-
unknown_archive_header_msg: "Unknown archive header. Expected: RGSSAD.",
295-
unknown_engine_type_msg:
296-
"Unknown archive game engine. Maybe, file's extension is spelled wrong?",
297-
output_files_already_exists_msg:
298-
"Output file already exists. Use --force to forcefully overwrite it.",
29934
};
30035

30136
let input_path_arg: Arg = Arg::new("input-path")
30237
.short('i')
30338
.long("input-file")
30439
.help(LOCALIZATION.input_path_arg_desc)
30540
.value_parser(value_parser!(PathBuf))
306-
.default_value("./");
41+
.default_value("./")
42+
.hide_default_value(true);
30743

30844
let output_path_arg: Arg = Arg::new("output-path")
30945
.short('o')
31046
.long("output-dir")
31147
.help(LOCALIZATION.output_path_arg_desc)
31248
.value_parser(value_parser!(PathBuf))
313-
.default_value("./");
49+
.default_value("./")
50+
.hide_default_value(true);
31451

31552
let force: Arg = Arg::new("force")
31653
.short('f')
@@ -342,12 +79,9 @@ fn main() {
34279
}
34380

34481
let mut output_path: &Path = matches.get_one::<PathBuf>("output-path").unwrap();
345-
346-
output_path = if *output_path.as_os_str() == *"./" {
347-
unsafe { input_path.parent().unwrap_unchecked() }
348-
} else {
349-
output_path
350-
};
82+
if *output_path.as_os_str() == *"./" {
83+
output_path = unsafe { input_path.parent().unwrap_unchecked() }
84+
}
35185

35286
if !output_path.exists() {
35387
panic!("{}", LOCALIZATION.output_path_missing_msg);
@@ -356,7 +90,10 @@ fn main() {
35690
let force_flag: bool = matches.get_flag("force");
35791

35892
let bytes: Vec<u8> = read(input_path).unwrap();
359-
Decrypter::new(bytes, LOCALIZATION).extract(output_path, force_flag);
93+
94+
Decrypter::new(bytes)
95+
.extract(output_path, force_flag)
96+
.unwrap();
36097

36198
println!("Elapsed: {}", start_time.elapsed().as_secs_f64())
36299
}

0 commit comments

Comments
 (0)