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