/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * 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 2 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 . */ #include "PlayerDump.h" #include "AccountMgr.h" #include "CharacterCache.h" #include "Common.h" #include "DatabaseEnv.h" #include "Log.h" #include "ObjectMgr.h" #include "Player.h" #include "World.h" #include #include // static data enum GuidType : uint8 { // 32 bit long guids GUID_TYPE_ACCOUNT, GUID_TYPE_MAIL, // 64 bit long guids GUID_TYPE_CHAR, GUID_TYPE_EQUIPMENT_SET, GUID_TYPE_ITEM, GUID_TYPE_PET, // 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 }, { "character_transmog_outfits", "setguid", "guid", GUID_TYPE_EQUIPMENT_SET } }; struct DumpTable { char const* Name; DumpTableType Type; }; 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_aura_effect", DTT_CHAR_TABLE }, { "character_cuf_profiles", DTT_CHAR_TABLE }, { "character_currency", DTT_CURRENCY }, { "character_declinedname", DTT_CHAR_TABLE }, { "character_favorite_auctions", DTT_CHAR_TABLE }, { "character_fishingsteps", DTT_CHAR_TABLE }, { "character_garrison", DTT_CHAR_TABLE }, { "character_garrison_blueprints", DTT_CHAR_TABLE }, { "character_garrison_buildings", DTT_CHAR_TABLE }, /// @todo: character_garrison_follower_abilities /// @todo: character_garrison_followers { "character_glyphs", DTT_CHAR_TABLE }, { "character_homebind", DTT_CHAR_TABLE }, { "character_inventory", DTT_INVENTORY }, { "character_pet", DTT_PET }, { "character_pet_declinedname", DTT_PET }, { "character_pvp_talent", DTT_CHAR_TABLE }, { "character_queststatus", DTT_CHAR_TABLE }, { "character_queststatus_daily", DTT_CHAR_TABLE }, { "character_queststatus_monthly", DTT_CHAR_TABLE }, { "character_queststatus_objectives", DTT_CHAR_TABLE }, { "character_queststatus_objectives_criteria", DTT_CHAR_TABLE }, { "character_queststatus_objectives_criteria_progress", DTT_CHAR_TABLE }, { "character_queststatus_rewarded", DTT_CHAR_TABLE }, { "character_queststatus_seasonal", DTT_CHAR_TABLE }, { "character_queststatus_weekly", DTT_CHAR_TABLE }, { "character_reputation", DTT_CHAR_TABLE }, { "character_skills", DTT_CHAR_TABLE }, { "character_spell", DTT_CHAR_TABLE }, { "character_spell_charges", DTT_CHAR_TABLE }, { "character_spell_cooldown", DTT_CHAR_TABLE }, { "character_talent", DTT_CHAR_TABLE }, { "character_transmog_outfits", DTT_CHAR_TRANSMOG }, /// @todo: character_void_storage { "mail", DTT_MAIL }, { "mail_items", DTT_MAIL_ITEM }, // must be after mail { "pet_aura", DTT_PET_TABLE }, // must be after character_pet { "pet_aura_effect", DTT_PET_TABLE }, // must be after character_pet { "pet_spell", DTT_PET_TABLE }, // must be after character_pet { "pet_spell_charges", 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_equipmentsets", DTT_EQSET_TABLE}, // must be after item_instance { "character_gifts", DTT_ITEM_GIFT }, // must be after item_instance { "item_instance_artifact", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_artifact_powers", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_azerite", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_azerite_empowered", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_azerite_milestone_power", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_azerite_unlocked_essence", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_gems", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_modifiers", DTT_ITEM_TABLE }, // must be after item_instance { "item_instance_transmog", DTT_ITEM_TABLE }, // 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; bool IsBinaryField = 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()) { TC_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) { TC_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()) { TC_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(); std::string typeName = (*result)[1].GetString(); t.FieldIndices.emplace(columnName, i++); TableField f; f.FieldName = columnName; f.IsBinaryField = !boost::ifind_first(typeName, "binary").empty() || !boost::ifind_first(typeName, "blob").empty(); 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_CURRENCY: MarkWhereField(t, "CharacterGuid"); MarkDependentColumn(t, "CharacterGuid", 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 = Trinity::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_CHAR_TRANSMOG: MarkWhereField(t, "guid"); MarkDependentColumn(t, "guid", GUID_TYPE_CHAR); MarkDependentColumn(t, "setguid", GUID_TYPE_EQUIPMENT_SET); 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_ITEM_TABLE: MarkWhereField(t, "itemGuid"); MarkDependentColumn(t, "itemGuid", 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: TC_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()) { TC_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); TC_LOG_INFO("server.loading", ">> Initialized tables for PlayerDump in %u ms.", GetMSTimeDiffToNow(oldMSTime)); } // Low level functions inline bool FindColumn(TableStruct const& ts, std::string const& str, std::string const& column, std::string::size_type& s, std::string::size_type& e) { int32 columnIndex = GetColumnIndexByName(ts, column); if (columnIndex == -1) return false; // array indices start at 0, compensate ++columnIndex; s = str.find("VALUES ("); if (s == std::string::npos) return false; s += 8; e = s; bool isQuoted = str[s] == '\''; if (isQuoted) { ++s; ++e; // find first unescaped quote do { e = str.find('\'', e); if (e == std::string::npos) return false; if (str[e - 1] == '\\') continue; if (e + 1 < str.length() && str[e + 1] == '\'') { ++e; continue; } break; } while (true); } else e = str.find_first_of(",)", e); for (int32 i = 1; i < columnIndex; ++i) { // if previous value was quoted, move old e to comma if (isQuoted) ++e; // move past ", " s = e + 2; e = s; isQuoted = str[s] == '\''; if (isQuoted) { ++s; ++e; // find first unescaped quote do { e = str.find('\'', e); if (e == std::string::npos) return false; if (str[e - 1] == '\\') continue; if (e + 1 < str.length() && str[e + 1] == '\'') { ++e; continue; } break; } while (str[e - 1] == '\\'); } else e = str.find_first_of(",)", e); } return true; } inline std::string GetTableName(std::string const& str) { // 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); } inline bool ValidateFields(TableStruct const& ts, std::string const& str, size_t lineNumber) { std::string::size_type s = str.find("` VALUES ("); if (s != std::string::npos) // old dump format (no column names) return true; // new format has insert with columns, need validation else we risk executing an invalid query s = str.find("` (`"); if (s == std::string::npos) { TC_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) { TC_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) { TC_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; } 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 (!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); } template class MapType, class... Rest> inline T RegisterNewGuid(T oldGuid, MapType& guidMap, T guidOffset) { auto itr = guidMap.find(oldGuid); if (itr != guidMap.end()) return itr->second; T newguid = guidOffset + T(guidMap.size()); guidMap.emplace(oldGuid, newguid); return newguid; } 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) { T oldGuid(atoull(GetColumn(ts, str, column).c_str())); if (allowZero && !oldGuid) return true; // not an error std::string chritem; T newGuid = RegisterNewGuid(oldGuid, guidMap, guidOffset); chritem = std::to_string(newGuid); return ChangeColumn(ts, str, column, chritem, allowZero); } inline void AppendTableDump(StringTransaction& trans, TableStruct const& tableStruct, QueryResult result) { if (!result) return; do { std::ostringstream ss; ss << "INSERT INTO `" << tableStruct.TableName << "` ("; for (auto itr = tableStruct.TableFields.begin(); itr != tableStruct.TableFields.end();) { ss << '`' << itr->FieldName << '`'; ++itr; if (itr != tableStruct.TableFields.end()) ss << ", "; } ss << ") VALUES ("; uint32 const fieldSize = uint32(tableStruct.TableFields.size()); Field* fields = result->Fetch(); for (uint32 i = 0; i < fieldSize;) { if (fields[i].IsNull()) ss << "'NULL'"; else { if (!tableStruct.TableFields[i].IsBinaryField) { std::string s(fields[i].GetString()); CharacterDatabase.EscapeString(s); ss << '\'' << s << '\''; } else { std::vector b(fields[i].GetBinary()); ss << "0x" << ByteArrayToHexStr(b); } } ++i; 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) continue; do { switch (baseTable.StoredType) { case GUID_TYPE_ITEM: if (ObjectGuid::LowType itemLowGuid = (*result)[0].GetUInt32()) _items.insert(itemLowGuid); break; case GUID_TYPE_MAIL: if (uint32 mailLowGuid = (*result)[0].GetUInt32()) _mails.insert(mailLowGuid); break; case GUID_TYPE_PET: if (uint32 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; } } while (result->NextRow()); } } 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: case DTT_ITEM_TABLE: 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: case DTT_CHAR_TRANSMOG: 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(ObjectGuid::LowType guid, std::string& 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: DO NOT apply it directly - it will irreversibly DAMAGE and CORRUPT your database! You have been warned!\n\n"; 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; dump += trans.GetBuffer(); /// @todo Add instance/group.. /// @todo Add a dump level option to skip some non-important tables return true; } DumpReturn PlayerDumpWriter::WriteDump(std::string const& file, ObjectGuid::LowType guid) { if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_PATHS)) if (strchr(file.c_str(), '\\') || strchr(file.c_str(), '/')) return DUMP_FILE_OPEN_ERROR; if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_OVERWRITE)) { // check if file exists already if (GetFileHandle(file.c_str(), "r")) return DUMP_FILE_OPEN_ERROR; } FileHandle fout = GetFileHandle(file.c_str(), "w"); if (!fout) return DUMP_FILE_OPEN_ERROR; DumpReturn ret = DUMP_SUCCESS; std::string dump; if (!GetDump(guid, dump)) ret = DUMP_CHARACTER_DELETED; fprintf(fout.get(), "%s", dump.c_str()); return ret; } // Reading - High-level functions inline void FixNULLfields(std::string& line) { 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); } } DumpReturn PlayerDumpReader::LoadDump(std::string const& file, uint32 account, std::string name, ObjectGuid::LowType guid) { uint32 charcount = AccountMgr::GetCharactersCount(account); if (charcount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_REALM)) return DUMP_TOO_MANY_CHARS; FileHandle fin = GetFileHandle(file.c_str(), "r"); if (!fin) return DUMP_FILE_OPEN_ERROR; std::string newguid, chraccount; // make sure the same guid doesn't already exist and is safe to use bool incHighest = true; if (guid && guid < sObjectMgr->GetGenerator().GetNextAfterMaxUsed()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_GUID); stmt->setUInt64(0, guid); if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) guid = sObjectMgr->GetGenerator().GetNextAfterMaxUsed(); // use first free if exists else incHighest = false; } else guid = sObjectMgr->GetGenerator().GetNextAfterMaxUsed(); // normalize the name if specified and check if it exists if (!normalizePlayerName(name)) name.clear(); if (ObjectMgr::CheckPlayerName(name, sWorld->GetDefaultDbcLocale(), true) == CHAR_NAME_SUCCESS) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME); stmt->setString(0, name); if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) name.clear(); // use the one from the dump } else name.clear(); // name encoded or empty newguid = std::to_string(guid); chraccount = std::to_string(account); std::map items; ObjectGuid::LowType itemLowGuidOffset = sObjectMgr->GetGenerator().GetNextAfterMaxUsed(); std::map mails; uint32 mailLowGuidOffset = sObjectMgr->_mailId; std::map petIds; uint32 petLowGuidOffset = sObjectMgr->_hiPetNumber; std::map equipmentSetIds; uint64 equipmentSetGuidOffset = sObjectMgr->_equipmentSetGuid; static size_t const BUFFER_SIZE = 32000; char buf[BUFFER_SIZE] = { }; uint8 gender = GENDER_NONE; uint8 race = RACE_NONE; uint8 playerClass = CLASS_NONE; uint8 level = 1; // for logs size_t lineNumber = 0; CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); while (!feof(fin.get())) { if (!fgets(buf, BUFFER_SIZE, fin.get())) { if (feof(fin.get())) break; return DUMP_FILE_BROKEN; } std::string line; line.assign(buf); ++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 the important notes static std::string const SkippedLine = "IMPORTANT NOTE:"; if (line.substr(nw_pos, SkippedLine.size()) == SkippedLine) continue; // determine table name and load type std::string tn = GetTableName(line); if (tn.empty()) { TC_LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") Can't extract table name!", lineNumber); return DUMP_FILE_BROKEN; } DumpTableType type = DTT_CHARACTER; uint32 i; for (i = 0; i < DUMP_TABLE_COUNT; ++i) { if (tn == DumpTables[i].Name) { type = DumpTables[i].Type; break; } } if (i == DUMP_TABLE_COUNT) { TC_LOG_ERROR("misc", "LoadPlayerDump: (line " SZFMTD ") Unknown table: `%s`!", lineNumber, tn.c_str()); return DUMP_FILE_BROKEN; } 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: { race = uint8(atoul(GetColumn(ts, line, "race").c_str())); playerClass = uint8(atoul(GetColumn(ts, line, "class").c_str())); gender = uint8(atoul(GetColumn(ts, line, "gender").c_str())); level = uint8(atoul(GetColumn(ts, line, "level").c_str())); if (name.empty()) { // generate a temporary name std::string guidPart = Trinity::StringFormat("%X", guid); std::size_t maxCharsFromOriginalName = MAX_PLAYER_NAME - guidPart.length(); name = GetColumn(ts, line, "name").substr(0, maxCharsFromOriginalName) + guidPart; // 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); trans->Append(line.c_str()); } CharacterDatabase.CommitTransaction(trans); // in case of name conflict player has to rename at login anyway sCharacterCache->AddCharacterCacheEntry(ObjectGuid::Create(guid), account, name, gender, race, playerClass, level, false); sObjectMgr->GetGenerator().Set(sObjectMgr->GetGenerator().GetNextAfterMaxUsed() + items.size()); sObjectMgr->_mailId += mails.size(); sObjectMgr->_hiPetNumber += petIds.size(); sObjectMgr->_equipmentSetGuid += equipmentSetIds.size(); if (incHighest) sObjectMgr->GetGenerator().Generate(); return DUMP_SUCCESS; }