コード例 #1
0
        public static void Main(string[] args)
        {
            string projectName;
            var    process = FindSuitableProcess(out projectName);

            if (process == null)
            {
                return;
            }

            var outputPath = Path.Combine("schemas", projectName);

            Directory.CreateDirectory(outputPath);

            using (var memory = new ProcessMemory())
            {
                if (memory.Open(process) == false)
                {
                    return;
                }

                ExportExpressionFunctions(memory, outputPath);
                var enums = ExportEnums(memory, outputPath);
                ExportSchemas(memory, outputPath, enums);
            }
        }
コード例 #2
0
        private static uint HashKeyValueList(ProcessMemory memory, uint baseAddress, uint hash)
        {
            var entries     = new List <KeyValuePair <string, string> >();
            var listAddress = memory.ReadU32(baseAddress);

            var count = memory.ReadS32(listAddress + 8);

            if (count > 0)
            {
                var entriesAddress = memory.ReadU32(listAddress + 4);
                var data           = memory.ReadAllBytes(entriesAddress, count * 8);

                for (int i = 0; i < count; i++)
                {
                    var name  = memory.ReadStringZ(BitConverter.ToUInt32(data, (i * 8) + 0));
                    var value = memory.ReadStringZ(BitConverter.ToUInt32(data, (i * 8) + 4));
                    entries.Add(new KeyValuePair <string, string>(name, value));
                }
            }

            var sb = new StringBuilder();

            foreach (var entry in entries.OrderBy(e => e.Key.ToLowerInvariant()))
            {
                sb.Append(entry.Key);
                sb.Append(entry.Value);
            }

            return(Adler32.Hash(sb.ToString(), hash));
        }
コード例 #3
0
        private static string TranslateArgumentType(
            Natives.ExpressionArgument arg,
            ProcessMemory memory)
        {
            switch (arg.Type)
            {
            case 0x0000:
                return("void");

            case 0x0002:
                return("int");

            case 0x0004:
                return("flt");

            case 0x0005:
                return("intarray");

            case 0x0006:
                return("floatarray");

            case 0x0007:
                return("Vec3");

            case 0x0008:
                return("Vec4");

            case 0x0009:
                return("Mat4");

            case 0x000A:
                return("Quat");

            case 0x000B:
                return("str");

            case 0x000C:
                return("multivalarray");

            case 0x0080:
                return("entityarray");

            case 0x0081:
                return(memory.ReadStringZ(arg.TypeNamePointer));

            case 0x0082:
                return("MultiVal");

            case 0x2809:
                return("loc");
            }

            return(string.Format("*INV:{0:X8}*", arg.Type));
        }
コード例 #4
0
 private static bool Locate(ProcessMemory memory, out uint result, params LocateDelegate[] locators)
 {
     for (int i = 0, j = locators.Length - 1; i < locators.Length; i++, j--)
     {
         if (locators[j](memory, out result) == true)
         {
             return(true);
         }
     }
     result = 0;
     return(false);
 }
コード例 #5
0
        private static void ExportExpressionFunction(
            string itemName,
            uint pointer,
            ProcessMemory memory,
            TextWriter writer)
        {
            var sb = new StringBuilder();

            var func = memory.ReadStructure <Natives.ExpressionFunction>(pointer);

            sb.AppendFormat("[{0:X8}] ", func.Handler);

            var funcName = memory.ReadStringZ(func.NamePointer);
            var codeName = memory.ReadStringZ(func.ExprCodeNamePointer);

            var tags = new string[12];

            for (int i = 0; i < 12; i++)
            {
                tags[i] = memory.ReadStringZ(func.Tags[i]);
            }

            var validTags = tags
                            .Where(t => string.IsNullOrWhiteSpace(t) == false)
                            .ToArray();

            if (validTags.Length > 0)
            {
                sb.AppendFormat("{0} : ",
                                string.Join(", ", validTags));
            }

            sb.AppendFormat("{0} {1}(",
                            TranslateArgumentType(func.ReturnType, memory),
                            funcName);

            for (int i = 0; i < func.ArgumentCount; i++)
            {
                if (i > 0)
                {
                    sb.Append(", ");
                }

                sb.AppendFormat("{0} {1}",
                                TranslateArgumentType(func.Arguments[i], memory),
                                memory.ReadStringZ(func.Arguments[i].NamePointer));
            }

            sb.Append(")");

            writer.WriteLine(sb.ToString());
        }
