/* * Copyright (C) 2012-2017 CypherCore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Framework.Collections; using Framework.Constants; using Framework.Database; using Framework.Dynamic; using Framework.GameMath; using System; using System.Collections.Generic; using System.IO; using System.Reflection; namespace Game.DataStorage { public class DB6Reader { internal static DB6Storage Read(string fileName, DB6Meta meta, HotfixStatements preparedStatement, HotfixStatements preparedStatementLocale = 0) where T : new() { DB6Storage storage = new DB6Storage(); if (!File.Exists(CliDB.DataPath + fileName)) { Log.outError(LogFilter.ServerLoading, "File {0} not found.", fileName); return storage; } //First lets load field Info var fields = typeof(T).GetFields(); DBClientHelper[] fieldsInfo = new DBClientHelper[fields.Length]; for (var i = 0; i < fields.Length; ++i) fieldsInfo[i] = new DBClientHelper(fields[i]); using (var fileReader = new BinaryReader(new MemoryStream(File.ReadAllBytes(CliDB.DataPath + fileName)))) { _header = ReadHeader(fileReader); var data = LoadData(fileReader); int commonDataFieldIndex = 0; foreach (var pair in data) { var dataReader = new DB6BinaryReader(pair.Value); var obj = new T(); int fieldIndex = 0; for (var x = 0; x < _header.FieldCount; ++x) { int arrayLength = meta.ArraySizes[x]; if (arrayLength > 1) { for (var z = 0; z < arrayLength; ++z) { var fieldInfo = fieldsInfo[fieldIndex++]; if (fieldInfo.IsArray) { //Field is Array Array array = (Array)fieldInfo.Getter(obj); for (var y = 0; y < array.Length; ++y) SetArrayValue(array, y, fieldInfo, dataReader, x); arrayLength -= array.Length; } else { //Only Data is Array if (Type.GetTypeCode(fieldInfo.FieldType) == TypeCode.Object) { switch (fieldInfo.FieldType.Name) { case "Vector2": fieldInfo.SetValue(obj, new Vector2(dataReader.ReadSingle(), dataReader.ReadSingle())); arrayLength -= 2; break; case "Vector3": fieldInfo.SetValue(obj, new Vector3(dataReader.ReadSingle(), dataReader.ReadSingle(), dataReader.ReadSingle())); arrayLength -= 3; break; case "LocalizedString": LocalizedString locString = new LocalizedString(); locString[Global.WorldMgr.GetDefaultDbcLocale()] = GetString(dataReader, x); fieldInfo.SetValue(obj, locString); arrayLength -= 1; break; case "FlagArray128": FlagArray128 flagArray128 = new FlagArray128(dataReader.GetUInt32(_header.GetFieldBytes(x)), dataReader.GetUInt32(_header.GetFieldBytes(x)), dataReader.GetUInt32(_header.GetFieldBytes(x)), dataReader.GetUInt32(_header.GetFieldBytes(x))); fieldInfo.SetValue(obj, flagArray128); arrayLength -= 4; break; default: Log.outError(LogFilter.ServerLoading, "Unknown Array Type {0} in DBClient File", fieldInfo.FieldType.Name, nameof(T)); break; } } else SetValue(obj, fieldInfo, dataReader, x); } } } else { var fieldInfo = fieldsInfo[fieldIndex++]; if (fieldInfo.IsArray) { Array array = (Array)fieldInfo.Getter(obj); for (var y = 0; y < array.Length; ++y) SetArrayValue(array, y, fieldInfo, dataReader, x + y); x += array.Length - 1; } else SetValue(obj, fieldInfo, dataReader, x); } } commonDataFieldIndex = fieldIndex; storage.Add((uint)pair.Key, obj); } //Get DB field Index uint index = 0; for (uint i = 0; i < _header.FieldCount && i < _header.IndexField; ++i) index += meta.ArraySizes[i]; ReadCommonData(commonDataFieldIndex, storage, meta, fieldsInfo); storage.LoadData(index, fieldsInfo, preparedStatement, preparedStatementLocale); } Global.DB2Mgr.AddDB2(_header.TableHash, storage); CliDB.LoadedFileCount++; return storage; } static void ReadCommonData(int fieldIndex, DB6Storage storage, DB6Meta meta, DBClientHelper[] helper) where T : new() { for (int x = (int)_header.FieldCount; x < _header.TotalFieldCount; ++x) { var fieldInfo = helper[fieldIndex++]; int arrayLength = meta.ArraySizes[x]; foreach (var recordId in _commandData[x]) { var dataReader = new DB6BinaryReader(recordId.Value); var record = storage.LookupByKey(recordId.Key); if (arrayLength > 1) { for (var z = 0; z < arrayLength; ++z) { if (fieldInfo.IsArray) { //Field is Array Array array = (Array)fieldInfo.Getter(record); for (var y = 0; y < array.Length; ++y) SetArrayValue(array, y, fieldInfo, dataReader, x); arrayLength -= array.Length; } else { //Only Data is Array if (Type.GetTypeCode(fieldInfo.FieldType) == TypeCode.Object) { Log.outError(LogFilter.Server, "We should not have custom classes in common data"); switch (fieldInfo.FieldType.Name) { case "Vector2": fieldInfo.SetValue(record, new Vector2(dataReader.ReadSingle(), dataReader.ReadSingle())); arrayLength -= 2; break; case "Vector3": fieldInfo.SetValue(record, new Vector3(dataReader.ReadSingle(), dataReader.ReadSingle(), dataReader.ReadSingle())); arrayLength -= 3; break; case "LocalizedString": LocalizedString locString = new LocalizedString(); locString[Global.WorldMgr.GetDefaultDbcLocale()] = GetString(dataReader, x); fieldInfo.SetValue(record, locString); arrayLength -= 1; break; case "FlagArray128": FlagArray128 flagArray128 = new FlagArray128(dataReader.ReadUInt32(), dataReader.ReadUInt32(), dataReader.ReadUInt32(), dataReader.ReadUInt32()); fieldInfo.SetValue(record, flagArray128); arrayLength -= 4; break; default: Log.outError(LogFilter.ServerLoading, "Unknown Array Type {0} in DBClient File", fieldInfo.FieldType.Name, nameof(T)); break; } } else SetValue(record, fieldInfo, dataReader, x); } } } else { if (fieldInfo.IsArray) { Array array = (Array)fieldInfo.Getter(record); for (var y = 0; y < array.Length; ++y) SetArrayValue(array, y, fieldInfo, dataReader, x + y); x += array.Length - 1; } else SetValue(record, fieldInfo, dataReader, x); } } } } static void SetArrayValue(Array array, int arrayIndex, DBClientHelper helper, DB6BinaryReader reader, int field) { switch (Type.GetTypeCode(helper.RealType)) { case TypeCode.SByte: helper.SetValue(array, reader.ReadSByte(), arrayIndex); break; case TypeCode.Byte: helper.SetValue(array, reader.ReadByte(), arrayIndex); break; case TypeCode.Int16: helper.SetValue(array, reader.ReadInt16(), arrayIndex); break; case TypeCode.UInt16: helper.SetValue(array, reader.ReadUInt16(), arrayIndex); break; case TypeCode.Int32: helper.SetValue(array, reader.GetInt32(_header.GetFieldBytes(field)), arrayIndex); break; case TypeCode.UInt32: helper.SetValue(array, reader.GetUInt32(_header.GetFieldBytes(field)), arrayIndex); break; case TypeCode.Single: helper.SetValue(array, reader.ReadSingle(), arrayIndex); break; case TypeCode.String: helper.SetValue(array, GetString(reader, field), arrayIndex); break; default: Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); break; } } static void SetValue(object obj, DBClientHelper helper, DB6BinaryReader reader, int field) { switch (Type.GetTypeCode(helper.RealType)) { case TypeCode.SByte: helper.SetValue(obj, reader.ReadSByte()); break; case TypeCode.Byte: helper.SetValue(obj, reader.ReadByte()); break; case TypeCode.Int16: helper.SetValue(obj, reader.ReadInt16()); break; case TypeCode.UInt16: helper.SetValue(obj, reader.ReadUInt16()); break; case TypeCode.Int32: helper.SetValue(obj, reader.GetInt32(_header.GetFieldBytes(field))); break; case TypeCode.UInt32: helper.SetValue(obj, reader.GetUInt32(_header.GetFieldBytes(field))); break; case TypeCode.Single: helper.SetValue(obj, reader.ReadSingle()); break; case TypeCode.String: string str = GetString(reader, field); helper.SetValue(obj, str); break; case TypeCode.Object: switch (helper.FieldType.Name) { case "Vector2": helper.SetValue(obj, new Vector2(reader.ReadSingle(), reader.ReadSingle())); break; case "Vector3": helper.SetValue(obj, new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle())); break; case "LocalizedString": LocalizedString locString = new LocalizedString(); locString[Global.WorldMgr.GetDefaultDbcLocale()] = GetString(reader, field); helper.SetValue(obj, locString); break; default: Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); break; } break; default: Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name); break; } } static string GetString(DB6BinaryReader reader, int field) { if (_stringTable != null) return _stringTable.LookupByKey(reader.GetUInt32(_header.GetFieldBytes(field))); return reader.ReadCString(); } static DB6Header ReadHeader(BinaryReader reader) { DB6Header header = new DB6Header(); header.Signature = reader.ReadStringFromChars(4); header.RecordCount = reader.ReadUInt32(); header.FieldCount = reader.ReadUInt32(); header.RecordSize = reader.ReadUInt32(); header.StringTableSize = reader.ReadUInt32(); // also offset for sparse table header.TableHash = reader.ReadUInt32(); header.LayoutHash = reader.ReadUInt32(); // 21737: changed from build number to layoutHash header.MinId = reader.ReadInt32(); header.MaxId = reader.ReadInt32(); header.Locale = reader.ReadInt32(); header.CopyTableSize = reader.ReadInt32(); header.Flags = reader.ReadUInt16(); header.IndexField = reader.ReadUInt16(); header.TotalFieldCount = reader.ReadUInt32(); header.CommonDataSize = reader.ReadUInt32(); for (int i = 0; i < header.FieldCount; i++) { header.columnMeta.Add(new DB6Header.FieldEntry() { UnusedBits = reader.ReadInt16(), Offset = (short)(reader.ReadInt16() + (header.HasIndexTable() ? 4 : 0)) }); } if (header.HasIndexTable()) { header.FieldCount++; header.columnMeta.Insert(0, new DB6Header.FieldEntry()); } return header; } static Dictionary LoadData(BinaryReader reader) { Dictionary Data = new Dictionary(); _stringTable = null; // headerSize long recordsOffset = 56 + (_header.HasIndexTable() ? _header.FieldCount - 1 : _header.FieldCount) * 4; long eof = reader.BaseStream.Length; long commonDataPos = eof - _header.CommonDataSize; long copyTablePos = commonDataPos - _header.CopyTableSize; long indexTablePos = copyTablePos - (_header.HasIndexTable() ? _header.RecordCount * 4 : 0); long stringTablePos = indexTablePos - (_header.IsSparseTable() ? 0 : _header.StringTableSize); // Index table int[] m_indexes = null; if (_header.HasIndexTable()) { reader.BaseStream.Position = indexTablePos; m_indexes = new int[_header.RecordCount]; for (int i = 0; i < _header.RecordCount; i++) m_indexes[i] = reader.ReadInt32(); } if (_header.IsSparseTable()) { // Records table reader.BaseStream.Position = _header.StringTableSize; int ofsTableSize = _header.MaxId - _header.MinId + 1; for (int i = 0; i < ofsTableSize; i++) { int offset = reader.ReadInt32(); int length = reader.ReadInt16(); if (offset == 0 || length == 0) continue; int id = _header.MinId + i; long oldPos = reader.BaseStream.Position; reader.BaseStream.Position = offset; byte[] recordBytes = reader.ReadBytes(length); byte[] newRecordBytes = new byte[recordBytes.Length + 4]; Array.Copy(BitConverter.GetBytes(id), newRecordBytes, 4); Array.Copy(recordBytes, 0, newRecordBytes, 4, recordBytes.Length); Data.Add(id, newRecordBytes); reader.BaseStream.Position = oldPos; } } else { // Records table reader.BaseStream.Position = recordsOffset; for (int i = 0; i < _header.RecordCount; i++) { reader.BaseStream.Position = recordsOffset + i * _header.RecordSize; byte[] recordBytes = reader.ReadBytes((int)_header.RecordSize); if (_header.HasIndexTable()) { byte[] newRecordBytes = new byte[_header.RecordSize + 4]; Array.Copy(BitConverter.GetBytes(m_indexes[i]), newRecordBytes, 4); Array.Copy(recordBytes, 0, newRecordBytes, 4, recordBytes.Length); Data.Add(m_indexes[i], newRecordBytes); } else { int numBytes = (32 - _header.columnMeta[_header.IndexField].UnusedBits) >> 3; int offset = _header.columnMeta[_header.IndexField].Offset; int id = 0; for (int j = 0; j < numBytes; j++) id |= (recordBytes[offset + j] << (j * 8)); Data.Add(id, recordBytes); } } // Strings table reader.BaseStream.Position = stringTablePos; _stringTable = new Dictionary(); while (reader.BaseStream.Position != stringTablePos + _header.StringTableSize) { int index = (int)(reader.BaseStream.Position - stringTablePos); _stringTable[index] = reader.ReadCString(); } } // Copy index table if (copyTablePos != reader.BaseStream.Length && _header.CopyTableSize != 0) { reader.BaseStream.Position = copyTablePos; while (reader.BaseStream.Position != reader.BaseStream.Length) { int id = reader.ReadInt32(); int idcopy = reader.ReadInt32(); byte[] copyRow = Data[idcopy]; byte[] newRow = new byte[copyRow.Length]; Array.Copy(copyRow, newRow, newRow.Length); Array.Copy(BitConverter.GetBytes(id), newRow, 4); Data.Add(id, newRow); } } if (_header.CommonDataSize != 0) { reader.BaseStream.Position = commonDataPos; int fieldsCount = reader.ReadInt32(); _commandData = new Dictionary[fieldsCount]; for (int i = 0; i < fieldsCount; i++) { int count = reader.ReadInt32(); byte type = reader.ReadByte(); _commandData[i] = new Dictionary(); for (int j = 0; j < count; j++) { int id = reader.ReadInt32(); switch (type) { case 1: // 2 bytes _commandData[i].Add(id, reader.ReadBytes(2)); reader.BaseStream.Position += 2; break; case 2: // 1 bytes _commandData[i].Add(id, reader.ReadBytes(1)); reader.BaseStream.Position += 3; break; case 3: // 4 bytes case 4: _commandData[i].Add(id, reader.ReadBytes(4)); break; default: throw new Exception("Invalid data type " + type); } } } } return Data; } static DB6Header _header; static Dictionary _stringTable; static Dictionary[] _commandData; } public class GameTableReader { internal static GameTable Read(string fileName) where T : new() { GameTable storage = new GameTable(); if (!File.Exists(CliDB.DataPath + fileName)) { Log.outError(LogFilter.ServerLoading, "File {0} not found.", fileName); return storage; } using (var reader = new StreamReader(CliDB.DataPath + fileName)) { string headers = reader.ReadLine(); if (headers.IsEmpty()) { Log.outError(LogFilter.ServerLoading, "GameTable file {0} is empty.", fileName); return storage; } var columnDefs = new StringArray(headers, '\t'); List data = new List(); data.Add(new T()); // row id 0, unused string line; while (!(line = reader.ReadLine()).IsEmpty()) { var values = new StringArray(line, '\t'); if (values.Length == 0) break; var obj = new T(); var fields = obj.GetType().GetFields(); for (int fieldIndex = 0, valueIndex = 1; fieldIndex < fields.Length && valueIndex < values.Length; ++fieldIndex, ++valueIndex) { var field = fields[fieldIndex]; if (field.FieldType.IsArray) { Array array = (Array)field.GetValue(obj); for (var i = 0; i < array.Length; ++i) array.SetValue(float.Parse(values[valueIndex++]), i); } else fields[fieldIndex].SetValue(obj, float.Parse(values[valueIndex])); } data.Add(obj); } storage.SetData(data); } CliDB.LoadedFileCount++; return storage; } } public struct DBClientHelper { public DBClientHelper(FieldInfo fieldInfo) { IsArray = false; FieldType = RealType = fieldInfo.FieldType; if (fieldInfo.FieldType.IsArray) { FieldType = RealType = fieldInfo.FieldType.GetElementType(); IsArray = true; } IsEnum = FieldType.IsEnum; if (IsEnum) { IsEnum = FieldType.IsEnum; RealType = FieldType.GetEnumUnderlyingType(); } Setter = fieldInfo.CompileSetter(); Getter = fieldInfo.CompileGetter(); } public void SetValue(Array array, object value, int arrayIndex) { if (!IsEnum) array.SetValue(Convert.ChangeType(value, FieldType), arrayIndex % array.Length); else array.SetValue(Enum.ToObject(FieldType, value), arrayIndex % array.Length); } public void SetValue(object obj, object value) { if (!IsEnum) Setter(obj, Convert.ChangeType(value, FieldType)); else Setter(obj, Enum.ToObject(FieldType, value)); } public Type FieldType; public Type RealType; public bool IsArray; bool IsEnum; Action Setter; public Func Getter; } class DB6Header { public bool IsValidDB6File() { return Signature == "WDB6"; } public bool IsSparseTable() { return Convert.ToBoolean(Flags & 0x1); } public bool HasIndexTable() { return Convert.ToBoolean(Flags & 0x4); } public int GetFieldBytes(int field) { if (columnMeta.Count <= field) return 4; return 4 - columnMeta[field].UnusedBits / 8; } public string Signature; public uint RecordCount; public uint FieldCount; public uint RecordSize; public uint StringTableSize; public uint TableHash; public uint LayoutHash; public int MinId; public int MaxId; public int Locale; public int CopyTableSize; public uint Flags; public int IndexField; public uint TotalFieldCount; public uint CommonDataSize; public List columnMeta = new List(); public struct FieldEntry { public short UnusedBits; public short Offset; } } class DB6BinaryReader : BinaryReader { public DB6BinaryReader(byte[] data) : base(new MemoryStream(data)) { } public int GetInt32(int fieldBytes) { switch (fieldBytes) { case 1: return ReadSByte(); case 2: return ReadInt16(); case 3: byte[] bytes = ReadBytes(fieldBytes); return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16); default: return ReadInt32(); } } public uint GetUInt32(int fieldBytes) { switch (fieldBytes) { case 1: return ReadByte(); case 2: return ReadUInt16(); case 3: byte[] bytes = ReadBytes(fieldBytes); return bytes[0] | ((uint)bytes[1] << 8) | ((uint)bytes[2] << 16); default: return ReadUInt32(); } } public float GetSingle(int fieldBytes) { switch (fieldBytes) { case 1: return ReadByte(); case 2: return ReadUInt16(); case 3: byte[] bytes = ReadBytes(fieldBytes); return bytes[0] | ((uint)bytes[1] << 8) | ((uint)bytes[2] << 16); default: return ReadSingle(); } } } public class LocalizedString { public bool HasString(LocaleConstant locale = SharedConst.DefaultLocale) { return !string.IsNullOrEmpty(stringStorage[(int)locale]); } public string this[LocaleConstant locale] { get { return stringStorage[(int)locale] ?? ""; } set { stringStorage[(int)locale] = value; } } StringArray stringStorage = new StringArray((int)LocaleConstant.Total); } }