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 ") .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>(path: P) -> Result { 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> { 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::()) .take_while(|s| !s.is_empty()) .collect::>(); 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>(path: P) -> Result> { 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, _key: Vec) -> Vec { 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, key: Vec) -> Vec { 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, }