コード例 #6
0
        private static void ExportExpressionFunctions(ProcessMemory memory, string outputPath)
        {
            // expression functions
            var exprFuncs = new List <KeyValuePair <string, uint> >();

            uint stashPointer;

            if (Locate(memory,
                       out stashPointer,
                       Locators.ExpressionFunctionTableLocator1.Locate,
                       Locators.ExpressionFunctionTableLocator2.Locate) == false)
            {
                Console.WriteLine("Warning: failed to locate expression function table.");
                return;
            }

            var stashCount        = memory.ReadS32(stashPointer + 0x08);
            var stashEntryPointer = memory.ReadU32(stashPointer + 0x14);
            var stashEntries      = memory.ReadAllBytes(stashEntryPointer, stashCount * 8);

            for (int i = 0; i < stashCount; i++)
            {
                var namePointer = BitConverter.ToUInt32(stashEntries, (i * 8) + 0);
                var dataPointer = BitConverter.ToUInt32(stashEntries, (i * 8) + 4);

                if (namePointer == 0 &&
                    dataPointer == 0)
                {
                    continue;
                }

                var name = memory.ReadStringZ(namePointer);
                exprFuncs.Add(new KeyValuePair <string, uint>(name, dataPointer));
            }

            if (exprFuncs.Count > 0)
            {
                using (var output = File.Create(Path.Combine(outputPath, "expression functions.txt")))
                {
                    var writer = new StreamWriter(output);
                    foreach (var kv in exprFuncs.OrderBy(kv => kv.Key))
                    {
                        ExportExpressionFunction(kv.Key, kv.Value, memory, writer);
                    }
                    writer.Flush();
                }
            }
        }
コード例 #7
0
        private static string GetEnumType(ProcessMemory memory, uint baseAddress)
        {
            switch (memory.ReadU32(baseAddress))
            {
            case 1:
            {
                return("int");
            }

            case 2:
            {
                return("string");
            }
            }

            throw new NotSupportedException();
        }
コード例 #8
0
        private static void ExportEnum(
            ProcessMemory memory,
            uint address,
            IEnumerable <KeyValuePair <string, string> > elements,
            XmlWriter xml)
        {
            xml.WriteStartElement("elements");
            xml.WriteAttributeString("type", GetEnumType(memory, address));

            foreach (var kv in elements)
            {
                xml.WriteStartElement("element");
                xml.WriteAttributeString("name", kv.Key);
                xml.WriteValue(kv.Value);
                xml.WriteEndElement();
            }

            xml.WriteEndElement();
        }
コード例 #9
0
        private static List <KeyValuePair <string, string> > ReadKeyValueList(ProcessMemory memory, uint baseAddress)
        {
            var entries     = new List <KeyValuePair <string, string> >();
            var listAddress = memory.ReadU32(baseAddress);

            if (listAddress != 0)
            {
                var count = memory.ReadS32(listAddress + 8);
                if (count > 0)
                {
                    var entriesAddress = memory.ReadU32(listAddress + 4);
                    var data           = memory.ReadAllBytes(entriesAddress, count * 8);

                    for (int i = 0; i < count; i++)
                    {
                        var name  = memory.ReadStringZ(BitConverter.ToUInt32(data, (i * 8) + 0));
                        var value = memory.ReadStringZ(BitConverter.ToUInt32(data, (i * 8) + 4));
                        entries.Add(new KeyValuePair <string, string>(name, value));
                    }
                }
            }

            return(entries);
        }
