TrinityCore
PlayerDump.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "PlayerDump.h"
19#include "AccountMgr.h"
20#include "CharacterCache.h"
21#include "Common.h"
22#include "DatabaseEnv.h"
23#include "Log.h"
24#include "ObjectMgr.h"
25#include "Player.h"
26#include "StringConvert.h"
27#include "World.h"
28#include <boost/algorithm/string/find.hpp>
29#include <fstream>
30#include <sstream>
31
32// static data
34{
35 // 32 bit long guids
38
39 // 64 bit long guids
44
45 // special types
46 GUID_TYPE_NULL // set to null
47};
48
49// for RAII
51{
52 void operator()(FILE* f) const
53 {
54 if (f)
55 fclose(f);
56 }
57};
58typedef std::unique_ptr<FILE, FileCloser> FileHandle;
59
60inline FileHandle GetFileHandle(char const* path, char const* mode)
61{
62 return FileHandle(fopen(path, mode), FileCloser());
63}
64
66{
67 char const* TableName;
68 char const* PrimaryKey;
69 char const* PlayerGuid;
70
72};
73
75{
76 { "character_pet", "id", "owner", GUID_TYPE_PET },
77 { "mail", "id", "receiver", GUID_TYPE_MAIL },
78 { "item_instance", "guid", "owner_guid", GUID_TYPE_ITEM },
79
80 { "character_equipmentsets", "setguid", "guid", GUID_TYPE_EQUIPMENT_SET },
81 { "character_transmog_outfits", "setguid", "guid", GUID_TYPE_EQUIPMENT_SET }
82};
83
85{
86 char const* Name;
88};
89
91{
92 { "characters", DTT_CHARACTER },
93 { "character_account_data", DTT_CHAR_TABLE },
94 { "character_achievement", DTT_CHAR_TABLE },
95 { "character_achievement_progress", DTT_CHAR_TABLE },
96 { "character_action", DTT_CHAR_TABLE },
97 { "character_aura", DTT_CHAR_TABLE },
98 { "character_aura_effect", DTT_CHAR_TABLE },
99 { "character_cuf_profiles", DTT_CHAR_TABLE },
100 { "character_currency", DTT_CURRENCY },
101 { "character_declinedname", DTT_CHAR_TABLE },
102 { "character_favorite_auctions", DTT_CHAR_TABLE },
103 { "character_fishingsteps", DTT_CHAR_TABLE },
104 { "character_garrison", DTT_CHAR_TABLE },
105 { "character_garrison_blueprints", DTT_CHAR_TABLE },
106 { "character_garrison_buildings", DTT_CHAR_TABLE },
109 { "character_glyphs", DTT_CHAR_TABLE },
110 { "character_homebind", DTT_CHAR_TABLE },
111 { "character_inventory", DTT_INVENTORY },
112 { "character_pet", DTT_PET },
113 { "character_pet_declinedname", DTT_PET },
114 { "character_pvp_talent", DTT_CHAR_TABLE },
115 { "character_queststatus", DTT_CHAR_TABLE },
116 { "character_queststatus_daily", DTT_CHAR_TABLE },
117 { "character_queststatus_monthly", DTT_CHAR_TABLE },
118 { "character_queststatus_objectives", DTT_CHAR_TABLE },
119 { "character_queststatus_objectives_criteria", DTT_CHAR_TABLE },
120 { "character_queststatus_objectives_criteria_progress", DTT_CHAR_TABLE },
121 { "character_queststatus_rewarded", DTT_CHAR_TABLE },
122 { "character_queststatus_seasonal", DTT_CHAR_TABLE },
123 { "character_queststatus_weekly", DTT_CHAR_TABLE },
124 { "character_reputation", DTT_CHAR_TABLE },
125 { "character_skills", DTT_CHAR_TABLE },
126 { "character_spell", DTT_CHAR_TABLE },
127 { "character_spell_charges", DTT_CHAR_TABLE },
128 { "character_spell_cooldown", DTT_CHAR_TABLE },
129 { "character_talent", DTT_CHAR_TABLE },
130 { "character_transmog_outfits", DTT_CHAR_TRANSMOG },
132 { "mail", DTT_MAIL },
133 { "mail_items", DTT_MAIL_ITEM }, // must be after mail
134 { "pet_aura", DTT_PET_TABLE }, // must be after character_pet
135 { "pet_aura_effect", DTT_PET_TABLE }, // must be after character_pet
136 { "pet_spell", DTT_PET_TABLE }, // must be after character_pet
137 { "pet_spell_charges", DTT_PET_TABLE }, // must be after character_pet
138 { "pet_spell_cooldown", DTT_PET_TABLE }, // must be after character_pet
139 { "item_instance", DTT_ITEM }, // must be after character_inventory and mail_items
140 { "character_equipmentsets", DTT_EQSET_TABLE}, // must be after item_instance
141 { "character_gifts", DTT_ITEM_GIFT }, // must be after item_instance
142 { "item_instance_artifact", DTT_ITEM_TABLE }, // must be after item_instance
143 { "item_instance_artifact_powers", DTT_ITEM_TABLE }, // must be after item_instance
144 { "item_instance_azerite", DTT_ITEM_TABLE }, // must be after item_instance
145 { "item_instance_azerite_empowered", DTT_ITEM_TABLE }, // must be after item_instance
146 { "item_instance_azerite_milestone_power", DTT_ITEM_TABLE }, // must be after item_instance
147 { "item_instance_azerite_unlocked_essence", DTT_ITEM_TABLE }, // must be after item_instance
148 { "item_instance_gems", DTT_ITEM_TABLE }, // must be after item_instance
149 { "item_instance_modifiers", DTT_ITEM_TABLE }, // must be after item_instance
150 { "item_instance_transmog", DTT_ITEM_TABLE }, // must be after item_instance
151};
152
153uint32 const DUMP_TABLE_COUNT = std::extent<decltype(DumpTables)>::value;
154
155// helper class to dump sql queries to a printable string
157{
158 public:
160
161 void Append(char const* sql)
162 {
163 std::ostringstream oss;
164 oss << sql << '\n';
165 _buf += oss.str();
166 }
167
168 char const* GetBuffer() const
169 {
170 return _buf.c_str();
171 }
172
173 private:
174 std::string _buf;
175};
176
177// dynamic data, loaded at startup
179{
180 std::string FieldName;
181
183 bool IsDependentField = false;
184 bool IsBinaryField = false;
185};
186
188{
189 std::string TableName;
190 std::string WhereFieldName;
191 std::vector<TableField> TableFields;
192
193 // for lookup
194 std::unordered_map<std::string /*fieldName*/, int32 /*index*/> FieldIndices;
195};
196
197std::vector<TableStruct> CharacterTables;
198
199inline bool StringsEqualCaseInsensitive(std::string const& left, std::string const& right)
200{
201 std::string upperLeftString = left;
202 bool leftResult = Utf8ToUpperOnlyLatin(upperLeftString);
203 ASSERT(leftResult);
204
205 std::string upperRightString = right;
206 bool rightResult = Utf8ToUpperOnlyLatin(upperRightString);
207 ASSERT(rightResult);
208
209 return upperLeftString == upperRightString;
210}
211
212inline auto FindColumnByName(TableStruct& tableStruct, std::string const& columnName) -> decltype(tableStruct.TableFields.begin())
213{
214 return std::find_if(tableStruct.TableFields.begin(), tableStruct.TableFields.end(), [columnName](TableField const& tableField) -> bool
215 {
216 return StringsEqualCaseInsensitive(tableField.FieldName, columnName);
217 });
218}
219
220inline int32 GetColumnIndexByName(TableStruct const& tableStruct, std::string const& columnName)
221{
222 auto itr = tableStruct.FieldIndices.find(columnName);
223 if (itr == tableStruct.FieldIndices.end())
224 return -1;
225
226 return itr->second;
227}
228
229inline void MarkDependentColumn(TableStruct& tableStruct, std::string const& columnName, GuidType dependentType)
230{
231 auto itr = FindColumnByName(tableStruct, columnName);
232 if (itr == tableStruct.TableFields.end())
233 {
234 TC_LOG_FATAL("server.loading", "Column `{}` declared in table `{}` marked as dependent but doesn't exist, PlayerDump will not work properly, please update table definitions",
235 columnName, tableStruct.TableName);
236 ABORT();
237 return;
238 }
239
240 if (itr->IsDependentField)
241 {
242 TC_LOG_FATAL("server.loading", "Attempt to mark column `{}` in table `{}` as dependent column but already marked! please check your code.",
243 columnName, tableStruct.TableName);
244 ABORT();
245 return;
246 }
247
248 itr->IsDependentField = true;
249 itr->FieldGuidType = dependentType;
250}
251
252inline void MarkWhereField(TableStruct& tableStruct, std::string const& whereField)
253{
254 ASSERT(tableStruct.WhereFieldName.empty());
255
256 auto whereFieldItr = FindColumnByName(tableStruct, whereField);
257 if (whereFieldItr == tableStruct.TableFields.end())
258 {
259 TC_LOG_FATAL("server.loading", "Column name `{}` set as 'WHERE' column for table `{}` doesn't exist. PlayerDump won't work properly",
260 whereField, tableStruct.TableName);
261 ABORT();
262 return;
263 }
264
265 tableStruct.WhereFieldName = whereField;
266}
267
268inline void AssertBaseTable(BaseTable const& baseTable)
269{
270 auto itr = std::find_if(CharacterTables.begin(), CharacterTables.end(), [baseTable](TableStruct const& tableStruct) -> bool
271 {
272 return StringsEqualCaseInsensitive(tableStruct.TableName, baseTable.TableName);
273 });
274
275 ASSERT(itr != CharacterTables.end());
276
277 auto columnItr = FindColumnByName(*itr, baseTable.PrimaryKey);
278 ASSERT(columnItr != itr->TableFields.end());
279
280 columnItr = FindColumnByName(*itr, baseTable.PlayerGuid);
281 ASSERT(columnItr != itr->TableFields.end());
282}
283
285{
286 uint32 oldMSTime = getMSTime();
287
288 for (DumpTable const& dumpTable : DumpTables)
289 {
290 TableStruct t;
291 t.TableName = dumpTable.Name;
292
293 QueryResult result = CharacterDatabase.PQuery("DESC {}", dumpTable.Name);
294 // prepared statement is correct (checked at startup) so table must exist
295 ASSERT(result);
296
297 int32 i = 0;
298 do
299 {
300 std::string columnName = (*result)[0].GetString();
301 std::string typeName = (*result)[1].GetString();
302 t.FieldIndices.emplace(columnName, i++);
303
304 TableField f;
305 f.FieldName = columnName;
306 f.IsBinaryField = !boost::ifind_first(typeName, "binary").empty() || !boost::ifind_first(typeName, "blob").empty();
307
308 bool toUpperResult = Utf8ToUpperOnlyLatin(columnName);
309 ASSERT(toUpperResult);
310
311 t.TableFields.emplace_back(std::move(f));
312 } while (result->NextRow());
313
314 switch (dumpTable.Type)
315 {
316 case DTT_CHARACTER:
317 MarkWhereField(t, "guid");
318
321
322 MarkDependentColumn(t, "deleteInfos_Account", GUID_TYPE_NULL);
323 MarkDependentColumn(t, "deleteInfos_Name", GUID_TYPE_NULL);
324 MarkDependentColumn(t, "deleteDate", GUID_TYPE_NULL);
325 break;
326 case DTT_CHAR_TABLE:
327 MarkWhereField(t, "guid");
328
330 break;
331 case DTT_CURRENCY:
332 MarkWhereField(t, "CharacterGuid");
333
334 MarkDependentColumn(t, "CharacterGuid", GUID_TYPE_CHAR);
335 break;
336 case DTT_EQSET_TABLE:
337 MarkWhereField(t, "guid");
338
341
342 // item0 - item18
343 for (uint32 j = 0; j < EQUIPMENT_SLOT_END; ++j)
344 {
345 std::string itColumn = Trinity::StringFormat("item{}", j);
347 }
348 break;
349 case DTT_INVENTORY:
350 MarkWhereField(t, "guid");
351
355 break;
357 MarkWhereField(t, "guid");
358
361 break;
362 case DTT_MAIL:
363 MarkWhereField(t, "receiver");
364
366 MarkDependentColumn(t, "receiver", GUID_TYPE_CHAR);
367 break;
368 case DTT_MAIL_ITEM:
369 MarkWhereField(t, "mail_id");
370
371 MarkDependentColumn(t, "mail_id", GUID_TYPE_MAIL);
372 MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM);
373 MarkDependentColumn(t, "receiver", GUID_TYPE_CHAR);
374 break;
375 case DTT_ITEM:
376 MarkWhereField(t, "guid");
377
379 MarkDependentColumn(t, "owner_guid", GUID_TYPE_CHAR);
380 break;
381 case DTT_ITEM_GIFT:
382 MarkWhereField(t, "item_guid");
383
385 MarkDependentColumn(t, "item_guid", GUID_TYPE_ITEM);
386 break;
387 case DTT_ITEM_TABLE:
388 MarkWhereField(t, "itemGuid");
389
390 MarkDependentColumn(t, "itemGuid", GUID_TYPE_ITEM);
391 break;
392 case DTT_PET:
393 MarkWhereField(t, "owner");
394
397 break;
398 case DTT_PET_TABLE:
399 MarkWhereField(t, "guid");
400
402 break;
403 default:
404 TC_LOG_FATAL("server.loading", "Wrong dump table type {}, probably added a new table type without updating code", uint32(dumpTable.Type));
405 ABORT();
406 return;
407 }
408
409 CharacterTables.emplace_back(std::move(t));
410 }
411
412 // perform some sanity checks
413 for (TableStruct const& tableStruct : CharacterTables)
414 {
415 if (tableStruct.WhereFieldName.empty())
416 {
417 TC_LOG_FATAL("server.loading", "Table `{}` defined in player dump doesn't have a WHERE query field", tableStruct.TableName);
418 ABORT();
419 }
420 }
421
422 for (BaseTable const& baseTable : BaseTables)
423 AssertBaseTable(baseTable);
424
426
427 TC_LOG_INFO("server.loading", ">> Initialized tables for PlayerDump in {} ms.", GetMSTimeDiffToNow(oldMSTime));
428}
429
430// Low level functions
431inline bool FindColumn(TableStruct const& ts, std::string const& str, std::string const& column, std::string::size_type& s, std::string::size_type& e)
432{
433 int32 columnIndex = GetColumnIndexByName(ts, column);
434 if (columnIndex == -1)
435 return false;
436
437 // array indices start at 0, compensate
438 ++columnIndex;
439
440 s = str.find("VALUES (");
441 if (s == std::string::npos)
442 return false;
443 s += 8;
444 e = s;
445
446 bool isQuoted = str[s] == '\'';
447 if (isQuoted)
448 {
449 ++s;
450 ++e;
451 // find first unescaped quote
452 do
453 {
454 e = str.find('\'', e);
455 if (e == std::string::npos)
456 return false;
457 if (str[e - 1] == '\\')
458 continue;
459 if (e + 1 < str.length() && str[e + 1] == '\'')
460 {
461 ++e;
462 continue;
463 }
464 break;
465 } while (true);
466 }
467 else
468 e = str.find_first_of(",)", e);
469
470 for (int32 i = 1; i < columnIndex; ++i)
471 {
472 // if previous value was quoted, move old e to comma
473 if (isQuoted)
474 ++e;
475
476 // move past ", "
477 s = e + 2;
478 e = s;
479 isQuoted = str[s] == '\'';
480 if (isQuoted)
481 {
482 ++s;
483 ++e;
484 // find first unescaped quote
485 do
486 {
487 e = str.find('\'', e);
488 if (e == std::string::npos)
489 return false;
490 if (str[e - 1] == '\\')
491 continue;
492 if (e + 1 < str.length() && str[e + 1] == '\'')
493 {
494 ++e;
495 continue;
496 }
497 break;
498 } while (str[e - 1] == '\\');
499 }
500 else
501 e = str.find_first_of(",)", e);
502 }
503
504 return true;
505}
506
507inline std::string GetTableName(std::string const& str)
508{
509 // length of "INSERT INTO `"
510 static std::string::size_type const s = 13;
511 std::string::size_type e = str.find('`', s);
512 if (e == std::string::npos)
513 return "";
514
515 return str.substr(s, e - s);
516}
517
518inline bool ValidateFields(TableStruct const& ts, std::string const& str, size_t lineNumber)
519{
520 std::string::size_type s = str.find("` VALUES (");
521 if (s != std::string::npos) // old dump format (no column names)
522 return true;
523
524 // new format has insert with columns, need validation else we risk executing an invalid query
525 s = str.find("` (`");
526 if (s == std::string::npos)
527 {
528 TC_LOG_ERROR("misc", "LoadPlayerDump: (line {}) dump format not recognized.", lineNumber);
529 return false;
530 }
531 s += 4;
532
533 std::string::size_type valPos = str.find("VALUES ('");
534 std::string::size_type e = str.find('`', s);
535 if (e == std::string::npos || valPos == std::string::npos)
536 {
537 TC_LOG_ERROR("misc", "LoadPlayerDump: (line {}) unexpected end of line", lineNumber);
538 return false;
539 }
540
541 do
542 {
543 std::string column = str.substr(s, e - s);
544 int32 columnIndex = GetColumnIndexByName(ts, column);
545 if (columnIndex == -1)
546 {
547 TC_LOG_ERROR("misc", "LoadPlayerDump: (line {}) unknown column name `{}` for table `{}`, aborting due to incompatible DB structure.", lineNumber, column, ts.TableName);
548 return false;
549 }
550
551 // length of "`, `"
552 s = e + 4;
553 e = str.find('`', s);
554 } while (e < valPos);
555
556 return true;
557}
558
559inline bool ChangeColumn(TableStruct const& ts, std::string& str, std::string const& column, std::string const& with, bool allowZero = false)
560{
561 std::string::size_type s, e;
562 if (!FindColumn(ts, str, column, s, e))
563 return false;
564
565 if (allowZero && str.substr(s, e - s) == "0")
566 return true; // not an error
567
568 str.replace(s, e - s, with);
569 return true;
570}
571
572inline std::string GetColumn(TableStruct const& ts, std::string& str, std::string const& column)
573{
574 std::string::size_type s, e;
575 if (!FindColumn(ts, str, column, s, e))
576 return "";
577
578 return str.substr(s, e - s);
579}
580
581template <typename T, template<class, class, class...> class MapType, class... Rest>
582inline T RegisterNewGuid(T oldGuid, MapType<T, T, Rest...>& guidMap, T guidOffset)
583{
584 auto itr = guidMap.find(oldGuid);
585 if (itr != guidMap.end())
586 return itr->second;
587
588 T newguid = guidOffset + T(guidMap.size());
589 guidMap.emplace(oldGuid, newguid);
590 return newguid;
591}
592
593template <typename T, template<class, class, class...> class MapType, class... Rest>
594inline bool ChangeGuid(TableStruct const& ts, std::string& str, std::string const& column, MapType<T, T, Rest...>& guidMap, T guidOffset, bool allowZero = false)
595{
596 T oldGuid = Trinity::StringTo<T>(GetColumn(ts, str, column)).template value_or<T>(0);
597 if (allowZero && !oldGuid)
598 return true; // not an error
599
600 std::string chritem;
601 T newGuid = RegisterNewGuid(oldGuid, guidMap, guidOffset);
602 chritem = std::to_string(newGuid);
603
604 return ChangeColumn(ts, str, column, chritem, allowZero);
605}
606
607inline void AppendTableDump(StringTransaction& trans, TableStruct const& tableStruct, QueryResult result)
608{
609 if (!result)
610 return;
611
612 do
613 {
614 std::ostringstream ss;
615 ss << "INSERT INTO `" << tableStruct.TableName << "` (";
616 for (auto itr = tableStruct.TableFields.begin(); itr != tableStruct.TableFields.end();)
617 {
618 ss << '`' << itr->FieldName << '`';
619 ++itr;
620
621 if (itr != tableStruct.TableFields.end())
622 ss << ", ";
623 }
624 ss << ") VALUES (";
625
626 uint32 const fieldSize = uint32(tableStruct.TableFields.size());
627 Field* fields = result->Fetch();
628
629 for (uint32 i = 0; i < fieldSize;)
630 {
631 if (fields[i].IsNull())
632 ss << "'NULL'";
633 else
634 {
635 if (!tableStruct.TableFields[i].IsBinaryField)
636 {
637 std::string s(fields[i].GetString());
638 CharacterDatabase.EscapeString(s);
639 ss << '\'' << s << '\'';
640 }
641 else
642 {
643 std::vector<uint8> b(fields[i].GetBinary());
644
645 if (!b.empty())
646 ss << "0x" << ByteArrayToHexStr(b);
647 else
648 ss << '\'' << '\'';
649 }
650 }
651
652 ++i;
653 if (i != fieldSize)
654 ss << ", ";
655 }
656 ss << ");";
657
658 trans.Append(ss.str().c_str());
659 } while (result->NextRow());
660}
661
662inline std::string GenerateWhereStr(std::string const& field, ObjectGuid::LowType guid)
663{
664 std::ostringstream whereStr;
665 whereStr << field << " = '" << guid << '\'';
666 return whereStr.str();
667}
668
669template <typename T, template<class, class...> class SetType, class... Rest>
670inline std::string GenerateWhereStr(std::string const& field, SetType<T, Rest...> const& guidSet)
671{
672 std::ostringstream whereStr;
673 whereStr << field << " IN ('";
674 for (auto itr = guidSet.begin(); itr != guidSet.end();)
675 {
676 whereStr << *itr;
677 ++itr;
678
679 if (whereStr.str().size() > MAX_QUERY_LEN - 50) // near to max query
680 break;
681
682 if (itr != guidSet.end())
683 whereStr << "','";
684 }
685 whereStr << "')";
686 return whereStr.str();
687}
688
689// Writing - High-level functions
691{
692 for (BaseTable const& baseTable : BaseTables)
693 {
694 switch (baseTable.StoredType)
695 {
696 case GUID_TYPE_ITEM:
697 case GUID_TYPE_MAIL:
698 case GUID_TYPE_PET:
700 break;
701 default:
702 return;
703 }
704
705 std::string whereStr = GenerateWhereStr(baseTable.PlayerGuid, guid);
706 QueryResult result = CharacterDatabase.PQuery("SELECT {} FROM {} WHERE {}", baseTable.PrimaryKey, baseTable.TableName, whereStr);
707 if (!result)
708 continue;
709
710 do
711 {
712 switch (baseTable.StoredType)
713 {
714 case GUID_TYPE_ITEM:
715 if (ObjectGuid::LowType itemLowGuid = (*result)[0].GetUInt32())
716 _items.insert(itemLowGuid);
717 break;
718 case GUID_TYPE_MAIL:
719 if (uint32 mailLowGuid = (*result)[0].GetUInt32())
720 _mails.insert(mailLowGuid);
721 break;
722 case GUID_TYPE_PET:
723 if (uint32 petLowGuid = (*result)[0].GetUInt32())
724 _pets.insert(petLowGuid);
725 break;
727 if (uint64 eqSetId = (*result)[0].GetUInt64())
728 _itemSets.insert(eqSetId);
729 break;
730 default:
731 break;
732 }
733 } while (result->NextRow());
734 }
735}
736
737bool PlayerDumpWriter::AppendTable(StringTransaction& trans, ObjectGuid::LowType guid, TableStruct const& tableStruct, DumpTable const& dumpTable)
738{
739 std::string whereStr;
740 switch (dumpTable.Type)
741 {
742 case DTT_ITEM:
743 case DTT_ITEM_GIFT:
744 case DTT_ITEM_TABLE:
745 if (_items.empty())
746 return true;
747
748 whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _items);
749 break;
750 case DTT_PET_TABLE:
751 if (_pets.empty())
752 return true;
753
754 whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _pets);
755 break;
756 case DTT_MAIL_ITEM:
757 if (_mails.empty())
758 return true;
759
760 whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _mails);
761 break;
762 case DTT_EQSET_TABLE:
764 if (_itemSets.empty())
765 return true;
766
767 whereStr = GenerateWhereStr(tableStruct.WhereFieldName, _itemSets);
768 break;
769 default:
770 // not set case, get single guid string
771 whereStr = GenerateWhereStr(tableStruct.WhereFieldName, guid);
772 break;
773 }
774
775 QueryResult result = CharacterDatabase.PQuery("SELECT * FROM {} WHERE {}", dumpTable.Name, whereStr);
776 switch (dumpTable.Type)
777 {
778 case DTT_CHARACTER:
779 if (result)
780 {
781 // characters.deleteInfos_Account - if filled error
782 int32 index = GetColumnIndexByName(tableStruct, "deleteInfos_Account");
783 ASSERT(index != -1); // checked at startup
784
785 if ((*result)[index].GetUInt32())
786 return false;
787 }
788 break;
789 default:
790 break;
791 }
792
793 AppendTableDump(trans, tableStruct, result);
794 return true;
795}
796
798{
799 dump = "IMPORTANT NOTE: THIS DUMPFILE IS MADE FOR USE WITH THE 'PDUMP' COMMAND ONLY - EITHER THROUGH INGAME CHAT OR ON CONSOLE!\n";
800 dump += "IMPORTANT NOTE: DO NOT apply it directly - it will irreversibly DAMAGE and CORRUPT your database! You have been warned!\n\n";
801
802 StringTransaction trans;
803
804 // collect guids
805 PopulateGuids(guid);
806 for (uint32 i = 0; i < DUMP_TABLE_COUNT; ++i)
807 if (!AppendTable(trans, guid, CharacterTables[i], DumpTables[i]))
808 return false;
809
810 dump += trans.GetBuffer();
811
814
815 return true;
816}
817
819{
820 if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_PATHS))
821 if (strchr(file.c_str(), '\\') || strchr(file.c_str(), '/'))
823
824 if (sWorld->getBoolConfig(CONFIG_PDUMP_NO_OVERWRITE))
825 {
826 // check if file exists already
827 if (GetFileHandle(file.c_str(), "r"))
829 }
830
831 FileHandle fout = GetFileHandle(file.c_str(), "w");
832 if (!fout)
834
836 std::string dump;
837 if (!GetDump(guid, dump))
839
840 fprintf(fout.get(), "%s", dump.c_str());
841 return ret;
842}
843
845{
847 if (!GetDump(guid, dump))
849 return ret;
850}
851
852// Reading - High-level functions
853inline void FixNULLfields(std::string& line)
854{
855 static std::string const NullString("'NULL'");
856 size_t pos = line.find(NullString);
857 while (pos != std::string::npos)
858 {
859 line.replace(pos, NullString.length(), "NULL");
860 pos = line.find(NullString);
861 }
862}
863
864DumpReturn PlayerDumpReader::LoadDump(std::istream& input, uint32 account, std::string name, ObjectGuid::LowType guid)
865{
866 uint32 charcount = AccountMgr::GetCharactersCount(account);
867 if (charcount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_REALM))
868 return DUMP_TOO_MANY_CHARS;
869
870 std::string newguid, chraccount;
871
872 // make sure the same guid doesn't already exist and is safe to use
873 bool incHighest = true;
874 if (guid && guid < sObjectMgr->GetGenerator<HighGuid::Player>().GetNextAfterMaxUsed())
875 {
877 stmt->setUInt64(0, guid);
878
879 if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
880 guid = sObjectMgr->GetGenerator<HighGuid::Player>().GetNextAfterMaxUsed(); // use first free if exists
881 else
882 incHighest = false;
883 }
884 else
885 guid = sObjectMgr->GetGenerator<HighGuid::Player>().GetNextAfterMaxUsed();
886
887 // normalize the name if specified and check if it exists
888 if (!normalizePlayerName(name))
889 name.clear();
890
891 if (ObjectMgr::CheckPlayerName(name, sWorld->GetDefaultDbcLocale(), true) == CHAR_NAME_SUCCESS)
892 {
894 stmt->setString(0, name);
895
896 if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
897 name.clear(); // use the one from the dump
898 }
899 else
900 name.clear();
901
902 // name encoded or empty
903 newguid = std::to_string(guid);
904 chraccount = std::to_string(account);
905
906 std::map<ObjectGuid::LowType, ObjectGuid::LowType> items;
907 ObjectGuid::LowType itemLowGuidOffset = sObjectMgr->GetGenerator<HighGuid::Item>().GetNextAfterMaxUsed();
908
909 std::map<uint64, uint64> mails;
910 uint64 mailLowGuidOffset = sObjectMgr->_mailId;
911
912 std::map<uint32, uint32> petIds;
913 uint32 petLowGuidOffset = sObjectMgr->_hiPetNumber;
914
915 std::map<uint64, uint64> equipmentSetIds;
916 uint64 equipmentSetGuidOffset = sObjectMgr->_equipmentSetGuid;
917
918 std::string line;
919
920 uint8 gender = GENDER_NONE;
921 uint8 race = RACE_NONE;
922 uint8 playerClass = CLASS_NONE;
923 uint8 level = 1;
924
925 // for logs
926 size_t lineNumber = 0;
927
928 CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
929 while (std::getline(input, line))
930 {
931 ++lineNumber;
932
933 // skip empty strings
934 size_t nw_pos = line.find_first_not_of(" \t\n\r\7");
935 if (nw_pos == std::string::npos)
936 continue;
937
938 // skip the important notes
939 static std::string const SkippedLine = "IMPORTANT NOTE:";
940 if (line.substr(nw_pos, SkippedLine.size()) == SkippedLine)
941 continue;
942
943 // determine table name and load type
944 std::string tn = GetTableName(line);
945 if (tn.empty())
946 {
947 TC_LOG_ERROR("misc", "LoadPlayerDump: (line {}) Can't extract table name!", lineNumber);
948 return DUMP_FILE_BROKEN;
949 }
950
952 uint32 i;
953 for (i = 0; i < DUMP_TABLE_COUNT; ++i)
954 {
955 if (tn == DumpTables[i].Name)
956 {
957 type = DumpTables[i].Type;
958 break;
959 }
960 }
961
962 if (i == DUMP_TABLE_COUNT)
963 {
964 TC_LOG_ERROR("misc", "LoadPlayerDump: (line {}) Unknown table: `{}`!", lineNumber, tn);
965 return DUMP_FILE_BROKEN;
966 }
967
968 TableStruct const& ts = CharacterTables[i];
969 if (!ValidateFields(ts, line, lineNumber))
970 return DUMP_FILE_BROKEN;
971
972 // per field guid offsetting
973 for (TableField const& field : ts.TableFields)
974 {
975 if (!field.IsDependentField)
976 continue;
977
978 switch (field.FieldGuidType)
979 {
981 if (!ChangeColumn(ts, line, field.FieldName, chraccount))
982 return DUMP_FILE_BROKEN;
983 break;
984 case GUID_TYPE_CHAR:
985 if (!ChangeColumn(ts, line, field.FieldName, newguid))
986 return DUMP_FILE_BROKEN;
987 break;
988 case GUID_TYPE_PET:
989 if (!ChangeGuid(ts, line, field.FieldName, petIds, petLowGuidOffset))
990 return DUMP_FILE_BROKEN;
991 break;
992 case GUID_TYPE_MAIL:
993 if (!ChangeGuid(ts, line, field.FieldName, mails, mailLowGuidOffset))
994 return DUMP_FILE_BROKEN;
995 break;
996 case GUID_TYPE_ITEM:
997 if (!ChangeGuid(ts, line, field.FieldName, items, itemLowGuidOffset, true))
998 return DUMP_FILE_BROKEN;
999 break;
1001 if (!ChangeGuid(ts, line, field.FieldName, equipmentSetIds, equipmentSetGuidOffset))
1002 return DUMP_FILE_BROKEN;
1003 break;
1004 case GUID_TYPE_NULL:
1005 {
1006 static std::string const NullString("NULL");
1007 if (!ChangeColumn(ts, line, field.FieldName, NullString))
1008 return DUMP_FILE_BROKEN;
1009 break;
1010 }
1011 }
1012 }
1013
1014 // extra modifications for other tables
1015 switch (type)
1016 {
1017 case DTT_CHARACTER:
1018 {
1019 race = Trinity::StringTo<uint8>(GetColumn(ts, line, "race")).value_or<uint8>(0);
1020 playerClass = Trinity::StringTo<uint8>(GetColumn(ts, line, "class")).value_or<uint8>(0);
1021 gender = Trinity::StringTo<uint8>(GetColumn(ts, line, "gender")).value_or<uint8>(0);
1022 level = Trinity::StringTo<uint8>(GetColumn(ts, line, "level")).value_or<uint8>(0);
1023 if (name.empty())
1024 {
1025 // generate a temporary name
1026 std::string guidPart = Trinity::StringFormat("{:X}", guid);
1027 std::size_t maxCharsFromOriginalName = MAX_PLAYER_NAME - guidPart.length();
1028
1029 name = GetColumn(ts, line, "name").substr(0, maxCharsFromOriginalName) + guidPart;
1030
1031 // characters.at_login set to "rename on login"
1032 if (!ChangeColumn(ts, line, "name", name))
1033 return DUMP_FILE_BROKEN;
1034 if (!ChangeColumn(ts, line, "at_login", "1"))
1035 return DUMP_FILE_BROKEN;
1036 }
1037 else if (!ChangeColumn(ts, line, "name", name)) // characters.name
1038 return DUMP_FILE_BROKEN;
1039 break;
1040 }
1041 default:
1042 break;
1043 }
1044
1045 FixNULLfields(line);
1046
1047 trans->Append(line.c_str());
1048 }
1049
1050 if (input.fail() && !input.eof())
1051 return DUMP_FILE_BROKEN;
1052
1053 CharacterDatabase.CommitTransaction(trans);
1054
1055 // in case of name conflict player has to rename at login anyway
1056 sCharacterCache->AddCharacterCacheEntry(ObjectGuid::Create<HighGuid::Player>(guid), account, name, gender, race, playerClass, level, false);
1057
1058 sObjectMgr->GetGenerator<HighGuid::Item>().Set(sObjectMgr->GetGenerator<HighGuid::Item>().GetNextAfterMaxUsed() + items.size());
1059 sObjectMgr->_mailId += mails.size();
1060 sObjectMgr->_hiPetNumber += petIds.size();
1061 sObjectMgr->_equipmentSetGuid += equipmentSetIds.size();
1062
1063 if (incHighest)
1064 sObjectMgr->GetGenerator<HighGuid::Player>().Generate();
1065
1066 sWorld->UpdateRealmCharCount(account);
1067
1068 return DUMP_SUCCESS;
1069}
1070
1071DumpReturn PlayerDumpReader::LoadDumpFromString(std::string const& dump, uint32 account, std::string name, ObjectGuid::LowType guid)
1072{
1073 std::istringstream input(dump);
1074 return LoadDump(input, account, name, guid);
1075}
1076
1077DumpReturn PlayerDumpReader::LoadDumpFromFile(std::string const& file, uint32 account, std::string name, ObjectGuid::LowType guid)
1078{
1079 std::ifstream input(file);
1080 if (!input)
1081 return DUMP_FILE_OPEN_ERROR;
1082 return LoadDump(input, account, name, guid);
1083}
#define sCharacterCache
@ CHAR_SEL_CHECK_NAME
@ CHAR_SEL_CHECK_GUID
#define MAX_QUERY_LEN
Definition: Common.h:122
SQLTransaction< CharacterDatabaseConnection > CharacterDatabaseTransaction
std::shared_ptr< ResultSet > QueryResult
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< CharacterDatabaseConnection > CharacterDatabase
Accessor to the character database.
Definition: DatabaseEnv.cpp:21
uint8_t uint8
Definition: Define.h:144
int32_t int32
Definition: Define.h:138
uint64_t uint64
Definition: Define.h:141
uint32_t uint32
Definition: Define.h:142
#define ABORT
Definition: Errors.h:74
#define ASSERT
Definition: Errors.h:68
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition: Log.h:159
#define TC_LOG_FATAL(filterType__,...)
Definition: Log.h:168
bool normalizePlayerName(std::string &name)
Definition: ObjectMgr.cpp:154
#define MAX_PLAYER_NAME
Definition: ObjectMgr.h:980
#define sObjectMgr
Definition: ObjectMgr.h:1946
void MarkDependentColumn(TableStruct &tableStruct, std::string const &columnName, GuidType dependentType)
Definition: PlayerDump.cpp:229
std::string GetColumn(TableStruct const &ts, std::string &str, std::string const &column)
Definition: PlayerDump.cpp:572
void AppendTableDump(StringTransaction &trans, TableStruct const &tableStruct, QueryResult result)
Definition: PlayerDump.cpp:607
void AssertBaseTable(BaseTable const &baseTable)
Definition: PlayerDump.cpp:268
bool ChangeGuid(TableStruct const &ts, std::string &str, std::string const &column, MapType< T, T, Rest... > &guidMap, T guidOffset, bool allowZero=false)
Definition: PlayerDump.cpp:594
bool ChangeColumn(TableStruct const &ts, std::string &str, std::string const &column, std::string const &with, bool allowZero=false)
Definition: PlayerDump.cpp:559
FileHandle GetFileHandle(char const *path, char const *mode)
Definition: PlayerDump.cpp:60
uint32 const DUMP_TABLE_COUNT
Definition: PlayerDump.cpp:153
GuidType
Definition: PlayerDump.cpp:34
@ GUID_TYPE_NULL
Definition: PlayerDump.cpp:46
@ GUID_TYPE_MAIL
Definition: PlayerDump.cpp:37
@ GUID_TYPE_PET
Definition: PlayerDump.cpp:43
@ GUID_TYPE_EQUIPMENT_SET
Definition: PlayerDump.cpp:41
@ GUID_TYPE_CHAR
Definition: PlayerDump.cpp:40
@ GUID_TYPE_ITEM
Definition: PlayerDump.cpp:42
@ GUID_TYPE_ACCOUNT
Definition: PlayerDump.cpp:36
bool StringsEqualCaseInsensitive(std::string const &left, std::string const &right)
Definition: PlayerDump.cpp:199
DumpTable const DumpTables[]
Definition: PlayerDump.cpp:90
int32 GetColumnIndexByName(TableStruct const &tableStruct, std::string const &columnName)
Definition: PlayerDump.cpp:220
T RegisterNewGuid(T oldGuid, MapType< T, T, Rest... > &guidMap, T guidOffset)
Definition: PlayerDump.cpp:582
BaseTable const BaseTables[]
Definition: PlayerDump.cpp:74
void MarkWhereField(TableStruct &tableStruct, std::string const &whereField)
Definition: PlayerDump.cpp:252
void FixNULLfields(std::string &line)
Definition: PlayerDump.cpp:853
std::unique_ptr< FILE, FileCloser > FileHandle
Definition: PlayerDump.cpp:58
std::vector< TableStruct > CharacterTables
Definition: PlayerDump.cpp:197
auto FindColumnByName(TableStruct &tableStruct, std::string const &columnName) -> decltype(tableStruct.TableFields.begin())
Definition: PlayerDump.cpp:212
bool FindColumn(TableStruct const &ts, std::string const &str, std::string const &column, std::string::size_type &s, std::string::size_type &e)
Definition: PlayerDump.cpp:431
std::string GetTableName(std::string const &str)
Definition: PlayerDump.cpp:507
std::string GenerateWhereStr(std::string const &field, ObjectGuid::LowType guid)
Definition: PlayerDump.cpp:662
bool ValidateFields(TableStruct const &ts, std::string const &str, size_t lineNumber)
Definition: PlayerDump.cpp:518
DumpReturn
Definition: PlayerDump.h:65
@ DUMP_FILE_OPEN_ERROR
Definition: PlayerDump.h:67
@ DUMP_CHARACTER_DELETED
Definition: PlayerDump.h:70
@ DUMP_SUCCESS
Definition: PlayerDump.h:66
@ DUMP_TOO_MANY_CHARS
Definition: PlayerDump.h:68
@ DUMP_FILE_BROKEN
Definition: PlayerDump.h:69
DumpTableType
Definition: PlayerDump.h:27
@ DTT_EQSET_TABLE
Definition: PlayerDump.h:38
@ DTT_INVENTORY
Definition: PlayerDump.h:40
@ DTT_CHAR_TABLE
Definition: PlayerDump.h:30
@ DTT_CURRENCY
Definition: PlayerDump.h:36
@ DTT_PET_TABLE
Definition: PlayerDump.h:61
@ DTT_MAIL
Definition: PlayerDump.h:44
@ DTT_MAIL_ITEM
Definition: PlayerDump.h:47
@ DTT_ITEM_TABLE
Definition: PlayerDump.h:55
@ DTT_CHAR_TRANSMOG
Definition: PlayerDump.h:42
@ DTT_PET
Definition: PlayerDump.h:60
@ DTT_ITEM
Definition: PlayerDump.h:50
@ DTT_ITEM_GIFT
Definition: PlayerDump.h:53
@ DTT_CHARACTER
Definition: PlayerDump.h:28
@ EQUIPMENT_SLOT_END
Definition: Player.h:650
@ RACE_NONE
Definition: RaceMask.h:27
@ CLASS_NONE
@ GENDER_NONE
@ CHAR_NAME_SUCCESS
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition: Timer.h:57
uint32 getMSTime()
Definition: Timer.h:33
bool Utf8ToUpperOnlyLatin(std::string &utf8String)
Definition: Util.cpp:795
std::string ByteArrayToHexStr(Container const &c, bool reverse=false)
Definition: Util.h:368
static uint32 GetCharactersCount(uint32 accountId)
Definition: AccountMgr.cpp:409
Class used to access individual fields of database query result.
Definition: Field.h:90
uint64 LowType
Definition: ObjectGuid.h:278
static ResponseCodes CheckPlayerName(std::string_view name, LocaleConstant locale, bool create=false)
Definition: ObjectMgr.cpp:8687
DumpReturn LoadDump(std::istream &input, uint32 account, std::string name, ObjectGuid::LowType guid)
Definition: PlayerDump.cpp:864
DumpReturn LoadDumpFromString(std::string const &dump, uint32 account, std::string name, ObjectGuid::LowType guid)
DumpReturn LoadDumpFromFile(std::string const &file, uint32 account, std::string name, ObjectGuid::LowType guid)
std::set< uint32 > _pets
Definition: PlayerDump.h:101
bool GetDump(ObjectGuid::LowType guid, std::string &dump)
Definition: PlayerDump.cpp:797
std::set< uint64 > _itemSets
Definition: PlayerDump.h:105
DumpReturn WriteDumpToString(std::string &dump, ObjectGuid::LowType guid)
Definition: PlayerDump.cpp:844
std::set< ObjectGuid::LowType > _items
Definition: PlayerDump.h:103
bool AppendTable(StringTransaction &trans, ObjectGuid::LowType guid, TableStruct const &tableStruct, DumpTable const &dumpTable)
Definition: PlayerDump.cpp:737
DumpReturn WriteDumpToFile(std::string const &file, ObjectGuid::LowType guid)
Definition: PlayerDump.cpp:818
void PopulateGuids(ObjectGuid::LowType guid)
Definition: PlayerDump.cpp:690
std::set< uint32 > _mails
Definition: PlayerDump.h:102
static void InitializeTables()
Definition: PlayerDump.cpp:284
void setString(const uint8 index, const std::string &value)
void setUInt64(const uint8 index, const uint64 value)
void Append(char const *sql)
Definition: PlayerDump.cpp:161
std::string _buf
Definition: PlayerDump.cpp:174
char const * GetBuffer() const
Definition: PlayerDump.cpp:168
#define sWorld
Definition: World.h:931
@ CONFIG_CHARACTERS_PER_REALM
Definition: World.h:254
@ CONFIG_PDUMP_NO_OVERWRITE
Definition: World.h:163
@ CONFIG_PDUMP_NO_PATHS
Definition: World.h:162
std::unordered_map< std::string, Player * > MapType
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
Definition: StringFormat.h:38
char const * TableName
Definition: PlayerDump.cpp:67
char const * PlayerGuid
Definition: PlayerDump.cpp:69
char const * PrimaryKey
Definition: PlayerDump.cpp:68
GuidType StoredType
Definition: PlayerDump.cpp:71
char const * Name
Definition: PlayerDump.cpp:86
DumpTableType Type
Definition: PlayerDump.cpp:87
void operator()(FILE *f) const
Definition: PlayerDump.cpp:52
bool IsDependentField
Definition: PlayerDump.cpp:183
bool IsBinaryField
Definition: PlayerDump.cpp:184
std::string FieldName
Definition: PlayerDump.cpp:180
GuidType FieldGuidType
Definition: PlayerDump.cpp:182
std::unordered_map< std::string, int32 > FieldIndices
Definition: PlayerDump.cpp:194
std::string TableName
Definition: PlayerDump.cpp:189
std::vector< TableField > TableFields
Definition: PlayerDump.cpp:191
std::string WhereFieldName
Definition: PlayerDump.cpp:190