using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Text; using DFEngine.Logging; namespace DFEngine.IO { /// /// Class to load and access Shine tables. /// public class ShineTable : IDisposable { /// /// Represents a comment. /// private const string COMMENT = ";"; /// /// Represents a replacement in the file. /// private const string REPLACE = "#exchange"; /// /// Represents an ignored part of the file. /// private const string IGNORE = "#ignore"; /// /// Represents the file's start of definitions. /// private const string DEFINE = "#define"; /// /// Represents the file's end of definitions. /// private const string ENDDEFINE = "#enddefine"; /// /// Represents a table indicator. /// private const string TABLE = "#table"; /// /// Represents the name of a column. /// private const string COLUMN_NAME = "#columnname"; /// /// Represents the type of a column. /// private const string COLUMN_TYPE = "#columntype"; /// /// Represents a record in the file. /// private const string RECORD = "#record"; /// /// Represents a record in a table. /// private const string RECORD_IN = "#recordin"; /// /// The name of the file we're reading from. /// private readonly string fileName; /// /// All of the tables in the file handle. /// private readonly Dictionary tables; /// /// Creates a new instance of the class. /// /// The path of the file. public ShineTable(string path) { fileName = path; tables = new Dictionary(); PopulateTables(); } /// /// Returns the table with the associated table name. /// /// The name of the table. public DataTable this[string tableName] => tables.GetSafe(tableName); /// /// Disposes the file handle. /// public void Dispose() { foreach (var table in tables.Values) { table.Dispose(); } tables.Clear(); } /// /// Checks a value for replacements. /// private string CheckValue(Tuple replacing, string input) { return replacing != null ? input.Replace(replacing.Item1, replacing.Item2) : input; } /// /// Converts the input string to a readable string. /// private string ConvertString(string input) { if (input.StartsWith("\\x")) { return ((char)Convert.ToByte(input.Substring(2), 16)).ToString(); } if (input.StartsWith("\\o")) { return ((char)Convert.ToByte(input.Substring(2), 8)).ToString(); } return input.Length > 0 ? input[0].ToString() : ""; } /// /// Gets the data type for a column. /// /// The name of the column's data type. private Type GetColumnDataType(string type) { var typeName = type.ToLower(); if (typeName.StartsWith("string[")) { return typeof(string); } switch (typeName) { case "byte": return typeof(byte); case "word": return typeof(short); case "": case "dwrd": case "dword": return typeof(int); case "qword": return typeof(long); case "index": return typeof(string); case "": case "string": return typeof(string); } return typeof(string); } /// /// Populates the file's tables with data from the file. /// private void PopulateTables() { if (!File.Exists(fileName)) { EngineLog.Write(EngineLogLevel.Warning, $"The ShineTable file '{fileName}' could not be found."); return; } using (var file = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new StreamReader(file, Encoding.Default)) { var definingStruct = false; var definingTable = false; var lineNumber = 0; DataTable currentTable = null; List columnTypes = null; var rowComment = ""; char? ignoreThis = null; Tuple replaceThis = null; var unkColumnCount = 0; while (!reader.EndOfStream) { lineNumber++; string line; if ((line = reader.ReadLine()) == null) { break; } if (line.Contains(COMMENT)) { var index = line.IndexOf(COMMENT, StringComparison.Ordinal); rowComment = line.Substring(index + 1); line = line.Remove(index); } if (ignoreThis.HasValue) { line = line.Replace(ignoreThis.Value.ToString(), ""); } if (line == string.Empty) { continue; } var lineLower = line.ToLower(); var items = line.Split('\t'); if (lineLower.StartsWith(REPLACE)) { replaceThis = new Tuple(ConvertString(items[0]), ConvertString(items[2])); } if (lineLower.StartsWith(IGNORE)) { ignoreThis = ConvertString(items[1])[0]; } if (lineLower.StartsWith(DEFINE)) { if (definingStruct || definingTable) { throw new Exception("Something is already being defined."); } var name = line.Substring(DEFINE.Length + 1); currentTable = new DataTable(name); definingStruct = true; continue; } if (lineLower.StartsWith(TABLE)) { if (definingStruct) { throw new Exception("Something is already being defined."); } var name = items[1].Trim(); currentTable = new DataTable(name); columnTypes = new List(); unkColumnCount = 0; tables.Add(name, currentTable); definingTable = true; continue; } if (lineLower.StartsWith(ENDDEFINE)) { if (!definingStruct) { throw new Exception("A structure hasn't started being defined."); } tables.Add(currentTable.TableName, currentTable); definingStruct = false; continue; } lineLower = lineLower.Trim(); if (definingStruct) { var columnName = rowComment.Trim(); if (columnName == string.Empty) { continue; } var column = new DataColumn(columnName, GetColumnDataType(lineLower)); if (columnName.Length < 2) { column.ColumnName = $"UnkCol{unkColumnCount++}"; } currentTable.Columns.Add(column); } else if (definingTable) { if (lineLower.StartsWith(COLUMN_TYPE)) { for (var i = 1; i < items.Length; i++) { var type = items[i].Trim(); if (type == string.Empty) { continue; } columnTypes.Add(type); } } else if (lineLower.StartsWith(COLUMN_NAME)) { for (int i = 1, j = 0; i < items.Length; i++) { var name = items[i].Trim(); if (name == string.Empty) { continue; } var columnType = columnTypes[j++]; var column = new DataColumn(name, GetColumnDataType(columnType)); if (name.Length < 2) { column.ColumnName = $"UnkCol{unkColumnCount++}"; } currentTable.Columns.Add(column); } } else if (lineLower.StartsWith(RECORD_IN)) { var tableName = items[1].Trim(); if (tables.ContainsKey(tableName)) { currentTable = tables[tableName]; var rowData = new object[currentTable.Columns.Count]; for (int i = 2, j = 0; i < items.Length; i++) { var item = items[i].Trim(); if (item == string.Empty) { continue; } rowData[j++] = CheckValue(replaceThis, item.TrimEnd(',')); } currentTable.Rows.Add(rowData); } } else if (lineLower.StartsWith(RECORD)) { var rowData = new object[currentTable.Columns.Count]; for (int i = 1, j = 0; i < items.Length; i++) { if (j >= currentTable.Columns.Count) { break; } var item = items[i].Trim(); if (item == string.Empty) { continue; } rowData[j++] = CheckValue(replaceThis, item.TrimEnd(',')); } currentTable.Rows.Add(rowData); } } else { if (tables.ContainsKey(items[0].Trim())) { var existingTable = tables[items[0].Trim()]; var columnCount = existingTable.Columns.Count; var readColumns = 0; var rowData = new object[columnCount]; for (var i = 1; ; i++) { if (readColumns == columnCount) { break; } if (items.Length < i) { throw new Exception($"Could not read all columns of line {lineNumber}"); } var columnText = items[i].Trim(); if (columnText == string.Empty) { continue; } columnText = columnText.TrimEnd(',').Trim('"'); rowData[readColumns++] = columnText; } existingTable.Rows.Add(rowData); } } } } } } }