コード例 #10
0
        public static uint HashTable(ProcessMemory memory, uint address, uint hash)
        {
            var columns        = new List <KeyValuePair <Natives.ParserColumn, string> >();
            var currentAddress = address;

            while (true)
            {
                var column = memory.ReadStructure <Natives.ParserColumn>(currentAddress);
                currentAddress += 40;

                var name = memory.ReadStringZ(column.NamePointer);

                if (column.Type == 0)
                {
                    if (string.IsNullOrEmpty(name) == true)
                    {
                        break;
                    }
                }

                columns.Add(new KeyValuePair <Natives.ParserColumn, string>(column, name));
            }

            foreach (var kv in columns)
            {
                var column = kv.Key;

                if (column.Flags.HasAnyOptions(Parser.ColumnFlags.REDUNDANTNAME |
                                               Parser.ColumnFlags.UNOWNED) == true)
                {
                    continue;
                }

                var name = kv.Value;

                if (string.IsNullOrEmpty(name) == false)
                {
                    hash = Adler32.Hash(name, hash);
                }

                hash = Adler32.Hash(column.Type, hash);

                var token = Parser.GlobalTokens.GetToken(column.Token);

                switch (token.GetParameter(column.Flags, 0))
                {
                case Parser.ColumnParameter.NumberOfElements:
                case Parser.ColumnParameter.Default:
                case Parser.ColumnParameter.StringLength:
                case Parser.ColumnParameter.Size:
                {
                    hash = Adler32.Hash(column.Parameter0, hash);
                    break;
                }

                case Parser.ColumnParameter.BitOffset:
                {
                    hash = Adler32.Hash(column.Parameter0 >> 16, hash);
                    break;
                }

                case Parser.ColumnParameter.DefaultString:
                case Parser.ColumnParameter.CommandString:
                {
                    if (column.Parameter0 != 0)
                    {
                        hash = Adler32.Hash(memory.ReadStringZ(column.Parameter0), hash);
                    }
                    break;
                }
                }

                var param1 = token.GetParameter(column.Flags, 1);

                if (column.Parameter1 != 0 &&
                    (column.Token == 20 || column.Token == 21) &&
                    address != column.Parameter1 &&
                    column.Flags.HasAnyOptions(Parser.ColumnFlags.STRUCT_NORECURSE) == false)
                {
                    hash = HashTable(memory, column.Parameter1, hash);
                }

                if (column.Parameter1 != 0 &&
                    param1 == Parser.ColumnParameter.StaticDefineList)
                {
                    hash = HashStaticDefineList(memory, column.Parameter1, hash);
                }

                if (column.Token == 23)
                {
                    var formatString = memory.ReadStringZ(column.FormatStringPointer);
                    if (string.IsNullOrEmpty(formatString) == false)
                    {
                        hash = Adler32.Hash(formatString, hash);
                    }
                }
            }

            return(hash);
        }
コード例 #11
0
        public static uint HashStaticDefineList(ProcessMemory memory, uint baseAddress, uint hash)
        {
            var valueType = 4;

            while (true)
            {
                var type = memory.ReadU32(baseAddress);
                if (type == 0)
                {
                    break;
                }

                switch (type)
                {
                case 1:
                {
                    valueType    = 1;
                    baseAddress += 8;
                    break;
                }

                case 2:
                {
                    valueType    = 2;
                    baseAddress += 8;
                    break;
                }

                case 3:
                {
                    var listAddress = memory.ReadU32(baseAddress + 4);
                    baseAddress += 8;

                    if (listAddress != 0)
                    {
                        hash = HashKeyValueList(memory, listAddress, hash);
                    }

                    break;
                }

                case 5:
                {
                    var parent = memory.ReadU32(baseAddress + 4);
                    return(HashStaticDefineList(memory, parent, hash));
                }

                default:
                {
                    var name = memory.ReadStringZ(type);
                    hash = Adler32.Hash(name, hash);

                    switch (valueType)
                    {
                    case 1:
                    {
                        var value = memory.ReadU32(baseAddress + 4);
                        hash         = Adler32.Hash(value, hash);
                        baseAddress += 8;
                        break;
                    }

                    case 2:
                    {
                        var value = memory.ReadStringZ(baseAddress + 4);
                        hash         = Adler32.Hash(value, hash);
                        baseAddress += 8;
                        break;
                    }

                    default:
                    {
                        throw new NotImplementedException();
                    }
                    }

                    break;
                }
                }
            }

            return(hash);
        }
