rmdec/RMDEC/MVProject.cs

514 lines
17 KiB
C#

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<byte> byteList = new List<byte>();
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<string> files = new List<string>();
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<string> files = new List<string>();
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);
}
}
}