From c6071a20b30d8155877d018a9e84fc0f9559a432 Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Sun, 8 Mar 2026 14:46:35 +0100 Subject: [PATCH 01/15] Add .idea in .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2b67f85..2c557a1 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ MigrationBackup/ # Private Providers DBCD/Providers/MirrorDBCProvider.cs + +# Rider +.idea/ From bba7d6b808704e43351a49890e3182779ae54105 Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Sun, 8 Mar 2026 14:51:02 +0100 Subject: [PATCH 02/15] Use DBDefsLib 1.0.1.1-beta and some minor optimizations and code style changes --- DBCD/DBCD.cs | 15 ++++------ DBCD/DBCD.csproj | 2 +- DBCD/DBCDBuilder.cs | 28 ++++++++---------- DBCD/DBCDStorage.cs | 72 +++++++++++++++++++-------------------------- 4 files changed, 50 insertions(+), 67 deletions(-) diff --git a/DBCD/DBCD.cs b/DBCD/DBCD.cs index 118c326..7f30cf8 100644 --- a/DBCD/DBCD.cs +++ b/DBCD/DBCD.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using DBDefsLib.Structs; namespace DBCD { @@ -14,7 +15,7 @@ public class DBCD private readonly IDBDProvider dbdProvider; private readonly bool useBDBD; - private readonly Dictionary BDBDCache; + private readonly Dictionary BDBDCache; /// /// Creates a DBCD instance that uses the given DBC and DBD providers. @@ -52,7 +53,7 @@ public IDBCDStorage Load(string tableName, string build = null, Locale locale = { var dbcStream = this.dbcProvider.StreamForTableName(tableName, build); - Structs.DBDefinition databaseDefinition; + DBDefinition databaseDefinition; if (!useBDBD) { @@ -73,12 +74,8 @@ public IDBCDStorage Load(string tableName, string build = null, Locale locale = var dbReader = new DBParser(dbcStream); var definition = builder.Build(dbReader, databaseDefinition, tableName, build); - var type = typeof(DBCDStorage<>).MakeGenericType(definition.Item1); - - return (IDBCDStorage)Activator.CreateInstance(type, new object[2] { - dbReader, - definition.Item2 - }); + var type = typeof(DBCDStorage<>).MakeGenericType(definition.Type); + return (IDBCDStorage)Activator.CreateInstance(type, dbReader, definition.Info); } } @@ -102,4 +99,4 @@ public enum Locale PtBR = PtPT, ItIT = 11, } -} \ No newline at end of file +} diff --git a/DBCD/DBCD.csproj b/DBCD/DBCD.csproj index 8628525..6d461d5 100644 --- a/DBCD/DBCD.csproj +++ b/DBCD/DBCD.csproj @@ -9,7 +9,7 @@ - + diff --git a/DBCD/DBCDBuilder.cs b/DBCD/DBCDBuilder.cs index 64f7f8e..f21aff9 100644 --- a/DBCD/DBCDBuilder.cs +++ b/DBCD/DBCDBuilder.cs @@ -7,10 +7,10 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using DBDefsLib.Structs; namespace DBCD { - public struct DBCDInfo { internal string tableName; @@ -20,9 +20,9 @@ public struct DBCDInfo internal class DBCDBuilder { - private ModuleBuilder moduleBuilder; - private int locStringSize; + private readonly ModuleBuilder moduleBuilder; private readonly Locale locale; + private int locStringSize; internal DBCDBuilder(Locale locale = Locale.None) { @@ -35,12 +35,11 @@ internal DBCDBuilder(Locale locale = Locale.None) this.locale = locale; } - internal Tuple Build(DBParser dbcReader, Structs.DBDefinition databaseDefinition, string name, string build) + internal (Type Type, DBCDInfo Info) Build(DBParser dbcReader, DBDefinition databaseDefinition, string name, string build) { - if (name == null) - name = Guid.NewGuid().ToString(); + name ??= Guid.NewGuid().ToString(); - Structs.VersionDefinitions? versionDefinition = null; + VersionDefinitions? versionDefinition = null; if (!string.IsNullOrWhiteSpace(build)) { @@ -69,17 +68,16 @@ internal Tuple Build(DBParser dbcReader, Structs.DBDefinition da var fields = versionDefinition.Value.definitions; var columns = new List(fields.Length); - bool localiseStrings = locale != Locale.None; + var localiseStrings = locale != Locale.None; var metadataIndex = 0; foreach (var fieldDefinition in fields) { var columnDefinition = databaseDefinition.columnDefinitions[fieldDefinition.name]; - bool isLocalisedString = columnDefinition.type == "locstring" && locStringSize > 1; - + var isLocalisedString = columnDefinition.type == "locstring" && locStringSize > 1; Type fieldType; - if (fieldDefinition.isRelation && fieldDefinition.isNonInline) + if (fieldDefinition is { isRelation: true, isNonInline: true }) { fieldType = fieldDefinition.arrLength == 0 ? typeof(int) : typeof(int[]); } @@ -95,8 +93,8 @@ internal Tuple Build(DBParser dbcReader, Structs.DBDefinition da if (fieldDefinition.isID) { AddAttribute(field, fieldDefinition.isNonInline); - } - + } + if (!fieldDefinition.isNonInline) { if (dbcReader.ColumnMeta != null && metadataIndex < dbcReader.ColumnMeta.Length) @@ -146,7 +144,7 @@ internal Tuple Build(DBParser dbcReader, Structs.DBDefinition da tableName = name }; - return new Tuple(type, info); + return (type, info); } private int GetLocStringSize(Build build) @@ -171,7 +169,7 @@ private void AddAttribute(FieldBuilder field, params object[] parameters) whe field.SetCustomAttribute(attributeBuilder); } - private Type FieldDefinitionToType(Structs.Definition field, Structs.ColumnDefinition column, bool localiseStrings) + private Type FieldDefinitionToType(Definition field, ColumnDefinition column, bool localiseStrings) { var isArray = field.arrLength != 0; diff --git a/DBCD/DBCDStorage.cs b/DBCD/DBCDStorage.cs index 444409c..245c930 100644 --- a/DBCD/DBCDStorage.cs +++ b/DBCD/DBCDStorage.cs @@ -1,11 +1,8 @@ using DBCD.Helpers; - using DBCD.IO; using DBCD.IO.Attributes; using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Dynamic; using System.IO; using System.Linq; @@ -18,7 +15,7 @@ public class DBCDRow : DynamicObject public int ID; private readonly dynamic raw; - private FieldAccessor fieldAccessor; + private readonly FieldAccessor fieldAccessor; internal DBCDRow(int ID, dynamic raw, FieldAccessor fieldAccessor) { @@ -81,25 +78,16 @@ internal DynamicKeyValuePair(T key, dynamic value) } } - public class RowConstructor + public class RowConstructor(IDBCDStorage storage) { - private readonly IDBCDStorage storage; - public RowConstructor(IDBCDStorage storage) - { - this.storage = storage; - } - public bool Create(int index, Action f) { var constructedRow = storage.ConstructRow(index); if (storage.ContainsKey(index)) return false; - else - { - f(constructedRow); - storage.Add(index, constructedRow); - } + f(constructedRow); + storage.Add(index, constructedRow); return true; } } @@ -129,6 +117,9 @@ public interface IDBCDStorage : IEnumerable>, IDictiona private readonly DBCDInfo info; private readonly DBParser parser; + private readonly (FieldInfo Field, Type ElementType, int Count, bool IsString)[] _arrayFieldCache; + private readonly FieldInfo[] _stringFieldCache; + string[] IDBCDStorage.AvailableColumns => this.info.availableColumns; public uint LayoutHash => this.storage.LayoutHash; public override string ToString() => $"{this.info.tableName}"; @@ -144,6 +135,17 @@ public DBCDStorage(DBParser parser, Storage storage, DBCDInfo info) : base(ne this.parser = parser; this.storage = storage; + var fields = typeof(T).GetFields(); + _arrayFieldCache = [.. fields + .Where(f => f.FieldType.IsArray) + .Select(f => + { + var elementType = f.FieldType.GetElementType()!; + var count = f.GetCustomAttribute()!.Count; + return (f, elementType, count, elementType == typeof(string)); + })]; + _stringFieldCache = [.. fields.Where(f => f.FieldType == typeof(string))]; + foreach (var record in storage) base.Add(record.Key, new DBCDRow(record.Key, record.Value, fieldAccessor)); } @@ -198,35 +200,21 @@ public void Save(string filename) public DBCDRow ConstructRow(int index) { T raw = new(); - var fields = typeof(T).GetFields(); - // Array Fields need to be initialized to fill their length - var arrayFields = fields.Where(x => x.FieldType.IsArray); - foreach (var arrayField in arrayFields) - { - var count = arrayField.GetCustomAttribute().Count; - var elementType = arrayField.FieldType.GetElementType(); - var isStringField = elementType == typeof(string); - Array rowRecords = Array.CreateInstance(elementType, count); - for (var i = 0; i < count; i++) - { - if (isStringField) - { - rowRecords.SetValue(string.Empty, i); - } else - { - rowRecords.SetValue(Activator.CreateInstance(elementType), i); - } - } - arrayField.SetValue(raw, rowRecords); + // Array fields: value type arrays are already zero-initialized by Array.CreateInstance; + // only string arrays need explicit filling. + foreach (var (field, elementType, count, isString) in _arrayFieldCache) + { + var arr = Array.CreateInstance(elementType, count); + if (isString) + Array.Fill((string[])arr, string.Empty); + field.SetValue(raw, arr); } - // String Fields need to be initialized to empty string rather than null; - var stringFields = fields.Where(x => x.FieldType == typeof(string)); - foreach (var stringField in stringFields) - { + // String fields must be initialized to empty string rather than null. + foreach (var stringField in _stringFieldCache) stringField.SetValue(raw, string.Empty); - } + return new DBCDRow(index, raw, fieldAccessor); } @@ -235,4 +223,4 @@ public Dictionary ToDictionary() return this; } } -} \ No newline at end of file +} From 7b084fd1a37e51db5ac4a480eafb41540d4bb0b6 Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Sun, 8 Mar 2026 17:35:48 +0100 Subject: [PATCH 03/15] Implement enum and flag mapping to field The ReadingTest has some usages on how the final syntax will look like --- DBCD.IO/Attributes/EnumAttribute.cs | 11 ++ DBCD.Tests/ReadingTest.cs | 27 +++- DBCD/DBCD.cs | 12 +- DBCD/DBCD.csproj | 2 +- DBCD/DBCDBuilder.cs | 183 ++++++++++++++++++----- DBCD/DBCDStorage.cs | 124 +++++++++++++-- DBCD/Providers/FilesystemEnumProvider.cs | 91 +++++++++++ DBCD/Providers/GithubEnumProvider.cs | 134 +++++++++++++++++ DBCD/Providers/IEnumProvider.cs | 21 +++ 9 files changed, 551 insertions(+), 54 deletions(-) create mode 100644 DBCD.IO/Attributes/EnumAttribute.cs create mode 100644 DBCD/Providers/FilesystemEnumProvider.cs create mode 100644 DBCD/Providers/GithubEnumProvider.cs create mode 100644 DBCD/Providers/IEnumProvider.cs diff --git a/DBCD.IO/Attributes/EnumAttribute.cs b/DBCD.IO/Attributes/EnumAttribute.cs new file mode 100644 index 0000000..849996d --- /dev/null +++ b/DBCD.IO/Attributes/EnumAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace DBCD.IO.Attributes +{ + [AttributeUsage(AttributeTargets.Field)] + public class EnumAttribute(string enumName, bool isFlags) : Attribute + { + public readonly string EnumName = enumName; + public readonly bool IsFlags = isFlags; + } +} diff --git a/DBCD.Tests/ReadingTest.cs b/DBCD.Tests/ReadingTest.cs index 92ac8a7..de90604 100644 --- a/DBCD.Tests/ReadingTest.cs +++ b/DBCD.Tests/ReadingTest.cs @@ -1,6 +1,7 @@ using DBCD.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Collections.Generic; using System.IO; namespace DBCD.Tests @@ -208,7 +209,6 @@ public void TestReadingAllDB2s() // System.Console.WriteLine($"B: {countBefore} => A: {countAfter}"); //} - //[TestMethod] //public void TestFilesystemDBDProvider() //{ @@ -217,5 +217,30 @@ public void TestReadingAllDB2s() // // Spell is present in Classic Era -> Retail: https://www.wowhead.com/spell=17/ // Assert.AreEqual("Power Word: Shield", storage[17]["Name_lang"]); //} + + [TestMethod] + public void TestEnumReadingSingle() + { + var dbcd = new DBCD(wagoDBCProvider, githubDBDProvider, new GithubEnumProvider(useCache: true)); + + var storage = dbcd.Load("SpellEffect", "12.0.1.66220"); + var spellEffectRow = storage[1177101]; + Assert.AreEqual(spellEffectRow.IsEnumMember("Effect", "SET_PLAYER_DATA_ELEMENT_ACCOUNT"), true); + } + + [TestMethod] + public void TestEnumReadingArray() + { + var dbcd = new DBCD(wagoDBCProvider, githubDBDProvider, new GithubEnumProvider(useCache: true)); + + var storage = dbcd.Load("SpellMisc", "12.0.1.66220"); + var spellMiscRow = storage[66253]; + + Assert.AreEqual(spellMiscRow.HasFlag("Attributes", 0, "ON_NEXT_SWING"), false); + Assert.AreEqual(spellMiscRow.HasFlag("Attributes", 0, "HIDDEN_CLIENTSIDE"), true); + + // Throws an exception because the Enum Member is not in the Enum + Assert.ThrowsException(() => spellMiscRow.HasFlag("Attributes", 1, "HIDDEN_CLIENTSIDE")); + } } } diff --git a/DBCD/DBCD.cs b/DBCD/DBCD.cs index 7f30cf8..d9159fb 100644 --- a/DBCD/DBCD.cs +++ b/DBCD/DBCD.cs @@ -8,11 +8,11 @@ namespace DBCD { - public class DBCD { private readonly IDBCProvider dbcProvider; private readonly IDBDProvider dbdProvider; + private readonly IEnumProvider enumProvider; private readonly bool useBDBD; private readonly Dictionary BDBDCache; @@ -22,10 +22,12 @@ public class DBCD /// /// The IDBCProvider for DBC files. /// The IDBDProvider for DBD files. - public DBCD(IDBCProvider dbcProvider, IDBDProvider dbdProvider) + /// The optional IEnumProvider for enum/flag metadata. + public DBCD(IDBCProvider dbcProvider, IDBDProvider dbdProvider, IEnumProvider enumProvider = null) { this.dbcProvider = dbcProvider; this.dbdProvider = dbdProvider; + this.enumProvider = enumProvider; this.useBDBD = false; } @@ -34,10 +36,12 @@ public DBCD(IDBCProvider dbcProvider, IDBDProvider dbdProvider) /// /// The IDBCProvider for DBC files. /// The stream for a BDBD (Binary DBD) file to load all definitions from. + /// The optional IEnumProvider for enum/flag metadata. /// WARNING: The usage of a BDBD file for supplying definitions is still experimental and currently has little to no advantages. - public DBCD(IDBCProvider dbcProvider, Stream bdbdStream) + public DBCD(IDBCProvider dbcProvider, Stream bdbdStream, IEnumProvider enumProvider = null) { this.dbcProvider = dbcProvider; + this.enumProvider = enumProvider; this.useBDBD = true; this.BDBDCache = BDBDReader.Read(bdbdStream); } @@ -69,7 +73,7 @@ public IDBCDStorage Load(string tableName, string build = null, Locale locale = databaseDefinition = tableInfo.dbd; } - var builder = new DBCDBuilder(locale); + var builder = new DBCDBuilder(locale, enumProvider); var dbReader = new DBParser(dbcStream); var definition = builder.Build(dbReader, databaseDefinition, tableName, build); diff --git a/DBCD/DBCD.csproj b/DBCD/DBCD.csproj index 6d461d5..add59fc 100644 --- a/DBCD/DBCD.csproj +++ b/DBCD/DBCD.csproj @@ -9,7 +9,7 @@ - + diff --git a/DBCD/DBCDBuilder.cs b/DBCD/DBCDBuilder.cs index f21aff9..12e1c66 100644 --- a/DBCD/DBCDBuilder.cs +++ b/DBCD/DBCDBuilder.cs @@ -1,38 +1,47 @@ using DBDefsLib; +using DBDefsLib.Constants; +using DBDefsLib.Structs; using DBCD.IO; using DBCD.IO.Attributes; +using DBCD.Providers; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; -using DBDefsLib.Structs; namespace DBCD { public struct DBCDInfo { internal string tableName; - internal string[] availableColumns; + + /// + /// Maps field name to a dynamically generated enum/flags Type for fields that have + /// an associated enum or flags definition. Use to convert + /// raw integer values to named enum members. + /// + public IReadOnlyDictionary EnumTypes; } internal class DBCDBuilder { private readonly ModuleBuilder moduleBuilder; private readonly Locale locale; + private readonly IEnumProvider enumProvider; private int locStringSize; - internal DBCDBuilder(Locale locale = Locale.None) + internal DBCDBuilder(Locale locale = Locale.None, IEnumProvider enumProvider = null) { var assemblyName = new AssemblyName("DBCDDefinitions"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); + moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); - this.moduleBuilder = moduleBuilder; this.locStringSize = 1; this.locale = locale; + this.enumProvider = enumProvider; } internal (Type Type, DBCDInfo Info) Build(DBParser dbcReader, DBDefinition databaseDefinition, string name, string build) @@ -40,12 +49,13 @@ internal DBCDBuilder(Locale locale = Locale.None) name ??= Guid.NewGuid().ToString(); VersionDefinitions? versionDefinition = null; + Build currentBuild = null; if (!string.IsNullOrWhiteSpace(build)) { - var dbBuild = new Build(build); - locStringSize = GetLocStringSize(dbBuild); - Utils.GetVersionDefinitionByBuild(databaseDefinition, dbBuild, out versionDefinition); + currentBuild = new Build(build); + locStringSize = GetLocStringSize(currentBuild); + Utils.GetVersionDefinitionByBuild(databaseDefinition, currentBuild, out versionDefinition); } if (versionDefinition == null && dbcReader.LayoutHash != 0) @@ -69,6 +79,7 @@ internal DBCDBuilder(Locale locale = Locale.None) var fields = versionDefinition.Value.definitions; var columns = new List(fields.Length); var localiseStrings = locale != Locale.None; + var enumTypes = new Dictionary(); var metadataIndex = 0; foreach (var fieldDefinition in fields) @@ -134,6 +145,55 @@ internal DBCDBuilder(Locale locale = Locale.None) columns.Add(fieldDefinition.name + "_mask"); } } + + // Enum/flags annotation — non-relation integer fields only + if (enumProvider != null && !fieldDefinition.isRelation) + { + if (fieldDefinition.arrLength == 0) + { + // Non-array: single definition applies to the whole field + var enumDef = enumProvider.GetEnumDefinition(name, fieldDefinition.name); + if (enumDef.HasValue) + { + var isFlags = enumDef.Value.metaType == MetaType.FLAGS; + var enumTypeName = $"{name}_{fieldDefinition.name}"; + var enumType = BuildEnumType(enumTypeName, enumDef.Value, currentBuild); + enumTypes[fieldDefinition.name] = enumType; + AddEnumAttribute(field, enumTypeName, isFlags); + } + } + else + { + // Array: definitions may vary per-index (null key = all indices) + var arrayEnumDefs = enumProvider.GetArrayEnumDefinitions(name, fieldDefinition.name); + if (arrayEnumDefs != null) + { + EnumDefinition? attributeHint = null; + + foreach (var (arrIndex, enumDef) in arrayEnumDefs) + { + var enumTypeName = arrIndex.HasValue + ? $"{name}_{fieldDefinition.name}_{arrIndex}" + : $"{name}_{fieldDefinition.name}"; + + var enumType = BuildEnumType(enumTypeName, enumDef, currentBuild); + + // null key -> all elements share this type, keyed by plain field name + // indexed key -> stored as "FieldName[n]" for per-element lookup + var typeKey = arrIndex.HasValue ? $"{fieldDefinition.name}[{arrIndex}]" : fieldDefinition.name; + enumTypes[typeKey] = enumType; + attributeHint ??= enumDef; + } + + // Tag the field with EnumAttribute so consumers know to check EnumTypes + if (attributeHint.HasValue) + { + var hintTypeName = $"{name}_{fieldDefinition.name}"; + AddEnumAttribute(field, hintTypeName, attributeHint.Value.metaType == MetaType.FLAGS); + } + } + } + } } var type = typeBuilder.CreateTypeInfo(); @@ -141,12 +201,63 @@ internal DBCDBuilder(Locale locale = Locale.None) var info = new DBCDInfo { availableColumns = columns.ToArray(), - tableName = name + tableName = name, + EnumTypes = enumTypes }; return (type, info); } + /// + /// Dynamically generates an enum type from an , + /// optionally filtered to entries that match . + /// + private Type BuildEnumType(string typeName, EnumDefinition enumDef, Build currentBuild) + { + var isFlags = enumDef.metaType == MetaType.FLAGS; + // FLAGS values can reach 0x80000000 so use uint; ENUM values are plain ints + var underlyingType = isFlags ? typeof(uint) : typeof(int); + + var enumBuilder = moduleBuilder.DefineEnum(typeName, TypeAttributes.Public, underlyingType); + + if (isFlags) + { + var flagsCtor = typeof(FlagsAttribute).GetConstructor(Type.EmptyTypes); + enumBuilder.SetCustomAttribute(new CustomAttributeBuilder(flagsCtor, [])); + } + + foreach (var entry in enumDef.entries) + { + if (string.IsNullOrEmpty(entry.name)) + continue; + + if (!EntryMatchesBuild(entry, currentBuild)) + continue; + + if (isFlags) + enumBuilder.DefineLiteral(entry.name, (uint)entry.value); + else + enumBuilder.DefineLiteral(entry.name, (int)entry.value); + } + + return enumBuilder.CreateTypeInfo(); + } + + /// + /// Returns true if the entry applies to the given build, or if the entry has no build + /// restrictions (applies to all builds), or if no build is specified. + /// + private static bool EntryMatchesBuild(EnumEntry entry, Build currentBuild) + { + if (currentBuild == null || (entry.builds.Length == 0 && entry.buildRanges.Length == 0)) + return true; + + if (entry.builds.Any(b => b.Equals(currentBuild))) + return true; + + return entry.buildRanges.Any(r => r.Contains(currentBuild)); + } + private int GetLocStringSize(Build build) { // post wotlk @@ -169,6 +280,18 @@ private void AddAttribute(FieldBuilder field, params object[] parameters) whe field.SetCustomAttribute(attributeBuilder); } + /// + /// Applies to a field. Uses a dedicated method because the + /// generic helper derives parameter types via + /// GetType(), which doesn't work correctly for bool literals. + /// + private static void AddEnumAttribute(FieldBuilder field, string enumName, bool isFlags) + { + var ctor = typeof(EnumAttribute).GetConstructor([typeof(string), typeof(bool)]); + var attributeBuilder = new CustomAttributeBuilder(ctor, [enumName, isFlags]); + field.SetCustomAttribute(attributeBuilder); + } + private Type FieldDefinitionToType(Definition field, ColumnDefinition column, bool localiseStrings) { var isArray = field.arrLength != 0; @@ -176,37 +299,27 @@ private Type FieldDefinitionToType(Definition field, ColumnDefinition column, bo switch (column.type) { case "int": + { + var signed = field.isSigned; + var type = field.size switch { - Type type = null; - var signed = field.isSigned; - - switch (field.size) - { - case 8: - type = signed ? typeof(sbyte) : typeof(byte); - break; - case 16: - type = signed ? typeof(short) : typeof(ushort); - break; - case 32: - type = signed ? typeof(int) : typeof(uint); - break; - case 64: - type = signed ? typeof(long) : typeof(ulong); - break; - } - - return isArray ? type.MakeArrayType() : type; - } + 8 => signed ? typeof(sbyte) : typeof(byte), + 16 => signed ? typeof(short) : typeof(ushort), + 32 => signed ? typeof(int) : typeof(uint), + 64 => signed ? typeof(long) : typeof(ulong), + _ => null + }; + return isArray ? type.MakeArrayType() : type; + } case "string": return isArray ? typeof(string[]) : typeof(string); case "locstring": - { - if (isArray && locStringSize > 1) - throw new NotSupportedException("Localised string arrays are not supported"); + { + if (isArray && locStringSize > 1) + throw new NotSupportedException("Localised string arrays are not supported"); - return (!localiseStrings && locStringSize > 1) || isArray ? typeof(string[]) : typeof(string); - } + return (!localiseStrings && locStringSize > 1) || isArray ? typeof(string[]) : typeof(string); + } case "float": return isArray ? typeof(float[]) : typeof(float); default: diff --git a/DBCD/DBCDStorage.cs b/DBCD/DBCDStorage.cs index 245c930..48edc70 100644 --- a/DBCD/DBCDStorage.cs +++ b/DBCD/DBCDStorage.cs @@ -16,11 +16,13 @@ public class DBCDRow : DynamicObject private readonly dynamic raw; private readonly FieldAccessor fieldAccessor; + private readonly IReadOnlyDictionary enumTypes; - internal DBCDRow(int ID, dynamic raw, FieldAccessor fieldAccessor) + internal DBCDRow(int ID, dynamic raw, FieldAccessor fieldAccessor, IReadOnlyDictionary enumTypes = null) { this.raw = raw; this.fieldAccessor = fieldAccessor; + this.enumTypes = enumTypes; this.ID = ID; } @@ -36,14 +38,32 @@ public override bool TrySetMember(SetMemberBinder binder, object value) public object this[string fieldName] { - get => fieldAccessor[this.raw, fieldName]; + get + { + var value = fieldAccessor[this.raw, fieldName]; + if (enumTypes != null && enumTypes.TryGetValue(fieldName, out var enumType)) + return Enum.ToObject(enumType, value); + return value; + } set => fieldAccessor[this.raw, fieldName] = value; } public object this[string fieldName, int index] { - get => ((Array)this[fieldName]).GetValue(index); - set => ((Array)this[fieldName]).SetValue(value, index); + get + { + var element = ((Array)fieldAccessor[this.raw, fieldName]).GetValue(index); + if (enumTypes != null) + { + var key = enumTypes.ContainsKey($"{fieldName}[{index}]") + ? $"{fieldName}[{index}]" + : fieldName; + if (enumTypes.TryGetValue(key, out var enumType)) + return Enum.ToObject(enumType, element); + } + return element; + } + set => ((Array)fieldAccessor[this.raw, fieldName]).SetValue(value, index); } public T Field(string fieldName) @@ -56,6 +76,80 @@ public T FieldAs(string fieldName) return fieldAccessor.GetMemberAs(this.raw, fieldName); } + /// + /// Returns true if the named flag is set in the flags field. + /// + public bool HasFlag(string fieldName, string flagName) + { + if (enumTypes == null || !enumTypes.TryGetValue(fieldName, out var enumType)) + return false; + + if (!Enum.IsDefined(enumType, flagName)) + throw new KeyNotFoundException($"{flagName} not in {enumType}"); + + var raw = Convert.ToUInt64(fieldAccessor[this.raw, fieldName]); + var flag = Convert.ToUInt64(Enum.Parse(enumType, flagName)); + return (raw & flag) == flag; + } + + /// + /// Returns true if the named flag is set in the flags field at the given array index. + /// Checks a per-index mapping first ("FieldName[n]"), then falls back to a whole-field mapping. + /// + public bool HasFlag(string fieldName, int index, string flagName) + { + if (enumTypes == null) + return false; + + var key = enumTypes.ContainsKey($"{fieldName}[{index}]") ? $"{fieldName}[{index}]" : fieldName; + if (!enumTypes.TryGetValue(key, out var enumType)) + return false; + + if (!Enum.IsDefined(enumType, flagName)) + throw new KeyNotFoundException($"{flagName} not in {enumType}"); + + var element = ((Array)fieldAccessor[this.raw, fieldName]).GetValue(index); + var raw = Convert.ToUInt64(element); + var flag = Convert.ToUInt64(Enum.Parse(enumType, flagName)); + return (raw & flag) == flag; + } + + /// + /// Returns true if the enum field's value matches the named enum member. + /// + public bool IsEnumMember(string fieldName, string memberName) + { + if (enumTypes == null || !enumTypes.TryGetValue(fieldName, out var enumType)) + return false; + + if (!Enum.IsDefined(enumType, memberName)) + throw new KeyNotFoundException($"{memberName} not in {enumType}"); + + var raw = Enum.ToObject(enumType, fieldAccessor[this.raw, fieldName]); + return raw.Equals(Enum.Parse(enumType, memberName)); + } + + /// + /// Returns true if the enum field at the given array index matches the named enum member. + /// Checks a per-index mapping first ("FieldName[n]"), then falls back to a whole-field mapping. + /// + public bool IsEnumMember(string fieldName, int index, string memberName) + { + if (enumTypes == null) + return false; + + var key = enumTypes.ContainsKey($"{fieldName}[{index}]") ? $"{fieldName}[{index}]" : fieldName; + if (!enumTypes.TryGetValue(key, out var enumType)) + return false; + + if (!Enum.IsDefined(enumType, memberName)) + throw new KeyNotFoundException($"{memberName} not in {enumType}"); + + var element = ((Array)fieldAccessor[this.raw, fieldName]).GetValue(index); + var raw = Enum.ToObject(enumType, element); + return raw.Equals(Enum.Parse(enumType, memberName)); + } + public override IEnumerable GetDynamicMemberNames() { return fieldAccessor.FieldNames; @@ -97,6 +191,8 @@ public interface IDBCDStorage : IEnumerable>, IDictiona string[] AvailableColumns { get; } uint LayoutHash { get; } + IReadOnlyDictionary EnumTypes { get; } + DBCDRow ConstructRow(int index); Dictionary GetEncryptedSections(); @@ -122,6 +218,7 @@ public interface IDBCDStorage : IEnumerable>, IDictiona string[] IDBCDStorage.AvailableColumns => this.info.availableColumns; public uint LayoutHash => this.storage.LayoutHash; + public IReadOnlyDictionary EnumTypes => this.info.EnumTypes; public override string ToString() => $"{this.info.tableName}"; public DBCDStorage(Stream stream, DBCDInfo info) : this(new DBParser(stream), info) { } @@ -136,18 +233,19 @@ public DBCDStorage(DBParser parser, Storage storage, DBCDInfo info) : base(ne this.storage = storage; var fields = typeof(T).GetFields(); - _arrayFieldCache = [.. fields + _arrayFieldCache = fields .Where(f => f.FieldType.IsArray) .Select(f => { - var elementType = f.FieldType.GetElementType()!; - var count = f.GetCustomAttribute()!.Count; + var elementType = f.FieldType.GetElementType(); + var count = f.GetCustomAttribute().Count; return (f, elementType, count, elementType == typeof(string)); - })]; - _stringFieldCache = [.. fields.Where(f => f.FieldType == typeof(string))]; + }) + .ToArray(); + _stringFieldCache = fields.Where(f => f.FieldType == typeof(string)).ToArray(); foreach (var record in storage) - base.Add(record.Key, new DBCDRow(record.Key, record.Value, fieldAccessor)); + base.Add(record.Key, new DBCDRow(record.Key, record.Value, fieldAccessor, info.EnumTypes)); } @@ -164,10 +262,10 @@ public void ApplyingHotfixes(HotfixReader hotfixReader, HotfixReader.RowProcesso #if NETSTANDARD2_0 foreach (var record in mutableStorage) - base[record.Key] = new DBCDRow(record.Key, record.Value, fieldAccessor); + base[record.Key] = new DBCDRow(record.Key, record.Value, fieldAccessor, info.EnumTypes); #else foreach (var (id, row) in mutableStorage) - base[id] = new DBCDRow(id, row, fieldAccessor); + base[id] = new DBCDRow(id, row, fieldAccessor, info.EnumTypes); #endif foreach (var key in base.Keys.Except(mutableStorage.Keys)) base.Remove(key); @@ -215,7 +313,7 @@ public DBCDRow ConstructRow(int index) foreach (var stringField in _stringFieldCache) stringField.SetValue(raw, string.Empty); - return new DBCDRow(index, raw, fieldAccessor); + return new DBCDRow(index, raw, fieldAccessor, info.EnumTypes); } public Dictionary ToDictionary() diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs new file mode 100644 index 0000000..4d04f5d --- /dev/null +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -0,0 +1,91 @@ +using DBDefsLib; +using DBDefsLib.Constants; +using DBDefsLib.Structs; +using System.Collections.Generic; +using System.IO; + +namespace DBCD.Providers +{ + /// + /// Resolves enum/flag definitions from a local WoWDBDefs meta directory, + /// using a .dbdm file to map table columns to their enum/flag files. + /// + public class FilesystemEnumProvider : IEnumProvider + { + private readonly string metaDirectory; + private readonly List mappings; + private readonly Dictionary cache = new(); + private readonly Dictionary?> arrayCache = new(); + + /// Absolute path to the .dbdm mapping file (e.g. WoWDBDefs/meta/Meta.dbdm). + public FilesystemEnumProvider(string dbdmFile) + { + metaDirectory = Path.GetDirectoryName(dbdmFile)!; + mappings = new DBDMReader().Read(dbdmFile); + } + + public EnumDefinition? GetEnumDefinition(string tableName, string columnName) + { + var cacheKey = $"{tableName}::{columnName}"; + if (cache.TryGetValue(cacheKey, out var cached)) + return cached; + + foreach (var mapping in mappings) + { + if (mapping.meta is MetaType.COLOR) + continue; + + if (mapping.arrIndex.HasValue) + continue; + + if (mapping.tableName != tableName || mapping.columnName != columnName) + continue; + + var enumDef = TryReadEnumFile(mapping); + cache[cacheKey] = enumDef; + return enumDef; + } + + cache[cacheKey] = null; + return null; + } + + public IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName) + { + var cacheKey = $"{tableName}::{columnName}"; + if (arrayCache.TryGetValue(cacheKey, out var cached)) + return cached; + + var result = new Dictionary(); + + foreach (var mapping in mappings) + { + if (mapping.meta is MetaType.COLOR) + continue; + + if (mapping.tableName != tableName || mapping.columnName != columnName) + continue; + + var enumDef = TryReadEnumFile(mapping); + if (enumDef.HasValue) + result[mapping.arrIndex] = enumDef.Value; + } + + IReadOnlyDictionary? value = result.Count > 0 ? result : null; + arrayCache[cacheKey] = value; + return value; + } + + private EnumDefinition? TryReadEnumFile(MappingDefinition mapping) + { + var dir = mapping.meta == MetaType.ENUM ? "enums" : "flags"; + var ext = mapping.meta == MetaType.ENUM ? ".dbde" : ".dbdf"; + var path = Path.Combine(metaDirectory, dir, $"{mapping.metaValue}{ext}"); + + if (!File.Exists(path)) + return null; + + return new DBDEnumReader().Read(path, mapping.meta); + } + } +} diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs new file mode 100644 index 0000000..076ec4e --- /dev/null +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -0,0 +1,134 @@ +using DBDefsLib; +using DBDefsLib.Constants; +using DBDefsLib.Structs; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; + +namespace DBCD.Providers +{ + /// + /// Resolves enum/flag definitions from the WoWDBDefs GitHub repository, + /// downloading the mapping file and individual enum/flag files on demand. + /// + public class GithubEnumProvider : IEnumProvider + { + private static readonly Uri BaseURI = new Uri("https://raw.githubusercontent.com/wowdev/WoWDBDefs/master/meta/"); + private readonly HttpClient client = new HttpClient(); + + private static bool UseCache = false; + private static string CachePath { get; } = "EnumCache/"; + private static readonly TimeSpan CacheExpiryTime = new TimeSpan(1, 0, 0, 0); + + private readonly List mappings; + private readonly Dictionary cache = new(); + private readonly Dictionary?> arrayCache = new(); + + public GithubEnumProvider(bool useCache = false) + { + UseCache = useCache; + client.BaseAddress = BaseURI; + + if (useCache && !Directory.Exists(CachePath)) + Directory.CreateDirectory(CachePath); + + var mappingStream = FetchFile("mapping.dbdm"); + mappings = new DBDMReader().Read(mappingStream); + } + + public EnumDefinition? GetEnumDefinition(string tableName, string columnName) + { + var cacheKey = $"{tableName}::{columnName}"; + if (cache.TryGetValue(cacheKey, out var cached)) + return cached; + + foreach (var mapping in mappings) + { + if (mapping.meta is MetaType.COLOR) + continue; + + if (mapping.arrIndex.HasValue) + continue; + + if (mapping.tableName != tableName || mapping.columnName != columnName) + continue; + + var enumDef = TryReadEnumFile(mapping); + cache[cacheKey] = enumDef; + return enumDef; + } + + cache[cacheKey] = null; + return null; + } + + public IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName) + { + var cacheKey = $"{tableName}::{columnName}"; + if (arrayCache.TryGetValue(cacheKey, out var cached)) + return cached; + + var result = new Dictionary(); + + foreach (var mapping in mappings) + { + if (mapping.meta is MetaType.COLOR) + continue; + + if (mapping.tableName != tableName || mapping.columnName != columnName) + continue; + + var enumDef = TryReadEnumFile(mapping); + if (enumDef.HasValue) + result[mapping.arrIndex] = enumDef.Value; + } + + IReadOnlyDictionary? value = result.Count > 0 ? result : null; + arrayCache[cacheKey] = value; + return value; + } + + private EnumDefinition? TryReadEnumFile(MappingDefinition mapping) + { + var dir = mapping.meta == MetaType.ENUM ? "enums" : "flags"; + var ext = mapping.meta == MetaType.ENUM ? ".dbde" : ".dbdf"; + var query = $"{dir}/{mapping.metaValue}{ext}"; + + try + { + var stream = FetchFile(query); + return new DBDEnumReader().Read(stream, mapping.meta); + } + catch + { + return null; + } + } + + private Stream FetchFile(string query) + { + if (UseCache) + { + var cacheFile = Path.Combine(CachePath, query.Replace('/', Path.DirectorySeparatorChar)); + if (File.Exists(cacheFile)) + { + var lastWrite = File.GetLastWriteTime(cacheFile); + if (DateTime.Now - lastWrite < CacheExpiryTime) + return new MemoryStream(File.ReadAllBytes(cacheFile)); + } + } + + var bytes = client.GetByteArrayAsync(query).Result; + + if (UseCache) + { + var cacheFile = Path.Combine(CachePath, query.Replace('/', Path.DirectorySeparatorChar)); + Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)!); + File.WriteAllBytes(cacheFile, bytes); + } + + return new MemoryStream(bytes); + } + } +} diff --git a/DBCD/Providers/IEnumProvider.cs b/DBCD/Providers/IEnumProvider.cs new file mode 100644 index 0000000..9f0e6d7 --- /dev/null +++ b/DBCD/Providers/IEnumProvider.cs @@ -0,0 +1,21 @@ +using DBDefsLib.Structs; +using System.Collections.Generic; + +namespace DBCD.Providers +{ + public interface IEnumProvider + { + /// + /// Returns the for a non-array table column, or null if none is mapped. + /// + EnumDefinition? GetEnumDefinition(string tableName, string columnName); + + /// + /// Returns per-index enum definitions for an array field. + /// A null key means the definition applies to all elements of the array. + /// A non-null key means it applies only to that specific array index. + /// Returns null if no mappings exist for this field. + /// + IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName); + } +} From 1d1a67e3fcf0dc7f6faaf91dbf5aa00f35cdeb1a Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Sun, 8 Mar 2026 17:49:11 +0100 Subject: [PATCH 04/15] Rename enumTypes field to adhere to code style --- DBCD/DBCDBuilder.cs | 10 ++-------- DBCD/DBCDStorage.cs | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/DBCD/DBCDBuilder.cs b/DBCD/DBCDBuilder.cs index 12e1c66..df2a337 100644 --- a/DBCD/DBCDBuilder.cs +++ b/DBCD/DBCDBuilder.cs @@ -17,13 +17,7 @@ public struct DBCDInfo { internal string tableName; internal string[] availableColumns; - - /// - /// Maps field name to a dynamically generated enum/flags Type for fields that have - /// an associated enum or flags definition. Use to convert - /// raw integer values to named enum members. - /// - public IReadOnlyDictionary EnumTypes; + internal IReadOnlyDictionary enumTypes; } internal class DBCDBuilder @@ -202,7 +196,7 @@ internal DBCDBuilder(Locale locale = Locale.None, IEnumProvider enumProvider = n { availableColumns = columns.ToArray(), tableName = name, - EnumTypes = enumTypes + enumTypes = enumTypes }; return (type, info); diff --git a/DBCD/DBCDStorage.cs b/DBCD/DBCDStorage.cs index 48edc70..04531d8 100644 --- a/DBCD/DBCDStorage.cs +++ b/DBCD/DBCDStorage.cs @@ -218,7 +218,7 @@ public interface IDBCDStorage : IEnumerable>, IDictiona string[] IDBCDStorage.AvailableColumns => this.info.availableColumns; public uint LayoutHash => this.storage.LayoutHash; - public IReadOnlyDictionary EnumTypes => this.info.EnumTypes; + public IReadOnlyDictionary EnumTypes => this.info.enumTypes; public override string ToString() => $"{this.info.tableName}"; public DBCDStorage(Stream stream, DBCDInfo info) : this(new DBParser(stream), info) { } @@ -245,7 +245,7 @@ public DBCDStorage(DBParser parser, Storage storage, DBCDInfo info) : base(ne _stringFieldCache = fields.Where(f => f.FieldType == typeof(string)).ToArray(); foreach (var record in storage) - base.Add(record.Key, new DBCDRow(record.Key, record.Value, fieldAccessor, info.EnumTypes)); + base.Add(record.Key, new DBCDRow(record.Key, record.Value, fieldAccessor, info.enumTypes)); } @@ -265,7 +265,7 @@ public void ApplyingHotfixes(HotfixReader hotfixReader, HotfixReader.RowProcesso base[record.Key] = new DBCDRow(record.Key, record.Value, fieldAccessor, info.EnumTypes); #else foreach (var (id, row) in mutableStorage) - base[id] = new DBCDRow(id, row, fieldAccessor, info.EnumTypes); + base[id] = new DBCDRow(id, row, fieldAccessor, info.enumTypes); #endif foreach (var key in base.Keys.Except(mutableStorage.Keys)) base.Remove(key); @@ -313,7 +313,7 @@ public DBCDRow ConstructRow(int index) foreach (var stringField in _stringFieldCache) stringField.SetValue(raw, string.Empty); - return new DBCDRow(index, raw, fieldAccessor, info.EnumTypes); + return new DBCDRow(index, raw, fieldAccessor, info.enumTypes); } public Dictionary ToDictionary() From 66ce14e2cd112b1ab8e14437e79c5f7284af3a36 Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Tue, 10 Mar 2026 12:17:51 +0100 Subject: [PATCH 05/15] Ignore case sensitivity for comparing table- and column name --- DBCD/Providers/FilesystemEnumProvider.cs | 9 +++++---- DBCD/Providers/GithubEnumProvider.cs | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs index 4d04f5d..cef11c8 100644 --- a/DBCD/Providers/FilesystemEnumProvider.cs +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -1,3 +1,4 @@ +using System; using DBDefsLib; using DBDefsLib.Constants; using DBDefsLib.Structs; @@ -26,7 +27,7 @@ public FilesystemEnumProvider(string dbdmFile) public EnumDefinition? GetEnumDefinition(string tableName, string columnName) { - var cacheKey = $"{tableName}::{columnName}"; + var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; if (cache.TryGetValue(cacheKey, out var cached)) return cached; @@ -38,7 +39,7 @@ public FilesystemEnumProvider(string dbdmFile) if (mapping.arrIndex.HasValue) continue; - if (mapping.tableName != tableName || mapping.columnName != columnName) + if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); @@ -52,7 +53,7 @@ public FilesystemEnumProvider(string dbdmFile) public IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName) { - var cacheKey = $"{tableName}::{columnName}"; + var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; if (arrayCache.TryGetValue(cacheKey, out var cached)) return cached; @@ -63,7 +64,7 @@ public FilesystemEnumProvider(string dbdmFile) if (mapping.meta is MetaType.COLOR) continue; - if (mapping.tableName != tableName || mapping.columnName != columnName) + if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs index 076ec4e..c0aff41 100644 --- a/DBCD/Providers/GithubEnumProvider.cs +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -39,7 +39,7 @@ public GithubEnumProvider(bool useCache = false) public EnumDefinition? GetEnumDefinition(string tableName, string columnName) { - var cacheKey = $"{tableName}::{columnName}"; + var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; if (cache.TryGetValue(cacheKey, out var cached)) return cached; @@ -51,7 +51,7 @@ public GithubEnumProvider(bool useCache = false) if (mapping.arrIndex.HasValue) continue; - if (mapping.tableName != tableName || mapping.columnName != columnName) + if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); @@ -65,7 +65,7 @@ public GithubEnumProvider(bool useCache = false) public IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName) { - var cacheKey = $"{tableName}::{columnName}"; + var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; if (arrayCache.TryGetValue(cacheKey, out var cached)) return cached; @@ -76,7 +76,7 @@ public GithubEnumProvider(bool useCache = false) if (mapping.meta is MetaType.COLOR) continue; - if (mapping.tableName != tableName || mapping.columnName != columnName) + if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); From 639c87ac01af36a4eb17aa092a7644ca8086ceac Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Tue, 10 Mar 2026 12:51:35 +0100 Subject: [PATCH 06/15] Expose Mappings list to public --- DBCD/Providers/FilesystemEnumProvider.cs | 9 +++++---- DBCD/Providers/GithubEnumProvider.cs | 9 +++++---- DBCD/Providers/IEnumProvider.cs | 5 +++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs index cef11c8..b00f165 100644 --- a/DBCD/Providers/FilesystemEnumProvider.cs +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -14,15 +14,16 @@ namespace DBCD.Providers public class FilesystemEnumProvider : IEnumProvider { private readonly string metaDirectory; - private readonly List mappings; private readonly Dictionary cache = new(); private readonly Dictionary?> arrayCache = new(); + public List Mappings { get; } + /// Absolute path to the .dbdm mapping file (e.g. WoWDBDefs/meta/Meta.dbdm). public FilesystemEnumProvider(string dbdmFile) { metaDirectory = Path.GetDirectoryName(dbdmFile)!; - mappings = new DBDMReader().Read(dbdmFile); + Mappings = new DBDMReader().Read(dbdmFile); } public EnumDefinition? GetEnumDefinition(string tableName, string columnName) @@ -31,7 +32,7 @@ public FilesystemEnumProvider(string dbdmFile) if (cache.TryGetValue(cacheKey, out var cached)) return cached; - foreach (var mapping in mappings) + foreach (var mapping in Mappings) { if (mapping.meta is MetaType.COLOR) continue; @@ -59,7 +60,7 @@ public FilesystemEnumProvider(string dbdmFile) var result = new Dictionary(); - foreach (var mapping in mappings) + foreach (var mapping in Mappings) { if (mapping.meta is MetaType.COLOR) continue; diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs index c0aff41..af2fc4d 100644 --- a/DBCD/Providers/GithubEnumProvider.cs +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -21,10 +21,11 @@ public class GithubEnumProvider : IEnumProvider private static string CachePath { get; } = "EnumCache/"; private static readonly TimeSpan CacheExpiryTime = new TimeSpan(1, 0, 0, 0); - private readonly List mappings; private readonly Dictionary cache = new(); private readonly Dictionary?> arrayCache = new(); + public List Mappings { get; } + public GithubEnumProvider(bool useCache = false) { UseCache = useCache; @@ -34,7 +35,7 @@ public GithubEnumProvider(bool useCache = false) Directory.CreateDirectory(CachePath); var mappingStream = FetchFile("mapping.dbdm"); - mappings = new DBDMReader().Read(mappingStream); + Mappings = new DBDMReader().Read(mappingStream); } public EnumDefinition? GetEnumDefinition(string tableName, string columnName) @@ -43,7 +44,7 @@ public GithubEnumProvider(bool useCache = false) if (cache.TryGetValue(cacheKey, out var cached)) return cached; - foreach (var mapping in mappings) + foreach (var mapping in Mappings) { if (mapping.meta is MetaType.COLOR) continue; @@ -71,7 +72,7 @@ public GithubEnumProvider(bool useCache = false) var result = new Dictionary(); - foreach (var mapping in mappings) + foreach (var mapping in Mappings) { if (mapping.meta is MetaType.COLOR) continue; diff --git a/DBCD/Providers/IEnumProvider.cs b/DBCD/Providers/IEnumProvider.cs index 9f0e6d7..973941c 100644 --- a/DBCD/Providers/IEnumProvider.cs +++ b/DBCD/Providers/IEnumProvider.cs @@ -5,6 +5,11 @@ namespace DBCD.Providers { public interface IEnumProvider { + /// + /// The list of instances, this contains the actual mapping of field -> meta type -> meta value etc. + /// + public List Mappings { get; } + /// /// Returns the for a non-array table column, or null if none is mapped. /// From 0dd0523e632fbf1974eac637f2949cf02d902a74 Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Tue, 10 Mar 2026 12:58:41 +0100 Subject: [PATCH 07/15] Fix whoopsie with string comparison --- DBCD/Providers/FilesystemEnumProvider.cs | 4 ++-- DBCD/Providers/GithubEnumProvider.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs index b00f165..08f5958 100644 --- a/DBCD/Providers/FilesystemEnumProvider.cs +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -40,7 +40,7 @@ public FilesystemEnumProvider(string dbdmFile) if (mapping.arrIndex.HasValue) continue; - if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) + if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); @@ -65,7 +65,7 @@ public FilesystemEnumProvider(string dbdmFile) if (mapping.meta is MetaType.COLOR) continue; - if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) + if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs index af2fc4d..fd381ad 100644 --- a/DBCD/Providers/GithubEnumProvider.cs +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -52,7 +52,7 @@ public GithubEnumProvider(bool useCache = false) if (mapping.arrIndex.HasValue) continue; - if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) + if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); @@ -77,7 +77,7 @@ public GithubEnumProvider(bool useCache = false) if (mapping.meta is MetaType.COLOR) continue; - if (mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) + if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) continue; var enumDef = TryReadEnumFile(mapping); From 8986e7cb337e01784d443d0dee9b6c3d1f517253 Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Tue, 10 Mar 2026 14:01:36 +0100 Subject: [PATCH 08/15] Do not return interfaces from methods --- DBCD/Providers/FilesystemEnumProvider.cs | 6 +++--- DBCD/Providers/GithubEnumProvider.cs | 6 +++--- DBCD/Providers/IEnumProvider.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs index 08f5958..62fd29a 100644 --- a/DBCD/Providers/FilesystemEnumProvider.cs +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -15,7 +15,7 @@ public class FilesystemEnumProvider : IEnumProvider { private readonly string metaDirectory; private readonly Dictionary cache = new(); - private readonly Dictionary?> arrayCache = new(); + private readonly Dictionary?> arrayCache = new(); public List Mappings { get; } @@ -52,7 +52,7 @@ public FilesystemEnumProvider(string dbdmFile) return null; } - public IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName) + public Dictionary? GetArrayEnumDefinitions(string tableName, string columnName) { var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; if (arrayCache.TryGetValue(cacheKey, out var cached)) @@ -73,7 +73,7 @@ public FilesystemEnumProvider(string dbdmFile) result[mapping.arrIndex] = enumDef.Value; } - IReadOnlyDictionary? value = result.Count > 0 ? result : null; + var value = result.Count > 0 ? result : null; arrayCache[cacheKey] = value; return value; } diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs index fd381ad..1dd7e4d 100644 --- a/DBCD/Providers/GithubEnumProvider.cs +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -22,7 +22,7 @@ public class GithubEnumProvider : IEnumProvider private static readonly TimeSpan CacheExpiryTime = new TimeSpan(1, 0, 0, 0); private readonly Dictionary cache = new(); - private readonly Dictionary?> arrayCache = new(); + private readonly Dictionary?> arrayCache = new(); public List Mappings { get; } @@ -64,7 +64,7 @@ public GithubEnumProvider(bool useCache = false) return null; } - public IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName) + public Dictionary? GetArrayEnumDefinitions(string tableName, string columnName) { var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; if (arrayCache.TryGetValue(cacheKey, out var cached)) @@ -85,7 +85,7 @@ public GithubEnumProvider(bool useCache = false) result[mapping.arrIndex] = enumDef.Value; } - IReadOnlyDictionary? value = result.Count > 0 ? result : null; + var value = result.Count > 0 ? result : null; arrayCache[cacheKey] = value; return value; } diff --git a/DBCD/Providers/IEnumProvider.cs b/DBCD/Providers/IEnumProvider.cs index 973941c..de2e218 100644 --- a/DBCD/Providers/IEnumProvider.cs +++ b/DBCD/Providers/IEnumProvider.cs @@ -13,7 +13,7 @@ public interface IEnumProvider /// /// Returns the for a non-array table column, or null if none is mapped. /// - EnumDefinition? GetEnumDefinition(string tableName, string columnName); + public EnumDefinition? GetEnumDefinition(string tableName, string columnName); /// /// Returns per-index enum definitions for an array field. @@ -21,6 +21,6 @@ public interface IEnumProvider /// A non-null key means it applies only to that specific array index. /// Returns null if no mappings exist for this field. /// - IReadOnlyDictionary? GetArrayEnumDefinitions(string tableName, string columnName); + public Dictionary? GetArrayEnumDefinitions(string tableName, string columnName); } } From 4f096990bb56f42684051e5fbc5968d7ba06490c Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Tue, 10 Mar 2026 15:08:47 +0100 Subject: [PATCH 09/15] Pre-populate the cache instead of loading on demand and get rid of GetArrayEnumDefinitions Instead we merged it into GetEnumDefinition with a nullable array index parameter, also introduce `IsEnumOrFlagField` to check whether that field is a Flag or Enum field --- DBCD/DBCDBuilder.cs | 50 +++++++++---------- DBCD/DBCDStorage.cs | 8 +++ DBCD/Providers/FilesystemEnumProvider.cs | 62 +++++++++++------------- DBCD/Providers/GithubEnumProvider.cs | 62 +++++++++++------------- DBCD/Providers/IEnumProvider.cs | 22 ++++++--- 5 files changed, 101 insertions(+), 103 deletions(-) diff --git a/DBCD/DBCDBuilder.cs b/DBCD/DBCDBuilder.cs index df2a337..495a058 100644 --- a/DBCD/DBCDBuilder.cs +++ b/DBCD/DBCDBuilder.cs @@ -140,8 +140,8 @@ internal DBCDBuilder(Locale locale = Locale.None, IEnumProvider enumProvider = n } } - // Enum/flags annotation — non-relation integer fields only - if (enumProvider != null && !fieldDefinition.isRelation) + // Enum/flags annotation — non-relation fields with a known mapping only + if (enumProvider != null && !fieldDefinition.isRelation && enumProvider.HasEnumDefinition(name, fieldDefinition.name)) { if (fieldDefinition.arrLength == 0) { @@ -158,33 +158,27 @@ internal DBCDBuilder(Locale locale = Locale.None, IEnumProvider enumProvider = n } else { - // Array: definitions may vary per-index (null key = all indices) - var arrayEnumDefs = enumProvider.GetArrayEnumDefinitions(name, fieldDefinition.name); - if (arrayEnumDefs != null) + // Array: query each index individually; specific-index mappings take priority + // over "applies to all" mappings (handled inside GetEnumDefinition). + EnumDefinition? attributeHint = null; + + for (var i = 0; i < fieldDefinition.arrLength; i++) + { + var enumDef = enumProvider.GetEnumDefinition(name, fieldDefinition.name, i); + if (!enumDef.HasValue) + continue; + + var enumTypeName = $"{name}_{fieldDefinition.name}_{i}"; + var enumType = BuildEnumType(enumTypeName, enumDef.Value, currentBuild); + enumTypes[$"{fieldDefinition.name}[{i}]"] = enumType; + attributeHint ??= enumDef; + } + + // Tag the field with EnumAttribute so consumers know to check EnumTypes + if (attributeHint.HasValue) { - EnumDefinition? attributeHint = null; - - foreach (var (arrIndex, enumDef) in arrayEnumDefs) - { - var enumTypeName = arrIndex.HasValue - ? $"{name}_{fieldDefinition.name}_{arrIndex}" - : $"{name}_{fieldDefinition.name}"; - - var enumType = BuildEnumType(enumTypeName, enumDef, currentBuild); - - // null key -> all elements share this type, keyed by plain field name - // indexed key -> stored as "FieldName[n]" for per-element lookup - var typeKey = arrIndex.HasValue ? $"{fieldDefinition.name}[{arrIndex}]" : fieldDefinition.name; - enumTypes[typeKey] = enumType; - attributeHint ??= enumDef; - } - - // Tag the field with EnumAttribute so consumers know to check EnumTypes - if (attributeHint.HasValue) - { - var hintTypeName = $"{name}_{fieldDefinition.name}"; - AddEnumAttribute(field, hintTypeName, attributeHint.Value.metaType == MetaType.FLAGS); - } + var hintTypeName = $"{name}_{fieldDefinition.name}"; + AddEnumAttribute(field, hintTypeName, attributeHint.Value.metaType == MetaType.FLAGS); } } } diff --git a/DBCD/DBCDStorage.cs b/DBCD/DBCDStorage.cs index 04531d8..dafa3fa 100644 --- a/DBCD/DBCDStorage.cs +++ b/DBCD/DBCDStorage.cs @@ -193,6 +193,12 @@ public interface IDBCDStorage : IEnumerable>, IDictiona IReadOnlyDictionary EnumTypes { get; } + /// + /// Returns true if the field name exists in the enum types dictionary. + /// For array fields, pass the key with index included (e.g. "Attributes[0]"). + /// + bool IsEnumOrFlagField(string fieldName); + DBCDRow ConstructRow(int index); Dictionary GetEncryptedSections(); @@ -221,6 +227,8 @@ public interface IDBCDStorage : IEnumerable>, IDictiona public IReadOnlyDictionary EnumTypes => this.info.enumTypes; public override string ToString() => $"{this.info.tableName}"; + public bool IsEnumOrFlagField(string fieldName) => info.enumTypes?.ContainsKey(fieldName) ?? false; + public DBCDStorage(Stream stream, DBCDInfo info) : this(new DBParser(stream), info) { } public DBCDStorage(DBParser dbParser, DBCDInfo info) : this(dbParser, dbParser.GetRecords(), info) { } diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs index 62fd29a..ece0372 100644 --- a/DBCD/Providers/FilesystemEnumProvider.cs +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -14,8 +14,7 @@ namespace DBCD.Providers public class FilesystemEnumProvider : IEnumProvider { private readonly string metaDirectory; - private readonly Dictionary cache = new(); - private readonly Dictionary?> arrayCache = new(); + private readonly Dictionary cache = new(); public List Mappings { get; } @@ -24,58 +23,53 @@ public FilesystemEnumProvider(string dbdmFile) { metaDirectory = Path.GetDirectoryName(dbdmFile)!; Mappings = new DBDMReader().Read(dbdmFile); + PopulateCache(); } - public EnumDefinition? GetEnumDefinition(string tableName, string columnName) + public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null) { - var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - if (cache.TryGetValue(cacheKey, out var cached)) - return cached; - - foreach (var mapping in Mappings) + if (arrayIndex.HasValue) { - if (mapping.meta is MetaType.COLOR) - continue; - - if (mapping.arrIndex.HasValue) - continue; + var specificKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}[{arrayIndex}]"; + if (cache.TryGetValue(specificKey, out var specific)) + return specific; - if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) - continue; + // Fall back to an "applies to all" mapping (null arrIndex), stored under the plain key + var fallbackKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; + if (cache.TryGetValue(fallbackKey, out var fallback)) + return fallback; - var enumDef = TryReadEnumFile(mapping); - cache[cacheKey] = enumDef; - return enumDef; + return null; } - cache[cacheKey] = null; - return null; + var key = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; + return cache.TryGetValue(key, out var cached) ? cached : null; } - public Dictionary? GetArrayEnumDefinitions(string tableName, string columnName) + private void PopulateCache() { - var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - if (arrayCache.TryGetValue(cacheKey, out var cached)) - return cached; - - var result = new Dictionary(); + // Deduplicate file reads: multiple mappings may point to the same enum/flag file. + var fileCache = new Dictionary(); foreach (var mapping in Mappings) { if (mapping.meta is MetaType.COLOR) continue; - if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) - continue; + var cacheKey = mapping.arrIndex.HasValue + ? $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}[{mapping.arrIndex}]" + : $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}"; + + var fileKey = $"{mapping.meta}::{mapping.metaValue}"; + if (!fileCache.TryGetValue(fileKey, out var enumDef)) + { + enumDef = TryReadEnumFile(mapping); + fileCache[fileKey] = enumDef; + } - var enumDef = TryReadEnumFile(mapping); if (enumDef.HasValue) - result[mapping.arrIndex] = enumDef.Value; + cache[cacheKey] = enumDef.Value; } - - var value = result.Count > 0 ? result : null; - arrayCache[cacheKey] = value; - return value; } private EnumDefinition? TryReadEnumFile(MappingDefinition mapping) diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs index 1dd7e4d..321a87c 100644 --- a/DBCD/Providers/GithubEnumProvider.cs +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -21,8 +21,7 @@ public class GithubEnumProvider : IEnumProvider private static string CachePath { get; } = "EnumCache/"; private static readonly TimeSpan CacheExpiryTime = new TimeSpan(1, 0, 0, 0); - private readonly Dictionary cache = new(); - private readonly Dictionary?> arrayCache = new(); + private readonly Dictionary cache = new(); public List Mappings { get; } @@ -36,58 +35,53 @@ public GithubEnumProvider(bool useCache = false) var mappingStream = FetchFile("mapping.dbdm"); Mappings = new DBDMReader().Read(mappingStream); + PopulateCache(); } - public EnumDefinition? GetEnumDefinition(string tableName, string columnName) + public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null) { - var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - if (cache.TryGetValue(cacheKey, out var cached)) - return cached; - - foreach (var mapping in Mappings) + if (arrayIndex.HasValue) { - if (mapping.meta is MetaType.COLOR) - continue; + var specificKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}[{arrayIndex}]"; + if (cache.TryGetValue(specificKey, out var specific)) + return specific; - if (mapping.arrIndex.HasValue) - continue; - - if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) - continue; + // Fall back to an "applies to all" mapping (null arrIndex), stored under the plain key + var fallbackKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; + if (cache.TryGetValue(fallbackKey, out var fallback)) + return fallback; - var enumDef = TryReadEnumFile(mapping); - cache[cacheKey] = enumDef; - return enumDef; + return null; } - cache[cacheKey] = null; - return null; + var key = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; + return cache.TryGetValue(key, out var cached) ? cached : null; } - public Dictionary? GetArrayEnumDefinitions(string tableName, string columnName) + private void PopulateCache() { - var cacheKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - if (arrayCache.TryGetValue(cacheKey, out var cached)) - return cached; - - var result = new Dictionary(); + // Deduplicate file fetches: multiple mappings may point to the same enum/flag file. + var fileCache = new Dictionary(); foreach (var mapping in Mappings) { if (mapping.meta is MetaType.COLOR) continue; - if (!mapping.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) || !mapping.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)) - continue; + var cacheKey = mapping.arrIndex.HasValue + ? $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}[{mapping.arrIndex}]" + : $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}"; + + var fileKey = $"{mapping.meta}::{mapping.metaValue}"; + if (!fileCache.TryGetValue(fileKey, out var enumDef)) + { + enumDef = TryReadEnumFile(mapping); + fileCache[fileKey] = enumDef; + } - var enumDef = TryReadEnumFile(mapping); if (enumDef.HasValue) - result[mapping.arrIndex] = enumDef.Value; + cache[cacheKey] = enumDef.Value; } - - var value = result.Count > 0 ? result : null; - arrayCache[cacheKey] = value; - return value; } private EnumDefinition? TryReadEnumFile(MappingDefinition mapping) diff --git a/DBCD/Providers/IEnumProvider.cs b/DBCD/Providers/IEnumProvider.cs index de2e218..fabb6b3 100644 --- a/DBCD/Providers/IEnumProvider.cs +++ b/DBCD/Providers/IEnumProvider.cs @@ -1,5 +1,8 @@ +using DBDefsLib.Constants; using DBDefsLib.Structs; +using System; using System.Collections.Generic; +using System.Linq; namespace DBCD.Providers { @@ -11,16 +14,21 @@ public interface IEnumProvider public List Mappings { get; } /// - /// Returns the for a non-array table column, or null if none is mapped. + /// Returns true if any enum or flag mapping exists for the given field, regardless of array index. + /// Use this as a fast pre-check before calling . /// - public EnumDefinition? GetEnumDefinition(string tableName, string columnName); + public bool HasEnumDefinition(string tableName, string columnName) => + Mappings.Any(m => + m.meta != MetaType.COLOR && + m.tableName.Equals(tableName, StringComparison.OrdinalIgnoreCase) && + m.columnName.Equals(columnName, StringComparison.OrdinalIgnoreCase)); /// - /// Returns per-index enum definitions for an array field. - /// A null key means the definition applies to all elements of the array. - /// A non-null key means it applies only to that specific array index. - /// Returns null if no mappings exist for this field. + /// Returns the for a table column, or null if none is mapped. + /// For non-array fields, omit (or pass null). + /// For array fields, pass the element index. A specific-index mapping takes priority over + /// an "applies to all elements" mapping (indicated by a null arrIndex in the source data). /// - public Dictionary? GetArrayEnumDefinitions(string tableName, string columnName); + public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null); } } From ccd52d2d9f4efdeb146b05804b530440da6a788e Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Tue, 10 Mar 2026 20:17:00 +0100 Subject: [PATCH 10/15] Improve provider logic for conditional values --- DBCD/Providers/FilesystemEnumProvider.cs | 47 ++++++++++++++++-------- DBCD/Providers/GithubEnumProvider.cs | 46 ++++++++++++++++------- DBCD/Providers/IEnumProvider.cs | 3 +- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/DBCD/Providers/FilesystemEnumProvider.cs b/DBCD/Providers/FilesystemEnumProvider.cs index ece0372..5316704 100644 --- a/DBCD/Providers/FilesystemEnumProvider.cs +++ b/DBCD/Providers/FilesystemEnumProvider.cs @@ -26,24 +26,32 @@ public FilesystemEnumProvider(string dbdmFile) PopulateCache(); } - public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null) + public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null, + string conditionalTable = null, string conditionalColumn = null, string conditionalValue = null) { + // Build base key (with or without array index) + var baseKey = arrayIndex.HasValue + ? $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}[{arrayIndex}]" + : $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; + + // If conditional context supplied, try conditional key first + if (!string.IsNullOrEmpty(conditionalTable)) + { + var conditionalKey = $"{baseKey}@{conditionalTable.ToLowerInvariant()}.{conditionalColumn!.ToLowerInvariant()}={conditionalValue}"; + if (cache.TryGetValue(conditionalKey, out var conditional)) + return conditional; + } + + // Fall back to unconditional (handles arrayIndex fallback too) if (arrayIndex.HasValue) { - var specificKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}[{arrayIndex}]"; - if (cache.TryGetValue(specificKey, out var specific)) + if (cache.TryGetValue(baseKey, out var specific)) return specific; - - // Fall back to an "applies to all" mapping (null arrIndex), stored under the plain key var fallbackKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - if (cache.TryGetValue(fallbackKey, out var fallback)) - return fallback; - - return null; + return cache.TryGetValue(fallbackKey, out var fallback) ? fallback : null; } - var key = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - return cache.TryGetValue(key, out var cached) ? cached : null; + return cache.TryGetValue(baseKey, out var cached) ? cached : null; } private void PopulateCache() @@ -56,10 +64,7 @@ private void PopulateCache() if (mapping.meta is MetaType.COLOR) continue; - var cacheKey = mapping.arrIndex.HasValue - ? $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}[{mapping.arrIndex}]" - : $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}"; - + var cacheKey = BuildCacheKey(mapping); var fileKey = $"{mapping.meta}::{mapping.metaValue}"; if (!fileCache.TryGetValue(fileKey, out var enumDef)) { @@ -83,5 +88,17 @@ private void PopulateCache() return new DBDEnumReader().Read(path, mapping.meta); } + + private static string BuildCacheKey(MappingDefinition m) + { + var key = m.arrIndex.HasValue + ? $"{m.tableName.ToLowerInvariant()}::{m.columnName.ToLowerInvariant()}[{m.arrIndex}]" + : $"{m.tableName.ToLowerInvariant()}::{m.columnName.ToLowerInvariant()}"; + + if (!string.IsNullOrEmpty(m.conditionalTable)) + key += $"@{m.conditionalTable.ToLowerInvariant()}.{m.conditionalColumn.ToLowerInvariant()}={m.conditionalValue}"; + + return key; + } } } diff --git a/DBCD/Providers/GithubEnumProvider.cs b/DBCD/Providers/GithubEnumProvider.cs index 321a87c..51c367a 100644 --- a/DBCD/Providers/GithubEnumProvider.cs +++ b/DBCD/Providers/GithubEnumProvider.cs @@ -38,24 +38,33 @@ public GithubEnumProvider(bool useCache = false) PopulateCache(); } - public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null) + public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null, + string conditionalTable = null, string conditionalColumn = null, string conditionalValue = null) { + // Build base key (with or without array index) + var baseKey = arrayIndex.HasValue + ? $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}[{arrayIndex}]" + : $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; + + // If conditional context supplied, try conditional key first + if (!string.IsNullOrEmpty(conditionalTable)) + { + var conditionalKey = $"{baseKey}@{conditionalTable.ToLowerInvariant()}.{conditionalColumn!.ToLowerInvariant()}={conditionalValue}"; + if (cache.TryGetValue(conditionalKey, out var conditional)) + return conditional; + } + + // Fall back to unconditional (handles arrayIndex fallback too) if (arrayIndex.HasValue) { - var specificKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}[{arrayIndex}]"; - if (cache.TryGetValue(specificKey, out var specific)) + if (cache.TryGetValue(baseKey, out var specific)) return specific; - // Fall back to an "applies to all" mapping (null arrIndex), stored under the plain key var fallbackKey = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - if (cache.TryGetValue(fallbackKey, out var fallback)) - return fallback; - - return null; + return cache.TryGetValue(fallbackKey, out var fallback) ? fallback : null; } - var key = $"{tableName.ToLowerInvariant()}::{columnName.ToLowerInvariant()}"; - return cache.TryGetValue(key, out var cached) ? cached : null; + return cache.TryGetValue(baseKey, out var cached) ? cached : null; } private void PopulateCache() @@ -68,10 +77,7 @@ private void PopulateCache() if (mapping.meta is MetaType.COLOR) continue; - var cacheKey = mapping.arrIndex.HasValue - ? $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}[{mapping.arrIndex}]" - : $"{mapping.tableName.ToLowerInvariant()}::{mapping.columnName.ToLowerInvariant()}"; - + var cacheKey = BuildCacheKey(mapping); var fileKey = $"{mapping.meta}::{mapping.metaValue}"; if (!fileCache.TryGetValue(fileKey, out var enumDef)) { @@ -125,5 +131,17 @@ private Stream FetchFile(string query) return new MemoryStream(bytes); } + + private static string BuildCacheKey(MappingDefinition m) + { + var key = m.arrIndex.HasValue + ? $"{m.tableName.ToLowerInvariant()}::{m.columnName.ToLowerInvariant()}[{m.arrIndex}]" + : $"{m.tableName.ToLowerInvariant()}::{m.columnName.ToLowerInvariant()}"; + + if (!string.IsNullOrEmpty(m.conditionalTable)) + key += $"@{m.conditionalTable.ToLowerInvariant()}.{m.conditionalColumn.ToLowerInvariant()}={m.conditionalValue}"; + + return key; + } } } diff --git a/DBCD/Providers/IEnumProvider.cs b/DBCD/Providers/IEnumProvider.cs index fabb6b3..61f17c6 100644 --- a/DBCD/Providers/IEnumProvider.cs +++ b/DBCD/Providers/IEnumProvider.cs @@ -29,6 +29,7 @@ public bool HasEnumDefinition(string tableName, string columnName) => /// For array fields, pass the element index. A specific-index mapping takes priority over /// an "applies to all elements" mapping (indicated by a null arrIndex in the source data). /// - public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null); + public EnumDefinition? GetEnumDefinition(string tableName, string columnName, int? arrayIndex = null, + string conditionalTable = null, string conditionalColumn = null, string conditionalValue = null); } } From 6c7f151ec9cd7bb1cf08554cce262e8d7aab9828 Mon Sep 17 00:00:00 2001 From: Martin Benjamins Date: Wed, 11 Mar 2026 01:12:19 +0100 Subject: [PATCH 11/15] Bump DBDefsLib --- DBCD/DBCD.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DBCD/DBCD.csproj b/DBCD/DBCD.csproj index add59fc..71b773d 100644 --- a/DBCD/DBCD.csproj +++ b/DBCD/DBCD.csproj @@ -9,7 +9,7 @@ - + From 3a4d3a9e38b0101d74590505f28ba3663bdfc14c Mon Sep 17 00:00:00 2001 From: MaxtorCoder Date: Wed, 11 Mar 2026 14:17:06 +0100 Subject: [PATCH 12/15] Use long as underlying type for defining the enum --- DBCD/DBCDBuilder.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/DBCD/DBCDBuilder.cs b/DBCD/DBCDBuilder.cs index 495a058..85e05c2 100644 --- a/DBCD/DBCDBuilder.cs +++ b/DBCD/DBCDBuilder.cs @@ -202,12 +202,9 @@ internal DBCDBuilder(Locale locale = Locale.None, IEnumProvider enumProvider = n /// private Type BuildEnumType(string typeName, EnumDefinition enumDef, Build currentBuild) { - var isFlags = enumDef.metaType == MetaType.FLAGS; - // FLAGS values can reach 0x80000000 so use uint; ENUM values are plain ints - var underlyingType = isFlags ? typeof(uint) : typeof(int); - - var enumBuilder = moduleBuilder.DefineEnum(typeName, TypeAttributes.Public, underlyingType); + var enumBuilder = moduleBuilder.DefineEnum(typeName, TypeAttributes.Public, typeof(long)); + var isFlags = enumDef.metaType == MetaType.FLAGS; if (isFlags) { var flagsCtor = typeof(FlagsAttribute).GetConstructor(Type.EmptyTypes); From c528b3d37b3f455695c41d9a5bacb4b2737211e2 Mon Sep 17 00:00:00 2001 From: Martin Benjamins Date: Wed, 11 Mar 2026 23:56:13 +0100 Subject: [PATCH 13/15] Fix missed spot for long change --- DBCD/DBCDBuilder.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/DBCD/DBCDBuilder.cs b/DBCD/DBCDBuilder.cs index 85e05c2..c6b8efc 100644 --- a/DBCD/DBCDBuilder.cs +++ b/DBCD/DBCDBuilder.cs @@ -219,10 +219,7 @@ private Type BuildEnumType(string typeName, EnumDefinition enumDef, Build curren if (!EntryMatchesBuild(entry, currentBuild)) continue; - if (isFlags) - enumBuilder.DefineLiteral(entry.name, (uint)entry.value); - else - enumBuilder.DefineLiteral(entry.name, (int)entry.value); + enumBuilder.DefineLiteral(entry.name, (long)entry.value); } return enumBuilder.CreateTypeInfo(); From 51d1896d18f904d2203a9bd0c47e9e43e631e162 Mon Sep 17 00:00:00 2001 From: Martin Benjamins Date: Thu, 12 Mar 2026 01:46:42 +0100 Subject: [PATCH 14/15] Update DBDefsLib --- DBCD/DBCD.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DBCD/DBCD.csproj b/DBCD/DBCD.csproj index 71b773d..91cf272 100644 --- a/DBCD/DBCD.csproj +++ b/DBCD/DBCD.csproj @@ -9,7 +9,7 @@ - + From 8779197878e07768b0441f2f4cc5d52b43fcfcf9 Mon Sep 17 00:00:00 2001 From: Martin Benjamins Date: Sat, 14 Mar 2026 22:46:16 +0100 Subject: [PATCH 15/15] Update DBDefsLib, fix up BDBD changes --- DBCD/DBCD.cs | 8 +++++--- DBCD/DBCD.csproj | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DBCD/DBCD.cs b/DBCD/DBCD.cs index d9159fb..f7a9d61 100644 --- a/DBCD/DBCD.cs +++ b/DBCD/DBCD.cs @@ -15,7 +15,7 @@ public class DBCD private readonly IEnumProvider enumProvider; private readonly bool useBDBD; - private readonly Dictionary BDBDCache; + private readonly Dictionary BDBDTableCache; /// /// Creates a DBCD instance that uses the given DBC and DBD providers. @@ -43,7 +43,9 @@ public DBCD(IDBCProvider dbcProvider, Stream bdbdStream, IEnumProvider enumProvi this.dbcProvider = dbcProvider; this.enumProvider = enumProvider; this.useBDBD = true; - this.BDBDCache = BDBDReader.Read(bdbdStream); + + var bdbd = BDBDReader.Read(bdbdStream); + BDBDTableCache = bdbd.tableDefinitions; } /// @@ -67,7 +69,7 @@ public IDBCDStorage Load(string tableName, string build = null, Locale locale = } else { - if (!BDBDCache.TryGetValue(tableName, out var tableInfo)) + if (!BDBDTableCache.TryGetValue(tableName, out var tableInfo)) throw new FileNotFoundException($"Table {tableName} not found in BDBD."); databaseDefinition = tableInfo.dbd; diff --git a/DBCD/DBCD.csproj b/DBCD/DBCD.csproj index 91cf272..0172015 100644 --- a/DBCD/DBCD.csproj +++ b/DBCD/DBCD.csproj @@ -9,7 +9,7 @@ - +