diff --git a/code/C#/DBDefsConverter/DBDDBMLSerializer.cs b/code/C#/DBDefsConverter/DBDDBMLSerializer.cs new file mode 100644 index 00000000000..168f584adc9 --- /dev/null +++ b/code/C#/DBDefsConverter/DBDDBMLSerializer.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.IO; +using static DBDefsLib.Structs; + +namespace DBDefsConverter; + +public class DBDDBMLSerializer +{ + public void Serialize(TextWriter textWriter, DBMLDocument document) + { + WriteProjectDefinition(textWriter, document.Project); + foreach (var table in document.Tables) + { + WriteTableDefinition(textWriter, table); + } + foreach (var @enum in document.Enums) + { + WriteEnumDefinition(textWriter, @enum); + } + } + + public void Serialize(string filename, DBDefinition definition) + { + throw new NotImplementedException(); + } + + public DBDefinition Deserialize(string filename) + { + throw new NotImplementedException(); + } + + private void WriteProjectDefinition(TextWriter textWriter, DBMLProject project) + { + textWriter.WriteLine($"Project {project.Name} {{"); + if (!string.IsNullOrEmpty(project.DatabaseType)) + textWriter.WriteLine($" database_type: {project.DatabaseType}"); + if (!string.IsNullOrEmpty(project.Note)) + textWriter.WriteLine($" Note: '{project.Note}'"); + textWriter.WriteLine("}"); + } + + private void WriteTableDefinition(TextWriter textWriter, DBMLTable table) + { + var schema = !string.IsNullOrEmpty(table.Schema) ? $"\"{table.Schema}\"." : string.Empty; + var alias = !string.IsNullOrEmpty(table.Alias) ? $"as {table.Alias} " : string.Empty; + var headerColor = !string.IsNullOrEmpty(table.Settings.HeaderColor) ? $"[headercolor: {table.Settings.HeaderColor}] " : string.Empty; + textWriter.WriteLine($"Table {schema}\"{table.Name}\" {alias}{headerColor}{{"); + foreach (var column in table.Columns) + { + WriteColumnDefinition(textWriter, column, table); + } + if (!string.IsNullOrEmpty(table.Note)) + textWriter.WriteLine($" Note: '{table.Note}'"); + textWriter.WriteLine("}"); + } + + private void WriteColumnDefinition(TextWriter textWriter, DBMLColumn column, DBMLTable table) + { + var options = ""; + string OptionsSeperator() + { + return !string.IsNullOrEmpty(options) ? ", " : string.Empty; + } + + var schema = !string.IsNullOrEmpty(table.Schema) ? $"\"{table.Schema}\"." : string.Empty; + + textWriter.Write($" \"{column.Name}\" {column.Type}"); + + if (column.Settings.IsPrimaryKey) + options += "primary key"; + + if (column.Settings.IsNullable.HasValue) + if (column.Settings.IsNullable.Value) + options += $"{OptionsSeperator()}null"; + else + options += $"{OptionsSeperator()}not null"; + + if (column.Settings.IsUnique) + options += $"{OptionsSeperator()}unique"; + + switch (column.Settings.DefaultValueType) + { + case DBMLColumnDefaultValueType.Number: + case DBMLColumnDefaultValueType.Boolean: + options += $"{OptionsSeperator()}default: {column.Settings.DefaultValue}"; + break; + case DBMLColumnDefaultValueType.String: + options += $"{OptionsSeperator()}default: '{column.Settings.DefaultValue}'"; + break; + case DBMLColumnDefaultValueType.Expression: + options += $"{OptionsSeperator()}default: `{column.Settings.DefaultValue}`"; + break; + case DBMLColumnDefaultValueType.None: + default: + break; + } + + switch (column.Settings.RelationshipType) + { + case DBMLColumnRelationshipType.OneToOne: + options += $"{OptionsSeperator()}ref: - {schema}\"{column.Settings.RelationshipTable}\".\"{column.Settings.RelationshipColumn}\""; + break; + case DBMLColumnRelationshipType.OneToMany: + options += $"{OptionsSeperator()}ref: < {schema}\"{column.Settings.RelationshipTable}\".\"{column.Settings.RelationshipColumn}\""; + break; + case DBMLColumnRelationshipType.ManyToOne: + options += $"{OptionsSeperator()}ref: > {schema}\"{column.Settings.RelationshipTable}\".\"{column.Settings.RelationshipColumn}\""; + break; + case DBMLColumnRelationshipType.ManyToMany: + options += $"{OptionsSeperator()}ref: <> {schema}\"{column.Settings.RelationshipTable}\".\"{column.Settings.RelationshipColumn}\""; + break; + case DBMLColumnRelationshipType.None: + default: + break; + } + + if (!string.IsNullOrEmpty(options)) + textWriter.Write($" [{options}]"); + textWriter.WriteLine(); + } + + private void WriteEnumDefinition(TextWriter textWriter, DBMLEnum @enum) + { + textWriter.WriteLine($"enum {@enum.Name} {{"); + foreach (var value in @enum.Values) + { + textWriter.WriteLine($" {value.Value}{(!string.IsNullOrEmpty(value.Note) ? $" [note: '{value.Note}']" : string.Empty)}"); + } + textWriter.WriteLine("}}"); + } +} + +public class DBMLDocument +{ + public DBMLProject Project { get; set; } = new(); + public List Tables { get; set; } = new(); + public List Enums { get; set; } = new(); + public List TableGroups { get; set; } = new(); +} + +public class DBMLProject +{ + public string Name { get; set; } + public string DatabaseType { get; set; } + public string Note { get; set; } +} + +public class DBMLTable +{ + public string Schema { get; set; } + public string Name { get; set; } + public string Alias { get; set; } + public List Columns { get; set; } = new(); + public string Note { get; set; } + public DBMLTableSettings Settings { get; set; } = new(); +} + +public class DBMLTableSettings +{ + public string HeaderColor { get; set; } = null; +} + +public class DBMLColumn +{ + public string Name { get; set; } + public string Type { get; set; } + public DBMLColumnSettings Settings { get; set; } = new(); +} + +public class DBMLColumnSettings +{ + public string Note { get; set; } = string.Empty; + public bool IsPrimaryKey { get; set; } = false; + public bool? IsNullable { get; set; } = null; + public bool IsUnique { get; set; } = false; + public string DefaultValue { get; set; } = string.Empty; + public DBMLColumnDefaultValueType DefaultValueType { get; set; } = DBMLColumnDefaultValueType.None; + public bool IsIncrement { get; set; } = false; + public string RelationshipTable { get; set; } + public string RelationshipColumn { get; set; } + public DBMLColumnRelationshipType RelationshipType { get; set; } +} + +public enum DBMLColumnDefaultValueType +{ + None, + Number, + String, + Expression, + Boolean +} + +public enum DBMLColumnRelationshipType +{ + None, + OneToMany, + ManyToOne, + OneToOne, + ManyToMany +} + +public class DBMLEnum +{ + public string Name { get; set; } + public List Values { get; set; } = new(); +} + +public class DBMLEnumValue +{ + public string Value { get; set; } + public string Note { get; set; } +} + +public class DBMLTableGroup +{ + public string Name { get; set; } + public List Tables { get; set; } = new(); +} \ No newline at end of file diff --git a/code/C#/DBDefsConverter/Program.cs b/code/C#/DBDefsConverter/Program.cs index 210aac4d93e..e3f7e8b7662 100644 --- a/code/C#/DBDefsConverter/Program.cs +++ b/code/C#/DBDefsConverter/Program.cs @@ -1,8 +1,10 @@ using DBDefsLib; using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.IO; using static DBDefsLib.Structs; +using System.Linq; namespace DBDefsConverter { @@ -10,14 +12,15 @@ class Program { static void Main(string[] args) { - if (args.Length < 1 || args.Length > 3) + if (args.Length < 1 || args.Length > 4) { - throw new ArgumentException("Invalid argument count, need at least 1 argument: indbdfile/indbddir (outdir, default current dir) (json, xml)"); + throw new ArgumentException("Invalid argument count, need at least 1 argument: indbdfile/indbddir (outdir, default current dir) (json, xml, dbml) (build number for dbml export)"); } var inFile = args[0]; var outDir = Directory.GetCurrentDirectory(); var exportFormat = "json"; + Build exportBuild = null; if (args.Length >= 2) { @@ -41,14 +44,51 @@ static void Main(string[] args) } } - if (Directory.Exists(args[0])) + if (args.Length == 4) { - var files = Directory.GetFiles(args[0]); - DoExport(exportFormat, outDir, files); + if (!args[2].Equals("dbml")) + { + throw new ArgumentException( + "Converting to a specific build is only supported for export format dbml"); + } + + exportFormat = args[2]; + var succeededParsingBuild = Build.TryParse(args[3], out var parsedBuild); + if (!succeededParsingBuild) + { + throw new ArgumentException( + "Couldn't parse build. Make sure format looks like this: \"Expansion.Major.Minor.Build\""); + } + + exportBuild = parsedBuild; } - else if (File.Exists(args[0])) + + if (Directory.Exists(inFile)) { - DoExport(exportFormat, outDir, args[0]); + var files = Directory.GetFiles(inFile); + switch (exportFormat) + { + case "xml": + case "json": + DoExport(exportFormat, outDir, files); + break; + case "dbml": + DoExportDBML(outDir, exportBuild, files); + break; + } + } + else if (File.Exists(inFile)) + { + switch (exportFormat) + { + case "xml": + case "json": + DoExport(exportFormat, outDir, inFile); + break; + case "dbml": + DoExportDBML(outDir, exportBuild, inFile); + break; + } } else { @@ -58,8 +98,8 @@ static void Main(string[] args) private static void DoExport(string exportFormat, string outDir, params string[] files) { - JsonSerializer jsonserializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore }; - DBDXMLSerializer xmlserializer = new DBDXMLSerializer(); + var jsonserializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore }; + var xmlserializer = new DBDXMLSerializer(); foreach (var file in files) { @@ -83,5 +123,93 @@ private static void DoExport(string exportFormat, string outDir, params string[] } } } + + private static void DoExportDBML(string outDir, Build build, params string[] files) + { + var dbmlSerializer = new DBDDBMLSerializer(); + var dbmlDocument = new DBMLDocument + { + Project = new DBMLProject + { + Name = $"DBC_{build.expansion}_{build.major}_{build.minor}_{build.build}", + Note = $"Database Client tables for World of Warcraft {build}" + } + }; + + var shippedTables = new List(); + + foreach (var file in files) + { + Console.WriteLine("Parsing " + file); + + var reader = new DBDReader(); + var dbdIdentifier = Path.GetFileNameWithoutExtension(file); + var dbDefinition = reader.Read(file); + var buildDefinition = dbDefinition.versionDefinitions + .Where(d => d.builds.Equals(build) || + d.buildRanges.Any(br => br.Contains(build))) + .Cast() + .FirstOrDefault(); + + if (buildDefinition is null) + { + continue; + } + + shippedTables.Add(dbdIdentifier); + var columns = new List(); + foreach (var definition in buildDefinition.Value.definitions) + { + if (!dbDefinition.columnDefinitions.ContainsKey(definition.name)) + continue; + + var columnDefinition = dbDefinition.columnDefinitions[definition.name]; + var column = new DBMLColumn + { + Name = definition.name, + Type = columnDefinition.type, + Settings = new DBMLColumnSettings + { + IsPrimaryKey = definition.isID, + RelationshipType = !string.IsNullOrEmpty(columnDefinition.foreignTable) + ? DBMLColumnRelationshipType.OneToMany + : DBMLColumnRelationshipType.None, + RelationshipTable = columnDefinition.foreignTable, + RelationshipColumn = columnDefinition.foreignColumn, + Note = columnDefinition.comment + } + }; + columns.Add(column); + } + + var table = new DBMLTable + { + Name = dbdIdentifier, + Note = $"{dbdIdentifier} client database table for version {build}", + Columns = columns, + Schema = "DBC" + }; + dbmlDocument.Tables.Add(table); + } + + // Filter out relations of unshipped tables + foreach (var table in dbmlDocument.Tables) + { + foreach (var column in table.Columns) + { + if (shippedTables.Contains(column.Settings.RelationshipTable)) + continue; + column.Settings.RelationshipTable = string.Empty; + column.Settings.RelationshipType = DBMLColumnRelationshipType.None; + } + } + + // Now we can write our DBML file + var outFile = $"{build}.dbml"; + Console.WriteLine($"Writing {outFile}"); + var target = Path.Combine(outDir, outFile); + using var writer = File.CreateText(target); + dbmlSerializer.Serialize(writer, dbmlDocument); + } } } diff --git a/code/C#/DBDefsDumper/DBMeta.cs b/code/C#/DBDefsDumper/DBMeta.cs index 3a4b0df1131..dcf72ca0d54 100644 --- a/code/C#/DBDefsDumper/DBMeta.cs +++ b/code/C#/DBDefsDumper/DBMeta.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace DBDefsDumper +namespace DBDefsDumper { public struct DBMeta { diff --git a/code/C#/DBDefsLib/Utils.cs b/code/C#/DBDefsLib/Utils.cs index 3b53de4f4b6..37bc4d0faff 100644 --- a/code/C#/DBDefsLib/Utils.cs +++ b/code/C#/DBDefsLib/Utils.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using static DBDefsLib.Structs; -using System.Linq; using System.IO; -using System.Text; +using System.Linq; +using static DBDefsLib.Structs; namespace DBDefsLib { diff --git a/code/C#/DBDefsMerge/MergeJSONManifests.cs b/code/C#/DBDefsMerge/MergeJSONManifests.cs index f920a5e0f14..a7e91abe73c 100644 --- a/code/C#/DBDefsMerge/MergeJSONManifests.cs +++ b/code/C#/DBDefsMerge/MergeJSONManifests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; diff --git a/definitions/ChrClasses.dbd b/definitions/ChrClasses.dbd index 06d4fbfcb4f..ce8eee20398 100644 --- a/definitions/ChrClasses.dbd +++ b/definitions/ChrClasses.dbd @@ -1,6 +1,6 @@ COLUMNS int ID -int DisplayPower +int DisplayPower // Not really an FK since that table does not exist in all versions, but that enum is still common across all versions. string PetNameToken locstring Name_lang locstring Name_female_lang diff --git a/definitions/FootstepTerrainLookup.dbd b/definitions/FootstepTerrainLookup.dbd index 31c5051d1bf..32060ac9599 100644 --- a/definitions/FootstepTerrainLookup.dbd +++ b/definitions/FootstepTerrainLookup.dbd @@ -1,7 +1,7 @@ COLUMNS int ID -int CreatureFootstepID -int TerrainSoundID +int CreatureFootstepID +int TerrainSoundID int SoundID int SoundIDSplash diff --git a/definitions/PowerType.dbd b/definitions/PowerType.dbd index 20f6c5c0650..274079e42ed 100644 --- a/definitions/PowerType.dbd +++ b/definitions/PowerType.dbd @@ -8,7 +8,7 @@ int Flags int MaxBasePower int MinPower string NameGlobalStringTag -int PowerTypeEnum +int PowerTypeEnum // Not really an FK since that table does not exist in all versions, but that enum is still common across all versions. float RegenCombat int RegenInterruptTimeMS float RegenPeace diff --git a/definitions/TerrainType.dbd b/definitions/TerrainType.dbd index 2924d86f16e..098a6ff842e 100644 --- a/definitions/TerrainType.dbd +++ b/definitions/TerrainType.dbd @@ -4,7 +4,7 @@ int TerrainID string TerrainDesc int FootstepSprayRun int FootstepSprayWalk -int SoundID +int SoundID int Flags LAYOUT 4BE35F10 @@ -111,7 +111,7 @@ BUILD 8.0.1.25902, 8.0.1.25976, 8.0.1.26010, 8.0.1.26032, 8.0.1.26095, 8.0.1.261 BUILD 7.3.5.25928, 7.3.5.25937, 7.3.5.25944, 7.3.5.25946, 7.3.5.25950, 7.3.5.25961, 7.3.5.25996, 7.3.5.26124, 7.3.5.26365, 7.3.5.26654, 7.3.5.26755, 7.3.5.26822, 7.3.5.26899, 7.3.5.26972 BUILD 7.3.2.25079, 7.3.2.25163, 7.3.2.25196, 7.3.2.25208, 7.3.2.25255, 7.3.2.25326, 7.3.2.25383, 7.3.2.25442, 7.3.2.25455, 7.3.2.25477, 7.3.2.25480, 7.3.2.25497, 7.3.2.25516, 7.3.2.25549 BUILD 7.3.0.24473, 7.3.0.24484, 7.3.0.24492, 7.3.0.24500, 7.3.0.24539, 7.3.0.24563, 7.3.0.24608, 7.3.0.24651, 7.3.0.24681, 7.3.0.24692, 7.3.0.24700, 7.3.0.24715, 7.3.0.24727, 7.3.0.24730, 7.3.0.24738, 7.3.0.24744, 7.3.0.24758, 7.3.0.24759, 7.3.0.24781, 7.3.0.24793, 7.3.0.24829, 7.3.0.24834, 7.3.0.24843, 7.3.0.24845, 7.3.0.24852, 7.3.0.24853, 7.3.0.24864, 7.3.0.24878, 7.3.0.24887, 7.3.0.24896, 7.3.0.24904, 7.3.0.24920, 7.3.0.24931, 7.3.0.24956, 7.3.0.24970, 7.3.0.24974, 7.3.0.25021, 7.3.0.25195 -$noninline,id$ID<32> +$noninline,id$TerrainID<32> TerrainDesc FootstepSprayRun FootstepSprayWalk @@ -123,7 +123,7 @@ BUILD 10.0.0.43342 BUILD 9.2.5.42850, 9.2.5.43022, 9.2.5.43057, 9.2.5.43180, 9.2.5.43254, 9.2.5.43412 BUILD 9.2.0.41089, 9.2.0.41257, 9.2.0.41360, 9.2.0.41462, 9.2.0.41726, 9.2.0.41827, 9.2.0.41962, 9.2.0.42069, 9.2.0.42174, 9.2.0.42257, 9.2.0.42277, 9.2.0.42354, 9.2.0.42399, 9.2.0.42423, 9.2.0.42488, 9.2.0.42521, 9.2.0.42538, 9.2.0.42560, 9.2.0.42578, 9.2.0.42614, 9.2.0.42698, 9.2.0.42825, 9.2.0.42852, 9.2.0.42937, 9.2.0.42979, 9.2.0.43114, 9.2.0.43206, 9.2.0.43340, 9.2.0.43345 BUILD 9.1.5.39977, 9.1.5.40071, 9.1.5.40078, 9.1.5.40196, 9.1.5.40290, 9.1.5.40383, 9.1.5.40496, 9.1.5.40622, 9.1.5.40696, 9.1.5.40738, 9.1.5.40772, 9.1.5.40843, 9.1.5.40871, 9.1.5.40906, 9.1.5.40944, 9.1.5.40966, 9.1.5.41031, 9.1.5.41079, 9.1.5.41288, 9.1.5.41323, 9.1.5.41359, 9.1.5.41488, 9.1.5.41793, 9.1.5.42010 -$noninline,id$ID<32> +$noninline,id$TerrainID<32> TerrainDesc FootstepSprayRun FootstepSprayWalk @@ -141,7 +141,7 @@ BUILD 10.0.2.45480, 10.0.2.45505, 10.0.2.45569, 10.0.2.45632, 10.0.2.45698, 10.0 BUILD 10.0.0.44167, 10.0.0.44351, 10.0.0.44402, 10.0.0.44475, 10.0.0.44559, 10.0.0.44592, 10.0.0.44622, 10.0.0.44645, 10.0.0.44649, 10.0.0.44691, 10.0.0.44707, 10.0.0.44795, 10.0.0.44840, 10.0.0.44876, 10.0.0.44895, 10.0.0.44933, 10.0.0.44999, 10.0.0.45025, 10.0.0.45093, 10.0.0.45141, 10.0.0.45193, 10.0.0.45232, 10.0.0.45335, 10.0.0.45454, 10.0.0.45570, 10.0.0.45661, 10.0.0.45697, 10.0.0.45778, 10.0.0.45970, 10.0.0.46047, 10.0.0.46112, 10.0.0.46181, 10.0.0.46247, 10.0.0.46270, 10.0.0.46293, 10.0.0.46313, 10.0.0.46340, 10.0.0.46366, 10.0.0.46455, 10.0.0.46547, 10.0.0.46549, 10.0.0.46597 BUILD 9.2.7.44444, 9.2.7.44767, 9.2.7.44931, 9.2.7.44981, 9.2.7.45114, 9.2.7.45161, 9.2.7.45338, 9.2.7.45745 BUILD 9.2.5.43519, 9.2.5.43630, 9.2.5.43741, 9.2.5.43810, 9.2.5.43903, 9.2.5.43971, 9.2.5.44015, 9.2.5.44061, 9.2.5.44127, 9.2.5.44232, 9.2.5.44325, 9.2.5.44730, 9.2.5.44908 -$noninline,id$ID<32> +$noninline,id$TerrainID<32> TerrainDesc FootstepSprayRun FootstepSprayWalk