514 lines
17 KiB
C#
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);
|
|
}
|
|
|
|
}
|
|
}
|