コード例 #12
0
        private static void ExportSchemas(ProcessMemory memory,
                                          string outputPath,
                                          List <KeyValuePair <string, uint> > enums)
        {
            uint stashPointer;

            if (Locate(memory,
                       out stashPointer,
                       Locators.ParserTableLocator1.Locate,
                       Locators.ParserTableLocator2.Locate) == false)
            {
                Console.WriteLine("Warning: failed to locate schema table.");
                return;
            }

            var parsers = new List <KeyValuePair <string, uint> >();

            var stashCount        = memory.ReadS32(stashPointer + 0x08);
            var stashEntryPointer = memory.ReadU32(stashPointer + 0x14);
            var stashEntries      = memory.ReadAllBytes(stashEntryPointer, stashCount * 8);

            for (int i = 0; i < stashCount; i++)
            {
                var namePointer = BitConverter.ToUInt32(stashEntries, (i * 8) + 0);
                var dataPointer = BitConverter.ToUInt32(stashEntries, (i * 8) + 4);

                if (namePointer == 0 &&
                    dataPointer == 0)
                {
                    continue;
                }

                var name = memory.ReadStringZ(namePointer);

                parsers.Add(new KeyValuePair <string, uint>(name, dataPointer));
            }

            if (parsers.Count > 0)
            {
                Directory.CreateDirectory(Path.Combine(outputPath, "schemas"));
                foreach (var kv in parsers.OrderBy(kv => kv.Key))
                {
                    var name    = kv.Key;
                    var pointer = kv.Value;

                    Console.WriteLine("[schema] {0}", name);

                    var settings = new XmlWriterSettings()
                    {
                        Indent = true,
                    };

                    var schemaPath = Path.Combine(outputPath, "schemas", name + ".schema.xml");
                    using (var xml = XmlWriter.Create(schemaPath, settings))
                    {
                        xml.WriteStartDocument();
                        xml.WriteStartElement("parser");
                        xml.WriteAttributeString("name", name);

                        xml.WriteStartElement("table");
                        ExportParserTable(memory, pointer, xml, parsers, enums);
                        xml.WriteEndElement();

                        xml.WriteEndElement();
                        xml.WriteEndDocument();
                    }
                }
            }
        }
コード例 #13
0
        private static IEnumerable <KeyValuePair <string, string> > ReadEnum(ProcessMemory memory, uint baseAddress)
        {
            var elements = new List <KeyValuePair <string, string> >();

            var valueType = 4;

            while (true)
            {
                var type = memory.ReadU32(baseAddress);
                if (type == 0)
                {
                    break;
                }

                switch (type)
                {
                case 1:
                {
                    valueType    = 1;
                    baseAddress += 8;
                    break;
                }

                case 2:
                {
                    valueType    = 2;
                    baseAddress += 8;
                    break;
                }

                // dynamic bullshit
                case 3:
                {
                    baseAddress += 8;
                    break;
                }

                case 5:
                {
                    var parent = memory.ReadU32(baseAddress + 4);
                    var nested = ReadEnum(memory, parent);
                    elements.AddRange(nested);
                    return(elements);
                }

                default:
                {
                    var name = memory.ReadStringZ(type);

                    object value;
                    switch (valueType)
                    {
                    case 1:
                    {
                        value        = memory.ReadS32(baseAddress + 4);
                        baseAddress += 8;
                        break;
                    }

                    case 2:
                    {
                        value        = memory.ReadStringZ(baseAddress + 4);
                        baseAddress += 8;
                        break;
                    }

                    default:
                    {
                        throw new NotImplementedException();
                    }
                    }

                    elements.Add(new KeyValuePair <string, string>(name, value.ToString()));
                    break;
                }
                }
            }

            return(elements);
        }
