diff --git a/src/server/game/Tools/PlayerDump.cpp b/src/server/game/Tools/PlayerDump.cpp index 7b8fb482e..4d8946df3 100644 --- a/src/server/game/Tools/PlayerDump.cpp +++ b/src/server/game/Tools/PlayerDump.cpp @@ -20,378 +20,714 @@ #include "CharacterCache.h" #include "Common.h" #include "DatabaseEnv.h" +#include "Log.h" #include "ObjectMgr.h" -#include "World.h" +#include "Player.h" #include "StringConvert.h" +#include "World.h" +#include +#include -constexpr auto DUMP_TABLE_COUNT = 27; + // static data +enum GuidType : uint8 +{ + // 32 bit long guids + GUID_TYPE_ACCOUNT, + GUID_TYPE_CHAR, + GUID_TYPE_PET, + GUID_TYPE_MAIL, + GUID_TYPE_ITEM, + + // 64 bit long guids + GUID_TYPE_EQUIPMENT_SET, + + // special types + GUID_TYPE_NULL // set to null +}; + +// for RAII +struct FileCloser +{ + void operator()(FILE* f) const + { + if (f) + fclose(f); + } +}; +typedef std::unique_ptr FileHandle; + +inline FileHandle GetFileHandle(char const* path, char const* mode) +{ + return FileHandle(fopen(path, mode), FileCloser()); +} + +struct BaseTable +{ + char const* TableName; + char const* PrimaryKey; + char const* PlayerGuid; + + GuidType StoredType; +}; + +BaseTable const BaseTables[] = +{ + { "character_pet", "id", "owner", GUID_TYPE_PET }, + { "mail", "id", "receiver", GUID_TYPE_MAIL }, + { "item_instance", "guid", "owner_guid", GUID_TYPE_ITEM }, + + { "character_equipmentsets", "setguid", "guid", GUID_TYPE_EQUIPMENT_SET } +}; struct DumpTable { - char const* name; - DumpTableType type; + char const* Name; + DumpTableType Type; }; -static DumpTable dumpTables[DUMP_TABLE_COUNT] = +DumpTable const DumpTables[] = { - { "characters", DTT_CHARACTER }, - { "character_account_data", DTT_CHAR_TABLE }, - { "character_achievement", DTT_CHAR_TABLE }, - { "character_achievement_progress", DTT_CHAR_TABLE }, - { "character_action", DTT_CHAR_TABLE }, - { "character_aura", DTT_CHAR_TABLE }, - { "character_declinedname", DTT_CHAR_TABLE }, - { "character_equipmentsets", DTT_EQSET_TABLE}, - { "character_glyphs", DTT_CHAR_TABLE }, - { "character_homebind", DTT_CHAR_TABLE }, - { "character_inventory", DTT_INVENTORY }, - { "character_pet", DTT_PET }, - { "character_pet_declinedname", DTT_PET }, - { "character_queststatus", DTT_CHAR_TABLE }, - { "character_queststatus_rewarded", DTT_CHAR_TABLE }, - { "character_reputation", DTT_CHAR_TABLE }, - { "character_skills", DTT_CHAR_TABLE }, - { "character_spell", DTT_CHAR_TABLE }, - { "character_spell_cooldown", DTT_CHAR_TABLE }, - { "character_talent", DTT_CHAR_TABLE }, - { "mail", DTT_MAIL }, - { "mail_items", DTT_MAIL_ITEM }, // must be after mail - { "pet_aura", DTT_PET_TABLE }, // must be after character_pet - { "pet_spell", DTT_PET_TABLE }, // must be after character_pet - { "pet_spell_cooldown", DTT_PET_TABLE }, // must be after character_pet - { "item_instance", DTT_ITEM }, // must be after character_inventory and mail_items - { "character_gifts", DTT_ITEM_GIFT }, // must be after item_instance + { "characters", DTT_CHARACTER }, + { "character_account_data", DTT_CHAR_TABLE }, + { "character_achievement", DTT_CHAR_TABLE }, + { "character_achievement_progress", DTT_CHAR_TABLE }, + { "character_action", DTT_CHAR_TABLE }, + { "character_aura", DTT_CHAR_TABLE }, + { "character_declinedname", DTT_CHAR_TABLE }, + { "character_equipmentsets", DTT_EQSET_TABLE }, + { "character_glyphs", DTT_CHAR_TABLE }, + { "character_homebind", DTT_CHAR_TABLE }, + { "character_inventory", DTT_INVENTORY }, + { "character_pet", DTT_PET }, + { "character_pet_declinedname", DTT_PET }, + { "character_queststatus", DTT_CHAR_TABLE }, + { "character_queststatus_daily", DTT_CHAR_TABLE }, + { "character_queststatus_weekly", DTT_CHAR_TABLE }, + { "character_queststatus_monthly", DTT_CHAR_TABLE }, + { "character_queststatus_seasonal", DTT_CHAR_TABLE }, + { "character_queststatus_rewarded", DTT_CHAR_TABLE }, + { "character_reputation", DTT_CHAR_TABLE }, + { "character_skills", DTT_CHAR_TABLE }, + { "character_spell", DTT_CHAR_TABLE }, + { "character_spell_cooldown", DTT_CHAR_TABLE }, + { "character_talent", DTT_CHAR_TABLE }, + { "mail", DTT_MAIL }, + { "mail_items", DTT_MAIL_ITEM }, // must be after mail + { "pet_aura", DTT_PET_TABLE }, // must be after character_pet + { "pet_spell", DTT_PET_TABLE }, // must be after character_pet + { "pet_spell_cooldown", DTT_PET_TABLE }, // must be after character_pet + { "item_instance", DTT_ITEM }, // must be after character_inventory and mail_items + { "character_gifts", DTT_ITEM_GIFT } // must be after item_instance }; +uint32 const DUMP_TABLE_COUNT = std::extent::value; + +// helper class to dump sql queries to a printable string +class StringTransaction +{ +public: + StringTransaction() : _buf() { } + + void Append(char const* sql) + { + std::ostringstream oss; + oss << sql << '\n'; + _buf += oss.str(); + } + + char const* GetBuffer() const + { + return _buf.c_str(); + } + +private: + std::string _buf; +}; + +// dynamic data, loaded at startup +struct TableField +{ + std::string FieldName; + + GuidType FieldGuidType = GUID_TYPE_ACCOUNT; + bool IsDependentField = false; +}; + +struct TableStruct +{ + std::string TableName; + std::string WhereFieldName; + std::vector TableFields; + + // for lookup + std::unordered_map FieldIndices; +}; + +std::vector CharacterTables; + +inline bool StringsEqualCaseInsensitive(std::string const& left, std::string const& right) +{ + std::string upperLeftString = left; + bool leftResult = Utf8ToUpperOnlyLatin(upperLeftString); + ASSERT(leftResult); + + std::string upperRightString = right; + bool rightResult = Utf8ToUpperOnlyLatin(upperRightString); + ASSERT(rightResult); + + return upperLeftString == upperRightString; +} + +inline auto FindColumnByName(TableStruct& tableStruct, std::string const& columnName) -> decltype(tableStruct.TableFields.begin()) +{ + return std::find_if(tableStruct.TableFields.begin(), tableStruct.TableFields.end(), [columnName](TableField const& tableField) -> bool + { + return StringsEqualCaseInsensitive(tableField.FieldName, columnName); + }); +} + +inline int32 GetColumnIndexByName(TableStruct const& tableStruct, std::string const& columnName) +{ + auto itr = tableStruct.FieldIndices.find(columnName); + if (itr == tableStruct.FieldIndices.end()) + return -1; + + return itr->second; +} + +inline void MarkDependentColumn(TableStruct& tableStruct, std::string const& columnName, GuidType dependentType) +{ + auto itr = FindColumnByName(tableStruct, columnName); + if (itr == tableStruct.TableFields.end()) + { + LOG_FATAL("server.loading", "Column `%s` declared in table `%s` marked as dependent but doesn't exist, PlayerDump will not work properly, please update table definitions", + columnName.c_str(), tableStruct.TableName.c_str()); + ABORT(); + return; + } + + if (itr->IsDependentField) + { + LOG_FATAL("server.loading", "Attempt to mark column `%s` in table `%s` as dependent column but already marked! please check your code.", + columnName.c_str(), tableStruct.TableName.c_str()); + ABORT(); + return; + } + + itr->IsDependentField = true; + itr->FieldGuidType = dependentType; +} + +inline void MarkWhereField(TableStruct& tableStruct, std::string const& whereField) +{ + ASSERT(tableStruct.WhereFieldName.empty()); + + auto whereFieldItr = FindColumnByName(tableStruct, whereField); + if (whereFieldItr == tableStruct.TableFields.end()) + { + LOG_FATAL("server.loading", "Column name `%s` set as 'WHERE' column for table `%s` doesn't exist. PlayerDump won't work properly", + whereField.c_str(), tableStruct.TableName.c_str()); + ABORT(); + return; + } + + tableStruct.WhereFieldName = whereField; +} + +inline void AssertBaseTable(BaseTable const& baseTable) +{ + auto itr = std::find_if(CharacterTables.begin(), CharacterTables.end(), [baseTable](TableStruct const& tableStruct) -> bool + { + return StringsEqualCaseInsensitive(tableStruct.TableName, baseTable.TableName); + }); + + ASSERT(itr != CharacterTables.end()); + + auto columnItr = FindColumnByName(*itr, baseTable.PrimaryKey); + ASSERT(columnItr != itr->TableFields.end()); + + columnItr = FindColumnByName(*itr, baseTable.PlayerGuid); + ASSERT(columnItr != itr->TableFields.end()); +} + +void PlayerDump::InitializeTables() +{ + uint32 oldMSTime = getMSTime(); + + for (DumpTable const& dumpTable : DumpTables) + { + TableStruct t; + t.TableName = dumpTable.Name; + + QueryResult result = CharacterDatabase.PQuery("DESC %s", dumpTable.Name); + // prepared statement is correct (checked at startup) so table must exist + ASSERT(result); + + int32 i = 0; + do + { + std::string columnName = (*result)[0].GetString(); + t.FieldIndices.emplace(columnName, i++); + + TableField f; + f.FieldName = columnName; + + bool toUpperResult = Utf8ToUpperOnlyLatin(columnName); + ASSERT(toUpperResult); + + t.TableFields.emplace_back(std::move(f)); + } while (result->NextRow()); + + switch (dumpTable.Type) + { + case DTT_CHARACTER: + MarkWhereField(t, "guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_CHAR); + MarkDependentColumn(t, "account", GUID_TYPE_ACCOUNT); + + MarkDependentColumn(t, "deleteInfos_Account", GUID_TYPE_NULL); + MarkDependentColumn(t, "deleteInfos_Name", GUID_TYPE_NULL); + MarkDependentColumn(t, "deleteDate", GUID_TYPE_NULL); + break; + case DTT_CHAR_TABLE: + MarkWhereField(t, "guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_CHAR); + break; + case DTT_EQSET_TABLE: + MarkWhereField(t, "guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_CHAR); + MarkDependentColumn(t, "setguid", GUID_TYPE_EQUIPMENT_SET); + + // item0 - item18 + for (uint32 j = 0; j < EQUIPMENT_SLOT_END; ++j) + { + std::string itColumn = Acore::StringFormat("item%u", j); + MarkDependentColumn(t, itColumn, GUID_TYPE_ITEM); + } + break; + case DTT_INVENTORY: + MarkWhereField(t, "guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_CHAR); + MarkDependentColumn(t, "bag", GUID_TYPE_ITEM); + MarkDependentColumn(t, "item", GUID_TYPE_ITEM); + break; + case DTT_MAIL: + MarkWhereField(t, "receiver"); + + MarkDependentColumn(t, "id", GUID_TYPE_MAIL); + MarkDependentColumn(t, "receiver", GUID_TYPE_CHAR); + break; + case DTT_MAIL_ITEM: + MarkWhereField(t, "mail_id"); + + MarkDependentColumn(t, "mail_id", GUID_TYPE_MAIL); + MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM); + MarkDependentColumn(t, "receiver", GUID_TYPE_CHAR); + break; + case DTT_ITEM: + MarkWhereField(t, "guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_ITEM); + MarkDependentColumn(t, "owner_guid", GUID_TYPE_CHAR); + break; + case DTT_ITEM_GIFT: + MarkWhereField(t, "item_guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_CHAR); + MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM); + break; + case DTT_PET: + MarkWhereField(t, "owner"); + + MarkDependentColumn(t, "id", GUID_TYPE_PET); + MarkDependentColumn(t, "owner", GUID_TYPE_CHAR); + break; + case DTT_PET_TABLE: + MarkWhereField(t, "guid"); + + MarkDependentColumn(t, "guid", GUID_TYPE_PET); + break; + default: + LOG_FATAL("server.loading", "Wrong dump table type %u, probably added a new table type without updating code", uint32(dumpTable.Type)); + ABORT(); + return; + } + + CharacterTables.emplace_back(std::move(t)); + } + + // perform some sanity checks + for (TableStruct const& tableStruct : CharacterTables) + { + if (tableStruct.WhereFieldName.empty()) + { + LOG_FATAL("server.loading", "Table `%s` defined in player dump doesn't have a WHERE query field", tableStruct.TableName.c_str()); + ABORT(); + } + } + + for (BaseTable const& baseTable : BaseTables) + AssertBaseTable(baseTable); + + ASSERT(CharacterTables.size() == DUMP_TABLE_COUNT); + + LOG_INFO("server.loading", ">> Initialized tables for PlayerDump in %u ms.", GetMSTimeDiffToNow(oldMSTime)); +} + // Low level functions -static bool findtoknth(std::string& str, int n, std::string::size_type& s, std::string::size_type& e) +inline bool FindColumn(TableStruct const& ts, std::string const& str, std::string const& column, std::string::size_type& s, std::string::size_type& e) { - int i; - s = e = 0; - std::string::size_type size = str.size(); - for (i = 1; s < size && i < n; s++) if (str[s] == ' ') ++i; - if (i < n) + int32 columnIndex = GetColumnIndexByName(ts, column); + if (columnIndex == -1) return false; - e = str.find(' ', s); + // array indices start at 0, compensate + ++columnIndex; - return e != std::string::npos; -} - -std::string gettoknth(std::string& str, int n) -{ - std::string::size_type s = 0, e = 0; - if (!findtoknth(str, n, s, e)) - return ""; - - return str.substr(s, e - s); -} - -bool findnth(std::string& str, int n, std::string::size_type& s, std::string::size_type& e) -{ - s = str.find("VALUES ('") + 9; - if (s == std::string::npos) return false; + s = str.find("VALUES ('"); + if (s == std::string::npos) + return false; + s += 9; do { e = str.find('\'', s); - if (e == std::string::npos) return false; + if (e == std::string::npos) + return false; } while (str[e - 1] == '\\'); - for (int i = 1; i < n; ++i) + for (int32 i = 1; i < columnIndex; ++i) { do { + // length of "', '" s = e + 4; e = str.find('\'', s); - if (e == std::string::npos) return false; + if (e == std::string::npos) + return false; } while (str[e - 1] == '\\'); } return true; } -std::string gettablename(std::string& str) +inline std::string GetTableName(std::string const& str) { - std::string::size_type s = 13; - std::string::size_type e = str.find("`", s); + // length of "INSERT INTO `" + static std::string::size_type const s = 13; + std::string::size_type e = str.find('`', s); if (e == std::string::npos) return ""; return str.substr(s, e - s); } -bool changenth(std::string& str, int n, const char* with, bool insert = false, bool nonzero = false) +inline bool ValidateFields(TableStruct const& ts, std::string const& str, size_t lineNumber) { - std::string::size_type s, e; - if (!findnth(str, n, s, e)) - return false; + std::string::size_type s = str.find("` VALUES ("); + if (s != std::string::npos) // old dump format (no column names) + return true; - if (nonzero && str.substr(s, e - s) == "0") - return true; // not an error - if (!insert) - str.replace(s, e - s, with); - else - str.insert(s, with); + // new format has insert with columns, need validation else we risk executing an invalid query + s = str.find("` (`"); + if (s == std::string::npos) + { + LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") dump format not recognized.", lineNumber); + return false; + } + s += 4; + + std::string::size_type valPos = str.find("VALUES ('"); + std::string::size_type e = str.find('`', s); + if (e == std::string::npos || valPos == std::string::npos) + { + LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") unexpected end of line", lineNumber); + return false; + } + + do + { + std::string column = str.substr(s, e - s); + int32 columnIndex = GetColumnIndexByName(ts, column); + if (columnIndex == -1) + { + LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") unknown column name `%s` for table `%s`, aborting due to incompatible DB structure.", lineNumber, column.c_str(), ts.TableName.c_str()); + return false; + } + + // length of "`, `" + s = e + 4; + e = str.find('`', s); + } while (e < valPos); return true; } -std::string getnth(std::string& str, int n) +inline bool ChangeColumn(TableStruct const& ts, std::string& str, std::string const& column, std::string const& with, bool allowZero = false) { std::string::size_type s, e; - if (!findnth(str, n, s, e)) + if (!FindColumn(ts, str, column, s, e)) + return false; + + if (allowZero && str.substr(s, e - s) == "0") + return true; // not an error + + str.replace(s, e - s, with); + return true; +} + +inline std::string GetColumn(TableStruct const& ts, std::string& str, std::string const& column) +{ + std::string::size_type s, e; + if (!FindColumn(ts, str, column, s, e)) return ""; return str.substr(s, e - s); } -bool changetoknth(std::string& str, int n, const char* with, bool insert = false, bool nonzero = false) +template class MapType, class... Rest> +inline T RegisterNewGuid(T oldGuid, MapType& guidMap, T guidOffset) { - std::string::size_type s = 0, e = 0; - if (!findtoknth(str, n, s, e)) - return false; - if (nonzero && str.substr(s, e - s) == "0") - return true; // not an error - if (!insert) - str.replace(s, e - s, with); - else - str.insert(s, with); - - return true; -} - -uint32 registerNewGuid(uint32 oldGuid, std::map& guidMap, uint32 hiGuid) -{ - std::map::const_iterator itr = guidMap.find(oldGuid); + auto itr = guidMap.find(oldGuid); if (itr != guidMap.end()) return itr->second; - uint32 newguid = hiGuid + guidMap.size(); - guidMap[oldGuid] = newguid; + T newguid = guidOffset + T(guidMap.size()); + guidMap.emplace(oldGuid, newguid); return newguid; } -bool changeGuid(std::string& str, int n, std::map& guidMap, uint32 hiGuid, bool nonzero = false) +template class MapType, class... Rest> +inline bool ChangeGuid(TableStruct const& ts, std::string& str, std::string const& column, MapType& guidMap, T guidOffset, bool allowZero = false) { - char chritem[20]; + T oldGuid(*Acore::StringTo(GetColumn(ts, str, column))); + if (allowZero && !oldGuid) + return true; // not an error - auto _oldGuid = Acore::StringTo(getnth(str, n)); - if (nonzero && (!_oldGuid || !*_oldGuid)) - return true; // not an error + std::string chritem; + T newGuid = RegisterNewGuid(oldGuid, guidMap, guidOffset); + chritem = std::to_string(newGuid); - uint32 oldGuid = *_oldGuid; - uint32 newGuid = registerNewGuid(oldGuid, guidMap, hiGuid); - snprintf(chritem, 20, "%u", newGuid); - - return changenth(str, n, chritem, false, nonzero); + return ChangeColumn(ts, str, column, chritem, allowZero); } -bool changetokGuid(std::string& str, int n, std::map& guidMap, uint32 hiGuid, bool nonzero = false) +inline void AppendTableDump(StringTransaction& trans, TableStruct const& tableStruct, QueryResult result) { - char chritem[20]; - auto _oldGuid = Acore::StringTo(getnth(str, n)); - if (nonzero && (!_oldGuid || !*_oldGuid)) - return true; // not an error - - uint32 oldGuid = *_oldGuid; - uint32 newGuid = registerNewGuid(oldGuid, guidMap, hiGuid); - snprintf(chritem, 20, "%u", newGuid); - - return changetoknth(str, n, chritem, false, nonzero); -} - -std::string CreateDumpString(char const* tableName, QueryResult result) -{ - if (!tableName || !result) return ""; - std::ostringstream ss; - ss << "INSERT INTO `" << tableName << "` VALUES ("; - Field* fields = result->Fetch(); - for (uint32 i = 0; i < result->GetFieldCount(); ++i) - { - if (i == 0) ss << '\''; - else ss << ", '"; - - std::string s = fields[i].GetString(); - CharacterDatabase.EscapeString(s); - ss << s; - - ss << '\''; - } - ss << ");"; - return ss.str(); -} - -std::string PlayerDumpWriter::GenerateWhereStr(char const* field, uint32 guid) -{ - std::ostringstream wherestr; - wherestr << field << " = '" << guid << '\''; - return wherestr.str(); -} - -std::string PlayerDumpWriter::GenerateWhereStr(char const* field, GUIDs const& guids, GUIDs::const_iterator& itr) -{ - std::ostringstream wherestr; - wherestr << field << " IN ('"; - for (; itr != guids.end(); ++itr) - { - wherestr << *itr; - - if (wherestr.str().size() > MAX_QUERY_LEN - 50) // near to max query - { - ++itr; - break; - } - - GUIDs::const_iterator itr2 = itr; - if (++itr2 != guids.end()) - wherestr << "', '"; - } - wherestr << "')"; - return wherestr.str(); -} - -void StoreGUID(QueryResult result, uint32 field, std::set& guids) -{ - Field* fields = result->Fetch(); - uint32 guid = fields[field].GetUInt32(); - if (guid) - guids.insert(guid); -} - -void StoreGUID(QueryResult result, uint32 data, uint32 field, std::set& guids) -{ - Field* fields = result->Fetch(); - std::string dataStr = fields[data].GetString(); - - if (auto guid = Acore::StringTo(gettoknth(dataStr, field))) - { - guids.insert(*guid); - } -} - -// Writing - High-level functions -bool PlayerDumpWriter::DumpTable(std::string& dump, uint32 guid, char const* tableFrom, char const* tableTo, DumpTableType type) -{ - GUIDs const* guids = nullptr; - char const* fieldname = nullptr; - - switch (type) - { - case DTT_ITEM: - fieldname = "guid"; - guids = &items; - break; - case DTT_ITEM_GIFT: - fieldname = "item_guid"; - guids = &items; - break; - case DTT_PET: - fieldname = "owner"; - break; - case DTT_PET_TABLE: - fieldname = "guid"; - guids = &pets; - break; - case DTT_MAIL: - fieldname = "receiver"; - break; - case DTT_MAIL_ITEM: - fieldname = "mail_id"; - guids = &mails; - break; - default: - fieldname = "guid"; - break; - } - - // for guid set stop if set is empty - if (guids && guids->empty()) - return true; // nothing to do - - // setup for guids case start position - GUIDs::const_iterator guids_itr; - if (guids) - guids_itr = guids->begin(); + if (!result) + return; do { - std::string wherestr; + std::ostringstream ss; + ss << "INSERT INTO `" << tableStruct.TableName << "` ("; + for (auto itr = tableStruct.TableFields.begin(); itr != tableStruct.TableFields.end();) + { + ss << '`' << itr->FieldName << '`'; + ++itr; - if (guids) // set case, get next guids string - wherestr = GenerateWhereStr(fieldname, *guids, guids_itr); - else // not set case, get single guid string - wherestr = GenerateWhereStr(fieldname, guid); + if (itr != tableStruct.TableFields.end()) + ss << ", "; + } + ss << ") VALUES ("; - QueryResult result = CharacterDatabase.PQuery("SELECT * FROM %s WHERE %s", tableFrom, wherestr.c_str()); + uint32 const fieldSize = uint32(tableStruct.TableFields.size()); + Field* fields = result->Fetch(); + + for (uint32 i = 0; i < fieldSize;) + { + char const* cString = fields[i].GetCString(); + ++i; + + // null pointer -> we have null + if (!cString) + ss << "'NULL'"; + else + { + std::string s(cString); + CharacterDatabase.EscapeString(s); + ss << '\'' << s << '\''; + } + + if (i != fieldSize) + ss << ", "; + } + ss << ");"; + + trans.Append(ss.str().c_str()); + } while (result->NextRow()); +} + +inline std::string GenerateWhereStr(std::string const& field, ObjectGuid::LowType guid) +{ + std::ostringstream whereStr; + whereStr << field << " = '" << guid << '\''; + return whereStr.str(); +} + +template class SetType, class... Rest> +inline std::string GenerateWhereStr(std::string const& field, SetType const& guidSet) +{ + std::ostringstream whereStr; + whereStr << field << " IN ('"; + for (auto itr = guidSet.begin(); itr != guidSet.end();) + { + whereStr << *itr; + ++itr; + + if (whereStr.str().size() > MAX_QUERY_LEN - 50) // near to max query + break; + + if (itr != guidSet.end()) + whereStr << "','"; + } + whereStr << "')"; + return whereStr.str(); +} + +// Writing - High-level functions +void PlayerDumpWriter::PopulateGuids(ObjectGuid::LowType guid) +{ + for (BaseTable const& baseTable : BaseTables) + { + switch (baseTable.StoredType) + { + case GUID_TYPE_ITEM: + case GUID_TYPE_MAIL: + case GUID_TYPE_PET: + case GUID_TYPE_EQUIPMENT_SET: + break; + default: + return; + } + + std::string whereStr = GenerateWhereStr(baseTable.PlayerGuid, guid); + QueryResult result = CharacterDatabase.PQuery("SELECT %s FROM %s WHERE %s", baseTable.PrimaryKey, baseTable.TableName, whereStr.c_str()); if (!result) - return true; + continue; do { - // collect guids - switch (type) + switch (baseTable.StoredType) { - case DTT_INVENTORY: - StoreGUID(result, 3, items); // item guid collection (character_inventory.item) - break; - case DTT_PET: - StoreGUID(result, 0, pets); // pet petnumber collection (character_pet.id) - break; - case DTT_MAIL: - StoreGUID(result, 0, mails); // mail id collection (mail.id) - break; - case DTT_MAIL_ITEM: - StoreGUID(result, 1, items); // item guid collection (mail_items.item_guid) - break; - case DTT_CHARACTER: - { - if (result->GetFieldCount() <= 74) // avoid crashes on next check - LOG_FATAL("entities.player.dump", "PlayerDumpWriter::DumpTable - Trying to access non-existing or wrong positioned field (`deleteInfos_Account`) in `characters` table."); - - if (result->Fetch()[74].GetUInt32()) // characters.deleteInfos_Account - if filled error - return false; - break; - } - default: - break; + case GUID_TYPE_ITEM: + if (ObjectGuid::LowType itemLowGuid = (*result)[0].GetUInt32()) + _items.insert(itemLowGuid); + break; + case GUID_TYPE_MAIL: + if (ObjectGuid::LowType mailLowGuid = (*result)[0].GetUInt32()) + _mails.insert(mailLowGuid); + break; + case GUID_TYPE_PET: + if (ObjectGuid::LowType petLowGuid = (*result)[0].GetUInt32()) + _pets.insert(petLowGuid); + break; + case GUID_TYPE_EQUIPMENT_SET: + if (uint64 eqSetId = (*result)[0].GetUInt64()) + _itemSets.insert(eqSetId); + break; + default: + break; } - - dump += CreateDumpString(tableTo, result); - dump += "\n"; } while (result->NextRow()); - } while (guids && guids_itr != guids->end()); // not set case iterate single time, set case iterate for all guids + } +} + +bool PlayerDumpWriter::AppendTable(StringTransaction& trans, ObjectGuid::LowType guid, TableStruct const& tableStruct, DumpTable const& dumpTable) +{ + std::string whereStr; + switch (dumpTable.Type) + { + case DTT_ITEM: + case DTT_ITEM_GIFT: + if (_items.empty()) + return true; + + whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _items); + break; + case DTT_PET_TABLE: + if (_pets.empty()) + return true; + + whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _pets); + break; + case DTT_MAIL_ITEM: + if (_mails.empty()) + return true; + + whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _mails); + break; + case DTT_EQSET_TABLE: + if (_itemSets.empty()) + return true; + + whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _itemSets); + break; + default: + // not set case, get single guid string + whereStr = GenerateWhereStr(tableStruct.WhereFieldName, guid); + break; + } + + QueryResult result = CharacterDatabase.PQuery("SELECT * FROM %s WHERE %s", dumpTable.Name, whereStr.c_str()); + switch (dumpTable.Type) + { + case DTT_CHARACTER: + if (result) + { + // characters.deleteInfos_Account - if filled error + int32 index = GetColumnIndexByName(tableStruct, "deleteInfos_Account"); + ASSERT(index != -1); // checked at startup + + if ((*result)[index].GetUInt32()) + return false; + } + break; + default: + break; + } + + AppendTableDump(trans, tableStruct, result); return true; } -bool PlayerDumpWriter::GetDump(uint32 guid, std::string& dump) +bool PlayerDumpWriter::GetDump(ObjectGuid::LowType guid, std::string& dump) { - dump = ""; - - dump += "IMPORTANT NOTE: THIS DUMPFILE IS MADE FOR USE WITH THE 'PDUMP' COMMAND ONLY - EITHER THROUGH INGAME CHAT OR ON CONSOLE!\n"; + dump = "IMPORTANT NOTE: THIS DUMPFILE IS MADE FOR USE WITH THE 'PDUMP' COMMAND ONLY - EITHER THROUGH INGAME CHAT OR ON CONSOLE!\n"; dump += "IMPORTANT NOTE: DO NOT apply it directly - it will irreversibly DAMAGE and CORRUPT your database! You have been warned!\n\n"; - for (int i = 0; i < DUMP_TABLE_COUNT; ++i) - if (!DumpTable(dump, guid, dumpTables[i].name, dumpTables[i].name, dumpTables[i].type)) + StringTransaction trans; + + // collect guids + PopulateGuids(guid); + for (uint32 i = 0; i < DUMP_TABLE_COUNT; ++i) + if (!AppendTable(trans, guid, CharacterTables[i], DumpTables[i])) return false; - // TODO: Add instance/group.. - // TODO: Add a dump level option to skip some non-important tables + dump += trans.GetBuffer(); + + /// @todo Add instance/group.. + /// @todo Add a dump level option to skip some non-important tables return true; } -DumpReturn PlayerDumpWriter::WriteDump(const std::string& file, uint32 guid) +DumpReturn PlayerDumpWriter::WriteDumpToFile(std::string const& file, ObjectGuid::LowType guid) { if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_PATHS)) - if (strstr(file.c_str(), "\\") || strstr(file.c_str(), "/")) + if (strchr(file.c_str(), '\\') || strchr(file.c_str(), '/')) return DUMP_FILE_OPEN_ERROR; + if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_OVERWRITE)) - if (FILE* f = fopen(file.c_str(), "r")) - { - fclose(f); + { + // check if file exists already + if (GetFileHandle(file.c_str(), "r")) return DUMP_FILE_OPEN_ERROR; - } - FILE* fout = fopen(file.c_str(), "w"); + } + + FileHandle fout = GetFileHandle(file.c_str(), "w"); if (!fout) return DUMP_FILE_OPEN_ERROR; @@ -400,46 +736,46 @@ DumpReturn PlayerDumpWriter::WriteDump(const std::string& file, uint32 guid) if (!GetDump(guid, dump)) ret = DUMP_CHARACTER_DELETED; - fprintf(fout, "%s\n", dump.c_str()); - fclose(fout); + fprintf(fout.get(), "%s", dump.c_str()); + return ret; +} + +DumpReturn PlayerDumpWriter::WriteDumpToString(std::string& dump, ObjectGuid::LowType guid) +{ + DumpReturn ret = DUMP_SUCCESS; + if (!GetDump(guid, dump)) + ret = DUMP_CHARACTER_DELETED; return ret; } // Reading - High-level functions -#define ROLLBACK(DR) {fclose(fin); return (DR);} - -void fixNULLfields(std::string& line) +inline void FixNULLfields(std::string& line) { - std::string nullString("'NULL'"); - size_t pos = line.find(nullString); + static std::string const NullString("'NULL'"); + size_t pos = line.find(NullString); while (pos != std::string::npos) { - line.replace(pos, nullString.length(), "NULL"); - pos = line.find(nullString); + line.replace(pos, NullString.length(), "NULL"); + pos = line.find(NullString); } } -DumpReturn PlayerDumpReader::LoadDump(const std::string& file, uint32 account, std::string name, uint32 guid) +DumpReturn PlayerDumpReader::LoadDump(std::istream& input, uint32 account, std::string name, ObjectGuid::LowType guid) { uint32 charcount = AccountMgr::GetCharactersCount(account); if (charcount >= 10) return DUMP_TOO_MANY_CHARS; - FILE* fin = fopen(file.c_str(), "r"); - if (!fin) - return DUMP_FILE_OPEN_ERROR; - - char newguid[20], chraccount[20], newpetid[20], currpetid[20], lastpetid[20]; + std::string newguid, chraccount; // make sure the same guid doesn't already exist and is safe to use bool incHighest = true; - if (guid != 0 && guid < sObjectMgr->GetGenerator().GetNextAfterMaxUsed()) + if (guid && guid < sObjectMgr->GetGenerator().GetNextAfterMaxUsed()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_GUID); stmt->setUInt32(0, guid); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (result) + if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) guid = sObjectMgr->GetGenerator().GetNextAfterMaxUsed(); // use first free if exists else incHighest = false; @@ -449,266 +785,198 @@ DumpReturn PlayerDumpReader::LoadDump(const std::string& file, uint32 account, s // normalize the name if specified and check if it exists if (!normalizePlayerName(name)) - name = ""; + name.clear(); if (ObjectMgr::CheckPlayerName(name, true) == CHAR_NAME_SUCCESS) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME); stmt->setString(0, name); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (result) - name = ""; // use the one from the dump + if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) + name.clear(); // use the one from the dump } else - name = ""; + name.clear(); // name encoded or empty + newguid = std::to_string(guid); + chraccount = std::to_string(account); - snprintf(newguid, 20, "%u", guid); - snprintf(chraccount, 20, "%u", account); - snprintf(newpetid, 20, "%u", sObjectMgr->GeneratePetNumber()); - snprintf(lastpetid, 20, "%s", ""); + std::map items; + ObjectGuid::LowType itemLowGuidOffset = sObjectMgr->GetGenerator().GetNextAfterMaxUsed(); - std::map items; - std::map mails; - char buf[32000] = ""; + std::map mails; + ObjectGuid::LowType mailLowGuidOffset = sObjectMgr->_mailId; - typedef std::map PetIds; // old->new petid relation - PetIds petids; + std::map petIds; + ObjectGuid::LowType petLowGuidOffset = sObjectMgr->_hiPetNumber; + + std::map equipmentSetIds; + uint64 equipmentSetGuidOffset = sObjectMgr->_equipmentSetGuid; + + std::string line; uint8 gender = GENDER_NONE; uint8 race = RACE_NONE; - uint8 playerClass = 0; + uint8 playerClass = CLASS_NONE; uint8 level = 1; - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - while (!feof(fin)) - { - if (!fgets(buf, 32000, fin)) - { - if (feof(fin)) - break; - ROLLBACK(DUMP_FILE_BROKEN); - } + // for logs + size_t lineNumber = 0; - std::string line; - line.assign(buf); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + while (std::getline(input, line)) + { + ++lineNumber; // skip empty strings size_t nw_pos = line.find_first_not_of(" \t\n\r\7"); if (nw_pos == std::string::npos) continue; - // skip logfile-side dump start notice, the important notes and dump end notices - if ((line.substr(nw_pos, 16) == "== START DUMP ==") || - (line.substr(nw_pos, 15) == "IMPORTANT NOTE:") || - (line.substr(nw_pos, 14) == "== END DUMP ==")) + // skip the important notes + static std::string const SkippedLine = "IMPORTANT NOTE:"; + if (line.substr(nw_pos, SkippedLine.size()) == SkippedLine) continue; - // add required_ check - /* - if (line.substr(nw_pos, 41) == "UPDATE character_db_version SET required_") - { - if (!CharacterDatabase.Execute(line.c_str())) - ROLLBACK(DUMP_FILE_BROKEN); - - continue; - } - */ - // determine table name and load type - std::string tn = gettablename(line); + std::string tn = GetTableName(line); if (tn.empty()) { - LOG_ERROR("entities.player.dump", "LoadPlayerDump: Can't extract table name from line: '%s'!", line.c_str()); - ROLLBACK(DUMP_FILE_BROKEN); + LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") Can't extract table name!", lineNumber); + return DUMP_FILE_BROKEN; } - DumpTableType type = DumpTableType(0); - uint8 i; + DumpTableType type = DTT_CHARACTER; + uint32 i; for (i = 0; i < DUMP_TABLE_COUNT; ++i) { - if (tn == dumpTables[i].name) + if (tn == DumpTables[i].Name) { - type = dumpTables[i].type; + type = DumpTables[i].Type; break; } } if (i == DUMP_TABLE_COUNT) { - LOG_ERROR("entities.player.dump", "LoadPlayerDump: Unknown table: '%s'!", tn.c_str()); - ROLLBACK(DUMP_FILE_BROKEN); + LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") Unknown table: `%s`!", lineNumber, tn.c_str()); + return DUMP_FILE_BROKEN; } - // change the data to server values + TableStruct const& ts = CharacterTables[i]; + if (!ValidateFields(ts, line, lineNumber)) + return DUMP_FILE_BROKEN; + + // per field guid offsetting + for (TableField const& field : ts.TableFields) + { + if (!field.IsDependentField) + continue; + + switch (field.FieldGuidType) + { + case GUID_TYPE_ACCOUNT: + if (!ChangeColumn(ts, line, field.FieldName, chraccount)) + return DUMP_FILE_BROKEN; + break; + case GUID_TYPE_CHAR: + if (!ChangeColumn(ts, line, field.FieldName, newguid)) + return DUMP_FILE_BROKEN; + break; + case GUID_TYPE_PET: + if (!ChangeGuid(ts, line, field.FieldName, petIds, petLowGuidOffset)) + return DUMP_FILE_BROKEN; + break; + case GUID_TYPE_MAIL: + if (!ChangeGuid(ts, line, field.FieldName, mails, mailLowGuidOffset)) + return DUMP_FILE_BROKEN; + break; + case GUID_TYPE_ITEM: + if (!ChangeGuid(ts, line, field.FieldName, items, itemLowGuidOffset, true)) + return DUMP_FILE_BROKEN; + break; + case GUID_TYPE_EQUIPMENT_SET: + if (!ChangeGuid(ts, line, field.FieldName, equipmentSetIds, equipmentSetGuidOffset)) + return DUMP_FILE_BROKEN; + break; + case GUID_TYPE_NULL: + { + static std::string const NullString("NULL"); + if (!ChangeColumn(ts, line, field.FieldName, NullString)) + return DUMP_FILE_BROKEN; + break; + } + } + } + + // extra modifications for other tables switch (type) { - case DTT_CHARACTER: - { - if (!changenth(line, 1, newguid)) // characters.guid update - ROLLBACK(DUMP_FILE_BROKEN); + case DTT_CHARACTER: + { + race = *Acore::StringTo(GetColumn(ts, line, "race")); + playerClass = *Acore::StringTo(GetColumn(ts, line, "class")); + gender = *Acore::StringTo(GetColumn(ts, line, "gender")); + level = *Acore::StringTo(GetColumn(ts, line, "level")); + if (name.empty()) + { + // generate a temporary name + std::string guidPart = Acore::StringFormat("%X", guid); + std::size_t maxCharsFromOriginalName = MAX_PLAYER_NAME - guidPart.length(); - if (!changenth(line, 2, chraccount)) // characters.account update - ROLLBACK(DUMP_FILE_BROKEN); + name = GetColumn(ts, line, "name").substr(0, maxCharsFromOriginalName) + guidPart; - race = uint8(atol(getnth(line, 4).c_str())); - playerClass = uint8(atol(getnth(line, 5).c_str())); - gender = uint8(atol(getnth(line, 6).c_str())); - level = uint8(atol(getnth(line, 7).c_str())); - if (name.empty()) - { - // check if the original name already exists - name = getnth(line, 3); - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME); - stmt->setString(0, name); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - - if (result) - if (!changenth(line, 38, "1")) // characters.at_login set to "rename on login" - ROLLBACK(DUMP_FILE_BROKEN); - } - else if (!changenth(line, 3, name.c_str())) // characters.name - ROLLBACK(DUMP_FILE_BROKEN); - - const char null[5] = "NULL"; - if (!changenth(line, 75, null)) // characters.deleteInfos_Account - ROLLBACK(DUMP_FILE_BROKEN); - if (!changenth(line, 76, null)) // characters.deleteInfos_Name - ROLLBACK(DUMP_FILE_BROKEN); - if (!changenth(line, 77, null)) // characters.deleteDate - ROLLBACK(DUMP_FILE_BROKEN); - break; - } - case DTT_CHAR_TABLE: - { - if (!changenth(line, 1, newguid)) // character_*.guid update - ROLLBACK(DUMP_FILE_BROKEN); - break; - } - case DTT_EQSET_TABLE: - { - if (!changenth(line, 1, newguid)) - ROLLBACK(DUMP_FILE_BROKEN); // character_equipmentsets.guid - - char newSetGuid[24]; - snprintf(newSetGuid, 24, UI64FMTD, sObjectMgr->GenerateEquipmentSetGuid()); - if (!changenth(line, 2, newSetGuid)) - ROLLBACK(DUMP_FILE_BROKEN); // character_equipmentsets.setguid - break; - } - case DTT_INVENTORY: - { - if (!changenth(line, 1, newguid)) // character_inventory.guid update - ROLLBACK(DUMP_FILE_BROKEN); - - if (!changeGuid(line, 2, items, sObjectMgr->GetGenerator().GetNextAfterMaxUsed(), true)) - ROLLBACK(DUMP_FILE_BROKEN); // character_inventory.bag update - if (!changeGuid(line, 4, items, sObjectMgr->GetGenerator().GetNextAfterMaxUsed())) - ROLLBACK(DUMP_FILE_BROKEN); // character_inventory.item update - break; - } - case DTT_MAIL: // mail - { - if (!changeGuid(line, 1, mails, sObjectMgr->_mailId)) - ROLLBACK(DUMP_FILE_BROKEN); // mail.id update - if (!changenth(line, 6, newguid)) // mail.receiver update - ROLLBACK(DUMP_FILE_BROKEN); - break; - } - case DTT_MAIL_ITEM: // mail_items - { - if (!changeGuid(line, 1, mails, sObjectMgr->_mailId)) - ROLLBACK(DUMP_FILE_BROKEN); // mail_items.id - if (!changeGuid(line, 2, items, sObjectMgr->GetGenerator().GetNextAfterMaxUsed())) - ROLLBACK(DUMP_FILE_BROKEN); // mail_items.item_guid - if (!changenth(line, 3, newguid)) // mail_items.receiver - ROLLBACK(DUMP_FILE_BROKEN); - break; - } - case DTT_ITEM: - { - // item, owner, data field:item, owner guid - if (!changeGuid(line, 1, items, sObjectMgr->GetGenerator().GetNextAfterMaxUsed())) - ROLLBACK(DUMP_FILE_BROKEN); // item_instance.guid update - if (!changenth(line, 3, newguid)) // item_instance.owner_guid update - ROLLBACK(DUMP_FILE_BROKEN); - break; - } - case DTT_ITEM_GIFT: - { - if (!changenth(line, 1, newguid)) // character_gifts.guid update - ROLLBACK(DUMP_FILE_BROKEN); - if (!changeGuid(line, 2, items, sObjectMgr->GetGenerator().GetNextAfterMaxUsed())) - ROLLBACK(DUMP_FILE_BROKEN); // character_gifts.item_guid update - break; - } - case DTT_PET: - { - //store a map of old pet id to new inserted pet id for use by type 5 tables - snprintf(currpetid, 20, "%s", getnth(line, 1).c_str()); - if (*lastpetid == '\0') - snprintf(lastpetid, 20, "%s", currpetid); - if (strcmp(lastpetid, currpetid) != 0) - { - snprintf(newpetid, 20, "%d", sObjectMgr->GeneratePetNumber()); - snprintf(lastpetid, 20, "%s", currpetid); - } - - auto const& petids_iter = petids.find(Acore::StringTo(currpetid).value_or(0)); - - if (petids_iter == petids.end()) - { - petids.emplace(Acore::StringTo(currpetid).value_or(0), Acore::StringTo(newpetid).value_or(0)); - } - - if (!changenth(line, 1, newpetid)) // character_pet.id update - ROLLBACK(DUMP_FILE_BROKEN); - if (!changenth(line, 3, newguid)) // character_pet.owner update - ROLLBACK(DUMP_FILE_BROKEN); - - break; - } - case DTT_PET_TABLE: // pet_aura, pet_spell, pet_spell_cooldown - { - snprintf(currpetid, 20, "%s", getnth(line, 1).c_str()); - - // lookup currpetid and match to new inserted pet id - std::map :: const_iterator petids_iter = petids.find(Acore::StringTo(currpetid).value_or(0)); - if (petids_iter == petids.end()) // couldn't find new inserted id - ROLLBACK(DUMP_FILE_BROKEN); - - snprintf(newpetid, 20, "%d", petids_iter->second); - - if (!changenth(line, 1, newpetid)) - ROLLBACK(DUMP_FILE_BROKEN); - - break; - } - default: - LOG_ERROR("entities.player.dump", "Unknown dump table type: %u", type); - break; + // characters.at_login set to "rename on login" + if (!ChangeColumn(ts, line, "name", name)) + return DUMP_FILE_BROKEN; + if (!ChangeColumn(ts, line, "at_login", "1")) + return DUMP_FILE_BROKEN; + } + else if (!ChangeColumn(ts, line, "name", name)) // characters.name + return DUMP_FILE_BROKEN; + break; + } + default: + break; } - fixNULLfields(line); + FixNULLfields(line); trans->Append(line.c_str()); } + if (input.fail() && !input.eof()) + return DUMP_FILE_BROKEN; + CharacterDatabase.CommitTransaction(trans); // in case of name conflict player has to rename at login anyway sCharacterCache->AddCharacterCacheEntry(ObjectGuid(HighGuid::Player, guid), account, name, gender, race, playerClass, level); sObjectMgr->GetGenerator().Set(sObjectMgr->GetGenerator().GetNextAfterMaxUsed() + items.size()); - sObjectMgr->_mailId += mails.size(); + sObjectMgr->_mailId += mails.size(); + sObjectMgr->_hiPetNumber += petIds.size(); + sObjectMgr->_equipmentSetGuid += equipmentSetIds.size(); if (incHighest) sObjectMgr->GetGenerator().Generate(); - fclose(fin); + sWorld->UpdateRealmCharCount(account); return DUMP_SUCCESS; } + +DumpReturn PlayerDumpReader::LoadDumpFromString(std::string const& dump, uint32 account, std::string name, ObjectGuid::LowType guid) +{ + std::istringstream input(dump); + return LoadDump(input, account, name, guid); +} + +DumpReturn PlayerDumpReader::LoadDumpFromFile(std::string const& file, uint32 account, std::string name, ObjectGuid::LowType guid) +{ + std::ifstream input(file); + if (!input) + return DUMP_FILE_OPEN_ERROR; + return LoadDump(input, account, name, guid); +} diff --git a/src/server/game/Tools/PlayerDump.h b/src/server/game/Tools/PlayerDump.h index 0d085ab31..493b6da85 100644 --- a/src/server/game/Tools/PlayerDump.h +++ b/src/server/game/Tools/PlayerDump.h @@ -18,37 +18,38 @@ #ifndef _PLAYER_DUMP_H #define _PLAYER_DUMP_H -#include "Define.h" +#include +#include #include #include -#include +#include "ObjectGuid.h" enum DumpTableType { DTT_CHARACTER, // // characters DTT_CHAR_TABLE, // // character_achievement, character_achievement_progress, - // character_action, character_aura, character_homebind, - // character_queststatus, character_queststatus_rewarded, character_reputation, - // character_spell, character_spell_cooldown, character_ticket, character_talent + // character_action, character_aura, character_homebind, + // character_queststatus, character_queststatus_rewarded, character_reputation, + // character_spell, character_spell_cooldown, character_ticket, character_talent DTT_EQSET_TABLE, // <- guid // character_equipmentsets DTT_INVENTORY, // -> item guids collection // character_inventory DTT_MAIL, // -> mail ids collection // mail - // -> item_text + // -> item_text DTT_MAIL_ITEM, // <- mail ids // mail_items - // -> item guids collection + // -> item guids collection DTT_ITEM, // <- item guids // item_instance - // -> item_text + // -> item_text DTT_ITEM_GIFT, // <- item guids // character_gifts DTT_PET, // -> pet guids collection // character_pet - DTT_PET_TABLE, // <- pet guids // pet_aura, pet_spell, pet_spell_cooldown + DTT_PET_TABLE // <- pet guids // pet_aura, pet_spell, pet_spell_cooldown }; enum DumpReturn @@ -56,15 +57,21 @@ enum DumpReturn DUMP_SUCCESS, DUMP_FILE_OPEN_ERROR, DUMP_TOO_MANY_CHARS, - DUMP_UNEXPECTED_END, DUMP_FILE_BROKEN, DUMP_CHARACTER_DELETED }; +struct DumpTable; +struct TableStruct; +class StringTransaction; + class PlayerDump { +public: + static void InitializeTables(); + protected: - PlayerDump() {} + PlayerDump() { } }; class PlayerDumpWriter : public PlayerDump @@ -72,18 +79,19 @@ class PlayerDumpWriter : public PlayerDump public: PlayerDumpWriter() { } - bool GetDump(uint32 guid, std::string& dump); - DumpReturn WriteDump(std::string const& file, uint32 guid); + bool GetDump(ObjectGuid::LowType guid, std::string& dump); + DumpReturn WriteDumpToFile(std::string const& file, ObjectGuid::LowType guid); + DumpReturn WriteDumpToString(std::string& dump, ObjectGuid::LowType guid); + private: - typedef std::set GUIDs; + bool AppendTable(StringTransaction& trans, ObjectGuid::LowType guid, TableStruct const& tableStruct, DumpTable const& dumpTable); + void PopulateGuids(ObjectGuid::LowType guid); - bool DumpTable(std::string& dump, uint32 guid, char const* tableFrom, char const* tableTo, DumpTableType type); - std::string GenerateWhereStr(char const* field, GUIDs const& guids, GUIDs::const_iterator& itr); - std::string GenerateWhereStr(char const* field, uint32 guid); + std::set _pets; + std::set _mails; + std::set _items; - GUIDs pets; - GUIDs mails; - GUIDs items; + std::set _itemSets; }; class PlayerDumpReader : public PlayerDump @@ -91,7 +99,11 @@ class PlayerDumpReader : public PlayerDump public: PlayerDumpReader() { } - DumpReturn LoadDump(std::string const& file, uint32 account, std::string name, uint32 guid); + DumpReturn LoadDumpFromFile(std::string const& file, uint32 account, std::string name, ObjectGuid::LowType guid); + DumpReturn LoadDumpFromString(std::string const& dump, uint32 account, std::string name, ObjectGuid::LowType guid); + +private: + DumpReturn LoadDump(std::istream& input, uint32 account, std::string name, ObjectGuid::LowType guid); }; #endif diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index a7bf0170b..0e2a29ba8 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -67,6 +67,7 @@ #include "OutdoorPvPMgr.h" #include "PetitionMgr.h" #include "Player.h" +#include "PlayerDump.h" #include "PoolMgr.h" #include "Realm.h" #include "ScriptMgr.h" @@ -1530,6 +1531,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading spell dbc data corrections..."); sSpellMgr->LoadDbcDataCorrections(); + LOG_INFO("server.loading", "Initializing PlayerDump tables..."); + PlayerDump::InitializeTables(); + LOG_INFO("server.loading", "Loading SpellInfo store..."); sSpellMgr->LoadSpellInfoStore(); diff --git a/src/server/scripts/Commands/cs_character.cpp b/src/server/scripts/Commands/cs_character.cpp index 8526fd056..6ce2241a0 100644 --- a/src/server/scripts/Commands/cs_character.cpp +++ b/src/server/scripts/Commands/cs_character.cpp @@ -816,51 +816,96 @@ public: if (!ValidatePDumpTarget(handler, name, characterName, characterGUID)) return false; - switch (PlayerDumpReader().LoadDump(fileName, account, name, characterGUID.value_or(0))) + switch (PlayerDumpReader().LoadDumpFromFile(fileName, account, name, characterGUID.value_or(0))) { - case DUMP_SUCCESS: - handler->PSendSysMessage(LANG_COMMAND_IMPORT_SUCCESS); - break; - case DUMP_FILE_OPEN_ERROR: - handler->PSendSysMessage(LANG_FILE_OPEN_FAIL, fileName.c_str()); - handler->SetSentErrorMessage(true); - return false; - case DUMP_FILE_BROKEN: - handler->PSendSysMessage(LANG_DUMP_BROKEN, fileName.c_str()); - handler->SetSentErrorMessage(true); - return false; - case DUMP_TOO_MANY_CHARS: - handler->PSendSysMessage(LANG_ACCOUNT_CHARACTER_LIST_FULL, account.GetName().c_str(), account.GetID()); - handler->SetSentErrorMessage(true); - return false; - default: - handler->PSendSysMessage(LANG_COMMAND_IMPORT_FAILED); - handler->SetSentErrorMessage(true); - return false; + case DUMP_SUCCESS: + handler->PSendSysMessage(LANG_COMMAND_IMPORT_SUCCESS); + break; + case DUMP_FILE_OPEN_ERROR: + handler->PSendSysMessage(LANG_FILE_OPEN_FAIL, fileName.c_str()); + handler->SetSentErrorMessage(true); + return false; + case DUMP_FILE_BROKEN: + handler->PSendSysMessage(LANG_DUMP_BROKEN, fileName.c_str()); + handler->SetSentErrorMessage(true); + return false; + case DUMP_TOO_MANY_CHARS: + handler->PSendSysMessage(LANG_ACCOUNT_CHARACTER_LIST_FULL, account.GetName().c_str(), account.GetID()); + handler->SetSentErrorMessage(true); + return false; + default: + handler->PSendSysMessage(LANG_COMMAND_IMPORT_FAILED); + handler->SetSentErrorMessage(true); + return false; } return true; } + static bool HandlePDumpCopyCommand(ChatHandler* handler, PlayerIdentifier player, AccountIdentifier account, Optional characterName, Optional characterGUID) + { + std::string name; + if (!ValidatePDumpTarget(handler, name, characterName, characterGUID)) + return false; + + std::string dump; + switch (PlayerDumpWriter().WriteDumpToString(dump, player.GetGUID().GetCounter())) + { + case DUMP_SUCCESS: + break; + case DUMP_CHARACTER_DELETED: + handler->PSendSysMessage(LANG_COMMAND_EXPORT_DELETED_CHAR); + handler->SetSentErrorMessage(true); + return false; + case DUMP_FILE_OPEN_ERROR: // this error code should not happen + default: + handler->PSendSysMessage(LANG_COMMAND_EXPORT_FAILED); + handler->SetSentErrorMessage(true); + return false; + } + + switch (PlayerDumpReader().LoadDumpFromString(dump, account, name, characterGUID.value_or(0))) + { + case DUMP_SUCCESS: + break; + case DUMP_TOO_MANY_CHARS: + handler->PSendSysMessage(LANG_ACCOUNT_CHARACTER_LIST_FULL, account.GetName().c_str(), account.GetID()); + handler->SetSentErrorMessage(true); + return false; + case DUMP_FILE_OPEN_ERROR: // this error code should not happen + case DUMP_FILE_BROKEN: // this error code should not happen + default: + handler->PSendSysMessage(LANG_COMMAND_IMPORT_FAILED); + handler->SetSentErrorMessage(true); + return false; + } + + // Original TC Notes from Refactor vvv + //ToDo: use a new trinity_string for this commands + handler->PSendSysMessage(LANG_COMMAND_IMPORT_SUCCESS); + + return true; + } + static bool HandlePDumpWriteCommand(ChatHandler* handler, std::string fileName, PlayerIdentifier player) { - switch (PlayerDumpWriter().WriteDump(fileName, player.GetGUID().GetCounter())) + switch (PlayerDumpWriter().WriteDumpToFile(fileName, player.GetGUID().GetCounter())) { - case DUMP_SUCCESS: - handler->PSendSysMessage(LANG_COMMAND_EXPORT_SUCCESS); - break; - case DUMP_FILE_OPEN_ERROR: - handler->PSendSysMessage(LANG_FILE_OPEN_FAIL, fileName.c_str()); - handler->SetSentErrorMessage(true); - return false; - case DUMP_CHARACTER_DELETED: - handler->PSendSysMessage(LANG_COMMAND_EXPORT_DELETED_CHAR); - handler->SetSentErrorMessage(true); - return false; - default: - handler->PSendSysMessage(LANG_COMMAND_EXPORT_FAILED); - handler->SetSentErrorMessage(true); - return false; + case DUMP_SUCCESS: + handler->PSendSysMessage(LANG_COMMAND_EXPORT_SUCCESS); + break; + case DUMP_FILE_OPEN_ERROR: + handler->PSendSysMessage(LANG_FILE_OPEN_FAIL, fileName.c_str()); + handler->SetSentErrorMessage(true); + return false; + case DUMP_CHARACTER_DELETED: + handler->PSendSysMessage(LANG_COMMAND_EXPORT_DELETED_CHAR); + handler->SetSentErrorMessage(true); + return false; + default: + handler->PSendSysMessage(LANG_COMMAND_EXPORT_FAILED); + handler->SetSentErrorMessage(true); + return false; } return true;