Initial Commit

This commit is contained in:
Rekai Musuka 2020-06-01 21:35:45 -05:00
commit 17672ce114
4 changed files with 278 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

120
Cargo.lock generated Normal file
View File

@ -0,0 +1,120 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "clap"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "hermit-abi"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "rpgmv_decryptor"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "rpgmv_decryptor"
version = "0.1.0"
authors = ["Rekai Musuka <musukarekai@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.31"
clap = "2.33.1"

146
src/main.rs Normal file
View File

@ -0,0 +1,146 @@
use anyhow::{anyhow, Context, Result};
use clap::{App, Arg};
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
/// # RPGMV Decryptor
///
fn main() -> Result<()> {
let matches = App::new("RPGMV Decoder")
.version("0.1.0")
.author("paoda <musukarekai@gmail.com>")
.about("Decodes .rpgmvp, .rpgmvm and .rpgmvo files")
.arg(
Arg::with_name("path")
.index(1)
.value_name("FILE")
.help("Path to the file which will be decrypted")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("key")
.short("k")
.long("key")
.value_name("KEY")
.help("The Encryption Key (look in www/data/System.json or www/js/rpg_core.js")
.takes_value(true)
.required(true),
)
.get_matches();
let path_string = matches.value_of("path").context("No file path provided")?;
let key_string = matches.value_of("key").context("No key provided")?;
let path = Path::new(path_string);
let key = get_key_bytes(key_string)?;
// let default_rpgmv_header: [u8; 16] = [
// 0x52, 0x50, 0x47, 0x4d, 0x56, 0x00, 0x00, 0x00, 0x00, 0x3, 0x1, 0x00, 0x00, 0x00, 0x00,
// 0x00,
// ];
let file_buf = get_file_data(path)?;
let decrypted = decrypt(file_buf, key);
let mut file;
let filename = path
.file_stem()
.context("Unable to determine file stem")?
.to_str()
.context("Unable to convert file stem to UTF-8 string")?;
match detect_original_extension(path)? {
RPGFile::Photo => file = File::create(format!("{}.png", filename))?, // .rpgmvps are .pngs
RPGFile::Video => file = File::create(format!("{}.m4a", filename))?, // .rpgmvms are .m4as
RPGFile::Audio => file = File::create(format!("{}.ogg", filename))?, // .rpgmvos are .oggs
}
file.write_all(&decrypted)?;
Ok(())
}
/// Determines the original file type for the encrypted .rpgmv* file
fn detect_original_extension<P: AsRef<Path>>(path: P) -> Result<RPGFile> {
match path.as_ref().extension() {
Some(ext) => {
let ext = ext
.to_str()
.context("File extension was not UTF-8 Compatible")?;
match ext {
"rpgmvp" => Ok(RPGFile::Photo),
"rpgmvm" => Ok(RPGFile::Video),
"rpgmvo" => Ok(RPGFile::Audio),
_ => Err(anyhow!("File was lacking a supported file extension")),
}
}
None => Err(anyhow!("File is lacking a file extension")),
}
}
/// get_key_bytes takes a 32-character hexadecimal string and interperets
/// every two characters as an unsined 8-bit integer.
///
/// # Examples
/// `71e7bdd9b28010980c2a4e8a1386e100` will be split up as:
///
/// `0x71`, `0xe7`, `0xbd`, `0xd9`, `0xb2`, `0x80`, `0x10`, `0x98`, `0x0c`, `0x2a`, `0x4e`, `0x8a`, `0x13`, `0x86`, `0xe1`, and `0x00`
fn get_key_bytes(key: &str) -> Result<Vec<u8>> {
let sub_len = 2;
let mut key_buf = Vec::with_capacity(key.len() / 2);
let mut chars = key.chars();
let sub_strings = (0..)
.map(|_| chars.by_ref().take(sub_len).collect::<String>())
.take_while(|s| !s.is_empty())
.collect::<Vec<_>>();
for string in sub_strings {
key_buf.push(u8::from_str_radix(&string, 16)?);
}
Ok(key_buf)
}
/// get_file_data returns a buffer containing a data of a file which is on disk.
fn get_file_data<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
let mut buf = Vec::new();
let mut file = File::open(path.as_ref())?;
file.read_to_end(&mut buf)?;
Ok(buf)
}
fn _encrypt(_bytes: Vec<u8>, _key: Vec<u8>) -> Vec<u8> {
unimplemented!();
}
/// Decrypts a RPGMV media file
///
/// The only part of the data that is actually encrypted (read: has a cipher applied to it)
/// is the header of the original file, which is bytes 17 -> 33 (16 bytes). Our key provides us with
/// 16 values which we can use to perform a bitwise XOR. The bitwise XOR will undo the cipher, returning the header to
/// it's original position. At this point, byte #17 -> end of file will now be recognized as their respected unencrypted
// file formats.
fn decrypt(bytes: Vec<u8>, key: Vec<u8>) -> Vec<u8> {
let mut decrypted = bytes[16..].to_owned(); // Throw out the first 16 bytes (the RPGMV Header)
for i in 0..16 as usize {
decrypted[i as usize] = decrypted[i] ^ key[i];
}
decrypted
}
/// An enum that represents the types of supported RPGMV media files.
enum RPGFile {
Photo,
Video,
Audio,
}