コード例 #14
0
        private static void ExportParserTable(
            ProcessMemory memory,
            uint address,
            XmlWriter xml,
            List <KeyValuePair <string, uint> > parsers,
            List <KeyValuePair <string, uint> > enums)
        {
            /*xml.WriteComment(string.Format(" {0:X8} ",
             *  0x00400000u + (address - (uint)memory.MainModuleAddress.ToInt32())));*/

            var currentAddress = address;

            var columns = new List <KeyValuePair <Natives.ParserColumn, string> >();

            while (true)
            {
                var column = memory.ReadStructure <Natives.ParserColumn>(currentAddress);
                currentAddress += 40;

                var name = memory.ReadStringZ(column.NamePointer);

                if (column.Type == 0)
                {
                    if (string.IsNullOrEmpty(name) == true)
                    {
                        break;
                    }
                }

                columns.Add(new KeyValuePair <Natives.ParserColumn, string>(column, name));
            }

            foreach (var kv in columns)
            {
                var column = kv.Key;
                var name   = kv.Value;

                xml.WriteStartElement("column");

                if (string.IsNullOrEmpty(name) == false)
                {
                    xml.WriteAttributeString("name", name);
                }

                var token = Parser.GlobalTokens.GetToken(column.Token);

                Parser.ColumnFlags flags;
                var tokenName = GetTokenName(column, out flags);

                xml.WriteAttributeString("type", tokenName);

                if (column.Token != 0 &&
                    column.Token != 1 &&
                    column.Token != 2)
                {
                    xml.WriteElementString("offset", column.Offset.ToString(CultureInfo.InvariantCulture));
                }

                var values = new List <string>();

                if (column.Flags.HasAnyOptions(Parser.ColumnFlags.PARSETABLE_INFO) == true)
                {
                    xml.WriteStartElement("flags");
                    xml.WriteElementString("flag", "PARSETABLE_INFO");
                    xml.WriteEndElement();

                    /* Star Trek Online replaces its format string with a
                     * stash table of parsed tokens.
                     */
                    string formatString = null;
                    if (column.FormatStringPointer != 0)
                    {
                        if (memory.ReadU32(column.FormatStringPointer) == 33)
                        {
                            formatString = memory.ReadStringZ(memory.ReadU32(column.FormatStringPointer + 4));
                        }
                        else
                        {
                            formatString = memory.ReadStringZ(column.FormatStringPointer);
                        }
                    }

                    if (string.IsNullOrEmpty(formatString) == false)
                    {
                        xml.WriteStartElement("format_strings");
                        ExportFormatStrings(xml, formatString);
                        xml.WriteEndElement();
                    }
                }
                else
                {
                    if ((flags & _ColumnFlagsMask) != 0)
                    {
                        xml.WriteStartElement("flags");

                        foreach (var flag in _ColumnFlagNames)
                        {
                            if ((flags & flag.Key) != 0)
                            {
                                xml.WriteElementString("flag", flag.Value);
                            }
                        }

                        xml.WriteEndElement();
                    }

                    if (column.Flags.HasAnyOptions(Parser.ColumnFlags.REDUNDANTNAME) == true)
                    {
                        var aliased = columns
                                      .Where(c => c.Key != column)
                                      .Where(c => c.Key.Flags.HasAnyOptions(Parser.ColumnFlags.REDUNDANTNAME) == false)
                                      .Where(c => c.Key.Offset == column.Offset)
                                      .Where(c => c.Key.Token == column.Token)
                                      .FirstOrDefault(c => c.Key.Token != 23 || c.Key.Parameter0 == column.Parameter0);
                        if (aliased.Key != null)
                        {
                            xml.WriteElementString("redundant_name", aliased.Value);
                        }
                    }
                    //else
                    {
                        if (column.MinBits != 0)
                        {
                            if (column.Token == 7)
                            {
                                xml.WriteElementString("float_rounding", _FloatRounding[column.MinBits]);
                            }
                            else
                            {
                                xml.WriteElementString("min_bits", column.MinBits.ToString(CultureInfo.InvariantCulture));
                            }
                        }

                        switch (token.GetParameter(column.Flags, 0))
                        {
                        case Parser.ColumnParameter.NumberOfElements:
                        {
                            xml.WriteElementString(
                                "num_elements",
                                ((int)column.Parameter0).ToString(CultureInfo.InvariantCulture));
                            break;
                        }

                        case Parser.ColumnParameter.Default:
                        {
                            if (column.Parameter0 != 0)
                            {
                                xml.WriteElementString(
                                    "default",
                                    ((int)column.Parameter0).ToString(CultureInfo.InvariantCulture));
                            }
                            break;
                        }

                        case Parser.ColumnParameter.StringLength:
                        {
                            xml.WriteElementString(
                                "string_length",
                                ((int)column.Parameter0).ToString(CultureInfo.InvariantCulture));
                            break;
                        }

                        case Parser.ColumnParameter.DefaultString:
                        {
                            if (column.Parameter0 != 0)
                            {
                                xml.WriteElementString("default_string", memory.ReadStringZ(column.Parameter0) ?? "");
                            }
                            break;
                        }

                        case Parser.ColumnParameter.CommandString:
                        {
                            if (column.Parameter0 != 0)
                            {
                                xml.WriteElementString("command_string", memory.ReadStringZ(column.Parameter0) ?? "");
                            }
                            break;
                        }

                        case Parser.ColumnParameter.Size:
                        {
                            xml.WriteElementString(
                                "size",
                                ((int)column.Parameter0).ToString(CultureInfo.InvariantCulture));
                            break;
                        }

                        case Parser.ColumnParameter.BitOffset:
                        {
                            xml.WriteElementString(
                                "bit_offset",
                                ((int)column.Parameter0).ToString(CultureInfo.InvariantCulture));
                            break;
                        }
                        }

                        switch (token.GetParameter(column.Flags, 1))
                        {
                        case Parser.ColumnParameter.StaticDefineList:
                        {
                            if (column.Parameter1 != 0)
                            {
                                xml.WriteStartElement("static_define_list");

                                var possibleEnum = enums.SingleOrDefault(e => e.Value == column.Parameter1);
                                if (possibleEnum.Key == null)
                                {
                                    xml.WriteComment(" dynamic enum? ");

                                    /*xml.WriteStartElement("enum");
                                     *  ExportEnum(memory, column.Parameter1, xml);
                                     *  xml.WriteEndElement();*/
                                }
                                else
                                {
                                    xml.WriteAttributeString("external", possibleEnum.Key);
                                }

                                //xml.WriteComment(string.Format(" {0:X8} ", column.Parameter1));
                                xml.WriteEndElement();
                            }
                            break;
                        }

                        case Parser.ColumnParameter.DictionaryName:
                        {
                            if (column.Parameter1 != 0)
                            {
                                xml.WriteElementString("dictionary_name",
                                                       memory.ReadStringZ(column.Parameter1) ?? "");
                            }
                            break;
                        }

                        case Parser.ColumnParameter.Subtable:
                        {
                            if (column.Parameter1 == 0)
                            {
                                xml.WriteElementString("subtable", "NULL?");
                            }
                            else
                            {
                                xml.WriteStartElement("subtable");

                                var parser = parsers.SingleOrDefault(p => p.Value == column.Parameter1);
                                if (parser.Key == null)
                                {
                                    //xml.WriteElementString("subtable", "NOT FOUND? " + column.Parameter1.ToString("X*"));

                                    xml.WriteStartElement("table");
                                    ExportParserTable(memory, column.Parameter1, xml, parsers, enums);
                                    xml.WriteEndElement();
                                }
                                else
                                {
                                    xml.WriteAttributeString("external", parser.Key);
                                }

                                xml.WriteEndElement();
                            }
                            break;
                        }
                        }

                        var format = column.Format & 0xFF;
                        if (format != 0)
                        {
                            if (format < _FormatNames.Length &&
                                _FormatNames[format] != null)
                            {
                                xml.WriteElementString("format", _FormatNames[format]);
                            }
                            else
                            {
                                xml.WriteElementString("format_raw", format.ToString(CultureInfo.InvariantCulture));
                            }
                        }

                        /* Star Trek Online replaces its format string with a
                         * stash table of parsed tokens.
                         */
                        string formatString = null;
                        if (column.FormatStringPointer != 0)
                        {
                            if (memory.ReadU32(column.FormatStringPointer) == 33)
                            {
                                formatString = memory.ReadStringZ(memory.ReadU32(column.FormatStringPointer + 4));
                            }
                            else
                            {
                                formatString = memory.ReadStringZ(column.FormatStringPointer);
                            }
                        }

                        if (string.IsNullOrEmpty(formatString) == false)
                        {
                            xml.WriteStartElement("format_strings");
                            ExportFormatStrings(xml, formatString);
                            xml.WriteEndElement();
                        }
                    }
                }

                xml.WriteEndElement();
            }
        }
