using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; namespace RMDEC { public class MVProject { public MVProject() { } //Private internal variables private byte[] encryptionKey = new byte[0x10]; private static byte[] pngHeader = new byte[0x10] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52 }; private static byte[] m4aHeader = new byte[0x10] { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20, 0x00, 0x00, 0x00, 0x00 }; private static byte[] oggHeader = new byte[0x10] { 0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; private bool isKeySet = false; private bool encryptedImages = false; private bool encryptedAudio = false; public bool MZ = false; private string gameTitle; private string filePath; private string systemJsonFile; private dynamic jsonData; //Public readable variables public Boolean IsEncrypted { get { if (EncryptedAudio == true || EncryptedImages == true) { return true; } else { return false; } } } public Byte[] EncryptionKey { get { if(isKeySet) { return encryptionKey; } else { genEncryptionKey(); return encryptionKey; } } } public String GameTitle { get { return gameTitle; } } public Boolean EncryptedImages { get { return encryptedImages; } set { if(jsonData != null) { jsonData.hasEncryptedImages = value; File.WriteAllText(systemJsonFile, jsonData.ToString(Formatting.None)); } encryptedImages = value; } } public Boolean EncryptedAudio { get { return encryptedAudio; } set { if (jsonData != null) { jsonData.hasEncryptedAudio = value; File.WriteAllText(systemJsonFile, jsonData.ToString(Formatting.None)); } encryptedAudio = value; } } public String FilePath { get { return filePath; } } //Private functions private static byte[] hexStr2Bytes(String HexStr) { if (HexStr.Length % 2 != 0) { throw new InvalidDataException(HexStr + " Is not divisible by 2!"); } List byteList = new List(); for (int i = 0; i < HexStr.Length; i += 2) { string curHex = HexStr.Substring(i, 2); byte hexByte = Byte.Parse(curHex, NumberStyles.HexNumber); byteList.Add(hexByte); } byte[] resultingByteArray = byteList.ToArray(); return resultingByteArray; } private static byte[] guessKey(string path) // Incase you think your smart { bool useOgg = false; string[] mzPngs = Directory.GetFiles(path, "*.png_", SearchOption.AllDirectories); string[] mvPngs = Directory.GetFiles(path, "*.rpgmvp", SearchOption.AllDirectories); string[] mzM4as = Directory.GetFiles(path, "*.m4a_", SearchOption.AllDirectories); string[] mvM4as = Directory.GetFiles(path, "*.rpgmvm", SearchOption.AllDirectories); string[] mzOggs = Directory.GetFiles(path, "*.ogg_", SearchOption.AllDirectories); string[] mvOggs = Directory.GetFiles(path, "*.rpgmvo", SearchOption.AllDirectories); List files = new List(); files.AddRange(mzPngs); files.AddRange(mvPngs); files.AddRange(mzM4as); files.AddRange(mvM4as); if (files.Count <= 0) { if(mzOggs.Length > 0 || mvOggs.Length > 0) // still encryted audio? { files.AddRange(mzOggs); files.AddRange(mvOggs); useOgg = true; } else { return new byte[0x10]; } } Random rng = new Random(); int index = rng.Next(0, files.Count); string file = files[index]; byte[] encryptedHeader = new byte[0x10]; FileStream fs = File.OpenRead(file); fs.Seek(0x10, SeekOrigin.Begin); fs.Read(encryptedHeader, 0x00, 0x10); if (useOgg) { fs.Seek(0x58, SeekOrigin.Begin); oggHeader[0x0E] = (byte)fs.ReadByte(); oggHeader[0x0F] = (byte)fs.ReadByte(); } fs.Close(); string filetype = Path.GetExtension(file).ToLower(); if (filetype == ".png_" || filetype == ".rpgmvp") { byte[] key = RMProject.Xor(encryptedHeader, pngHeader); if(!tryKey(path, key)) { throw new Exception("Cannot find key (ITS TOO STRONG!)"); } else { return key; } } else if (filetype == ".m4a_" || filetype == ".rpgmvm") { byte[] key = RMProject.Xor(encryptedHeader, m4aHeader); if (!tryKey(path, key)) { throw new Exception("Cannot find key (ITS TOO STRONG!)"); } else { return key; } } else if (filetype == ".ogg_" || filetype == ".rpgmvo") { byte[] key = RMProject.Xor(encryptedHeader, oggHeader); if (!tryKey(path, key)) { throw new Exception("Cannot find key (ITS TOO STRONG!)"); } else { return key; } } throw new Exception("Cannot find key (ITS TOO STRONG!)"); } private static bool tryKey(string path, byte[] key) // Checks if the key really works { string[] mzPngs = Directory.GetFiles(path, "*.png_", SearchOption.AllDirectories); string[] mvPngs = Directory.GetFiles(path, "*.rpgmvp", SearchOption.AllDirectories); string[] mzM4as = Directory.GetFiles(path, "*.m4a_", SearchOption.AllDirectories); string[] mvM4as = Directory.GetFiles(path, "*.rpgmvm", SearchOption.AllDirectories); string[] mzOggs = Directory.GetFiles(path, "*.ogg_", SearchOption.AllDirectories); string[] mvOggs = Directory.GetFiles(path, "*.rpgmvo", SearchOption.AllDirectories); List files = new List(); files.AddRange(mzPngs); files.AddRange(mvPngs); files.AddRange(mzM4as); files.AddRange(mvM4as); files.AddRange(mzOggs); files.AddRange(mvOggs); if (files.Count <= 0) return false; Random rng = new Random(); int index = rng.Next(0, files.Count); string file = files[index]; string filetype = Path.GetExtension(file).ToLower(); byte[] encryptedHeader = new byte[0x10]; FileStream fs = File.OpenRead(file); fs.Seek(0x10, SeekOrigin.Begin); fs.Read(encryptedHeader, 0x00, 0x10); if (filetype == ".ogg_" || filetype == ".rpgmvo") { fs.Seek(0x58, SeekOrigin.Begin); oggHeader[0x0E] = (byte)fs.ReadByte(); oggHeader[0x0F] = (byte)fs.ReadByte(); } fs.Close(); byte[] plaintextHeader = RMProject.Xor(encryptedHeader, key); if (filetype == ".png_" || filetype == ".rpgmvp") if (plaintextHeader.SequenceEqual(pngHeader)) return true; else return false; else if (filetype == ".ogg_" || filetype == ".rpgmvo") if (plaintextHeader.SequenceEqual(oggHeader)) return true; else return false; else if (filetype == ".m4a_" || filetype == ".rpgmvm") if (plaintextHeader.SequenceEqual(m4aHeader)) return true; else return false; else // No encrypted files? return false; } private static MVProject GuessProject(string folder, bool triedThat=false) { MVProject mvp = new MVProject(); mvp.MZ = false; mvp.encryptedImages = false; mvp.encryptedAudio = false; folder = Path.GetDirectoryName(Path.GetDirectoryName(folder)); if(File.Exists(Path.Combine(folder, "data", "System.json")) && !triedThat) { throw new Exception("Quit"); } string[] mzPngs = Directory.GetFiles(folder, "*.png_", SearchOption.AllDirectories); string[] mvPngs = Directory.GetFiles(folder, "*.rpgmvp", SearchOption.AllDirectories); string[] mzM4as = Directory.GetFiles(folder, "*.m4a_", SearchOption.AllDirectories); string[] mvM4as = Directory.GetFiles(folder, "*.rpgmvm", SearchOption.AllDirectories); string[] mzOggs = Directory.GetFiles(folder, "*.ogg_", SearchOption.AllDirectories); string[] mvOggs = Directory.GetFiles(folder, "*.rpgmvo", SearchOption.AllDirectories); string[] mzArtifacts = Directory.GetFiles(folder, "rmmz_*", SearchOption.AllDirectories); string[] mvArtifacts = Directory.GetFiles(folder, "rpg_*", SearchOption.AllDirectories); // Look for PNG if (mvPngs.Length > 0) { mvp.MZ = false; mvp.encryptedImages = true; } else if (mzPngs.Length > 0) { mvp.MZ = true; mvp.encryptedImages = true; } // Look for M4A if(mvM4as.Length > 0) { mvp.MZ = false; mvp.encryptedAudio = true; } else if (mzM4as.Length > 0) { mvp.MZ = true; mvp.encryptedAudio = true; } // Look for OGG if (mvOggs.Length > 0) { mvp.MZ = false; mvp.encryptedAudio = true; } else if (mzOggs.Length > 0) { mvp.MZ = true; mvp.encryptedAudio = true; } // Determine MV or MZ if(mzArtifacts.Length > 0) { mvp.MZ = true; mvp.filePath = Path.GetDirectoryName(Path.GetDirectoryName(mzArtifacts[0])); } else if(mvArtifacts.Length > 0) { mvp.MZ = false; mvp.filePath = Path.GetDirectoryName(Path.GetDirectoryName(mvArtifacts[0])); } if(mvp.IsEncrypted) { byte[] key = guessKey(folder); mvp.encryptionKey = key; mvp.isKeySet = true; } mvp.systemJsonFile = null; mvp.jsonData = null; mvp.gameTitle = "Unknown Title"; return mvp; } private void genEncryptionKey() { byte[] keyData = new byte[0x10]; Random rng = new Random((int)DateTime.Now.Ticks); rng.NextBytes(keyData); encryptionKey = keyData; if(jsonData != null) { jsonData.encryptionKey = BitConverter.ToString(keyData, 0x00, keyData.Length).Replace("-", ""); File.WriteAllText(systemJsonFile, jsonData.ToString(Formatting.None)); } isKeySet = true; } //Public functions public static MVProject ParseSystemJson(string path) { //Check if valid system.json if (File.Exists(path) && Path.GetExtension(path).ToLower() == ".json") { MVProject mvp = new MVProject(); string jsonStr = File.ReadAllText(path, Encoding.UTF8); dynamic systemJson = JObject.Parse(jsonStr); if (systemJson.gameTitle != null) { mvp.gameTitle = systemJson.gameTitle; } else { return GuessProject(path, true); } if (systemJson.hasEncryptedAudio != null) { mvp.encryptedAudio = systemJson.hasEncryptedAudio; } if (systemJson.hasEncryptedImages != null) { mvp.encryptedImages = systemJson.hasEncryptedImages; } if (systemJson.encryptionKey != null) { string encKey = systemJson.encryptionKey; byte[] key = hexStr2Bytes(encKey); if(tryKey(Path.GetDirectoryName(Path.GetDirectoryName(path)), key)) { mvp.encryptionKey = key; mvp.isKeySet = true; } else { key = guessKey(Path.GetDirectoryName(Path.GetDirectoryName(path))); mvp.encryptionKey = key; mvp.isKeySet = true; } } mvp.filePath = Path.GetDirectoryName(Path.GetDirectoryName(path)); // Detect MV or MZ if (File.Exists(Path.Combine(mvp.filePath, "js", "rmmz_core.js"))) { mvp.MZ = true; } else { mvp.MZ = false; } mvp.systemJsonFile = path; mvp.jsonData = systemJson; return mvp; } else { return GuessProject(path); } } public void EncryptFile(Stream inStream, Stream outStream, byte[] key = null) { if (key == null) key = EncryptionKey; outStream.Seek(0x00, SeekOrigin.Begin); outStream.SetLength(0x00); BinaryWriter boutStream = new BinaryWriter(outStream); string magic = "RPGMV"; byte[] magicBytes = Encoding.UTF8.GetBytes(magic); outStream.Write(magicBytes, 0x00, magicBytes.Length); boutStream.Write(0x00); boutStream.Write(0x103); outStream.Write(new byte[0x3], 0x00, 0x3); inStream.Seek(0x00, SeekOrigin.Begin); byte[] plaintextHeader = new byte[0x10]; inStream.Read(plaintextHeader, 0x00, 0x10); byte[] encryptedHeader = RMProject.Xor(plaintextHeader, key); outStream.Write(encryptedHeader, 0x00, encryptedHeader.Length); inStream.CopyTo(outStream); } public void DecryptFile(Stream inStream, Stream outStream, byte[] key=null) { if (key == null) key = EncryptionKey; inStream.Seek(0x00, SeekOrigin.Begin); byte[] magic = new byte[0x05]; inStream.Read(magic, 0x00,0x05); string magicStr = Encoding.UTF8.GetString(magic); if(magicStr != "RPGMV") { throw new InvalidDataException("Not an encrypted file!"); } inStream.Seek(0x10, SeekOrigin.Begin); byte[] encryptedHeader = new byte[0x10]; inStream.Read(encryptedHeader, 0x00, 0x10); byte[] plaintextHeader = RMProject.Xor(encryptedHeader, key); outStream.Seek(0x00, SeekOrigin.Begin); outStream.SetLength(0); outStream.Write(plaintextHeader, 0x00, plaintextHeader.Length); inStream.CopyTo(outStream); } } }