cxml-decompiler/AppInfoCli/CXML.cs

738 lines
27 KiB
C#

using Ionic.Zlib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using General;
using System.IO;
using System.Text;
using System.Xml;
namespace CXML
{
enum AttributeType
{
TYPE_NONE,
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING,
TYPE_CHAR,
TYPE_HASH_ID,
TYPE_INTEGER_ARRAY,
TYPE_FLOAT_ARRAY,
TYPE_FILE,
TYPE_ID_STRING_LOOPBACK,
TYPE_ID_STRING,
TYPE_ID_INT_LOOPBACK,
TYPE_ID_INT
};
class CXMLParser
{
private bool _checkMagicNumber()
{
String Magic = Tools.ReadStringAt(InfoFile, 0x00);
MagicNumber = Magic.Substring(0,4);
if (Magic.StartsWith("PSMA"))
{
return true;
}
else if(Magic.StartsWith("RCOF"))
{
return true;
}
else if (Magic.StartsWith("RCSF"))
{
return true;
}
else
{
return false;
}
}
String MainDir = "";
String FileDir = "";
String XMLFilename = "";
String MagicNumber = "";
FileStream InfoFile;
MemoryStream TreeTable;
MemoryStream StringIDTable;
MemoryStream IntIDTable;
MemoryStream StringTable;
MemoryStream CharTable;
MemoryStream HashIDTable;
MemoryStream IntArrayTable;
MemoryStream FloatArrayTable;
MemoryStream FileTable;
Boolean IsInitalized = false;
BinaryReader bTreeTable;
BinaryReader bIntIDTable;
BinaryReader bFloatArrayTable;
BinaryReader bIntArrayTable;
BinaryReader bHashIDTable;
BinaryReader bStringIDTable;
public class LoopbackHandler
{
public String FileName;
public String OldFileName;
public Int64 FilePointer;
}
List<LoopbackHandler> FileList = new List<LoopbackHandler>();
XmlWriter XMLFile;
public bool ProcessFiles = false;
public bool WaitExit = false;
public void Init(string path, bool CheckMagic = true)
{
InfoFile = File.Open(path, FileMode.Open, FileAccess.Read);
bool MagicValid = _checkMagicNumber();
if (CheckMagic)
{
if (!MagicValid)
{
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());
HashIDTable = Tools.ByteToStream(GetHashIDTable());
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);
bHashIDTable = new BinaryReader(HashIDTable);
bStringIDTable = new BinaryReader(StringIDTable);
MainDir = Path.GetFileNameWithoutExtension(path);
FileDir = Path.Combine(MainDir, "files");
XMLFilename = Path.GetFileNameWithoutExtension(path) + ".xml";
IsInitalized = true;
return;
}
public void Term()
{
InfoFile.Close();
TreeTable.Close();
StringIDTable.Close();
IntIDTable.Close();
StringTable.Close();
CharTable.Close();
HashIDTable.Close();
IntArrayTable.Close();
FloatArrayTable.Close();
FileTable.Close();
bTreeTable.Close();
bIntIDTable.Close();
bFloatArrayTable.Close();
bIntArrayTable.Close();
bHashIDTable.Close();
FileList.Clear();
IsInitalized = false;
}
public int GetTreeTableOffset()
{
return Tools.ReadIntAt(InfoFile,0x8);
}
public int GetTreeTableSize()
{
return Tools.ReadIntAt(InfoFile, 0xC);
}
public int GetIDStringTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x10);
}
public int GetIDStringTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x14);
}
public int GetIDIntTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x18);
}
public int GetIDIntTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x1C);
}
public int GetStringTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x20);
}
public int GetStringTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x24);
}
public int GetCharTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x28);
}
public int GetCharTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x2C);
}
public int GetHashIDTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x30);
}
public int GetHashIDTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x34);
}
public int GetIntArrayTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x38);
}
public int GetIntArrayTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x3C);
}
public int GetFloatArrayTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x40);
}
public int GetFloatArrayTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x44);
}
public int GetFileTableOffset()
{
return Tools.ReadIntAt(InfoFile, 0x48);
}
public int GetFileTableSize()
{
return Tools.ReadIntAt(InfoFile, 0x4C);
}
public 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 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 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 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 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 byte[] GetHashIDTable()
{
int HashIDTableOffset = GetHashIDTableOffset();
int HashIDTableSize = GetHashIDTableSize();
InfoFile.Seek(HashIDTableOffset, SeekOrigin.Begin);
byte[] HashIDTable = new byte[HashIDTableSize];
InfoFile.Read(HashIDTable, 0x00, HashIDTableSize);
return HashIDTable;
}
public 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 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 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 void DecompileCXML(String CXMLFile, bool force = false)
{
if (!IsInitalized)
Init(CXMLFile,force);
if (Directory.Exists(MainDir))
Directory.Delete(MainDir, true);
if (!Directory.Exists(FileDir))
Directory.CreateDirectory(FileDir);
XmlWriterSettings XMLSettings = new XmlWriterSettings();
XMLSettings.Indent = true;
string XMLPath = Path.Combine(MainDir, XMLFilename);
XMLFile = XmlWriter.Create(XMLPath, XMLSettings);
XMLFile.WriteStartDocument();
XMLFile.WriteComment("Decompiled with CXML Decompiler v6 By SilicaAndPina (Magic: \"" + MagicNumber + "\")");
ReadElements();
XMLFile.WriteEndDocument();
XMLFile.Flush();
XMLFile.Close();
// Make corrections
string XmlData = File.ReadAllText(XMLPath, Encoding.UTF8);
foreach(LoopbackHandler lpHandler in FileList)
{
if(lpHandler.OldFileName != null)
{
if(lpHandler.FileName != null)
{
string oldName = Path.Combine(lpHandler.OldFileName);
string extension = Path.GetExtension(oldName);
string folderPath = Path.GetDirectoryName(oldName);
string newPath = Path.ChangeExtension(Path.Combine(folderPath, lpHandler.FileName), extension);
File.Move(oldName, newPath);
string xmlRelOldPath = oldName.Substring(Tools.GetRootFolder(oldName).Length + 1);
string xmlRelNewPath = newPath.Substring(Tools.GetRootFolder(newPath).Length + 1);
Console.WriteLine("Moved " + xmlRelOldPath + " => " + xmlRelNewPath);
XmlData = XmlData.Replace(xmlRelOldPath, xmlRelNewPath);
if (ProcessFiles)
ProcessFile(newPath);
}
else
{
Console.WriteLine("Generated Filename used for " + lpHandler.OldFileName);
if (ProcessFiles)
ProcessFile(lpHandler.OldFileName);
}
}
}
File.WriteAllText(XMLPath, XmlData);
Term();
}
public void ProcessFile(String FileName)
{
String Extension = Path.GetExtension(FileName);
if (Extension == ".rcs") // Recursive Decompile, remove this if statement to remove RCS processing
{
Console.WriteLine("Decompiling " + Path.GetFileName(FileName));
string DirectoryName = Path.Combine(FileDir, "converted", "RCStoXML", Path.GetFileNameWithoutExtension(FileName));
if (!Directory.Exists(DirectoryName))
Directory.CreateDirectory(DirectoryName);
try
{
CXMLParser cxmlParser = new CXMLParser();
cxmlParser.Init(FileName);
cxmlParser.ProcessFiles = this.ProcessFiles;
cxmlParser.MainDir = DirectoryName;
cxmlParser.FileDir = Path.Combine(cxmlParser.MainDir,"files");
cxmlParser.DecompileCXML(FileName);
}
catch (Exception) { };
}
if (Extension == ".vag") //Remove this IF statment if you dont want VAG Conversion.
{
Console.WriteLine("Decoding: " + Path.GetFileName(FileName));
byte[] WaveData = VAG.VAGAudio.Vag2Wav(FileName);
String WaveName = Path.GetFileNameWithoutExtension(FileName) + "-" + VAG.VAGAudio.GetFilename(FileName) + ".wav";
String DirectoryName = Path.Combine(FileDir , "converted", "VAGtoWAV");
if (!Directory.Exists(DirectoryName))
Directory.CreateDirectory(DirectoryName);
File.WriteAllBytes(Path.Combine(DirectoryName, WaveName), WaveData);
Console.WriteLine("Decoded file written to: " + WaveName);
}
if (Extension == ".gim") //Remove this IF statement if you dont want GIM Conversion.
{
if (File.Exists(Path.Combine("GimConv", "GimConv.exe")))
{
String DirectoryName = Path.Combine(FileDir, "converted", "GIMtoPNG");
if (!Directory.Exists(DirectoryName))
Directory.CreateDirectory(DirectoryName);
Console.WriteLine("Decoding GIM.");
Process Proc = new Process();
Proc.StartInfo.FileName = Path.Combine("GimConv", "GimConv.exe");
Proc.StartInfo.Arguments = Path.GetFileName(FileName) + " -o " + Path.Combine(Environment.CurrentDirectory, DirectoryName, Path.GetFileName(Path.ChangeExtension(FileName, "png")));
Proc.StartInfo.RedirectStandardOutput = true;
Proc.StartInfo.RedirectStandardError = true;
Proc.StartInfo.UseShellExecute = false;
Proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(FileName);
Proc.Start();
Proc.WaitForExit();
Console.WriteLine(Proc.StandardOutput.ReadToEnd());
Console.WriteLine("Done!");
}
}
if (Extension == ".z")
{
Console.WriteLine("Decompressing " + FileName);
Byte[] FileData = File.ReadAllBytes(FileName);
Byte[] DecompressedData = ZlibStream.UncompressBuffer(FileData);
Extension = Tools.GetFileExtension(DecompressedData);
String DirectoryName = Path.Combine(FileDir, "decompressed");
if (!Directory.Exists(DirectoryName))
Directory.CreateDirectory(DirectoryName);
String DecompressedFilename = Path.Combine(DirectoryName, Path.ChangeExtension(Path.GetFileNameWithoutExtension(FileName), Extension));
Console.WriteLine("Decompressed file written to: " + DecompressedFilename);
File.WriteAllBytes(DecompressedFilename, DecompressedData);
ProcessFile(DecompressedFilename);
}
}
public void ChangeFilename(Int64 ElementPtr, String NewName, Boolean FileEntry)
{
Console.WriteLine("Adding Entry for Loopback: 0x" + ElementPtr.ToString("X8") + " (" + NewName + ")");
for(int i = 0; i < FileList.Count; i++)
{
if(FileList[i].FilePointer == ElementPtr)
{
if (FileEntry)
{
if (FileList[i].OldFileName != null)
break;
FileList[i].OldFileName = NewName;
return;
}
else
{
FileList[i].FileName = NewName;
return;
}
}
}
LoopbackHandler lpHandler = new LoopbackHandler();
if (FileEntry)
{
lpHandler.OldFileName = NewName;
}
else
{
lpHandler.FileName = NewName;
}
lpHandler.FilePointer = ElementPtr;
FileList.Add(lpHandler);
return;
}
public void ReadAttribute(String ElementName = "", Int64 ElementPtr = 0)
{
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();
Console.WriteLine("Int - Value: " + AttributeValue.ToString() + " sz:" + bTreeTable.ReadInt32());
break;
case AttributeType.TYPE_FLOAT:
float FloatValue = bTreeTable.ReadSingle();
AttributeValue = FloatValue.ToString()+"f";
Console.WriteLine("Float - Value: " + AttributeValue.ToString() + " sz:" + bTreeTable.ReadInt32());
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.UTF8.GetString(Encoding.UTF8.GetBytes(Encoding.Unicode.GetString(CharBytes)));
break;
case AttributeType.TYPE_HASH_ID:
int HashTableOffset = bTreeTable.ReadInt32();
int HashTableSize = bTreeTable.ReadInt32();
HashIDTable.Seek(HashTableOffset * 4, SeekOrigin.Begin);
Console.WriteLine("Hash ID Offset:" + HashTableOffset.ToString() + " size: " + HashTableSize);
int HashId = bHashIDTable.ReadInt32();
AttributeValue = "@"+HashId.ToString("X8");
break;
case AttributeType.TYPE_INTEGER_ARRAY:
int IntArrayOffset = bTreeTable.ReadInt32();
int IntArraySize = bTreeTable.ReadInt32();
IntArrayTable.Seek(IntArrayOffset*4, 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*4, SeekOrigin.Begin);
List<string> StrList = new List<string>();
for(int i = 0; i < FloatArraySize; i++)
{
FloatValue = bFloatArrayTable.ReadSingle();
StrList.Add(FloatValue.ToString()+"f");
}
string[] StrArray = StrList.ToArray();
AttributeValue = "[" + String.Join(", ", StrArray) + "]";
break;
case AttributeType.TYPE_FILE:
int FilePtr = bTreeTable.ReadInt32();
int FileSz = bTreeTable.ReadInt32();
Byte[] FileData = new Byte[FileSz];
FileTable.Seek(FilePtr, SeekOrigin.Begin);
FileTable.Read(FileData, 0, FileSz);
string FileHash = Tools.GenerateHash(FileData);
String Extension = Tools.GetFileExtension(FileData);
String FileName = Path.Combine(FileDir, ElementName, FileHash + Extension);
if (!File.Exists(FileName))
{
Console.WriteLine("Writing: " + FileName);
if (!Directory.Exists(Path.GetDirectoryName(FileName)))
Directory.CreateDirectory(Path.GetDirectoryName(FileName));
File.WriteAllBytes(FileName, FileData);
ChangeFilename(ElementPtr, FileName, true);
}
else
{
Console.WriteLine("File allready extracted \n(theres a VERY low chance that it acturally is a different file that has the same hash.)");
}
string xmlRelPath = FileName.Substring(Tools.GetRootFolder(FileName).Length + 1);
AttributeValue = xmlRelPath;
break;
case AttributeType.TYPE_ID_STRING_LOOPBACK:
int StringIdTableOffset = bTreeTable.ReadInt32();
StringIDTable.Seek(StringIdTableOffset, SeekOrigin.Begin);
int LoopbackPtr = bStringIDTable.ReadInt32();
int StringPtr = Tools.ReadIntAt(TreeTable, LoopbackPtr);
string LoopbackAttribute = Tools.ReadStringAt(StringTable, StringPtr);
Console.WriteLine("Loopback: " + LoopbackAttribute +" "+ LoopbackPtr.ToString("X")+" ("+ElementPtr.ToString("X")+")");
AttributeValue = Tools.ReadString(StringIDTable);
ChangeFilename(LoopbackPtr, AttributeValue.ToString(), false);
Console.WriteLine("Loopback String: " + StringIdTableOffset + " sz: " + bTreeTable.ReadInt32());
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);
LoopbackPtr = bIntIDTable.ReadInt32();
StringPtr = Tools.ReadIntAt(TreeTable, LoopbackPtr);
int IDValue = bIntIDTable.ReadInt32();
LoopbackAttribute = Tools.ReadStringAt(StringTable, StringPtr);
Console.WriteLine("Loopback: " + LoopbackAttribute + " " + LoopbackPtr.ToString("X") + " (" + ElementPtr.ToString("X") + ")");
AttributeValue = "$"+IDValue.ToString("X8");
ChangeFilename(LoopbackPtr, IDValue.ToString("X8"), false);
Console.WriteLine("Loopback Int: " + IntIdTableOffset + " sz: " + bTreeTable.ReadInt32());
break;
case AttributeType.TYPE_ID_INT:
IntIdTableOffset = bTreeTable.ReadInt32();
IntIDTable.Seek(IntIdTableOffset + 4, SeekOrigin.Begin);
IDValue = bIntIDTable.ReadInt32();
AttributeValue = "#"+IDValue.ToString("X8");
Console.WriteLine("Int Id: " + IntIdTableOffset + " sz: " + bTreeTable.ReadInt32());
break;
default:
Console.WriteLine("UNKNOWN TYPE @ " + TreeTable.Position);
break;
};
Console.WriteLine(AttributeName + "=" + AttributeValue.ToString());
XMLFile.WriteAttributeString(AttributeName, AttributeValue.ToString());
XMLFile.Flush();
}
public void ReadElements()
{
Int64 ElementLocation = TreeTable.Position;
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, ElementLocation);
}
}
if (FirstChild != -1)
{
TreeTable.Seek(FirstChild, SeekOrigin.Begin);
ReadElements();
}
XMLFile.WriteEndElement();
XMLFile.Flush();
if (NextSibling != -1)
{
TreeTable.Seek(NextSibling, SeekOrigin.Begin);
ReadElements();
}
}
}
}