cxml-decompiler/AppInfoCli/CXML.cs

781 lines
26 KiB
C#

using Ionic.Zlib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Xml;
namespace CXML
{
enum AttributeType
{
TYPE_NONE,
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING,
TYPE_CHAR,
TYPE_STYLE_ID,
TYPE_INTEGER_ARRAY,
TYPE_FLOAT_ARRAY,
TYPE_FILE,
TYPE_ID_STRING_LOOPBACK,
TYPE_ID_STRING,
TYPE_ID_INT_LOOPBACK,
TYPE_ID_INT
};
class Tools
{
public static String RemoveDecimals(float FloatValue)
{
String FloatStr = FloatValue.ToString();
String[] FloatParts = FloatStr.Split('.');
if (FloatParts.Length > 1)
if(FloatParts[1].Length > 2)
FloatParts[1] = FloatParts[1].Substring(0, 2);
FloatStr = String.Join(".", FloatParts);
return FloatStr;
}
public static MemoryStream ByteToStream(byte[] Array)
{
MemoryStream ms = new MemoryStream();
ms.Write(Array, 0x00, Array.Length);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
public static byte[] StreamToByte(Stream stream)
{
int StreamLen = (int)stream.Length;
byte[] Bytes = new byte[StreamLen];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(Bytes, 0x00, StreamLen);
return Bytes;
}
public static string GetFileExtension(byte[] Bytes)
{
if(IsImage(Bytes))
{
return ".png";
}
else if(IsZlib(Bytes))
{
return ".zlib";
}
else if(IsGim(Bytes))
{
return ".gim";
}
else if(IsRcf(Bytes))
{
return ".rcs";
}
else if(IsDDS(Bytes))
{
return ".dds";
}
else if (IsVAG(Bytes))
{
return ".vag";
}
else
{
return ".bin";
}
}
public static bool IsVAG(byte[] Bytes)
{
MemoryStream Data = ByteToStream(Bytes);
String header = ReadString(Data, 4);
if (header.StartsWith("VAG"))
{
return true;
}
else
{
return false;
}
}
public static bool IsDDS(byte[] Bytes)
{
MemoryStream Data = ByteToStream(Bytes);
String header = ReadString(Data, 4);
if (header.StartsWith("DDS"))
{
return true;
}
else
{
return false;
}
}
public static bool IsRcf(byte[] Bytes)
{
MemoryStream Data = ByteToStream(Bytes);
String header = ReadString(Data, 5);
if (header.StartsWith("RCSF"))
{
return true;
}
else
{
return false;
}
}
public static bool IsGim(byte[] Bytes)
{
MemoryStream Data = ByteToStream(Bytes);
String header = ReadString(Data,4);
if(header.StartsWith("MIG"))
{
return true;
}
else
{
return false;
}
}
public static bool IsZlib(byte[] Bytes)
{
if(Bytes[0] == 0x78)
{
if (Bytes[1] == 0x01)
return true;
if (Bytes[1] == 0x9C)
return true;
if (Bytes[1] == 0xDA)
return true;
}
return false;
}
public static bool IsImage(byte[] Bytes)
{
try
{
GetBitmap(Bytes);
}
catch(Exception)
{
return false;
}
return true;
}
public static Bitmap GetBitmap(byte[] BitmapBytes)
{
MemoryStream ms = ByteToStream(BitmapBytes);
Bitmap bmp = new Bitmap(ms);
ms.Dispose();
return bmp;
}
public static String ReadStringAt(Stream ms, int location)
{
long ogPos = ms.Position;
ms.Seek(location, SeekOrigin.Begin);
String str = ReadString(ms);
ms.Seek(ogPos, SeekOrigin.Begin);
return str;
}
public static int ReadIntAt(Stream ms, int location)
{
BinaryReader BinReader = new BinaryReader(ms);
long ogPos = ms.Position;
ms.Seek(location, SeekOrigin.Begin);
int i = BinReader.ReadInt32();
ms.Seek(ogPos, SeekOrigin.Begin);
return i;
}
public static int ReadInt(Stream ms)
{
BinaryReader BinReader = new BinaryReader(ms);
int i = BinReader.ReadInt32();
return i;
}
public static String ReadString(Stream ms, int limit = -1)
{
int i = 0xFF;
MemoryStream StringStream = new MemoryStream();
do
{
i = ms.ReadByte();
if (i == 0 && i != limit)
break;
StringStream.WriteByte((byte)i);
}
while (true);
byte[] StringData = StringStream.ToArray();
String str = Encoding.UTF8.GetString(StringData);
return str;
}
}
class Parser
{
private static bool _checkMagicNumber()
{
String Magic = Tools.ReadStringAt(InfoFile, 0x00);
if (Magic.StartsWith("PSMA"))
{
return true;
}
else if(Magic.StartsWith("RCOF"))
{
return true;
}
else if (Magic.StartsWith("RCSF"))
{
return true;
}
else
{
return false;
}
}
static FileStream InfoFile;
static MemoryStream TreeTable;
static MemoryStream StringIDTable;
static MemoryStream IntIDTable;
static MemoryStream StringTable;
static MemoryStream CharTable;
static MemoryStream StylesIDTable;
static MemoryStream IntArrayTable;
static MemoryStream FloatArrayTable;
static MemoryStream FileTable;
static BinaryReader bTreeTable;
static BinaryReader bIntIDTable;
static BinaryReader bFloatArrayTable;
static BinaryReader bIntArrayTable;
static BinaryReader bStylesIDTable;
static XmlWriter XMLFile;
public static void Init(string Path, bool CheckMagic = true)
{
InfoFile = File.Open(Path, FileMode.Open, FileAccess.Read);
if(CheckMagic)
{
if (!_checkMagicNumber())
{
throw new Exception("Incorrect magic number.");
}
}
TreeTable = Tools.ByteToStream(GetTreeTable());
StringIDTable = Tools.ByteToStream(GetStringIDTable());
IntIDTable = Tools.ByteToStream(GetIntIDTable());
StringTable = Tools.ByteToStream(GetStringTable());
CharTable = Tools.ByteToStream(GetCharTable());
StylesIDTable = Tools.ByteToStream(GetStyleIDTable());
IntArrayTable = Tools.ByteToStream(GetIntArrayTable());
FloatArrayTable = Tools.ByteToStream(GetFloatArrayTable());
FileTable = Tools.ByteToStream(GetFileTable());
bTreeTable = new BinaryReader(TreeTable);
bIntIDTable = new BinaryReader(IntIDTable);
bFloatArrayTable = new BinaryReader(FloatArrayTable);
bIntArrayTable = new BinaryReader(IntArrayTable);
bStylesIDTable = new BinaryReader(StylesIDTable);
return;
}
public static void Term()
{
InfoFile.Close();
TreeTable.Close();
StringIDTable.Close();
IntIDTable.Close();
StringTable.Close();
CharTable.Close();
StylesIDTable.Close();
IntArrayTable.Close();
FloatArrayTable.Close();
FileTable.Close();
bTreeTable.Close();
bIntIDTable.Close();
bFloatArrayTable.Close();
bIntArrayTable.Close();
bStylesIDTable.Close();
}
public static int GetTreeTableOffset()
{
return Tools.ReadIntAt(InfoFile,0x8);
}
public static int GetTreeTableSize()
{
return Tools.ReadIntAt(InfoFile, 0xC);
}
public static int GetIDStringTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x10);
}
public static int GetIDStringTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x14);
}
public static int GetIDIntTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x18);
}
public static int GetIDIntTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x1C);
}
public static int GetStringTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x20);
}
public static int GetStringTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x24);
}
public static int GetCharTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x28);
}
public static int GetCharTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x2C);
}
public static int GetStyleIDTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x30);
}
public static int GetStyleIDTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x34);
}
public static int GetIntArrayTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x38);
}
public static int GetIntArrayTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x3C);
}
public static int GetFloatArrayTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x40);
}
public static int GetFloatArrayTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x44);
}
public static int GetFileTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x48);
}
public static int GetFileTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x4C);
}
public static byte[] GetTreeTable()
{
int TableOffset = GetTreeTableOffset();
int TableSize = GetTreeTableSize();
InfoFile.Seek(TableOffset, SeekOrigin.Begin);
byte[] Table = new byte[TableSize];
InfoFile.Read(Table, 0x00, TableSize);
return Table;
}
public static byte[] GetStringIDTable()
{
int IDStrTableOffset = GetIDStringTableOffset();
int IDStrTableSize = GetIDStringTableSize();
InfoFile.Seek(IDStrTableOffset, SeekOrigin.Begin);
byte[] IDStringTable = new byte[IDStrTableSize];
InfoFile.Read(IDStringTable, 0x00, IDStrTableSize);
return IDStringTable;
}
public static byte[] GetIntIDTable()
{
int IDIntTableOffset = GetIDIntTableOffset();
int IDIntTableSize = GetIDIntTableSize();
InfoFile.Seek(IDIntTableOffset, SeekOrigin.Begin);
byte[] IDIntTable = new byte[IDIntTableSize];
InfoFile.Read(IDIntTable, 0x00, IDIntTableSize);
return IDIntTable;
}
public static byte[] GetStringTable()
{
int StringTableOffset = GetStringTableOffset();
int StringTableSize = GetStringTableSize();
InfoFile.Seek(StringTableOffset, SeekOrigin.Begin);
byte[] StringTable = new byte[StringTableSize];
InfoFile.Read(StringTable, 0x00, StringTableSize);
return StringTable;
}
public static byte[] GetCharTable()
{
int CharTableOffset = GetCharTableOffset();
int CharTableSize = GetCharTableSize();
InfoFile.Seek(CharTableOffset, SeekOrigin.Begin);
byte[] CharTable = new byte[CharTableSize];
InfoFile.Read(CharTable, 0x00, CharTableSize);
return CharTable;
}
public static byte[] GetStyleIDTable()
{
int StyleIDTableOffset = GetStyleIDTableOffset();
int StyleIDTableSize = GetStyleIDTableSize();
InfoFile.Seek(StyleIDTableOffset, SeekOrigin.Begin);
byte[] StyleIDTable = new byte[StyleIDTableSize];
InfoFile.Read(StyleIDTable, 0x00, StyleIDTableSize);
return StyleIDTable;
}
public static byte[] GetIntArrayTable()
{
int IntArrayTableOffset = GetIntArrayTableOffset();
int IntArrayTableSize = GetIntArrayTableSize();
InfoFile.Seek(IntArrayTableOffset, SeekOrigin.Begin);
byte[] IntArrayTable = new byte[IntArrayTableSize];
InfoFile.Read(IntArrayTable, 0x00, IntArrayTableSize);
return IntArrayTable;
}
public static byte[] GetFloatArrayTable()
{
int FloatArrayTableOffset = GetFloatArrayTableOffset();
int FloatArrayTableSize = GetFloatArrayTableSize();
InfoFile.Seek(FloatArrayTableOffset, SeekOrigin.Begin);
byte[] FloatArrayTable = new byte[FloatArrayTableSize];
InfoFile.Read(FloatArrayTable, 0x00, FloatArrayTableSize);
return FloatArrayTable;
}
public static byte[] GetFileTable()
{
int DataOffset = GetFileTableOffset();
int DataLength = GetFileTableSize();
InfoFile.Seek(DataOffset, SeekOrigin.Begin);
byte[] FileTable = new byte[DataLength];
InfoFile.Read(FileTable, 0x00, DataLength);
return FileTable;
}
public static void DecompileCXML(String CXMLFile, bool force = false)
{
if(Directory.Exists("files"))
Directory.Delete("files", true);
Term();
Init(CXMLFile,force);
XmlWriterSettings XMLSettings = new XmlWriterSettings();
XMLSettings.Indent = true;
XMLFile = XmlWriter.Create(Path.GetFileNameWithoutExtension(CXMLFile)+".xml", XMLSettings);
XMLFile.WriteStartDocument();
ReadElement();
XMLFile.WriteEndDocument();
XMLFile.Flush();
Term();
}
public static void ReadAttribute(String ElementName = "")
{
int AttributePtr = bTreeTable.ReadInt32();
AttributeType Type = (AttributeType)bTreeTable.ReadInt32();
String AttributeName = Tools.ReadStringAt(StringTable, AttributePtr);
object AttributeValue = "";
Console.WriteLine("AttributeType: " + Type.ToString() + " - "+ TreeTable.Position.ToString());
switch (Type)
{
case AttributeType.TYPE_NONE:
Console.WriteLine("UNSUPPORTED TYPE @ " + TreeTable.Position);
Console.ReadKey();
break;
case AttributeType.TYPE_INT:
AttributeValue = bTreeTable.ReadInt32();
TreeTable.Seek(4, SeekOrigin.Current);
break;
case AttributeType.TYPE_FLOAT:
float FloatValue = bTreeTable.ReadSingle();
AttributeValue = Tools.RemoveDecimals(FloatValue);
TreeTable.Seek(4, SeekOrigin.Current);
break;
case AttributeType.TYPE_STRING:
int StringOffset = bTreeTable.ReadInt32();
int StringLen = bTreeTable.ReadInt32();
byte[] StringBytes = new byte[StringLen];
StringTable.Seek(StringOffset, 0x00);
StringTable.Read(StringBytes, 0x00, StringLen);
AttributeValue = Encoding.UTF8.GetString(StringBytes);
break;
case AttributeType.TYPE_CHAR:
int CharOffset = bTreeTable.ReadInt32() * 2;
int CharLen = bTreeTable.ReadInt32();
byte[] CharBytes = new byte[CharLen];
CharTable.Seek(CharOffset, 0x00);
CharTable.Read(CharBytes, 0x00, CharLen);
AttributeValue = Encoding.Unicode.GetString(CharBytes);
break;
case AttributeType.TYPE_STYLE_ID:
int StyleTableOffset = bTreeTable.ReadInt32();
int StyleTableSize = bTreeTable.ReadInt32();
byte[] StyleTableData = new byte[StyleTableSize];
StylesIDTable.Seek(StyleTableOffset, SeekOrigin.Begin);
StylesIDTable.Read(StyleTableData, 0x00, StyleTableSize);
AttributeValue = BitConverter.ToString(StyleTableData).Replace("-","");
break;
case AttributeType.TYPE_INTEGER_ARRAY:
int IntArrayOffset = bTreeTable.ReadInt32();
int IntArraySize = bTreeTable.ReadInt32();
IntArrayTable.Seek(IntArrayOffset, SeekOrigin.Begin);
List<int> IntList = new List<int>();
for (int i = 0; i < IntArraySize; i++)
{
int IntValue = bIntArrayTable.ReadInt32();
IntList.Add(IntValue);
}
int[] IntArray = IntList.ToArray();
AttributeValue = String.Join(", ", IntArray);
break;
case AttributeType.TYPE_FLOAT_ARRAY:
int FloatArrayOffset = bTreeTable.ReadInt32();
int FloatArraySize = bTreeTable.ReadInt32();
FloatArrayTable.Seek(FloatArrayOffset, SeekOrigin.Begin);
List<string> StrList = new List<string>();
for(int i = 0; i < FloatArraySize; i++)
{
FloatValue = bFloatArrayTable.ReadSingle();
StrList.Add(Tools.RemoveDecimals(FloatValue));
}
string[] StrArray = StrList.ToArray();
AttributeValue = String.Join(", ", StrArray);
break;
case AttributeType.TYPE_FILE:
int FilePtr = bTreeTable.ReadInt32();
int FileSz = bTreeTable.ReadInt32();
String FileName = "";
Byte[] FileData = new Byte[FileSz];
FileTable.Seek(FilePtr, SeekOrigin.Begin);
FileTable.Read(FileData, 0, FileSz);
if (!Directory.Exists("files"))
Directory.CreateDirectory("files");
int count = 0;
string CounterStr = count.ToString();
do
{
String Extension = Tools.GetFileExtension(FileData);
CounterStr = count.ToString();
if (count == 0)
CounterStr = "";
FileName = "files/" + ElementName + "-" + AttributeName + CounterStr + Extension;
count++;
}
while (File.Exists(FileName));
Console.WriteLine("Writing: " + FileName);
if(Path.GetExtension(FileName) == ".zlib")
{
Console.WriteLine("Decompressing " + FileName);
Byte[] DecompressedData = ZlibStream.UncompressBuffer(FileData);
String Extension = Tools.GetFileExtension(DecompressedData);
if (!Directory.Exists("files/decompressed"))
Directory.CreateDirectory("files/decompressed");
String DecompressedFilename = "files/decompressed/" + ElementName + "-" + AttributeName + CounterStr + Extension;
Console.WriteLine("Decompressed file written to: " + DecompressedFilename);
File.WriteAllBytes(DecompressedFilename, DecompressedData);
if(Extension == ".gim")
{
if(File.Exists("GimConv/GimConv.exe"))
{
if (!Directory.Exists("files/converted"))
Directory.CreateDirectory("files/converted");
Console.WriteLine("Decoding GIM.");
Process Proc = new Process();
Proc.StartInfo.FileName = "GimConv/GimConv.exe";
Proc.StartInfo.Arguments = Path.GetFileName(DecompressedFilename) + " -o " + "../converted/" + ElementName + "-" + AttributeName + CounterStr + ".png";
Proc.StartInfo.RedirectStandardOutput = true;
Proc.StartInfo.RedirectStandardError = true;
Proc.StartInfo.UseShellExecute = false;
Proc.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory()+ "/files/decompressed";
Proc.Start();
Proc.WaitForExit();
Console.WriteLine(Proc.StandardOutput.ReadToEnd());
Console.WriteLine("Done!");
}
}
}
File.WriteAllBytes(FileName, FileData);
AttributeValue = FileName;
break;
case AttributeType.TYPE_ID_STRING_LOOPBACK:
int StringIdTableOffset = bTreeTable.ReadInt32();
AttributeValue = Tools.ReadStringAt(StringIDTable, StringIdTableOffset + 0x4);
TreeTable.Seek(4, SeekOrigin.Current);
break;
case AttributeType.TYPE_ID_STRING:
Console.WriteLine("UNSUPPORTED TYPE @ " + TreeTable.Position);
Console.ReadKey();
break;
case AttributeType.TYPE_ID_INT_LOOPBACK:
int IntIdTableOffset = bTreeTable.ReadInt32();
IntIDTable.Seek(IntIdTableOffset, SeekOrigin.Begin);
int Loopback = bIntIDTable.ReadInt32();
int IDValue = bIntIDTable.ReadInt32();
int StringPtr = Tools.ReadIntAt(TreeTable, Loopback);
String LoopbackAttribute = Tools.ReadStringAt(StringTable, StringPtr);
Console.WriteLine("Loopback: " + LoopbackAttribute);
AttributeValue = IDValue.ToString("X8");
TreeTable.Seek(4, SeekOrigin.Current);
break;
case AttributeType.TYPE_ID_INT:
IntIdTableOffset = bTreeTable.ReadInt32();
IntIDTable.Seek(IntIdTableOffset + 4, SeekOrigin.Begin);
IDValue = bIntIDTable.ReadInt32();
AttributeValue = IDValue.ToString("X8");
TreeTable.Seek(4, SeekOrigin.Current);
break;
default:
Console.WriteLine("UNKNOWN TYPE @ " + TreeTable.Position);
break;
};
Console.WriteLine(AttributeName + "=" + AttributeValue.ToString());
XMLFile.WriteAttributeString(AttributeName, AttributeValue.ToString());
XMLFile.Flush();
}
public static void ReadElement()
{
int ElementPtr = bTreeTable.ReadInt32();
int NumAttributes = bTreeTable.ReadInt32();
int ParentPtr = bTreeTable.ReadInt32();
int PrevSibling = bTreeTable.ReadInt32();
int NextSibling = bTreeTable.ReadInt32();
int FirstChild = bTreeTable.ReadInt32();
int LastChild = bTreeTable.ReadInt32();
String ElementName = Tools.ReadStringAt(StringTable, ElementPtr);
Console.WriteLine("Creating Element: " + ElementName);
Console.WriteLine("Attribute Count: " + NumAttributes);
Console.WriteLine("ParentPtr: " + ParentPtr);
Console.WriteLine("PrevSibling: " + PrevSibling);
Console.WriteLine("NextSibling: " + NextSibling);
Console.WriteLine("FirstChild: " + FirstChild);
Console.WriteLine("LastChild: " + LastChild);
XMLFile.WriteStartElement(ElementName);
if(NumAttributes > 0)
{
for (int i = 0; i < NumAttributes; i++)
{
ReadAttribute(ElementName);
}
}
if (FirstChild != -1)
{
TreeTable.Seek(FirstChild, SeekOrigin.Begin);
ReadElement();
}
XMLFile.WriteEndElement();
XMLFile.Flush();
if (NextSibling != -1)
{
TreeTable.Seek(NextSibling, SeekOrigin.Begin);
ReadElement();
}
}
}
}