using Ionic.Zlib;
using General;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Globalization;

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;
            }
        }

        public class TypingInformation
        {
            public string key;
            public string value;
        }
        String MainDir = "";
        String FileDir = "";
        String XMLFilename = "";
        String MagicNumber = "";

        List<TypingInformation> SilicaTypingInformationList = new List<TypingInformation>();

        String MagicReplacePattern = Tools.GenerateReplacePattern();
        String SilicaTypingInformation = "SilicaTypingInformation{{[[";
        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;

        List<LoopbackHandler> FileList = new List<LoopbackHandler>();

        XmlWriter XMLWriter;

        public bool ProcessFiles = false;
        public bool WaitExit = false;
        public bool NoRecursive = 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 int ReadVersion()
        {
            return Tools.ReadIntAt(InfoFile, 0x4);
        }
        public void AddTypingInfo(string key, object value)
        {
            foreach(TypingInformation tInfo in SilicaTypingInformationList)
            {
                if(tInfo.key == key)
                {
                    if (tInfo.value.ToString() != value.ToString())
                        throw new Exception("Very Weird CXML");
                    return;
                }    
            }
            TypingInformation typingInfo = new TypingInformation();
            typingInfo.key = key;
            typingInfo.value = value.ToString();
            SilicaTypingInformationList.Add(typingInfo);
        }
        public void Align(Stream s, int align)
        {

            int size = Convert.ToInt32(s.Position);
            if (size % align != 0)
            {
                int totalAlign = (align - (size % align));
                for (int i = 0; i < totalAlign; i++)
                {
                    s.Seek(0x1, SeekOrigin.Current);
                }
            }
        }
        public void GotoEnd()
        {
            InfoFile.Seek(GetFileTableOffset(), SeekOrigin.Begin);
            InfoFile.Seek(GetFileTableSize(), SeekOrigin.Current);
            Align(InfoFile, 0x10);
        }

        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;
            XMLSettings.Encoding = Encoding.UTF8;

            string XMLPath = Path.Combine(MainDir, XMLFilename);

            StreamWriter sw = new StreamWriter(new MemoryStream(), Encoding.UTF8);


            XMLWriter = XmlWriter.Create(sw, XMLSettings);
            XMLWriter.WriteStartDocument();
            XMLWriter.WriteComment(MagicReplacePattern);
            AddTypingInfo("MAGIC", MagicNumber);
            AddTypingInfo("VERSION", ReadVersion());
            ReadElements();
            XMLWriter.WriteEndDocument();
            XMLWriter.Flush();
            XMLWriter.Close();

            GotoEnd();
            int bytesRemaining = Convert.ToInt32(InfoFile.Length - InfoFile.Position);
            if (bytesRemaining > 0) // Some RCS files have unexplainable random XML Data at the end of them, Dont ask me. im just the messanger
            {
                Console.WriteLine(bytesRemaining.ToString("X8", CultureInfo.InvariantCulture) + " Excess Bytes Found, Shoving it in SilicaTypingInformation.");
                byte[] ExcessData = new byte[bytesRemaining];
                InfoFile.Read(ExcessData, 0x00, bytesRemaining);
                byte[] Compressed = ZlibStream.CompressBuffer(ExcessData);
                string base64 = Convert.ToBase64String(Compressed).Replace("=", "*");
                AddTypingInfo("EXCESS", base64);
            }
            for(int i = 0; i < SilicaTypingInformationList.Count; i++)
            {
                TypingInformation tinfo = SilicaTypingInformationList[i];
                SilicaTypingInformation += tinfo.key + "=" + tinfo.value;
                if(i+1 != SilicaTypingInformationList.Count)
                {
                    SilicaTypingInformation += ",";
                }
            }

            SilicaTypingInformation += "]]}}SilicaTypingInformation";
            byte[] XMLBytes = new byte[sw.BaseStream.Length];

            sw.Flush();
            sw.BaseStream.Seek(0x00, SeekOrigin.Begin);
            sw.BaseStream.Read(XMLBytes, 0x00, XMLBytes.Length);
            sw.Close();

            string XMLData = Encoding.UTF8.GetString(XMLBytes);

            // Resolve Replace Patterns

            XMLData = XMLData.Replace(MagicReplacePattern, SilicaTypingInformation);
            foreach(LoopbackHandler lpHandler in FileList)
            {
                if (lpHandler.ReplacePattern != null && lpHandler.OldFileName != null)
                {

                    string replacePattern = lpHandler.ReplacePattern;
                    string oldName = lpHandler.OldFileName;
                    string extension = Path.GetExtension(oldName);
                    string folderPath = Path.GetDirectoryName(oldName);
                    byte[] fileData = lpHandler.FileData;

                    if (lpHandler.FileName != null)
                    {

                        string newPath = Path.ChangeExtension(Path.Combine(folderPath, lpHandler.FileName), extension);

                        if (!Directory.Exists(folderPath))
                            Directory.CreateDirectory(folderPath);

                        if (!File.Exists(newPath))
                        {
                            File.WriteAllBytes(newPath, fileData);
                            string xmlRelNewPath = newPath.Substring(Tools.GetRootFolder(newPath).Length + 1);
                            Console.WriteLine("Resolved " + replacePattern + " => " + xmlRelNewPath);
                            XMLData = XMLData.Replace(replacePattern, xmlRelNewPath);
                        }

                        if (ProcessFiles)
                            ProcessFile(newPath);
                    }
                    else
                    {
                        Console.WriteLine("Unable to resolve: " + oldName);
                        if (!File.Exists(oldName))
                            File.WriteAllBytes(oldName, fileData);

                        if (ProcessFiles)
                            ProcessFile(oldName);
                    }
                }
                
            }

            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
            {
                if (NoRecursive)
                    return;

                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 RegisterFile(Int64 ElementPtr, String NewName, Boolean FileEntry, String ReplacePattern=null, byte[] FileData=null, String IdealName=null)
        {
            Console.WriteLine("Adding Entry for Loopback: 0x" + ElementPtr.ToString("X8", CultureInfo.InvariantCulture) + " (" + 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;
                        FileList[i].FileName = IdealName;
                        return;
                    }
                    else
                    {
                        FileList[i].FileName = NewName;
                        return;
                    }
                }
            }

            LoopbackHandler lpHandler = new LoopbackHandler();
            if (FileEntry)
            {
                lpHandler.OldFileName = NewName;
                lpHandler.FileName = IdealName;
                lpHandler.FileData = FileData;
                lpHandler.ReplacePattern = ReplacePattern;
            }
            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();
                    int sz = bTreeTable.ReadInt32();
                    Console.WriteLine("Int - Value: " + AttributeValue.ToString() + " sz:" + sz);
                    break;
                case AttributeType.TYPE_FLOAT:
                    float FloatValue = bTreeTable.ReadSingle();
                    AttributeValue = FloatValue.ToString("G9", CultureInfo.InvariantCulture) + "f";
                    sz = bTreeTable.ReadInt32();
                    Console.WriteLine("Float - Value: " + AttributeValue.ToString() + " sz:" + sz);
                    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);
                    Console.WriteLine("String: " + AttributeValue);
                    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)));
                    Console.WriteLine("Char: " + AttributeValue);
                    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(CultureInfo.InvariantCulture) + " size: " + HashTableSize);

                    int HashId = bHashIDTable.ReadInt32();

                    AttributeValue = HashId.ToString("X8", CultureInfo.InvariantCulture);

                    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) + "]";
                    Console.WriteLine("Int Array: " + AttributeValue.ToString());
                    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("G9", CultureInfo.InvariantCulture) +"f");
                    }
                    string[] StrArray = StrList.ToArray();
                    AttributeValue = "[" + String.Join(", ", StrArray) + "]";
                    Console.WriteLine("Float Array: " + AttributeValue.ToString());
                    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 FileSmallHash = Tools.GenerateShortHash(FileData);
                    String Extension = Tools.GetFileExtension(FileData);
                    String FileName = Path.Combine(FileDir, "original", FilePtr.ToString("X", CultureInfo.InvariantCulture) + "-" + FileHash + Extension);
                    String IdealName = ElementName + "-" + AttributeName + "-" + FileSmallHash + Extension;
                    String ReplacePattern = Tools.GenerateReplacePattern();

                    //File.WriteAllBytes(FileName, FileData);
                    RegisterFile(ElementPtr, FileName, true, ReplacePattern, FileData, IdealName);

                    AttributeValue = ReplacePattern;
                    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);
                    RegisterFile(LoopbackPtr, AttributeValue.ToString(), false);

                    sz = bTreeTable.ReadInt32();

                    Console.WriteLine("Loopback ID String: " + StringIdTableOffset + " sz: " + sz);
                    break;
                case AttributeType.TYPE_ID_STRING: // This is probably right, tbh
                    StringIdTableOffset = bTreeTable.ReadInt32();
                    StringIDTable.Seek(StringIdTableOffset + 4, SeekOrigin.Begin);

                    AttributeValue = Tools.ReadString(StringIDTable);

                    sz = bTreeTable.ReadInt32();
                    Console.WriteLine("ID String: " + StringIdTableOffset + " sz: " + sz);
                    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("Int Loopback: " + LoopbackAttribute + " " + LoopbackPtr.ToString("X", CultureInfo.InvariantCulture) + " (" + ElementPtr.ToString("X") + ")");
                    
                    AttributeValue = IDValue.ToString("X8", CultureInfo.InvariantCulture);
                    RegisterFile(LoopbackPtr, AttributeValue.ToString(), false);

                    sz = bTreeTable.ReadInt32();
                    Console.WriteLine("Loopback Int: " + IntIdTableOffset + " sz: " + sz);
                    break;
                case AttributeType.TYPE_ID_INT:
                    IntIdTableOffset = bTreeTable.ReadInt32();
                    IntIDTable.Seek(IntIdTableOffset + 4, SeekOrigin.Begin);
                    IDValue = bIntIDTable.ReadInt32();

                    AttributeValue = IDValue.ToString("X8", CultureInfo.InvariantCulture);
                    sz = bTreeTable.ReadInt32();
                    Console.WriteLine("Int Id: " + IntIdTableOffset + " sz: " + sz);
                    break;
                default:
                    Console.WriteLine("UNKNOWN TYPE @ " + TreeTable.Position);
                    Console.ReadKey();
                    break;
            };

            AddTypingInfo(ElementName + ":" + AttributeName, ((int)Type).ToString(CultureInfo.InvariantCulture));
            Console.WriteLine(AttributeName + "=" + AttributeValue.ToString());
            XMLWriter.WriteAttributeString(AttributeName, AttributeValue.ToString());
            XMLWriter.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);

            XMLWriter.WriteStartElement(ElementName);

            if(NumAttributes > 0)
            {
                for (int i = 0; i < NumAttributes; i++)
                {
                    ReadAttribute(ElementName, ElementLocation);
                }
            }

            if (FirstChild != -1)
            {
                TreeTable.Seek(FirstChild, SeekOrigin.Begin);
                ReadElements();
            }


            XMLWriter.WriteEndElement();
            XMLWriter.Flush();

            if (NextSibling != -1)
            {
                TreeTable.Seek(NextSibling, SeekOrigin.Begin);
                ReadElements();
            }

        }

    }
};