コード例 #15
0
        private static List <KeyValuePair <string, uint> > ExportEnums(ProcessMemory memory, string outputPath)
        {
            var enums = new List <KeyValuePair <string, uint> >();

            uint stashPointer;

            if (Locate(memory,
                       out stashPointer,
                       Locators.EnumTableLocator1.Locate,
                       Locators.EnumTableLocator2.Locate,
                       Locators.EnumTableLocator3.Locate) == false)
            {
                Console.WriteLine("Warning: failed to locate enum table.");
                return(enums);
            }

            var stashCount        = memory.ReadS32(stashPointer + 0x08);
            var stashEntryPointer = memory.ReadU32(stashPointer + 0x14);
            var stashEntries      = memory.ReadAllBytes(stashEntryPointer, stashCount * 8);

            var enumLocations = new Dictionary <uint, string>();

            for (int i = 0; i < stashCount; i++)
            {
                var namePointer = BitConverter.ToUInt32(stashEntries, (i * 8) + 0);
                var dataPointer = BitConverter.ToUInt32(stashEntries, (i * 8) + 4);

                if (namePointer == 0 &&
                    dataPointer == 0)
                {
                    continue;
                }

                var name = memory.ReadStringZ(namePointer);
                if (enumLocations.ContainsKey(dataPointer) == true)
                {
                    var otherName = enumLocations[dataPointer];
                    if (name != otherName)
                    {
                        // Cryptic pls
                        if (name != "PowerCategory" && otherName != "PowerCategories")
                        {
                            throw new InvalidOperationException();
                        }
                    }
                    continue;
                }

                enumLocations.Add(dataPointer, name);
            }

            if (enumLocations.Count > 0)
            {
                Directory.CreateDirectory(Path.Combine(outputPath, "enums"));

                foreach (var kv in enumLocations.OrderBy(kv => kv.Value))
                {
                    var name    = kv.Value;
                    var pointer = kv.Key;

                    var elements = ReadEnum(memory, pointer);
                    if (elements == null)
                    {
                        continue;
                    }

                    enums.Add(new KeyValuePair <string, uint>(name, pointer));

                    Console.WriteLine("[enum] {0}", name);

                    var settings = new XmlWriterSettings()
                    {
                        Indent = true,
                    };

                    using (
                        var xml = XmlWriter.Create(Path.Combine(outputPath, "enums", name + ".enum.xml"), settings))
                    {
                        xml.WriteStartDocument();
                        xml.WriteStartElement("enum");
                        xml.WriteAttributeString("name", name);

                        ExportEnum(memory, pointer, elements, xml);

                        xml.WriteEndElement();
                        xml.WriteEndDocument();
                    }
                }
            }

            return(enums);
        }