Local snapshot for Docker build (includes mod-ale)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
#
|
||||
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
#
|
||||
# This file is free software; as a special exception the author gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
|
||||
CollectSourceFiles(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PRIVATE_SOURCES)
|
||||
|
||||
# Group sources
|
||||
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_library(shared
|
||||
${PRIVATE_SOURCES})
|
||||
|
||||
CollectIncludeDirectories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PUBLIC_INCLUDES)
|
||||
|
||||
target_include_directories(shared
|
||||
PUBLIC
|
||||
${PUBLIC_INCLUDES}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(shared
|
||||
PRIVATE
|
||||
acore-core-interface
|
||||
PUBLIC
|
||||
database)
|
||||
|
||||
set_target_properties(shared
|
||||
PROPERTIES
|
||||
FOLDER
|
||||
"server")
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DBCDatabaseLoader.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Errors.h"
|
||||
#include "QueryResult.h"
|
||||
#include "StringFormat.h"
|
||||
|
||||
DBCDatabaseLoader::DBCDatabaseLoader(char const* tableName, char const* dbcFormatString, std::vector<char*>& stringPool)
|
||||
: _sqlTableName(tableName),
|
||||
_dbcFormat(dbcFormatString),
|
||||
_sqlIndexPos(0),
|
||||
_recordSize(0),
|
||||
_stringPool(stringPool)
|
||||
{
|
||||
// Get sql index position
|
||||
int32 indexPos = -1;
|
||||
_recordSize = DBCFileLoader::GetFormatRecordSize(_dbcFormat, &indexPos);
|
||||
|
||||
ASSERT(_recordSize);
|
||||
}
|
||||
|
||||
char* DBCDatabaseLoader::Load(uint32& records, char**& indexTable)
|
||||
{
|
||||
std::string query = Acore::StringFormat("SELECT * FROM `{}` ORDER BY `ID` DESC", _sqlTableName);
|
||||
|
||||
// no error if empty set
|
||||
QueryResult result = WorldDatabase.Query(query);
|
||||
if (!result)
|
||||
return nullptr;
|
||||
|
||||
// Check if sql index pos is valid
|
||||
if (int32(result->GetFieldCount() - 1) < _sqlIndexPos)
|
||||
{
|
||||
ASSERT(false, "Invalid index pos for dbc: '{}'", _sqlTableName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Resize index table
|
||||
// database query *MUST* contain ORDER BY `index_field` DESC clause
|
||||
uint32 indexTableSize = std::max(records, (*result)[_sqlIndexPos].Get<uint32>() + 1);
|
||||
if (indexTableSize > records)
|
||||
{
|
||||
char** tmpIdxTable = new char* [indexTableSize];
|
||||
memset(tmpIdxTable, 0, indexTableSize * sizeof(char*));
|
||||
memcpy(tmpIdxTable, indexTable, records * sizeof(char*));
|
||||
delete[] indexTable;
|
||||
indexTable = tmpIdxTable;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> dataTable = std::make_unique<char[]>(result->GetRowCount() * _recordSize);
|
||||
std::unique_ptr<uint32[]> newIndexes = std::make_unique<uint32[]>(result->GetRowCount());
|
||||
uint32 newRecords = 0;
|
||||
|
||||
// Insert sql data into the data array
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 indexValue = fields[_sqlIndexPos].Get<uint32>();
|
||||
char* dataValue = indexTable[indexValue];
|
||||
|
||||
// If exist in DBC file override from DB
|
||||
newIndexes[newRecords] = indexValue;
|
||||
dataValue = &dataTable[newRecords++ * _recordSize];
|
||||
|
||||
uint32 dataOffset = 0;
|
||||
uint32 sqlColumnNumber = 0;
|
||||
char const* dbcFormat = _dbcFormat;
|
||||
|
||||
for (; (*dbcFormat); ++dbcFormat)
|
||||
{
|
||||
switch (*dbcFormat)
|
||||
{
|
||||
case FT_FLOAT:
|
||||
*reinterpret_cast<float*>(&dataValue[dataOffset]) = fields[sqlColumnNumber].Get<float>();
|
||||
dataOffset += sizeof(float);
|
||||
break;
|
||||
case FT_IND:
|
||||
case FT_INT:
|
||||
*reinterpret_cast<uint32*>(&dataValue[dataOffset]) = fields[sqlColumnNumber].Get<uint32>();
|
||||
dataOffset += sizeof(uint32);
|
||||
break;
|
||||
case FT_BYTE:
|
||||
*reinterpret_cast<uint8*>(&dataValue[dataOffset]) = fields[sqlColumnNumber].Get<uint8>();
|
||||
dataOffset += sizeof(uint8);
|
||||
break;
|
||||
case FT_STRING:
|
||||
*reinterpret_cast<char**>(&dataValue[dataOffset]) = CloneStringToPool(fields[sqlColumnNumber].Get<std::string>());
|
||||
dataOffset += sizeof(char*);
|
||||
break;
|
||||
case FT_SORT:
|
||||
case FT_NA:
|
||||
case FT_NA_BYTE:
|
||||
break;
|
||||
default:
|
||||
ASSERT(false, "Unsupported data type '{}' in table '{}'", *dbcFormat, _sqlTableName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
++sqlColumnNumber;
|
||||
}
|
||||
|
||||
ASSERT(sqlColumnNumber == result->GetFieldCount(), "SQL format string does not match database for table: '{}'", _sqlTableName);
|
||||
ASSERT(dataOffset == _recordSize);
|
||||
} while (result->NextRow());
|
||||
|
||||
ASSERT(newRecords == result->GetRowCount());
|
||||
|
||||
// insert new records to index table
|
||||
for (uint32 i = 0; i < newRecords; ++i)
|
||||
{
|
||||
// cppcheck-suppress autoVariables
|
||||
indexTable[newIndexes[i]] = &dataTable[i * _recordSize];
|
||||
}
|
||||
|
||||
records = indexTableSize;
|
||||
|
||||
return dataTable.release();
|
||||
}
|
||||
|
||||
char* DBCDatabaseLoader::CloneStringToPool(std::string const& str)
|
||||
{
|
||||
char* buf = new char[str.size() + 1];
|
||||
memcpy(buf, str.c_str(), str.size() + 1);
|
||||
_stringPool.push_back(buf);
|
||||
return buf;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DBCDatabaseLoader_h__
|
||||
#define DBCDatabaseLoader_h__
|
||||
|
||||
#include "DBCFileLoader.h"
|
||||
#include <vector>
|
||||
|
||||
struct DBCDatabaseLoader
|
||||
{
|
||||
DBCDatabaseLoader(char const* dbTable, char const* dbcFormatString, std::vector<char*>& stringPool);
|
||||
|
||||
char* Load(uint32& records, char**& indexTable);
|
||||
|
||||
private:
|
||||
char const* _sqlTableName;
|
||||
char const* _dbcFormat;
|
||||
int32 _sqlIndexPos;
|
||||
uint32 _recordSize;
|
||||
std::vector<char*>& _stringPool;
|
||||
char* CloneStringToPool(std::string const& str);
|
||||
|
||||
DBCDatabaseLoader(DBCDatabaseLoader const& right) = delete;
|
||||
DBCDatabaseLoader& operator=(DBCDatabaseLoader const& right) = delete;
|
||||
};
|
||||
|
||||
#endif // DBCDatabaseLoader_h__
|
||||
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DBCENUMS_H
|
||||
#define DBCENUMS_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct DBCPosition3D
|
||||
{
|
||||
float X;
|
||||
float Y;
|
||||
float Z;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Client expected level limitation, like as used in DBC item max levels for "until max player level"
|
||||
// use as default max player level, must be fit max level for used client
|
||||
// also see MAX_LEVEL and STRONG_MAX_LEVEL define
|
||||
#define DEFAULT_MAX_LEVEL 80
|
||||
|
||||
// client supported max level for player/pets/etc. Avoid overflow or client stability affected.
|
||||
// also see GT_MAX_LEVEL define
|
||||
#define MAX_LEVEL 100
|
||||
|
||||
// Server side limitation. Base at used code requirements.
|
||||
// also see MAX_LEVEL and GT_MAX_LEVEL define
|
||||
#define STRONG_MAX_LEVEL 255
|
||||
|
||||
enum BattlegroundBracketId : uint8 // bracketId for level ranges
|
||||
{
|
||||
BG_BRACKET_ID_FIRST = 0,
|
||||
BG_BRACKET_ID_LAST = 15
|
||||
};
|
||||
|
||||
// must be max value in PvPDificulty slot+1
|
||||
#define MAX_BATTLEGROUND_BRACKETS 16
|
||||
|
||||
enum AreaTeams
|
||||
{
|
||||
AREATEAM_NONE = 0,
|
||||
AREATEAM_ALLY = 2,
|
||||
AREATEAM_HORDE = 4,
|
||||
AREATEAM_ANY = 6
|
||||
};
|
||||
|
||||
enum AchievementFaction
|
||||
{
|
||||
ACHIEVEMENT_FACTION_HORDE = 0,
|
||||
ACHIEVEMENT_FACTION_ALLIANCE = 1,
|
||||
ACHIEVEMENT_FACTION_ANY = -1,
|
||||
};
|
||||
|
||||
enum AchievementFlags
|
||||
{
|
||||
ACHIEVEMENT_FLAG_COUNTER = 0x00000001, // Just count statistic (never stop and complete)
|
||||
ACHIEVEMENT_FLAG_HIDDEN = 0x00000002, // Not sent to client - internal use only
|
||||
ACHIEVEMENT_FLAG_STORE_MAX_VALUE = 0x00000004, // Store only max value? used only in "Reach level xx"
|
||||
ACHIEVEMENT_FLAG_SUMM = 0x00000008, // Use summ criteria value from all reqirements (and calculate max value)
|
||||
ACHIEVEMENT_FLAG_MAX_USED = 0x00000010, // Show max criteria (and calculate max value ??)
|
||||
ACHIEVEMENT_FLAG_REQ_COUNT = 0x00000020, // Use not zero req count (and calculate max value)
|
||||
ACHIEVEMENT_FLAG_AVERAGE = 0x00000040, // Show as average value (value / time_in_days) depend from other flag (by def use last criteria value)
|
||||
ACHIEVEMENT_FLAG_BAR = 0x00000080, // Show as progress bar (value / max vale) depend from other flag (by def use last criteria value)
|
||||
ACHIEVEMENT_FLAG_REALM_FIRST_REACH = 0x00000100, //
|
||||
ACHIEVEMENT_FLAG_REALM_FIRST_KILL = 0x00000200, //
|
||||
};
|
||||
|
||||
#define MAX_CRITERIA_REQUIREMENTS 2
|
||||
|
||||
enum AchievementCriteriaCondition
|
||||
{
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_NONE = 0,
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_NO_DEATH = 1, // reset progress on death
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_UNK1 = 2, // only used in "Complete a daily quest every day for five consecutive days"
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_BG_MAP = 3, // requires you to be on specific map, reset at change
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_NO_LOSE = 4, // only used in "Win 10 arenas without losing"
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_NO_SPELL_HIT = 9, // requires the player not to be hit by specific spell
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_NOT_IN_GROUP = 10, // requires the player not to be in group
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_UNK3 = 13, // unk
|
||||
ACHIEVEMENT_CRITERIA_CONDITION_TOTAL = 14
|
||||
};
|
||||
|
||||
enum AchievementCriteriaFlags
|
||||
{
|
||||
ACHIEVEMENT_CRITERIA_FLAG_SHOW_PROGRESS_BAR = 0x00000001, // Show progress as bar
|
||||
ACHIEVEMENT_CRITERIA_FLAG_HIDDEN = 0x00000002, // Not show criteria in client
|
||||
ACHIEVEMENT_CRITERIA_FLAG_FAIL_ACHIEVEMENT = 0x00000004, // BG related??
|
||||
ACHIEVEMENT_CRITERIA_FLAG_RESET_ON_START = 0x00000008, //
|
||||
ACHIEVEMENT_CRITERIA_FLAG_IS_DATE = 0x00000010, // not used
|
||||
ACHIEVEMENT_CRITERIA_FLAG_MONEY_COUNTER = 0x00000020 // Displays counter as money
|
||||
};
|
||||
|
||||
enum AchievementCriteriaTimedTypes
|
||||
{
|
||||
ACHIEVEMENT_TIMED_TYPE_EVENT = 1, // Timer is started by internal event with id in timerStartEvent
|
||||
ACHIEVEMENT_TIMED_TYPE_QUEST = 2, // Timer is started by accepting quest with entry in timerStartEvent
|
||||
ACHIEVEMENT_TIMED_TYPE_SPELL_CASTER = 5, // Timer is started by casting a spell with entry in timerStartEvent
|
||||
ACHIEVEMENT_TIMED_TYPE_SPELL_TARGET = 6, // Timer is started by being target of spell with entry in timerStartEvent
|
||||
ACHIEVEMENT_TIMED_TYPE_CREATURE = 7, // Timer is started by killing creature with entry in timerStartEvent
|
||||
ACHIEVEMENT_TIMED_TYPE_ITEM = 9, // Timer is started by using item with entry in timerStartEvent
|
||||
|
||||
ACHIEVEMENT_TIMED_TYPE_MAX,
|
||||
};
|
||||
|
||||
enum AchievementCriteriaTypes
|
||||
{
|
||||
ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE = 0,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_WIN_BG = 1,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_REACH_LEVEL = 5,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL = 7,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_ACHIEVEMENT = 8,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT = 9,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST_DAILY = 10, // you have to complete a daily quest x times in a row
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUESTS_IN_ZONE = 11,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE = 13,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_DAILY_QUEST = 14,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_BATTLEGROUND = 15,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_DEATH_AT_MAP = 16,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_DEATH = 17,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_DEATH_IN_DUNGEON = 18,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_RAID = 19,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_CREATURE = 20,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_KILLED_BY_PLAYER = 23,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_FALL_WITHOUT_DYING = 24,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_DEATHS_FROM = 26,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST = 27,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET = 28,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL = 29,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_BG_OBJECTIVE_CAPTURE = 30,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA = 31,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_WIN_ARENA = 32,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_PLAY_ARENA = 33,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LEARN_SPELL = 34,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL = 35,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM = 36,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA = 37, /// @todo: the archievements 1162 and 1163 requires a special rating which can't be found in the dbc
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_TEAM_RATING = 38,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_PERSONAL_RATING = 39,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL = 40,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM = 41,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM = 42,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA = 43,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_OWN_RANK = 44,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_BUY_BANK_SLOT = 45,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GAIN_REPUTATION = 46,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GAIN_EXALTED_REPUTATION = 47,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP = 48, // note: rewarded as soon as the player payed, not at taking place at the seat
|
||||
ACHIEVEMENT_CRITERIA_TYPE_EQUIP_EPIC_ITEM = 49,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED_ON_LOOT = 50, /// @todo: itemlevel is mentioned in text but not present in dbc
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED_ON_LOOT = 51,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS = 52,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HK_RACE = 53,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_DO_EMOTE = 54,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HEALING_DONE = 55,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GET_KILLING_BLOWS = 56, /// @todo: in some cases map not present, and in some cases need do without die
|
||||
ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM = 57,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_VENDORS = 59,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TALENTS = 60,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_NUMBER_OF_TALENT_RESETS = 61,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_QUEST_REWARD = 62,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING = 63,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_AT_BARBER = 65,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL = 66,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY = 67,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_USE_GAMEOBJECT = 68,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET2 = 69,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL = 70,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_FISH_IN_GAMEOBJECT = 72,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ON_LOGIN = 74, /// @todo: title id is not mentioned in dbc
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILLLINE_SPELLS = 75,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_WIN_DUEL = 76,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LOSE_DUEL = 77,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE_TYPE = 78, /// @todo: creature type (demon, undead etc.) is not stored in dbc
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS = 80,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_CREATE_AUCTION = 82,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_BID = 83,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS = 84,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_SOLD = 85,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_GOLD_VALUE_OWNED = 86,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GAIN_REVERED_REPUTATION = 87,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_GAIN_HONORED_REPUTATION = 88,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_KNOWN_FACTIONS = 89,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LOOT_EPIC_ITEM = 90,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM = 91,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED = 93,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED = 94,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALTH = 95,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_POWER = 96,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_STAT = 97,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_SPELLPOWER = 98,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_ARMOR = 99,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_RATING = 100,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT = 101,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_RECEIVED = 102,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED = 103,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEAL_CASTED = 104,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_TOTAL_HEALING_RECEIVED = 105,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEALING_RECEIVED = 106,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_QUEST_ABANDONED = 107,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_FLIGHT_PATHS_TAKEN = 108,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LOOT_TYPE = 109,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_CAST_SPELL2 = 110, /// @todo: target entry is missing
|
||||
ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LINE = 112,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL = 113,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ACCEPTED_SUMMONINGS = 114,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_EARN_ACHIEVEMENT_POINTS = 115,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_ROLL_DISENCHANT = 117,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_USE_LFD_TO_GROUP_WITH_PLAYERS = 119,
|
||||
ACHIEVEMENT_CRITERIA_TYPE_TOTAL = 124, // 0..123 => 124 criteria types total
|
||||
};
|
||||
|
||||
enum AchievementCategory
|
||||
{
|
||||
CATEGORY_CHILDRENS_WEEK = 163,
|
||||
};
|
||||
|
||||
enum AreaFlags
|
||||
{
|
||||
AREA_FLAG_UNK0 = 0x00000001, // Unknown
|
||||
AREA_FLAG_UNK1 = 0x00000002, // Razorfen Downs, Naxxramas and Acherus: The Ebon Hold (3.3.5a)
|
||||
AREA_FLAG_UNK2 = 0x00000004, // Only used for areas on map 571 (development before)
|
||||
AREA_FLAG_SLAVE_CAPITAL = 0x00000008, // city and city subsones
|
||||
AREA_FLAG_UNK3 = 0x00000010, // can't find common meaning
|
||||
AREA_FLAG_SLAVE_CAPITAL2 = 0x00000020, // slave capital city flag?
|
||||
AREA_FLAG_ALLOW_DUELS = 0x00000040, // allow to duel here
|
||||
AREA_FLAG_ARENA = 0x00000080, // arena, both instanced and world arenas
|
||||
AREA_FLAG_CAPITAL = 0x00000100, // main capital city flag
|
||||
AREA_FLAG_CITY = 0x00000200, // only for one zone named "City" (where it located?)
|
||||
AREA_FLAG_OUTLAND = 0x00000400, // expansion zones? (only Eye of the Storm not have this flag, but have 0x00004000 flag)
|
||||
AREA_FLAG_SANCTUARY = 0x00000800, // sanctuary area (PvP disabled)
|
||||
AREA_FLAG_NEED_FLY = 0x00001000, // Respawn alive at the graveyard without corpse
|
||||
AREA_FLAG_UNUSED1 = 0x00002000, // Unused in 3.3.5a
|
||||
AREA_FLAG_OUTLAND2 = 0x00004000, // expansion zones? (only Circle of Blood Arena not have this flag, but have 0x00000400 flag)
|
||||
AREA_FLAG_OUTDOOR_PVP = 0x00008000, // pvp objective area? (Death's Door also has this flag although it's no pvp object area)
|
||||
AREA_FLAG_ARENA_INSTANCE = 0x00010000, // used by instanced arenas only
|
||||
AREA_FLAG_UNUSED2 = 0x00020000, // Unused in 3.3.5a
|
||||
AREA_FLAG_CONTESTED_AREA = 0x00040000, // On PvP servers these areas are considered contested, even though the zone it is contained in is a Horde/Alliance territory.
|
||||
AREA_FLAG_UNK4 = 0x00080000, // Valgarde and Acherus: The Ebon Hold
|
||||
AREA_FLAG_LOWLEVEL = 0x00100000, // used for some starting areas with area_level <= 15
|
||||
AREA_FLAG_TOWN = 0x00200000, // small towns with Inn
|
||||
AREA_FLAG_REST_ZONE_HORDE = 0x00400000, // Instead of using areatriggers, the zone will act as one for Horde players (Warsong Hold, Acherus: The Ebon Hold, New Agamand Inn, Vengeance Landing Inn, Sunreaver Pavilion, etc)
|
||||
AREA_FLAG_REST_ZONE_ALLIANCE = 0x00800000, // Instead of using areatriggers, the zone will act as one for Alliance players (Valgarde, Acherus: The Ebon Hold, Westguard Inn, Silver Covenant Pavilion, etc)
|
||||
AREA_FLAG_WINTERGRASP = 0x01000000, // Wintergrasp and it's subzones
|
||||
AREA_FLAG_INSIDE = 0x02000000, // used for determinating spell related inside/outside questions in Map::IsOutdoors
|
||||
AREA_FLAG_OUTSIDE = 0x04000000, // used for determinating spell related inside/outside questions in Map::IsOutdoors
|
||||
AREA_FLAG_WINTERGRASP_2 = 0x08000000, // Can Hearth And Resurrect From Area
|
||||
AREA_FLAG_NO_FLY_ZONE = 0x20000000 // Marks zones where you cannot fly
|
||||
};
|
||||
|
||||
enum Difficulty : uint8
|
||||
{
|
||||
REGULAR_DIFFICULTY = 0,
|
||||
|
||||
DUNGEON_DIFFICULTY_NORMAL = 0,
|
||||
DUNGEON_DIFFICULTY_HEROIC = 1,
|
||||
DUNGEON_DIFFICULTY_EPIC = 2,
|
||||
|
||||
RAID_DIFFICULTY_10MAN_NORMAL = 0,
|
||||
RAID_DIFFICULTY_25MAN_NORMAL = 1,
|
||||
RAID_DIFFICULTY_10MAN_HEROIC = 2,
|
||||
RAID_DIFFICULTY_25MAN_HEROIC = 3,
|
||||
};
|
||||
|
||||
#define RAID_DIFFICULTY_MASK_25MAN 1 // since 25man difficulties are 1 and 3, we can check them like that
|
||||
|
||||
#define MAX_DUNGEON_DIFFICULTY 3
|
||||
#define MAX_RAID_DIFFICULTY 4
|
||||
#define MAX_DIFFICULTY 4
|
||||
|
||||
enum SpawnMask
|
||||
{
|
||||
SPAWNMASK_CONTINENT = (1 << REGULAR_DIFFICULTY), // any any maps without spawn modes
|
||||
|
||||
SPAWNMASK_DUNGEON_NORMAL = (1 << DUNGEON_DIFFICULTY_NORMAL),
|
||||
SPAWNMASK_DUNGEON_HEROIC = (1 << DUNGEON_DIFFICULTY_HEROIC),
|
||||
SPAWNMASK_DUNGEON_ALL = (SPAWNMASK_DUNGEON_NORMAL | SPAWNMASK_DUNGEON_HEROIC),
|
||||
|
||||
SPAWNMASK_RAID_10MAN_NORMAL = (1 << RAID_DIFFICULTY_10MAN_NORMAL),
|
||||
SPAWNMASK_RAID_25MAN_NORMAL = (1 << RAID_DIFFICULTY_25MAN_NORMAL),
|
||||
SPAWNMASK_RAID_NORMAL_ALL = (SPAWNMASK_RAID_10MAN_NORMAL | SPAWNMASK_RAID_25MAN_NORMAL),
|
||||
|
||||
SPAWNMASK_RAID_10MAN_HEROIC = (1 << RAID_DIFFICULTY_10MAN_HEROIC),
|
||||
SPAWNMASK_RAID_25MAN_HEROIC = (1 << RAID_DIFFICULTY_25MAN_HEROIC),
|
||||
SPAWNMASK_RAID_HEROIC_ALL = (SPAWNMASK_RAID_10MAN_HEROIC | SPAWNMASK_RAID_25MAN_HEROIC),
|
||||
|
||||
SPAWNMASK_RAID_ALL = (SPAWNMASK_RAID_NORMAL_ALL | SPAWNMASK_RAID_HEROIC_ALL),
|
||||
};
|
||||
|
||||
enum FactionFlags
|
||||
{
|
||||
FACTION_FLAG_NONE = 0x00, // no faction flag
|
||||
FACTION_FLAG_VISIBLE = 0x01, // makes visible in client (set or can be set at interaction with target of this faction)
|
||||
FACTION_FLAG_AT_WAR = 0x02, // enable AtWar-button in client. player controlled (except opposition team always war state), Flag only set on initial creation
|
||||
FACTION_FLAG_HIDDEN = 0x04, // hidden faction from reputation pane in client (player can gain reputation, but this update not sent to client)
|
||||
FACTION_FLAG_INVISIBLE_FORCED = 0x08, // always overwrite FACTION_FLAG_VISIBLE and hide faction in rep.list, used for hide opposite team factions
|
||||
FACTION_FLAG_PEACE_FORCED = 0x10, // always overwrite FACTION_FLAG_AT_WAR, used for prevent war with own team factions
|
||||
FACTION_FLAG_INACTIVE = 0x20, // player controlled, state stored in characters.data (CMSG_SET_FACTION_INACTIVE)
|
||||
FACTION_FLAG_RIVAL = 0x40, // flag for the two competing outland factions
|
||||
FACTION_FLAG_SPECIAL = 0x80 // horde and alliance home cities and their northrend allies have this flag
|
||||
};
|
||||
|
||||
enum FactionTemplateFlags
|
||||
{
|
||||
FACTION_TEMPLATE_FLAG_RESPOND_TO_CALL_FOR_HELP = 0x0001, /// @todo: Not Yet Implemented (NYI)
|
||||
FACTION_TEMPLATE_FLAG_BROADCAST_TO_ENEMIES_LOW_PRIORITY = 0x0002, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_BROADCAST_TO_ENEMIES_MED_PRIORITY = 0x0004, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_BROADCAST_TO_ENEMIES_HIGH_PRIORITY = 0x0008, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_SEARCH_FOR_ENEMIES_LOW_PRIORITY = 0x0010, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_SEARCH_FOR_ENEMIES_MED_PRIORITY = 0x0020, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_SEARCH_FOR_ENEMIES_HIGH_PRIORITY = 0x0040, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_SEARCH_FOR_FRIENDS_LOW_PRIORITY = 0x0080, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_SEARCH_FOR_FRIENDS_MED_PRIORITY = 0x0100, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_SEARCH_FOR_FRIENDS_HIGH_PRIORITY = 0x0200, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_FLEE_FROM_CALL_FOR_HELP = 0x0400, /// @todo: NYI
|
||||
FACTION_TEMPLATE_FLAG_ASSIST_PLAYERS = 0x0800, // Old title: FACTION_TEMPLATE_FLAG_ASSIST_PLAYERS, Old comment: flagged for PvP //@todo: Should see if this is implemented correctly.
|
||||
FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS = 0x1000, // Old title: FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS, Old comment: faction will attack players that were involved in PvP combats //@todo: Should see if this is implemented correctly.
|
||||
FACTION_TEMPLATE_FLAG_HATES_ALL_EXCEPT_FRIENDS = 0x2000, // Old title: FACTION_TEMPLATE_FLAG_HATES_ALL_EXCEPT_FRIENDS, Old comment: //@todo: Should see if this is implemented correctly.
|
||||
};
|
||||
|
||||
enum FactionMasks
|
||||
{
|
||||
FACTION_MASK_PLAYER = 1, // any player
|
||||
FACTION_MASK_ALLIANCE = 2, // player or creature from alliance team
|
||||
FACTION_MASK_HORDE = 4, // player or creature from horde team
|
||||
FACTION_MASK_MONSTER = 8 // aggressive creature from monster team
|
||||
// if none flags set then non-aggressive creature
|
||||
};
|
||||
|
||||
enum MapTypes // Lua_IsInInstance
|
||||
{
|
||||
MAP_COMMON = 0, // none
|
||||
MAP_INSTANCE = 1, // party
|
||||
MAP_RAID = 2, // raid
|
||||
MAP_BATTLEGROUND = 3, // pvp
|
||||
MAP_ARENA = 4 // arena
|
||||
};
|
||||
|
||||
enum MapFlags
|
||||
{
|
||||
MAP_FLAG_DYNAMIC_DIFFICULTY = 0x100
|
||||
};
|
||||
|
||||
enum AbilytyLearnType
|
||||
{
|
||||
SKILL_LINE_ABILITY_LEARNED_ON_SKILL_VALUE = 1, // Spell state will update depending on skill value
|
||||
SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN = 2 // Spell will be learned/removed together with entire skill
|
||||
};
|
||||
|
||||
enum ItemEnchantmentType
|
||||
{
|
||||
ITEM_ENCHANTMENT_TYPE_NONE = 0,
|
||||
ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL = 1,
|
||||
ITEM_ENCHANTMENT_TYPE_DAMAGE = 2,
|
||||
ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL = 3,
|
||||
ITEM_ENCHANTMENT_TYPE_RESISTANCE = 4,
|
||||
ITEM_ENCHANTMENT_TYPE_STAT = 5,
|
||||
ITEM_ENCHANTMENT_TYPE_TOTEM = 6,
|
||||
ITEM_ENCHANTMENT_TYPE_USE_SPELL = 7,
|
||||
ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET = 8
|
||||
};
|
||||
|
||||
enum ItemLimitCategoryMode
|
||||
{
|
||||
ITEM_LIMIT_CATEGORY_MODE_HAVE = 0, // limit applied to amount items in inventory/bank
|
||||
ITEM_LIMIT_CATEGORY_MODE_EQUIP = 1, // limit applied to amount equipped items (including used gems)
|
||||
};
|
||||
|
||||
enum SkillRaceClassInfoFlags
|
||||
{
|
||||
SKILL_FLAG_NO_SKILLUP_MESSAGE = 0x2,
|
||||
SKILL_FLAG_ALWAYS_MAX_VALUE = 0x10,
|
||||
SKILL_FLAG_UNLEARNABLE = 0x20, // Skill can be unlearned
|
||||
SKILL_FLAG_INCLUDE_IN_SORT = 0x80, // Spells belonging to a skill with this flag will additionally compare skill ids when sorting spellbook in client
|
||||
SKILL_FLAG_NOT_TRAINABLE = 0x100,
|
||||
SKILL_FLAG_MONO_VALUE = 0x400 // Skill always has value 1 - clientside display flag, real value can be different
|
||||
};
|
||||
|
||||
enum SpellCategoryFlags
|
||||
{
|
||||
SPELL_CATEGORY_FLAG_COOLDOWN_SCALES_WITH_WEAPON_SPEED = 0x01, // unused
|
||||
SPELL_CATEGORY_FLAG_COOLDOWN_STARTS_ON_EVENT = 0x04
|
||||
};
|
||||
|
||||
enum TotemCategoryType
|
||||
{
|
||||
TOTEM_CATEGORY_TYPE_KNIFE = 1,
|
||||
TOTEM_CATEGORY_TYPE_TOTEM = 2,
|
||||
TOTEM_CATEGORY_TYPE_ROD = 3,
|
||||
TOTEM_CATEGORY_TYPE_PICK = 21,
|
||||
TOTEM_CATEGORY_TYPE_STONE = 22,
|
||||
TOTEM_CATEGORY_TYPE_HAMMER = 23,
|
||||
TOTEM_CATEGORY_TYPE_SPANNER = 24
|
||||
};
|
||||
|
||||
// SummonProperties.dbc, col 1
|
||||
enum SummonPropGroup
|
||||
{
|
||||
SUMMON_PROP_GROUP_NONE = 0, // 1160 spells in 3.0.3
|
||||
SUMMON_PROP_GROUP_GUARDIAN = 1, /// @todo: NYI 861 spells in 3.0.3
|
||||
SUMMON_PROP_GROUP_PETS = 2, /// @todo: NYI 52 spells in 3.0.3, pets mostly
|
||||
SUMMON_PROP_GROUP_CONTROLLABLE = 3, /// @todo: NYI 13 spells in 3.0.3, mostly controllable
|
||||
SUMMON_PROP_GROUP_VEHICLE = 4 /// @todo: NYI 86 spells in 3.0.3, taxi/mounts
|
||||
};
|
||||
|
||||
// SummonProperties.dbc, col 5
|
||||
enum SummonPropFlags
|
||||
{
|
||||
SUMMON_PROP_FLAG_NONE = 0x00000000, // 1342 spells in 3.0.3
|
||||
SUMMON_PROP_FLAG_ATTACK_SUMMONER = 0x00000001, /// @todo: NYI 75 spells in 3.0.3, something unfriendly
|
||||
SUMMON_PROP_FLAG_ASSIST_COMBAT_SUMMON = 0x00000002, /// @todo: NYI 616 spells in 3.0.3, something friendly
|
||||
SUMMON_PROP_FLAG_USE_LEVEL_OFFSET = 0x00000004, /// @todo: NYI 22 spells in 3.0.3, no idea...
|
||||
SUMMON_PROP_FLAG_DESPAWN_ON_SUMMONER_DEATH = 0x00000008, /// @todo: NYI 49 spells in 3.0.3, some mounts
|
||||
SUMMON_PROP_FLAG_ONLY_VISIBLE_TO_SUMMONER = 0x00000010, // 25 spells in 3.0.3, quest related?
|
||||
SUMMON_PROP_FLAG_CANNOT_DISMISS_PET = 0x00000020, /// @todo: NYI 0 spells in 3.3.5, unused
|
||||
SUMMON_PROP_FLAG_USE_DEMON_TIMEOUT = 0x00000040, /// @todo: NYI 12 spells in 3.0.3, no idea
|
||||
SUMMON_PROP_FLAG_UNLIMITED_SUMMONS = 0x00000080, /// @todo: NYI 4 spells in 3.0.3, no idea
|
||||
SUMMON_PROP_FLAG_USE_CREATURE_LEVEL = 0x00000100, /// @todo: NYI 51 spells in 3.0.3, no idea, many quest related
|
||||
SUMMON_PROP_FLAG_JOIN_SUMMONER_SPAWN_GROUP = 0x00000200, /// @todo: NYI 51 spells in 3.0.3, something defensive
|
||||
SUMMON_PROP_FLAG_DO_NOT_TOGGLE = 0x00000400, /// @todo: NYI 3 spells, requires something near?
|
||||
SUMMON_PROP_FLAG_DESPAWN_WHEN_EXPIRED = 0x00000800, /// @todo: NYI 30 spells in 3.0.3, no idea
|
||||
SUMMON_PROP_FLAG_USE_SUMMONER_FACTION = 0x00001000, /// Lightwell, Jeeves, Gnomish Alarm-o-bot, Build vehicles(wintergrasp)
|
||||
SUMMON_PROP_FLAG_DO_NOT_FOLLOW_MOUNTED_SUMMONER = 0x00002000, /// @todo: NYI Guides, player follows
|
||||
SUMMON_PROP_FLAG_SAVE_PET_AUTOCAST = 0x00004000, /// @todo: NYI Force of Nature, Shadowfiend, Feral Spirit, Summon Water Elemental
|
||||
SUMMON_PROP_FLAG_IGNORE_SUMMONER_PHASE = 0x00008000, /// @todo: NYI Light/Dark Bullet, Soul/Fiery Consumption, Twisted Visage, Twilight Whelp. Phase related?
|
||||
};
|
||||
|
||||
enum VehicleSeatFlags
|
||||
{
|
||||
VEHICLE_SEAT_FLAG_HAS_LOWER_ANIM_FOR_ENTER = 0x00000001,
|
||||
VEHICLE_SEAT_FLAG_HAS_LOWER_ANIM_FOR_RIDE = 0x00000002,
|
||||
VEHICLE_SEAT_FLAG_UNK3 = 0x00000004,
|
||||
VEHICLE_SEAT_FLAG_SHOULD_USE_VEH_SEAT_EXIT_ANIM_ON_VOLUNTARY_EXIT = 0x00000008,
|
||||
VEHICLE_SEAT_FLAG_UNK5 = 0x00000010,
|
||||
VEHICLE_SEAT_FLAG_UNK6 = 0x00000020,
|
||||
VEHICLE_SEAT_FLAG_UNK7 = 0x00000040,
|
||||
VEHICLE_SEAT_FLAG_UNK8 = 0x00000080,
|
||||
VEHICLE_SEAT_FLAG_UNK9 = 0x00000100,
|
||||
VEHICLE_SEAT_FLAG_HIDE_PASSENGER = 0x00000200, // Passenger is hidden
|
||||
VEHICLE_SEAT_FLAG_ALLOW_TURNING = 0x00000400, // needed for CGCamera__SyncFreeLookFacing
|
||||
VEHICLE_SEAT_FLAG_CAN_CONTROL = 0x00000800, // Lua_UnitInVehicleControlSeat
|
||||
VEHICLE_SEAT_FLAG_CAN_CAST_MOUNT_SPELL = 0x00001000, // Can cast spells with SPELL_AURA_MOUNTED from seat (possibly 4.x only, 0 seats on 3.3.5a)
|
||||
VEHICLE_SEAT_FLAG_UNCONTROLLED = 0x00002000, // can override !& VEHICLE_SEAT_FLAG_CAN_ENTER_OR_EXIT
|
||||
VEHICLE_SEAT_FLAG_CAN_ATTACK = 0x00004000, // Can attack, cast spells and use items from vehicle
|
||||
VEHICLE_SEAT_FLAG_SHOULD_USE_VEH_SEAT_EXIT_ANIM_ON_FORCED_EXIT = 0x00008000,
|
||||
VEHICLE_SEAT_FLAG_UNK17 = 0x00010000,
|
||||
VEHICLE_SEAT_FLAG_UNK18 = 0x00020000,
|
||||
VEHICLE_SEAT_FLAG_HAS_VEH_EXIT_ANIM_VOLUNTARY_EXIT = 0x00040000,
|
||||
VEHICLE_SEAT_FLAG_HAS_VEH_EXIT_ANIM_FORCED_EXIT = 0x00080000,
|
||||
VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE = 0x00100000,
|
||||
VEHICLE_SEAT_FLAG_UNK22 = 0x00200000,
|
||||
VEHICLE_SEAT_FLAG_REC_HAS_VEHICLE_ENTER_ANIM = 0x00400000,
|
||||
VEHICLE_SEAT_FLAG_IS_USING_VEHICLE_CONTROLS = 0x00800000, // Lua_IsUsingVehicleControls
|
||||
VEHICLE_SEAT_FLAG_ENABLE_VEHICLE_ZOOM = 0x01000000,
|
||||
VEHICLE_SEAT_FLAG_CAN_ENTER_OR_EXIT = 0x02000000, // Lua_CanExitVehicle - can enter and exit at free will
|
||||
VEHICLE_SEAT_FLAG_CAN_SWITCH = 0x04000000, // Lua_CanSwitchVehicleSeats
|
||||
VEHICLE_SEAT_FLAG_HAS_START_WARITING_FOR_VEH_TRANSITION_ANIM_ENTER = 0x08000000,
|
||||
VEHICLE_SEAT_FLAG_HAS_START_WARITING_FOR_VEH_TRANSITION_ANIM_EXIT = 0x10000000,
|
||||
VEHICLE_SEAT_FLAG_CAN_CAST = 0x20000000, // Lua_UnitHasVehicleUI
|
||||
VEHICLE_SEAT_FLAG_UNK2 = 0x40000000, // checked in conjunction with 0x800 in CastSpell2
|
||||
VEHICLE_SEAT_FLAG_ALLOWS_INTERACTION = 0x80000000
|
||||
};
|
||||
|
||||
enum VehicleSeatFlagsB
|
||||
{
|
||||
VEHICLE_SEAT_FLAG_B_NONE = 0x00000000,
|
||||
VEHICLE_SEAT_FLAG_B_USABLE_FORCED = 0x00000002,
|
||||
VEHICLE_SEAT_FLAG_B_TARGETS_IN_RAIDUI = 0x00000008, // Lua_UnitTargetsVehicleInRaidUI
|
||||
VEHICLE_SEAT_FLAG_B_EJECTABLE = 0x00000020, // ejectable
|
||||
VEHICLE_SEAT_FLAG_B_USABLE_FORCED_2 = 0x00000040,
|
||||
VEHICLE_SEAT_FLAG_B_USABLE_FORCED_3 = 0x00000100,
|
||||
VEHICLE_SEAT_FLAG_B_KEEP_PET = 0x00020000,
|
||||
VEHICLE_SEAT_FLAG_B_USABLE_FORCED_4 = 0x02000000,
|
||||
VEHICLE_SEAT_FLAG_B_CAN_SWITCH = 0x04000000,
|
||||
VEHICLE_SEAT_FLAG_B_VEHICLE_PLAYERFRAME_UI = 0x80000000, // Lua_UnitHasVehiclePlayerFrameUI - actually checked for flagsb &~ 0x80000000
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DBCStorageIterator_h__
|
||||
#define DBCStorageIterator_h__
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
template <class T>
|
||||
class DBCStorageIterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = T*;
|
||||
using reference = T&;
|
||||
|
||||
DBCStorageIterator() : _index(nullptr) { }
|
||||
DBCStorageIterator(T** index, uint32 size, uint32 pos = 0) : _index(index), _pos(pos), _end(size)
|
||||
{
|
||||
if (_pos < _end)
|
||||
{
|
||||
while (_pos < _end && !_index[_pos])
|
||||
++_pos;
|
||||
}
|
||||
}
|
||||
|
||||
T const* operator->() { return _index[_pos]; }
|
||||
T const* operator*() { return _index[_pos]; }
|
||||
|
||||
bool operator==(DBCStorageIterator const& right) const { /*ASSERT(_index == right._index, "Iterator belongs to a different container")*/ return _pos == right._pos; }
|
||||
bool operator!=(DBCStorageIterator const& right) const { return !(*this == right); }
|
||||
|
||||
DBCStorageIterator& operator++()
|
||||
{
|
||||
if (_pos < _end)
|
||||
{
|
||||
do
|
||||
++_pos;
|
||||
while (_pos < _end && !_index[_pos]);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
DBCStorageIterator operator++(int)
|
||||
{
|
||||
DBCStorageIterator tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
T** _index;
|
||||
uint32 _pos{0};
|
||||
uint32 _end{0};
|
||||
};
|
||||
|
||||
#endif // DBCStorageIterator_h__
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DBCStore.h"
|
||||
#include "DBCDatabaseLoader.h"
|
||||
|
||||
DBCStorageBase::DBCStorageBase(char const* fmt) : _fieldCount(0), _fileFormat(fmt), _dataTable(nullptr), _indexTableSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
DBCStorageBase::~DBCStorageBase()
|
||||
{
|
||||
delete[] _dataTable;
|
||||
for (char* strings : _stringPool)
|
||||
delete[] strings;
|
||||
}
|
||||
|
||||
bool DBCStorageBase::Load(char const* path, char**& indexTable)
|
||||
{
|
||||
indexTable = nullptr;
|
||||
|
||||
DBCFileLoader dbc;
|
||||
|
||||
// Check if load was sucessful, only then continue
|
||||
if (!dbc.Load(path, _fileFormat))
|
||||
return false;
|
||||
|
||||
_fieldCount = dbc.GetCols();
|
||||
|
||||
// load raw non-string data
|
||||
_dataTable = dbc.AutoProduceData(_fileFormat, _indexTableSize, indexTable);
|
||||
|
||||
// load strings from dbc data
|
||||
if (char* stringBlock = dbc.AutoProduceStrings(_fileFormat, _dataTable))
|
||||
_stringPool.push_back(stringBlock);
|
||||
|
||||
// error in dbc file at loading if nullptr
|
||||
return indexTable != nullptr;
|
||||
}
|
||||
|
||||
bool DBCStorageBase::LoadStringsFrom(char const* path, char** indexTable)
|
||||
{
|
||||
// DBC must be already loaded using Load
|
||||
if (!indexTable)
|
||||
return false;
|
||||
|
||||
DBCFileLoader dbc;
|
||||
|
||||
// Check if load was successful, only then continue
|
||||
if (!dbc.Load(path, _fileFormat))
|
||||
return false;
|
||||
|
||||
// load strings from another locale dbc data
|
||||
if (char* stringBlock = dbc.AutoProduceStrings(_fileFormat, _dataTable))
|
||||
_stringPool.push_back(stringBlock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DBCStorageBase::LoadFromDB(char const* table, char const* format, char**& indexTable)
|
||||
{
|
||||
_stringPool.push_back(DBCDatabaseLoader(table, format, _stringPool).Load(_indexTableSize, indexTable));
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DBCSTORE_H
|
||||
#define DBCSTORE_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "DBCStorageIterator.h"
|
||||
#include "Errors.h"
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
/// Interface class for common access
|
||||
class DBCStorageBase
|
||||
{
|
||||
public:
|
||||
DBCStorageBase(char const* fmt);
|
||||
virtual ~DBCStorageBase();
|
||||
|
||||
[[nodiscard]] char const* GetFormat() const { return _fileFormat; }
|
||||
[[nodiscard]] uint32 GetFieldCount() const { return _fieldCount; }
|
||||
|
||||
virtual bool Load(char const* path) = 0;
|
||||
virtual bool LoadStringsFrom(char const* path) = 0;
|
||||
virtual void LoadFromDB(char const* table, char const* format) = 0;
|
||||
|
||||
protected:
|
||||
bool Load(char const* path, char**& indexTable);
|
||||
bool LoadStringsFrom(char const* path, char** indexTable);
|
||||
void LoadFromDB(char const* table, char const* format, char**& indexTable);
|
||||
|
||||
uint32 _fieldCount;
|
||||
char const* _fileFormat;
|
||||
char* _dataTable;
|
||||
std::vector<char*> _stringPool;
|
||||
uint32 _indexTableSize;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class DBCStorage : public DBCStorageBase
|
||||
{
|
||||
public:
|
||||
typedef DBCStorageIterator<T> iterator;
|
||||
|
||||
explicit DBCStorage(char const* fmt) : DBCStorageBase(fmt)
|
||||
{
|
||||
_indexTable.AsT = nullptr;
|
||||
}
|
||||
|
||||
~DBCStorage() override
|
||||
{
|
||||
delete[] reinterpret_cast<char*>(_indexTable.AsT);
|
||||
}
|
||||
|
||||
[[nodiscard]] T const* LookupEntry(uint32 id) const { return (id >= _indexTableSize) ? nullptr : _indexTable.AsT[id]; }
|
||||
[[nodiscard]] T const* AssertEntry(uint32 id) const { return ASSERT_NOTNULL(LookupEntry(id)); }
|
||||
|
||||
void SetEntry(uint32 id, T* t)
|
||||
{
|
||||
if (id >= _indexTableSize)
|
||||
{
|
||||
// Resize
|
||||
typedef char* ptr;
|
||||
std::size_t newSize = id + 1;
|
||||
ptr* newArr = new ptr[newSize];
|
||||
memset(newArr, 0, newSize * sizeof(ptr));
|
||||
memcpy(newArr, _indexTable.AsChar, _indexTableSize * sizeof(ptr));
|
||||
delete[] reinterpret_cast<char*>(_indexTable.AsT);
|
||||
_indexTable.AsChar = newArr;
|
||||
_indexTableSize = newSize;
|
||||
}
|
||||
|
||||
delete _indexTable.AsT[id];
|
||||
_indexTable.AsT[id] = t;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32 GetNumRows() const { return _indexTableSize; }
|
||||
|
||||
bool Load(char const* path) override
|
||||
{
|
||||
return DBCStorageBase::Load(path, _indexTable.AsChar);
|
||||
}
|
||||
|
||||
bool LoadStringsFrom(char const* path) override
|
||||
{
|
||||
return DBCStorageBase::LoadStringsFrom(path, _indexTable.AsChar);
|
||||
}
|
||||
|
||||
void LoadFromDB(char const* table, char const* format) override
|
||||
{
|
||||
DBCStorageBase::LoadFromDB(table, format, _indexTable.AsChar);
|
||||
}
|
||||
|
||||
iterator begin() { return iterator(_indexTable.AsT, _indexTableSize); }
|
||||
iterator end() { return iterator(_indexTable.AsT, _indexTableSize, _indexTableSize); }
|
||||
|
||||
private:
|
||||
union
|
||||
{
|
||||
T** AsT;
|
||||
char** AsChar;
|
||||
}
|
||||
_indexTable;
|
||||
|
||||
DBCStorage(DBCStorage const& right) = delete;
|
||||
DBCStorage& operator=(DBCStorage const& right) = delete;
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_DBCSFRM_H
|
||||
#define ACORE_DBCSFRM_H
|
||||
|
||||
char constexpr Achievementfmt[] = "niixssssssssssssssssxxxxxxxxxxxxxxxxxxiixixxxxxxxxxxxxxxxxxxii";
|
||||
char constexpr AchievementCategoryfmt[] = "nixxxxxxxxxxxxxxxxxx";
|
||||
char constexpr AchievementCriteriafmt[] = "niiiiiiiixxxxxxxxxxxxxxxxxiiiix";
|
||||
char constexpr AreaTableEntryfmt[] = "niiiixxxxxissssssssssssssssxiiiiixxx";
|
||||
char constexpr AreaGroupEntryfmt[] = "niiiiiii";
|
||||
char constexpr AreaPOIEntryfmt[] = "niiiiiiiiiiifffixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxix";
|
||||
char constexpr AuctionHouseEntryfmt[] = "niiixxxxxxxxxxxxxxxxx";
|
||||
char constexpr BankBagSlotPricesEntryfmt[] = "ni";
|
||||
char constexpr BarberShopStyleEntryfmt[] = "nixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxiii";
|
||||
char constexpr BattlemasterListEntryfmt[] = "niiiiiiiiixssssssssssssssssxiixx";
|
||||
char constexpr CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr CharTitlesEntryfmt[] = "nxssssssssssssssssxssssssssssssssssxi";
|
||||
char constexpr ChatChannelsEntryfmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxx"; // ChatChannelsEntryfmt, index not used (more compact store)
|
||||
char constexpr ChrClassesEntryfmt[] = "nxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixii";
|
||||
char constexpr ChrRacesEntryfmt[] = "niixiixixxxxiissssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxi";
|
||||
char constexpr CinematicCameraEntryfmt[] = "nsiffff";
|
||||
char constexpr CinematicSequencesEntryfmt[] = "nxixxxxxxx";
|
||||
char constexpr CreatureDisplayInfofmt[] = "nixifxxxxxxxxxxx";
|
||||
char constexpr CreatureDisplayInfoExtrafmt[] = "dixxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr CreatureFamilyfmt[] = "nfifiiiiixssssssssssssssssxx";
|
||||
char constexpr CreatureModelDatafmt[] = "nixxfxxxxxxxxxfffxxxxxxxxxxx";
|
||||
char constexpr CreatureSpellDatafmt[] = "niiiixxxx";
|
||||
char constexpr CreatureTypefmt[] = "nxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr CurrencyTypesfmt[] = "xnxi";
|
||||
char constexpr DestructibleModelDatafmt[] = "nxxixxxixxxixxxixxx";
|
||||
char constexpr DungeonEncounterfmt[] = "niixissssssssssssssssxx";
|
||||
char constexpr DurabilityCostsfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiii";
|
||||
char constexpr DurabilityQualityfmt[] = "nf";
|
||||
char constexpr EmotesEntryfmt[] = "nxxiiix";
|
||||
char constexpr EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx";
|
||||
char constexpr FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr FactionTemplateEntryfmt[] = "niiiiiiiiiiiii";
|
||||
char constexpr GameObjectArtKitfmt[] = "nxxxxxxx";
|
||||
char constexpr GameObjectDisplayInfofmt[] = "nsxxxxxxxxxxffffffx";
|
||||
char constexpr GemPropertiesEntryfmt[] = "nixxi";
|
||||
char constexpr GlyphPropertiesfmt[] = "niix";
|
||||
char constexpr GlyphSlotfmt[] = "nii";
|
||||
char constexpr GtBarberShopCostBasefmt[] = "df";
|
||||
char constexpr GtCombatRatingsfmt[] = "df";
|
||||
char constexpr GtChanceToMeleeCritBasefmt[] = "df";
|
||||
char constexpr GtChanceToMeleeCritfmt[] = "df";
|
||||
char constexpr GtChanceToSpellCritBasefmt[] = "df";
|
||||
char constexpr GtChanceToSpellCritfmt[] = "df";
|
||||
char constexpr GtNPCManaCostScalerfmt[] = "df";
|
||||
char constexpr GtOCTClassCombatRatingScalarfmt[] = "df";
|
||||
char constexpr GtOCTRegenHPfmt[] = "df";
|
||||
//char constexpr GtOCTRegenMPfmt[] = "f";
|
||||
char constexpr GtRegenHPPerSptfmt[] = "df";
|
||||
char constexpr GtRegenMPPerSptfmt[] = "df";
|
||||
char constexpr Holidaysfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiixxsiix";
|
||||
char constexpr Itemfmt[] = "niiiiiii";
|
||||
char constexpr ItemBagFamilyfmt[] = "nxxxxxxxxxxxxxxxxx";
|
||||
char constexpr ItemDisplayTemplateEntryfmt[] = "nxxxxsxxxxxxxxxxxxxxxxxxx";
|
||||
//char constexpr ItemCondExtCostsEntryfmt[] = "xiii";
|
||||
char constexpr ItemExtendedCostEntryfmt[] = "niiiiiiiiiiiiiix";
|
||||
char constexpr ItemLimitCategoryEntryfmt[] = "nxxxxxxxxxxxxxxxxxii";
|
||||
char constexpr ItemRandomPropertiesfmt[] = "nxiiiiissssssssssssssssx";
|
||||
char constexpr ItemRandomSuffixfmt[] = "nssssssssssssssssxxiiiiiiiiii";
|
||||
char constexpr ItemSetEntryfmt[] = "dssssssssssssssssxiiiiiiiiiixxxxxxxiiiiiiiiiiiiiiiiii";
|
||||
char constexpr LFGDungeonEntryfmt[] = "nssssssssssssssssxiiiiiiiiixxixixxxxxxxxxxxxxxxxx";
|
||||
char constexpr LightEntryfmt[] = "nifffxxxxxxxxxx";
|
||||
char constexpr LiquidTypefmt[] = "nxxixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr LockEntryfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiixxxxxxxx";
|
||||
char constexpr MailTemplateEntryfmt[] = "nxxxxxxxxxxxxxxxxxssssssssssssssssx";
|
||||
char constexpr MapEntryfmt[] = "nxiixssssssssssssssssxixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixiffxixi";
|
||||
char constexpr MapDifficultyEntryfmt[] = "diisxxxxxxxxxxxxxxxxiix";
|
||||
char constexpr MovieEntryfmt[] = "nxx";
|
||||
char constexpr NamesReservedfmt[] = "xsx";
|
||||
char constexpr NamesProfanityfmt[] = "xsx";
|
||||
char constexpr OverrideSpellDatafmt[] = "niiiiiiiiiix";
|
||||
char constexpr PowerDisplayfmt[] = "nixxxx";
|
||||
char constexpr QuestSortEntryfmt[] = "nxxxxxxxxxxxxxxxxx";
|
||||
char constexpr QuestXPfmt[] = "niiiiiiiiii";
|
||||
char constexpr QuestFactionRewardfmt[] = "niiiiiiiiii";
|
||||
char constexpr PvPDifficultyfmt[] = "diiiii";
|
||||
char constexpr RandomPropertiesPointsfmt[] = "niiiiiiiiiiiiiii";
|
||||
char constexpr ScalingStatDistributionfmt[] = "niiiiiiiiiiiiiiiiiiiii";
|
||||
char constexpr ScalingStatValuesfmt[] = "iniiiiiiiiiiiiiiiiiiiiii";
|
||||
char constexpr SkillLinefmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxxixxxxxxxxxxxxxxxxxi";
|
||||
char constexpr SkillLineAbilityfmt[] = "niiiixxiiiiixx";
|
||||
char constexpr SkillRaceClassInfofmt[] = "diiiixix";
|
||||
char constexpr SkillTiersfmt[] = "nxxxxxxxxxxxxxxxxiiiiiiiiiiiiiiii";
|
||||
char constexpr SoundEntriesfmt[] = "nxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr SpellCastTimefmt[] = "nixx";
|
||||
char constexpr SpellCategoryfmt[] = "ni";
|
||||
char constexpr SpellDifficultyfmt[] = "niiii";
|
||||
char constexpr SpellDurationfmt[] = "niii";
|
||||
char constexpr SpellEntryfmt[] = "niiiiiiiiiiiixixiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifxiiiiiiiiiiiiiiiiiiiiiiiiiiiifffiiiiiiiiiiiiiiiiiiiiifffiiiiiiiiiiiiiiifffiiiiiiiiiiiiiissssssssssssssssxssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxiiiiiiiiiiixfffxxxiiiiixxfffxx";
|
||||
char constexpr SpellFocusObjectfmt[] = "nxxxxxxxxxxxxxxxxx";
|
||||
char constexpr SpellItemEnchantmentfmt[] = "niiiiiiixxxiiissssssssssssssssxiiiiiii";
|
||||
char constexpr SpellItemEnchantmentConditionfmt[] = "nbbbbbxxxxxbbbbbbbbbbiiiiiXXXXX";
|
||||
char constexpr SpellRadiusfmt[] = "nfff";
|
||||
char constexpr SpellRangefmt[] = "nffffixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr SpellRuneCostfmt[] = "niiii";
|
||||
char constexpr SpellShapeshiftFormEntryfmt[] = "nxxxxxxxxxxxxxxxxxxiixiiixxiiiiiiii";
|
||||
char constexpr SpellVisualfmt[] = "dxxxxxxiixxxxxxxxxxxxxxxxxxxxxxx";
|
||||
char constexpr StableSlotPricesfmt[] = "ni";
|
||||
char constexpr SummonPropertiesfmt[] = "niiiii";
|
||||
char constexpr TalentEntryfmt[] = "niiiiiiiixxxxixxixxixxx";
|
||||
char constexpr TalentTabEntryfmt[] = "nxxxxxxxxxxxxxxxxxxxiiix";
|
||||
char constexpr TaxiNodesEntryfmt[] = "nifffssssssssssssssssxii";
|
||||
char constexpr TaxiPathEntryfmt[] = "niii";
|
||||
char constexpr TaxiPathNodeEntryfmt[] = "diiifffiiii";
|
||||
char constexpr TeamContributionPointsfmt[] = "df";
|
||||
char constexpr TotemCategoryEntryfmt[] = "nxxxxxxxxxxxxxxxxxii";
|
||||
char constexpr TransportAnimationfmt[] = "diifffx";
|
||||
char constexpr TransportRotationfmt[] = "diiffff";
|
||||
char constexpr VehicleEntryfmt[] = "niffffiiiiiiiifffffffffffffffssssfifiixx";
|
||||
char constexpr VehicleSeatEntryfmt[] = "niiffffffffffiiiiiifffffffiiifffiiiiiiiffiiiiixxxxxxxxxxxx";
|
||||
char constexpr WMOAreaTableEntryfmt[] = "niiixxxxxiixxxxxxxxxxxxxxxxx";
|
||||
char constexpr WorldMapAreaEntryfmt[] = "xinxffffixx";
|
||||
char constexpr WorldMapOverlayEntryfmt[] = "nxiiiixxxxxxxxxxx";
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ASYNCACCEPT_H_
|
||||
#define __ASYNCACCEPT_H_
|
||||
|
||||
#include "IpAddress.h"
|
||||
#include "Log.h"
|
||||
#include "Socket.h"
|
||||
#include "Systemd.h"
|
||||
#include <atomic>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <functional>
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
constexpr auto ACORE_MAX_LISTEN_CONNECTIONS = boost::asio::socket_base::max_listen_connections;
|
||||
|
||||
class AsyncAcceptor
|
||||
{
|
||||
public:
|
||||
typedef void(*AcceptCallback)(IoContextTcpSocket&& newSocket, uint32 threadIndex);
|
||||
|
||||
AsyncAcceptor(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, bool supportSocketActivation = false) :
|
||||
_acceptor(ioContext), _endpoint(Acore::Net::make_address(bindIp), port),
|
||||
_socket(ioContext), _closed(false), _socketFactory([this](){ return DefaultSocketFactory(); }),
|
||||
_supportSocketActivation(supportSocketActivation)
|
||||
{
|
||||
int const listen_fd = get_listen_fd();
|
||||
if (_supportSocketActivation && listen_fd > 0)
|
||||
{
|
||||
LOG_DEBUG("network", "Using socket from systemd socket activation");
|
||||
boost::system::error_code errorCode;
|
||||
_acceptor.assign(boost::asio::ip::tcp::v4(), listen_fd, errorCode);
|
||||
if (errorCode)
|
||||
LOG_WARN("network", "Failed to assign socket {}", errorCode.message());
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void AsyncAccept();
|
||||
|
||||
template<AcceptCallback acceptCallback>
|
||||
void AsyncAcceptWithCallback()
|
||||
{
|
||||
IoContextTcpSocket* socket;
|
||||
uint32 threadIndex;
|
||||
std::tie(socket, threadIndex) = _socketFactory();
|
||||
_acceptor.async_accept(*socket, [this, socket, threadIndex](boost::system::error_code error)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket->non_blocking(true);
|
||||
|
||||
acceptCallback(std::move(*socket), threadIndex);
|
||||
}
|
||||
catch (boost::system::system_error const& err)
|
||||
{
|
||||
LOG_INFO("network", "Failed to initialize client's socket {}", err.what());
|
||||
}
|
||||
}
|
||||
|
||||
if (!_closed)
|
||||
this->AsyncAcceptWithCallback<acceptCallback>();
|
||||
});
|
||||
}
|
||||
|
||||
bool Bind()
|
||||
{
|
||||
boost::system::error_code errorCode;
|
||||
// with socket activation the acceptor is already open and bound
|
||||
if (!_acceptor.is_open())
|
||||
{
|
||||
_acceptor.open(_endpoint.protocol(), errorCode);
|
||||
if (errorCode)
|
||||
{
|
||||
LOG_INFO("network", "Failed to open acceptor {}", errorCode.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
|
||||
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode);
|
||||
if (errorCode)
|
||||
{
|
||||
LOG_INFO("network", "Failed to set reuse_address option on acceptor {}", errorCode.message());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
_acceptor.bind(_endpoint, errorCode);
|
||||
if (errorCode)
|
||||
{
|
||||
LOG_INFO("network", "Could not bind to {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_acceptor.listen(ACORE_MAX_LISTEN_CONNECTIONS, errorCode);
|
||||
if (errorCode)
|
||||
{
|
||||
LOG_INFO("network", "Failed to start listening on {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
if (_closed.exchange(true))
|
||||
return;
|
||||
|
||||
boost::system::error_code err;
|
||||
_acceptor.close(err);
|
||||
}
|
||||
|
||||
void SetSocketFactory(std::function<std::pair<IoContextTcpSocket*, uint32>()> func) { _socketFactory = std::move(func); }
|
||||
|
||||
private:
|
||||
std::pair<IoContextTcpSocket*, uint32> DefaultSocketFactory() { return std::make_pair(&_socket, 0); }
|
||||
|
||||
boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, IoContextTcpSocket::executor_type> _acceptor;
|
||||
boost::asio::ip::tcp::endpoint _endpoint;
|
||||
IoContextTcpSocket _socket;
|
||||
std::atomic<bool> _closed;
|
||||
std::function<std::pair<IoContextTcpSocket*, uint32>()> _socketFactory;
|
||||
bool _supportSocketActivation;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void AsyncAcceptor::AsyncAccept()
|
||||
{
|
||||
_acceptor.async_accept(_socket, [this](boost::system::error_code error)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this-> is required here to fix an segmentation fault in gcc 4.7.2 - reason is lambdas in a templated class
|
||||
std::make_shared<T>(std::move(this->_socket))->Start();
|
||||
}
|
||||
catch (boost::system::system_error const& err)
|
||||
{
|
||||
LOG_INFO("network", "Failed to retrieve client's remote address {}", err.what());
|
||||
}
|
||||
}
|
||||
|
||||
// lets slap some more this-> on this so we can fix this bug with gcc 4.7.2 throwing internals in yo face
|
||||
if (!_closed)
|
||||
this->AsyncAccept<T>();
|
||||
});
|
||||
}
|
||||
|
||||
#endif /* __ASYNC ACCEPT_H_ */
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NetworkThread_h__
|
||||
#define NetworkThread_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include "Errors.h"
|
||||
#include "IoContext.h"
|
||||
#include "Log.h"
|
||||
#include "Socket.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
template<class SocketType>
|
||||
class NetworkThread
|
||||
{
|
||||
public:
|
||||
NetworkThread() :
|
||||
_ioContext(1), _acceptSocket(_ioContext), _updateTimer(_ioContext), _proxyHeaderReadingEnabled(false) { }
|
||||
|
||||
virtual ~NetworkThread()
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (_thread)
|
||||
{
|
||||
Wait();
|
||||
}
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
_stopped = true;
|
||||
_ioContext.stop();
|
||||
}
|
||||
|
||||
bool Start()
|
||||
{
|
||||
if (_thread)
|
||||
return false;
|
||||
|
||||
_thread = std::make_unique<std::thread>([this]() { NetworkThread::Run(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void Wait()
|
||||
{
|
||||
ASSERT(_thread);
|
||||
|
||||
if (_thread->joinable())
|
||||
{
|
||||
_thread->join();
|
||||
}
|
||||
|
||||
_thread.reset();
|
||||
}
|
||||
|
||||
[[nodiscard]] int32 GetConnectionCount() const
|
||||
{
|
||||
return _connections;
|
||||
}
|
||||
|
||||
virtual void AddSocket(std::shared_ptr<SocketType> sock)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_newSocketsLock);
|
||||
|
||||
++_connections;
|
||||
_newSockets.emplace_back(sock);
|
||||
SocketAdded(sock);
|
||||
}
|
||||
|
||||
IoContextTcpSocket* GetSocketForAccept() { return &_acceptSocket; }
|
||||
|
||||
void EnableProxyProtocol() { _proxyHeaderReadingEnabled = true; }
|
||||
|
||||
protected:
|
||||
virtual void SocketAdded(std::shared_ptr<SocketType> const& /*sock*/) { }
|
||||
virtual void SocketRemoved(std::shared_ptr<SocketType> const& /*sock*/) { }
|
||||
|
||||
void AddNewSockets()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_newSocketsLock);
|
||||
|
||||
if (_newSockets.empty())
|
||||
return;
|
||||
|
||||
if (!_proxyHeaderReadingEnabled)
|
||||
{
|
||||
for (std::shared_ptr<SocketType> sock : _newSockets)
|
||||
{
|
||||
if (!sock->IsOpen())
|
||||
{
|
||||
SocketRemoved(sock);
|
||||
--_connections;
|
||||
continue;
|
||||
}
|
||||
|
||||
_sockets.emplace_back(sock);
|
||||
|
||||
sock->Start();
|
||||
}
|
||||
|
||||
_newSockets.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleNewSocketsProxyReadingOnConnect();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleNewSocketsProxyReadingOnConnect()
|
||||
{
|
||||
std::size_t index = 0;
|
||||
std::vector<int> newSocketsToRemoveIndexes;
|
||||
for (auto sock_iter = _newSockets.begin(); sock_iter != _newSockets.end(); ++sock_iter, ++index)
|
||||
{
|
||||
std::shared_ptr<SocketType> sock = *sock_iter;
|
||||
|
||||
if (!sock->IsOpen())
|
||||
{
|
||||
newSocketsToRemoveIndexes.emplace_back(index);
|
||||
SocketRemoved(sock);
|
||||
--_connections;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto proxyHeaderReadingState = sock->GetProxyHeaderReadingState();
|
||||
if (proxyHeaderReadingState == PROXY_HEADER_READING_STATE_STARTED)
|
||||
continue;
|
||||
|
||||
switch (proxyHeaderReadingState) {
|
||||
case PROXY_HEADER_READING_STATE_NOT_STARTED:
|
||||
sock->AsyncReadProxyHeader();
|
||||
break;
|
||||
|
||||
case PROXY_HEADER_READING_STATE_FINISHED:
|
||||
newSocketsToRemoveIndexes.emplace_back(index);
|
||||
_sockets.emplace_back(sock);
|
||||
|
||||
sock->Start();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
newSocketsToRemoveIndexes.emplace_back(index);
|
||||
SocketRemoved(sock);
|
||||
--_connections;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = newSocketsToRemoveIndexes.rbegin(); it != newSocketsToRemoveIndexes.rend(); ++it)
|
||||
_newSockets.erase(_newSockets.begin() + *it);
|
||||
}
|
||||
|
||||
void Run()
|
||||
{
|
||||
LOG_DEBUG("misc", "Network Thread Starting");
|
||||
|
||||
_updateTimer.expires_at(std::chrono::steady_clock::now() + std::chrono::milliseconds(1));
|
||||
_updateTimer.async_wait([this](boost::system::error_code const&) { Update(); });
|
||||
_ioContext.run();
|
||||
|
||||
LOG_DEBUG("misc", "Network Thread exits");
|
||||
_newSockets.clear();
|
||||
_sockets.clear();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_stopped)
|
||||
return;
|
||||
|
||||
_updateTimer.expires_at(std::chrono::steady_clock::now() + std::chrono::milliseconds(1));
|
||||
_updateTimer.async_wait([this](boost::system::error_code const&) { Update(); });
|
||||
|
||||
AddNewSockets();
|
||||
|
||||
_sockets.erase(std::remove_if(_sockets.begin(), _sockets.end(), [this](std::shared_ptr<SocketType> sock)
|
||||
{
|
||||
if (!sock->Update())
|
||||
{
|
||||
if (sock->IsOpen())
|
||||
sock->CloseSocket();
|
||||
|
||||
this->SocketRemoved(sock);
|
||||
|
||||
--this->_connections;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}), _sockets.end());
|
||||
}
|
||||
|
||||
private:
|
||||
using SocketContainer = std::vector<std::shared_ptr<SocketType>>;
|
||||
|
||||
std::atomic<int32> _connections{};
|
||||
std::atomic<bool> _stopped{};
|
||||
|
||||
std::unique_ptr<std::thread> _thread;
|
||||
|
||||
SocketContainer _sockets;
|
||||
|
||||
std::mutex _newSocketsLock;
|
||||
SocketContainer _newSockets;
|
||||
|
||||
Acore::Asio::IoContext _ioContext;
|
||||
IoContextTcpSocket _acceptSocket;
|
||||
boost::asio::steady_timer _updateTimer;
|
||||
|
||||
bool _proxyHeaderReadingEnabled;
|
||||
};
|
||||
|
||||
#endif // NetworkThread_h__
|
||||
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SOCKET_H__
|
||||
#define __SOCKET_H__
|
||||
|
||||
#include "Log.h"
|
||||
#include "MessageBuffer.h"
|
||||
#include <atomic>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <type_traits>
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
#define READ_BLOCK_SIZE 4096
|
||||
#ifdef BOOST_ASIO_HAS_IOCP
|
||||
#define AC_SOCKET_USE_IOCP
|
||||
#endif
|
||||
|
||||
// Specialize boost socket for io_context executor instead of type-erased any_io_executor
|
||||
// This avoids the type-erasure overhead of any_io_executor
|
||||
using IoContextTcpSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::io_context::executor_type>;
|
||||
|
||||
enum class SocketReadCallbackResult
|
||||
{
|
||||
KeepReading,
|
||||
Stop
|
||||
};
|
||||
|
||||
enum class SocketState : uint8
|
||||
{
|
||||
Open = 0,
|
||||
Closing = 1,
|
||||
Closed = 2
|
||||
};
|
||||
|
||||
enum ProxyHeaderReadingState {
|
||||
PROXY_HEADER_READING_STATE_NOT_STARTED,
|
||||
PROXY_HEADER_READING_STATE_STARTED,
|
||||
PROXY_HEADER_READING_STATE_FINISHED,
|
||||
PROXY_HEADER_READING_STATE_FAILED,
|
||||
};
|
||||
|
||||
enum ProxyHeaderAddressFamilyAndProtocol {
|
||||
PROXY_HEADER_ADDRESS_FAMILY_AND_PROTOCOL_TCP_V4 = 0x11,
|
||||
PROXY_HEADER_ADDRESS_FAMILY_AND_PROTOCOL_TCP_V6 = 0x21,
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class Socket : public std::enable_shared_from_this<T>
|
||||
{
|
||||
public:
|
||||
explicit Socket(IoContextTcpSocket&& socket) : _socket(std::move(socket)), _remoteAddress(_socket.remote_endpoint().address()),
|
||||
_remotePort(_socket.remote_endpoint().port()), _readBuffer(), _state(SocketState::Open), _isWritingAsync(false),
|
||||
_proxyHeaderReadingState(PROXY_HEADER_READING_STATE_NOT_STARTED)
|
||||
{
|
||||
_readBuffer.Resize(READ_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
virtual ~Socket()
|
||||
{
|
||||
_state = SocketState::Closed;
|
||||
boost::system::error_code error;
|
||||
_socket.close(error);
|
||||
}
|
||||
|
||||
virtual void Start() = 0;
|
||||
|
||||
virtual bool Update()
|
||||
{
|
||||
SocketState state = _state.load();
|
||||
if (state == SocketState::Closed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef AC_SOCKET_USE_IOCP
|
||||
if (_isWritingAsync || (_writeQueue.empty() && state != SocketState::Closing))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (; HandleQueue();)
|
||||
;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] boost::asio::ip::address GetRemoteIpAddress() const
|
||||
{
|
||||
return _remoteAddress;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint16 GetRemotePort() const
|
||||
{
|
||||
return _remotePort;
|
||||
}
|
||||
|
||||
void AsyncRead()
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_readBuffer.Normalize();
|
||||
_readBuffer.EnsureFreeSpace();
|
||||
_socket.async_read_some(boost::asio::buffer(_readBuffer.GetWritePointer(), _readBuffer.GetRemainingSpace()),
|
||||
std::bind(&Socket<T>::ReadHandlerInternal, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void AsyncReadProxyHeader()
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_proxyHeaderReadingState = PROXY_HEADER_READING_STATE_STARTED;
|
||||
|
||||
_readBuffer.Normalize();
|
||||
_readBuffer.EnsureFreeSpace();
|
||||
_socket.async_read_some(boost::asio::buffer(_readBuffer.GetWritePointer(), _readBuffer.GetRemainingSpace()),
|
||||
std::bind(&Socket<T>::ProxyReadHeaderHandler, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void AsyncReadWithCallback(void (T::*callback)(boost::system::error_code, std::size_t))
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_readBuffer.Normalize();
|
||||
_readBuffer.EnsureFreeSpace();
|
||||
|
||||
_socket.async_read_some(boost::asio::buffer(_readBuffer.GetWritePointer(), _readBuffer.GetRemainingSpace()),
|
||||
std::bind(callback, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
void QueuePacket(MessageBuffer&& buffer)
|
||||
{
|
||||
_writeQueue.push(std::move(buffer));
|
||||
|
||||
#ifdef AC_SOCKET_USE_IOCP
|
||||
AsyncProcessQueue();
|
||||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] ProxyHeaderReadingState GetProxyHeaderReadingState() const { return _proxyHeaderReadingState; }
|
||||
|
||||
[[nodiscard]] bool IsOpen() const { return _state.load() == SocketState::Open; }
|
||||
|
||||
void CloseSocket()
|
||||
{
|
||||
SocketState expected = SocketState::Open;
|
||||
if (!_state.compare_exchange_strong(expected, SocketState::Closed))
|
||||
{
|
||||
// If it was Closing, try to transition to Closed
|
||||
expected = SocketState::Closing;
|
||||
if (!_state.compare_exchange_strong(expected, SocketState::Closed))
|
||||
return; // Already closed
|
||||
}
|
||||
|
||||
boost::system::error_code shutdownError;
|
||||
_socket.shutdown(boost::asio::socket_base::shutdown_send, shutdownError);
|
||||
|
||||
if (shutdownError)
|
||||
LOG_DEBUG("network", "Socket::CloseSocket: {} errored when shutting down socket: {} ({})", GetRemoteIpAddress().to_string(),
|
||||
shutdownError.value(), shutdownError.message());
|
||||
|
||||
OnClose();
|
||||
}
|
||||
|
||||
/// Marks the socket for closing after write buffer becomes empty
|
||||
void DelayedCloseSocket()
|
||||
{
|
||||
SocketState expected = SocketState::Open;
|
||||
_state.compare_exchange_strong(expected, SocketState::Closing);
|
||||
}
|
||||
|
||||
MessageBuffer& GetReadBuffer() { return _readBuffer; }
|
||||
|
||||
protected:
|
||||
virtual void OnClose() { }
|
||||
virtual SocketReadCallbackResult ReadHandler() = 0;
|
||||
|
||||
bool AsyncProcessQueue()
|
||||
{
|
||||
if (_isWritingAsync)
|
||||
return false;
|
||||
|
||||
_isWritingAsync = true;
|
||||
|
||||
#ifdef AC_SOCKET_USE_IOCP
|
||||
MessageBuffer& buffer = _writeQueue.front();
|
||||
_socket.async_write_some(boost::asio::buffer(buffer.GetReadPointer(), buffer.GetActiveSize()), std::bind(&Socket<T>::WriteHandler,
|
||||
this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
|
||||
#else
|
||||
_socket.async_wait(boost::asio::socket_base::wait_write, [self = this->shared_from_this()](boost::system::error_code error)
|
||||
{
|
||||
self->WriteHandlerWrapper(error, 0);
|
||||
});
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetNoDelay(bool enable)
|
||||
{
|
||||
boost::system::error_code err;
|
||||
_socket.set_option(tcp::no_delay(enable), err);
|
||||
|
||||
if (err)
|
||||
LOG_DEBUG("network", "Socket::SetNoDelay: failed to set_option(boost::asio::ip::tcp::no_delay) for {} - {} ({})",
|
||||
GetRemoteIpAddress().to_string(), err.value(), err.message());
|
||||
}
|
||||
|
||||
private:
|
||||
void ReadHandlerInternal(boost::system::error_code error, std::size_t transferredBytes)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
_readBuffer.WriteCompleted(transferredBytes);
|
||||
if (ReadHandler() == SocketReadCallbackResult::KeepReading)
|
||||
AsyncRead();
|
||||
}
|
||||
|
||||
// ProxyReadHeaderHandler reads Proxy Protocol v2 header (v1 is not supported).
|
||||
// See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt (2.2. Binary header format (version 2)) for more details.
|
||||
void ProxyReadHeaderHandler(boost::system::error_code error, std::size_t transferredBytes)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
_readBuffer.WriteCompleted(transferredBytes);
|
||||
|
||||
MessageBuffer& packet = GetReadBuffer();
|
||||
|
||||
const int minimumProxyProtocolV2Size = 28;
|
||||
if (packet.GetActiveSize() < minimumProxyProtocolV2Size)
|
||||
{
|
||||
AsyncReadProxyHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8* readPointer = packet.GetReadPointer();
|
||||
|
||||
const uint8 signatureSize = 12;
|
||||
const uint8 expectedSignature[signatureSize] = {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A};
|
||||
if (memcmp(packet.GetReadPointer(), expectedSignature, signatureSize) != 0)
|
||||
{
|
||||
_proxyHeaderReadingState = PROXY_HEADER_READING_STATE_FAILED;
|
||||
LOG_ERROR("network", "Socket::ProxyReadHeaderHandler: received bad PROXY Protocol v2 signature for {}", GetRemoteIpAddress().to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8 version = (readPointer[signatureSize] & 0xF0) >> 4;
|
||||
const uint8 command = (readPointer[signatureSize] & 0xF);
|
||||
|
||||
if (version != 2)
|
||||
{
|
||||
_proxyHeaderReadingState = PROXY_HEADER_READING_STATE_FAILED;
|
||||
LOG_ERROR("network", "Socket::ProxyReadHeaderHandler: received bad PROXY Protocol v2 signature for {}", GetRemoteIpAddress().to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8 addressFamily = readPointer[13];
|
||||
const uint16 len = (readPointer[14] << 8) | readPointer[15];
|
||||
if (static_cast<size_t>(len+16) > packet.GetActiveSize())
|
||||
{
|
||||
AsyncReadProxyHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection created by a proxy itself (health checks?), ignore and do nothing.
|
||||
if (command == 0)
|
||||
{
|
||||
packet.ReadCompleted(len+16);
|
||||
_proxyHeaderReadingState = PROXY_HEADER_READING_STATE_FINISHED;
|
||||
return;
|
||||
}
|
||||
|
||||
auto remainingLen = packet.GetActiveSize() - 16;
|
||||
readPointer += 16; // Skip strait to address.
|
||||
|
||||
switch (addressFamily) {
|
||||
case PROXY_HEADER_ADDRESS_FAMILY_AND_PROTOCOL_TCP_V4:
|
||||
{
|
||||
if (remainingLen < 12)
|
||||
{
|
||||
AsyncReadProxyHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::ip::address_v4::bytes_type b;
|
||||
auto addressSize = sizeof(b);
|
||||
|
||||
std::copy(readPointer, readPointer+addressSize, b.begin());
|
||||
_remoteAddress = boost::asio::ip::address_v4(b);
|
||||
|
||||
readPointer += 2 * addressSize; // Skip server address.
|
||||
_remotePort = (readPointer[0] << 8) | readPointer[1];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PROXY_HEADER_ADDRESS_FAMILY_AND_PROTOCOL_TCP_V6:
|
||||
{
|
||||
if (remainingLen < 36)
|
||||
{
|
||||
AsyncReadProxyHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::ip::address_v6::bytes_type b;
|
||||
auto addressSize = sizeof(b);
|
||||
|
||||
std::copy(readPointer, readPointer+addressSize, b.begin());
|
||||
_remoteAddress = boost::asio::ip::address_v6(b);
|
||||
|
||||
readPointer += 2 * addressSize; // Skip server address.
|
||||
_remotePort = (readPointer[0] << 8) | readPointer[1];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
_proxyHeaderReadingState = PROXY_HEADER_READING_STATE_FAILED;
|
||||
LOG_ERROR("network", "Socket::ProxyReadHeaderHandler: unsupported address family type {}", GetRemoteIpAddress().to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
packet.ReadCompleted(len+16);
|
||||
_proxyHeaderReadingState = PROXY_HEADER_READING_STATE_FINISHED;
|
||||
}
|
||||
|
||||
#ifdef AC_SOCKET_USE_IOCP
|
||||
void WriteHandler(boost::system::error_code error, std::size_t transferedBytes)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
_isWritingAsync = false;
|
||||
_writeQueue.front().ReadCompleted(transferedBytes);
|
||||
|
||||
if (!_writeQueue.front().GetActiveSize())
|
||||
_writeQueue.pop();
|
||||
|
||||
if (!_writeQueue.empty())
|
||||
AsyncProcessQueue();
|
||||
else if (_state.load() == SocketState::Closing)
|
||||
CloseSocket();
|
||||
}
|
||||
else
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void WriteHandlerWrapper(boost::system::error_code /*error*/, std::size_t /*transferedBytes*/)
|
||||
{
|
||||
_isWritingAsync = false;
|
||||
HandleQueue();
|
||||
}
|
||||
|
||||
bool HandleQueue()
|
||||
{
|
||||
if (_writeQueue.empty())
|
||||
return false;
|
||||
|
||||
MessageBuffer& queuedMessage = _writeQueue.front();
|
||||
|
||||
std::size_t bytesToSend = queuedMessage.GetActiveSize();
|
||||
|
||||
boost::system::error_code error;
|
||||
std::size_t bytesSent = _socket.write_some(boost::asio::buffer(queuedMessage.GetReadPointer(), bytesToSend), error);
|
||||
|
||||
if (error)
|
||||
{
|
||||
if (error == boost::asio::error::would_block || error == boost::asio::error::try_again)
|
||||
{
|
||||
return AsyncProcessQueue();
|
||||
}
|
||||
|
||||
_writeQueue.pop();
|
||||
|
||||
if (_state.load() == SocketState::Closing && _writeQueue.empty())
|
||||
{
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (bytesSent == 0)
|
||||
{
|
||||
_writeQueue.pop();
|
||||
|
||||
if (_state.load() == SocketState::Closing && _writeQueue.empty())
|
||||
{
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (bytesSent < bytesToSend) // now n > 0
|
||||
{
|
||||
queuedMessage.ReadCompleted(bytesSent);
|
||||
return AsyncProcessQueue();
|
||||
}
|
||||
|
||||
_writeQueue.pop();
|
||||
|
||||
if (_state.load() == SocketState::Closing && _writeQueue.empty())
|
||||
{
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
return !_writeQueue.empty();
|
||||
}
|
||||
#endif
|
||||
|
||||
IoContextTcpSocket _socket;
|
||||
|
||||
boost::asio::ip::address _remoteAddress;
|
||||
uint16 _remotePort;
|
||||
|
||||
MessageBuffer _readBuffer;
|
||||
std::queue<MessageBuffer> _writeQueue;
|
||||
|
||||
std::atomic<SocketState> _state;
|
||||
|
||||
bool _isWritingAsync;
|
||||
|
||||
ProxyHeaderReadingState _proxyHeaderReadingState;
|
||||
};
|
||||
|
||||
#endif // __SOCKET_H__
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SocketMgr_h__
|
||||
#define SocketMgr_h__
|
||||
|
||||
#include "AsyncAcceptor.h"
|
||||
#include "Config.h"
|
||||
#include "Errors.h"
|
||||
#include "NetworkThread.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <memory>
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
template<class SocketType>
|
||||
class SocketMgr
|
||||
{
|
||||
public:
|
||||
virtual ~SocketMgr()
|
||||
{
|
||||
ASSERT(!_threads && !_acceptor && !_threadCount, "StopNetwork must be called prior to SocketMgr destruction");
|
||||
}
|
||||
|
||||
virtual bool StartNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount)
|
||||
{
|
||||
ASSERT(threadCount > 0);
|
||||
|
||||
std::unique_ptr<AsyncAcceptor> acceptor;
|
||||
try
|
||||
{
|
||||
bool supportSocketActivation = sConfigMgr->GetOption<bool>("Network.UseSocketActivation", false);
|
||||
acceptor = std::make_unique<AsyncAcceptor>(ioContext, bindIp, port, supportSocketActivation);
|
||||
}
|
||||
catch (boost::system::system_error const& err)
|
||||
{
|
||||
LOG_ERROR("network", "Exception caught in SocketMgr.StartNetwork ({}:{}): {}", bindIp, port, err.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!acceptor->Bind())
|
||||
{
|
||||
LOG_ERROR("network", "StartNetwork failed to bind socket acceptor");
|
||||
return false;
|
||||
}
|
||||
|
||||
_acceptor = std::move(acceptor);
|
||||
_threadCount = threadCount;
|
||||
_threads = std::unique_ptr<NetworkThread<SocketType>[]>(CreateThreads());
|
||||
|
||||
ASSERT(_threads);
|
||||
|
||||
for (int32 i = 0; i < _threadCount; ++i)
|
||||
_threads[i].Start();
|
||||
|
||||
_acceptor->SetSocketFactory([this]() { return GetSocketForAccept(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void StopNetwork()
|
||||
{
|
||||
_acceptor->Close();
|
||||
|
||||
for (int32 i = 0; i < _threadCount; ++i)
|
||||
_threads[i].Stop();
|
||||
|
||||
Wait();
|
||||
|
||||
_acceptor.reset();
|
||||
_threads.reset();
|
||||
_threadCount = 0;
|
||||
}
|
||||
|
||||
void Wait()
|
||||
{
|
||||
for (int32 i = 0; i < _threadCount; ++i)
|
||||
_threads[i].Wait();
|
||||
}
|
||||
|
||||
virtual void OnSocketOpen(IoContextTcpSocket&& sock, uint32 threadIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::shared_ptr<SocketType> newSocket = std::make_shared<SocketType>(std::move(sock));
|
||||
_threads[threadIndex].AddSocket(newSocket);
|
||||
}
|
||||
catch (boost::system::system_error const& err)
|
||||
{
|
||||
LOG_WARN("network", "Failed to retrieve client's remote address {}", err.what());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int32 GetNetworkThreadCount() const { return _threadCount; }
|
||||
|
||||
[[nodiscard]] uint32 SelectThreadWithMinConnections() const
|
||||
{
|
||||
uint32 min = 0;
|
||||
|
||||
for (int32 i = 1; i < _threadCount; ++i)
|
||||
if (_threads[i].GetConnectionCount() < _threads[min].GetConnectionCount())
|
||||
min = i;
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
std::pair<IoContextTcpSocket*, uint32> GetSocketForAccept()
|
||||
{
|
||||
uint32 threadIndex = SelectThreadWithMinConnections();
|
||||
return { _threads[threadIndex].GetSocketForAccept(), threadIndex };
|
||||
}
|
||||
|
||||
protected:
|
||||
SocketMgr() = default;
|
||||
|
||||
virtual NetworkThread<SocketType>* CreateThreads() const = 0;
|
||||
|
||||
std::unique_ptr<AsyncAcceptor> _acceptor;
|
||||
std::unique_ptr<NetworkThread<SocketType>[]> _threads;
|
||||
int32 _threadCount{};
|
||||
};
|
||||
|
||||
#endif // SocketMgr_h__
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ByteBuffer.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "MessageBuffer.h"
|
||||
#include "Timer.h"
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <utf8.h>
|
||||
|
||||
ByteBuffer::ByteBuffer(MessageBuffer&& buffer) :
|
||||
_rpos(0), _wpos(0), _storage(buffer.Move()) { }
|
||||
|
||||
ByteBufferPositionException::ByteBufferPositionException(bool add, std::size_t pos, std::size_t size, std::size_t valueSize)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "Attempted to " << (add ? "put" : "get") << " value with size: "
|
||||
<< valueSize << " in ByteBuffer (pos: " << pos << " size: " << size
|
||||
<< ")";
|
||||
|
||||
message().assign(ss.str());
|
||||
}
|
||||
|
||||
ByteBufferSourceException::ByteBufferSourceException(std::size_t pos, std::size_t size, std::size_t valueSize)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "Attempted to put a "
|
||||
<< (valueSize > 0 ? "NULL-pointer" : "zero-sized value")
|
||||
<< " in ByteBuffer (pos: " << pos << " size: " << size << ")";
|
||||
|
||||
message().assign(ss.str());
|
||||
}
|
||||
|
||||
ByteBufferInvalidValueException::ByteBufferInvalidValueException(char const* type, char const* value)
|
||||
{
|
||||
message().assign(Acore::StringFormat("Invalid {} value ({}) found in ByteBuffer", type, value));
|
||||
}
|
||||
|
||||
ByteBuffer& ByteBuffer::operator>>(float& value)
|
||||
{
|
||||
value = read<float>();
|
||||
|
||||
if (!std::isfinite(value))
|
||||
throw ByteBufferInvalidValueException("float", "infinity");
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& ByteBuffer::operator>>(double& value)
|
||||
{
|
||||
value = read<double>();
|
||||
|
||||
if (!std::isfinite(value))
|
||||
throw ByteBufferInvalidValueException("double", "infinity");
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string ByteBuffer::ReadCString(bool requireValidUtf8 /*= true*/)
|
||||
{
|
||||
std::string value;
|
||||
|
||||
while (rpos() < size()) // prevent crash the wrong string format in a packet
|
||||
{
|
||||
char c = read<char>();
|
||||
if (c == 0)
|
||||
break;
|
||||
value += c;
|
||||
}
|
||||
|
||||
if (requireValidUtf8 && !utf8::is_valid(value.begin(), value.end()))
|
||||
throw ByteBufferInvalidValueException("string", value.c_str());
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32 ByteBuffer::ReadPackedTime()
|
||||
{
|
||||
auto packedDate = read<uint32>();
|
||||
tm lt = tm();
|
||||
|
||||
lt.tm_min = packedDate & 0x3F;
|
||||
lt.tm_hour = (packedDate >> 6) & 0x1F;
|
||||
//lt.tm_wday = (packedDate >> 11) & 7;
|
||||
lt.tm_mday = ((packedDate >> 14) & 0x3F) + 1;
|
||||
lt.tm_mon = (packedDate >> 20) & 0xF;
|
||||
lt.tm_year = ((packedDate >> 24) & 0x1F) + 100;
|
||||
|
||||
return uint32(mktime(<));
|
||||
}
|
||||
|
||||
void ByteBuffer::append(uint8 const* src, std::size_t cnt)
|
||||
{
|
||||
ASSERT(src, "Attempted to put a NULL-pointer in ByteBuffer (pos: {} size: {})", _wpos, size());
|
||||
ASSERT(cnt, "Attempted to put a zero-sized value in ByteBuffer (pos: {} size: {})", _wpos, size());
|
||||
ASSERT(size() < 10000000);
|
||||
|
||||
std::size_t const newSize = _wpos + cnt;
|
||||
|
||||
if (_storage.capacity() < newSize) // custom memory allocation rules
|
||||
{
|
||||
if (newSize < 100)
|
||||
_storage.reserve(300);
|
||||
else if (newSize < 750)
|
||||
_storage.reserve(2500);
|
||||
else if (newSize < 6000)
|
||||
_storage.reserve(10000);
|
||||
else
|
||||
_storage.reserve(400000);
|
||||
}
|
||||
|
||||
if (_storage.size() < newSize)
|
||||
_storage.resize(newSize);
|
||||
|
||||
std::memcpy(&_storage[_wpos], src, cnt);
|
||||
_wpos = newSize;
|
||||
}
|
||||
|
||||
void ByteBuffer::AppendPackedTime(time_t time)
|
||||
{
|
||||
tm lt = Acore::Time::TimeBreakdown(time);
|
||||
append<uint32>((lt.tm_year - 100) << 24 | lt.tm_mon << 20 | (lt.tm_mday - 1) << 14 | lt.tm_wday << 11 | lt.tm_hour << 6 | lt.tm_min);
|
||||
}
|
||||
|
||||
void ByteBuffer::put(std::size_t pos, uint8 const* src, std::size_t cnt)
|
||||
{
|
||||
ASSERT(pos + cnt <= size(), "Attempted to put value with size: {} in ByteBuffer (pos: {} size: {})", cnt, pos, size());
|
||||
ASSERT(src, "Attempted to put a NULL-pointer in ByteBuffer (pos: {} size: {})", pos, size());
|
||||
ASSERT(cnt, "Attempted to put a zero-sized value in ByteBuffer (pos: {} size: {})", pos, size());
|
||||
|
||||
std::memcpy(&_storage[pos], src, cnt);
|
||||
}
|
||||
|
||||
void ByteBuffer::print_storage() const
|
||||
{
|
||||
if (!sLog->ShouldLog("network.opcode.buffer", LogLevel::LOG_LEVEL_TRACE)) // optimize disabled trace output
|
||||
return;
|
||||
|
||||
std::ostringstream o;
|
||||
o << "STORAGE_SIZE: " << size();
|
||||
|
||||
for (uint32 i = 0; i < size(); ++i)
|
||||
o << read<uint8>(i) << " - ";
|
||||
|
||||
o << " ";
|
||||
|
||||
LOG_TRACE("network.opcode.buffer", "{}", o.str());
|
||||
}
|
||||
|
||||
void ByteBuffer::textlike() const
|
||||
{
|
||||
if (!sLog->ShouldLog("network.opcode.buffer", LogLevel::LOG_LEVEL_TRACE)) // optimize disabled trace output
|
||||
return;
|
||||
|
||||
std::ostringstream o;
|
||||
o << "STORAGE_SIZE: " << size();
|
||||
|
||||
for (uint32 i = 0; i < size(); ++i)
|
||||
{
|
||||
char buf[2];
|
||||
snprintf(buf, 2, "%c", read<uint8>(i));
|
||||
o << buf;
|
||||
}
|
||||
|
||||
o << " ";
|
||||
|
||||
LOG_TRACE("network.opcode.buffer", "{}", o.str());
|
||||
}
|
||||
|
||||
void ByteBuffer::hexlike() const
|
||||
{
|
||||
if (!sLog->ShouldLog("network.opcode.buffer", LogLevel::LOG_LEVEL_TRACE)) // optimize disabled trace output
|
||||
return;
|
||||
|
||||
uint32 j = 1, k = 1;
|
||||
|
||||
std::ostringstream o;
|
||||
o << "STORAGE_SIZE: " << size();
|
||||
|
||||
for (uint32 i = 0; i < size(); ++i)
|
||||
{
|
||||
char buf[4];
|
||||
snprintf(buf, 4, "%2X ", read<uint8>(i));
|
||||
|
||||
if ((i == (j * 8)) && ((i != (k * 16))))
|
||||
{
|
||||
o << "| ";
|
||||
++j;
|
||||
}
|
||||
else if (i == (k * 16))
|
||||
{
|
||||
o << "\n";
|
||||
++k;
|
||||
++j;
|
||||
}
|
||||
|
||||
o << buf;
|
||||
}
|
||||
|
||||
o << " ";
|
||||
|
||||
LOG_TRACE("network.opcode.buffer", "{}", o.str());
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _BYTEBUFFER_H
|
||||
#define _BYTEBUFFER_H
|
||||
|
||||
#include "ByteConverter.h"
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class MessageBuffer;
|
||||
|
||||
// Root of ByteBuffer exception hierarchy
|
||||
class AC_SHARED_API ByteBufferException : public std::exception
|
||||
{
|
||||
public:
|
||||
~ByteBufferException() noexcept override = default;
|
||||
|
||||
[[nodiscard]] char const* what() const noexcept override { return msg_.c_str(); }
|
||||
|
||||
protected:
|
||||
std::string & message() noexcept { return msg_; }
|
||||
|
||||
private:
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
class AC_SHARED_API ByteBufferPositionException : public ByteBufferException
|
||||
{
|
||||
public:
|
||||
ByteBufferPositionException(bool add, std::size_t pos, std::size_t size, std::size_t valueSize);
|
||||
|
||||
~ByteBufferPositionException() noexcept override = default;
|
||||
};
|
||||
|
||||
class AC_SHARED_API ByteBufferSourceException : public ByteBufferException
|
||||
{
|
||||
public:
|
||||
ByteBufferSourceException(std::size_t pos, std::size_t size, std::size_t valueSize);
|
||||
|
||||
~ByteBufferSourceException() noexcept override = default;
|
||||
};
|
||||
|
||||
class AC_SHARED_API ByteBufferInvalidValueException : public ByteBufferException
|
||||
{
|
||||
public:
|
||||
ByteBufferInvalidValueException(char const* type, char const* value);
|
||||
|
||||
~ByteBufferInvalidValueException() noexcept override = default;
|
||||
};
|
||||
|
||||
class AC_SHARED_API ByteBuffer
|
||||
{
|
||||
public:
|
||||
constexpr static std::size_t DEFAULT_SIZE = 0x1000;
|
||||
|
||||
// constructor
|
||||
ByteBuffer()
|
||||
{
|
||||
_storage.reserve(DEFAULT_SIZE);
|
||||
}
|
||||
|
||||
explicit ByteBuffer(std::size_t reserve) : _rpos(0), _wpos(0)
|
||||
{
|
||||
_storage.reserve(reserve);
|
||||
}
|
||||
|
||||
ByteBuffer(ByteBuffer&& buf) noexcept :
|
||||
_rpos(buf._rpos), _wpos(buf._wpos), _storage(std::move(buf._storage))
|
||||
{
|
||||
buf._rpos = 0;
|
||||
buf._wpos = 0;
|
||||
}
|
||||
|
||||
ByteBuffer(ByteBuffer const& right) = default;
|
||||
explicit ByteBuffer(MessageBuffer&& buffer);
|
||||
virtual ~ByteBuffer() = default;
|
||||
|
||||
ByteBuffer& operator=(ByteBuffer const& right)
|
||||
{
|
||||
if (this != &right)
|
||||
{
|
||||
_rpos = right._rpos;
|
||||
_wpos = right._wpos;
|
||||
_storage = right._storage;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator=(ByteBuffer&& right) noexcept
|
||||
{
|
||||
if (this != &right)
|
||||
{
|
||||
_rpos = right._rpos;
|
||||
right._rpos = 0;
|
||||
_wpos = right._wpos;
|
||||
right._wpos = 0;
|
||||
_storage = std::move(right._storage);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
_storage.clear();
|
||||
_rpos = _wpos = 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void append(T value)
|
||||
{
|
||||
static_assert(std::is_fundamental<T>::value, "append(compound)");
|
||||
EndianConvert(value);
|
||||
append((uint8*)&value, sizeof(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void put(std::size_t pos, T value)
|
||||
{
|
||||
static_assert(std::is_fundamental<T>::value, "append(compound)");
|
||||
EndianConvert(value);
|
||||
put(pos, (uint8*)&value, sizeof(value));
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(bool value)
|
||||
{
|
||||
append<uint8>(value ? 1 : 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(uint8 value)
|
||||
{
|
||||
append<uint8>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(uint16 value)
|
||||
{
|
||||
append<uint16>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(uint32 value)
|
||||
{
|
||||
append<uint32>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(uint64 value)
|
||||
{
|
||||
append<uint64>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// signed as in 2e complement
|
||||
ByteBuffer& operator<<(int8 value)
|
||||
{
|
||||
append<int8>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(int16 value)
|
||||
{
|
||||
append<int16>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(int32 value)
|
||||
{
|
||||
append<int32>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(int64 value)
|
||||
{
|
||||
append<int64>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// floating points
|
||||
ByteBuffer& operator<<(float value)
|
||||
{
|
||||
append<float>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(double value)
|
||||
{
|
||||
append<double>(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(std::string_view value)
|
||||
{
|
||||
if (std::size_t len = value.length())
|
||||
{
|
||||
append(reinterpret_cast<uint8 const*>(value.data()), len);
|
||||
}
|
||||
|
||||
append(static_cast<uint8>(0));
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(std::string const& str)
|
||||
{
|
||||
return operator<<(std::string_view(str));
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(char const* str)
|
||||
{
|
||||
return operator<<(std::string_view(str ? str : ""));
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(bool& value)
|
||||
{
|
||||
value = read<char>() > 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(uint8& value)
|
||||
{
|
||||
value = read<uint8>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(uint16& value)
|
||||
{
|
||||
value = read<uint16>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(uint32& value)
|
||||
{
|
||||
value = read<uint32>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(uint64& value)
|
||||
{
|
||||
value = read<uint64>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
//signed as in 2e complement
|
||||
ByteBuffer& operator>>(int8& value)
|
||||
{
|
||||
value = read<int8>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(int16& value)
|
||||
{
|
||||
value = read<int16>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(int32& value)
|
||||
{
|
||||
value = read<int32>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(int64& value)
|
||||
{
|
||||
value = read<int64>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(float& value);
|
||||
ByteBuffer& operator>>(double& value);
|
||||
|
||||
ByteBuffer& operator>>(std::string& value)
|
||||
{
|
||||
value = ReadCString(true);
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8& operator[](std::size_t const pos)
|
||||
{
|
||||
if (pos >= size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, pos, 1, size());
|
||||
}
|
||||
|
||||
return _storage[pos];
|
||||
}
|
||||
|
||||
uint8 const& operator[](std::size_t const pos) const
|
||||
{
|
||||
if (pos >= size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, pos, 1, size());
|
||||
}
|
||||
|
||||
return _storage[pos];
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t rpos() const { return _rpos; }
|
||||
|
||||
std::size_t rpos(std::size_t rpos_)
|
||||
{
|
||||
_rpos = rpos_;
|
||||
return _rpos;
|
||||
}
|
||||
|
||||
void rfinish()
|
||||
{
|
||||
_rpos = wpos();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t wpos() const { return _wpos; }
|
||||
|
||||
std::size_t wpos(std::size_t wpos_)
|
||||
{
|
||||
_wpos = wpos_;
|
||||
return _wpos;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void read_skip() { read_skip(sizeof(T)); }
|
||||
|
||||
void read_skip(std::size_t skip)
|
||||
{
|
||||
if (_rpos + skip > size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, _rpos, skip, size());
|
||||
}
|
||||
|
||||
_rpos += skip;
|
||||
}
|
||||
|
||||
template <typename T> T read()
|
||||
{
|
||||
T r = read<T>(_rpos);
|
||||
_rpos += sizeof(T);
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename T> [[nodiscard]] T read(std::size_t pos) const
|
||||
{
|
||||
if (pos + sizeof(T) > size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, pos, sizeof(T), size());
|
||||
}
|
||||
|
||||
T val = *((T const*)&_storage[pos]);
|
||||
EndianConvert(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void read(uint8* dest, std::size_t len)
|
||||
{
|
||||
if (_rpos + len > size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, _rpos, len, size());
|
||||
}
|
||||
|
||||
std::memcpy(dest, &_storage[_rpos], len);
|
||||
_rpos += len;
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
void read(std::array<uint8, Size>& arr)
|
||||
{
|
||||
read(arr.data(), Size);
|
||||
}
|
||||
|
||||
void readPackGUID(uint64& guid)
|
||||
{
|
||||
if (rpos() + 1 > size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, _rpos, 1, size());
|
||||
}
|
||||
|
||||
guid = 0;
|
||||
|
||||
uint8 guidmark = 0;
|
||||
(*this) >> guidmark;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if (guidmark & (uint8(1) << i))
|
||||
{
|
||||
if (rpos() + 1 > size())
|
||||
{
|
||||
throw ByteBufferPositionException(false, _rpos, 1, size());
|
||||
}
|
||||
|
||||
uint8 bit;
|
||||
(*this) >> bit;
|
||||
guid |= (uint64(bit) << (i * 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ReadCString(bool requireValidUtf8 = true);
|
||||
uint32 ReadPackedTime();
|
||||
|
||||
ByteBuffer& ReadPackedTime(uint32& time)
|
||||
{
|
||||
time = ReadPackedTime();
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8* contents()
|
||||
{
|
||||
if (_storage.empty())
|
||||
{
|
||||
throw ByteBufferException();
|
||||
}
|
||||
|
||||
return _storage.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] uint8 const* contents() const
|
||||
{
|
||||
if (_storage.empty())
|
||||
{
|
||||
throw ByteBufferException();
|
||||
}
|
||||
|
||||
return _storage.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t size() const { return _storage.size(); }
|
||||
[[nodiscard]] bool empty() const { return _storage.empty(); }
|
||||
|
||||
void resize(std::size_t newsize)
|
||||
{
|
||||
_storage.resize(newsize, 0);
|
||||
_rpos = 0;
|
||||
_wpos = size();
|
||||
}
|
||||
|
||||
void reserve(std::size_t ressize)
|
||||
{
|
||||
if (ressize > size())
|
||||
{
|
||||
_storage.reserve(ressize);
|
||||
}
|
||||
}
|
||||
|
||||
void shrink_to_fit()
|
||||
{
|
||||
_storage.shrink_to_fit();
|
||||
}
|
||||
|
||||
void append(char const* src, std::size_t cnt)
|
||||
{
|
||||
return append((uint8 const*)src, cnt);
|
||||
}
|
||||
|
||||
template<class T> void append(const T* src, std::size_t cnt)
|
||||
{
|
||||
return append((uint8 const*)src, cnt * sizeof(T));
|
||||
}
|
||||
|
||||
void append(uint8 const* src, std::size_t cnt);
|
||||
|
||||
void append(ByteBuffer const& buffer)
|
||||
{
|
||||
if (buffer.wpos())
|
||||
{
|
||||
append(buffer.contents(), buffer.wpos());
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
void append(std::array<uint8, Size> const& arr)
|
||||
{
|
||||
append(arr.data(), Size);
|
||||
}
|
||||
|
||||
// can be used in SMSG_MONSTER_MOVE opcode
|
||||
void appendPackXYZ(float x, float y, float z)
|
||||
{
|
||||
uint32 packed = 0;
|
||||
packed |= ((int)(x / 0.25f) & 0x7FF);
|
||||
packed |= ((int)(y / 0.25f) & 0x7FF) << 11;
|
||||
packed |= ((int)(z / 0.25f) & 0x3FF) << 22;
|
||||
*this << packed;
|
||||
}
|
||||
|
||||
void appendPackGUID(uint64 guid)
|
||||
{
|
||||
uint8 packGUID[8 + 1];
|
||||
packGUID[0] = 0;
|
||||
std::size_t size = 1;
|
||||
|
||||
for (uint8 i = 0; guid != 0;++i)
|
||||
{
|
||||
if (guid & 0xFF)
|
||||
{
|
||||
packGUID[0] |= uint8(1 << i);
|
||||
packGUID[size] = uint8(guid & 0xFF);
|
||||
++size;
|
||||
}
|
||||
|
||||
guid >>= 8;
|
||||
}
|
||||
|
||||
append(packGUID, size);
|
||||
}
|
||||
|
||||
void AppendPackedTime(time_t time);
|
||||
void put(std::size_t pos, uint8 const* src, std::size_t cnt);
|
||||
void print_storage() const;
|
||||
void textlike() const;
|
||||
void hexlike() const;
|
||||
|
||||
protected:
|
||||
std::size_t _rpos{0}, _wpos{0};
|
||||
std::vector<uint8> _storage;
|
||||
};
|
||||
|
||||
/// @todo Make a ByteBuffer.cpp and move all this inlining to it.
|
||||
template<>
|
||||
inline std::string ByteBuffer::read<std::string>()
|
||||
{
|
||||
std::string tmp;
|
||||
*this >> tmp;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ByteBuffer::read_skip<char*>()
|
||||
{
|
||||
std::string temp;
|
||||
*this >> temp;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ByteBuffer::read_skip<char const*>()
|
||||
{
|
||||
read_skip<char*>();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ByteBuffer::read_skip<std::string>()
|
||||
{
|
||||
read_skip<char*>();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Realm.h"
|
||||
#include "IpNetwork.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <memory> // NOTE: this import is NEEDED (even though some IDEs report it as unused)
|
||||
|
||||
boost::asio::ip::tcp_endpoint Realm::GetAddressForClient(boost::asio::ip::address const& clientAddr) const
|
||||
{
|
||||
boost::asio::ip::address realmIp;
|
||||
|
||||
// Attempt to send best address for a client
|
||||
if (clientAddr.is_loopback())
|
||||
{
|
||||
// Try guessing if realm is also connected locally
|
||||
if (LocalAddress->is_loopback() || ExternalAddress->is_loopback())
|
||||
{
|
||||
realmIp = clientAddr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume that user connecting from the machine that bnetserver is located on
|
||||
// has all realms available in his local network
|
||||
realmIp = *LocalAddress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (clientAddr.is_v4() && Acore::Net::IsInNetwork(LocalAddress->to_v4(), LocalSubnetMask->to_v4(), clientAddr.to_v4()))
|
||||
{
|
||||
realmIp = *LocalAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
realmIp = *ExternalAddress;
|
||||
}
|
||||
}
|
||||
|
||||
// Return external IP
|
||||
return { realmIp, Port };
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Realm_h__
|
||||
#define Realm_h__
|
||||
|
||||
#include "AsioHacksFwd.h"
|
||||
#include "Common.h"
|
||||
#include <memory> // NOTE: this import is NEEDED (even though some IDEs report it as unused)
|
||||
|
||||
enum RealmFlags
|
||||
{
|
||||
REALM_FLAG_NONE = 0x00,
|
||||
REALM_FLAG_VERSION_MISMATCH = 0x01,
|
||||
REALM_FLAG_OFFLINE = 0x02,
|
||||
REALM_FLAG_SPECIFYBUILD = 0x04,
|
||||
REALM_FLAG_UNK1 = 0x08,
|
||||
REALM_FLAG_UNK2 = 0x10,
|
||||
REALM_FLAG_RECOMMENDED = 0x20,
|
||||
REALM_FLAG_NEW = 0x40,
|
||||
REALM_FLAG_FULL = 0x80
|
||||
};
|
||||
|
||||
struct AC_SHARED_API RealmHandle
|
||||
{
|
||||
RealmHandle() = default;
|
||||
RealmHandle(uint32 index) : Realm(index) { }
|
||||
|
||||
uint32 Realm{0}; // primary key in `realmlist` table
|
||||
|
||||
bool operator<(RealmHandle const& r) const
|
||||
{
|
||||
return Realm < r.Realm;
|
||||
}
|
||||
};
|
||||
|
||||
/// Type of server, this is values from second column of Cfg_Configs.dbc
|
||||
enum RealmType
|
||||
{
|
||||
REALM_TYPE_NORMAL = 0,
|
||||
REALM_TYPE_PVP = 1,
|
||||
REALM_TYPE_NORMAL2 = 4,
|
||||
REALM_TYPE_RP = 6,
|
||||
REALM_TYPE_RPPVP = 8,
|
||||
|
||||
MAX_CLIENT_REALM_TYPE = 14,
|
||||
|
||||
REALM_TYPE_FFA_PVP = 16 // custom, free for all pvp mode like arena PvP in all zones except rest activated places and sanctuaries
|
||||
// replaced by REALM_PVP in realm list
|
||||
};
|
||||
|
||||
// Storage object for a realm
|
||||
struct AC_SHARED_API Realm
|
||||
{
|
||||
RealmHandle Id;
|
||||
uint32 Build;
|
||||
std::unique_ptr<boost::asio::ip::address> ExternalAddress;
|
||||
std::unique_ptr<boost::asio::ip::address> LocalAddress;
|
||||
std::unique_ptr<boost::asio::ip::address> LocalSubnetMask;
|
||||
uint16 Port;
|
||||
std::string Name;
|
||||
uint8 Type;
|
||||
RealmFlags Flags;
|
||||
uint8 Timezone;
|
||||
AccountTypes AllowedSecurityLevel;
|
||||
float PopulationLevel;
|
||||
|
||||
[[nodiscard]] boost::asio::ip::tcp_endpoint GetAddressForClient(boost::asio::ip::address const& clientAddr) const;
|
||||
};
|
||||
|
||||
#endif // Realm_h__
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RealmList.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "QueryResult.h"
|
||||
#include "Resolver.h"
|
||||
#include "SteadyTimer.h"
|
||||
#include "Util.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <memory>
|
||||
|
||||
RealmList::RealmList() : _updateInterval(0) { }
|
||||
|
||||
RealmList* RealmList::Instance()
|
||||
{
|
||||
static RealmList instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
// Load the realm list from the database
|
||||
void RealmList::Initialize(Acore::Asio::IoContext& ioContext, uint32 updateInterval)
|
||||
{
|
||||
_updateInterval = updateInterval;
|
||||
_updateTimer = std::make_unique<boost::asio::steady_timer>(ioContext);
|
||||
_resolver = std::make_unique<Acore::Asio::Resolver>(ioContext);
|
||||
|
||||
LoadBuildInfo();
|
||||
|
||||
// Get the content of the realmlist table in the database
|
||||
UpdateRealms(boost::system::error_code());
|
||||
}
|
||||
|
||||
void RealmList::Close()
|
||||
{
|
||||
_updateTimer->cancel();
|
||||
}
|
||||
|
||||
void RealmList::LoadBuildInfo()
|
||||
{
|
||||
// 0 1 2 3 4 5 6
|
||||
if (auto result = LoginDatabase.Query("SELECT majorVersion, minorVersion, bugfixVersion, hotfixVersion, build, winChecksumSeed, macChecksumSeed FROM build_info ORDER BY build ASC"))
|
||||
{
|
||||
for (auto const& fields : *result)
|
||||
{
|
||||
RealmBuildInfo& build = _builds.emplace_back();
|
||||
build.MajorVersion = fields[0].Get<uint32>();
|
||||
build.MinorVersion = fields[1].Get<uint32>();
|
||||
build.BugfixVersion = fields[2].Get<uint32>();
|
||||
std::string hotfixVersion = fields[3].Get<std::string>();
|
||||
|
||||
if (hotfixVersion.length() < build.HotfixVersion.size())
|
||||
{
|
||||
std::copy(hotfixVersion.begin(), hotfixVersion.end(), build.HotfixVersion.begin());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(hotfixVersion.begin(), hotfixVersion.end(), '\0');
|
||||
}
|
||||
|
||||
build.Build = fields[4].Get<uint32>();
|
||||
std::string windowsHash = fields[5].Get<std::string>();
|
||||
|
||||
if (windowsHash.length() == build.WindowsHash.size() * 2)
|
||||
{
|
||||
HexStrToByteArray(windowsHash, build.WindowsHash);
|
||||
}
|
||||
|
||||
std::string macHash = fields[6].Get<std::string>();
|
||||
|
||||
if (macHash.length() == build.MacHash.size() * 2)
|
||||
{
|
||||
HexStrToByteArray(macHash, build.MacHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RealmList::UpdateRealm(RealmHandle const& id, uint32 build, std::string const& name,
|
||||
boost::asio::ip::address&& address, boost::asio::ip::address&& localAddr, boost::asio::ip::address&& localSubmask,
|
||||
uint16 port, uint8 icon, RealmFlags flag, uint8 realmTimezone, AccountTypes allowedSecurityLevel, float population)
|
||||
{
|
||||
// Create new if not exist or update existed
|
||||
Realm& realm = _realms[id];
|
||||
|
||||
realm.Id = id;
|
||||
realm.Build = build;
|
||||
realm.Name = name;
|
||||
realm.Type = icon;
|
||||
realm.Flags = flag;
|
||||
realm.Timezone = realmTimezone;
|
||||
realm.AllowedSecurityLevel = allowedSecurityLevel;
|
||||
realm.PopulationLevel = population;
|
||||
|
||||
if (!realm.ExternalAddress || *realm.ExternalAddress != address)
|
||||
{
|
||||
realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(std::move(address));
|
||||
}
|
||||
|
||||
if (!realm.LocalAddress || *realm.LocalAddress != localAddr)
|
||||
{
|
||||
realm.LocalAddress = std::make_unique<boost::asio::ip::address>(std::move(localAddr));
|
||||
}
|
||||
|
||||
if (!realm.LocalSubnetMask || *realm.LocalSubnetMask != localSubmask)
|
||||
{
|
||||
realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(std::move(localSubmask));
|
||||
}
|
||||
|
||||
realm.Port = port;
|
||||
}
|
||||
|
||||
void RealmList::UpdateRealms(boost::system::error_code const& error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
// Skip update if have errors
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("server.authserver", "Updating Realm List...");
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALMLIST);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
|
||||
std::map<RealmHandle, std::string> existingRealms;
|
||||
for (auto const& [handle, realm] : _realms)
|
||||
{
|
||||
existingRealms[handle] = realm.Name;
|
||||
}
|
||||
|
||||
_realms.clear();
|
||||
|
||||
// Circle through results and add them to the realm map
|
||||
if (result)
|
||||
{
|
||||
for (auto const& fields : *result)
|
||||
{
|
||||
try
|
||||
{
|
||||
uint32 realmId = fields[0].Get<uint32>();
|
||||
std::string name = fields[1].Get<std::string>();
|
||||
std::string externalAddressString = fields[2].Get<std::string>();
|
||||
std::string localAddressString = fields[3].Get<std::string>();
|
||||
std::string localSubmaskString = fields[4].Get<std::string>();
|
||||
uint16 port = fields[5].Get<uint16>();
|
||||
|
||||
Optional<boost::asio::ip::tcp::endpoint> externalAddress = _resolver->Resolve(boost::asio::ip::tcp::v4(), externalAddressString, "");
|
||||
if (!externalAddress)
|
||||
{
|
||||
LOG_ERROR("server.authserver", "Could not resolve address {} for realm \"{}\" id {}", externalAddressString, name, realmId);
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<boost::asio::ip::tcp::endpoint> localAddress = _resolver->Resolve(boost::asio::ip::tcp::v4(), localAddressString, "");
|
||||
if (!localAddress)
|
||||
{
|
||||
LOG_ERROR("server.authserver", "Could not resolve localAddress {} for realm \"{}\" id {}", localAddressString, name, realmId);
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<boost::asio::ip::tcp::endpoint> localSubmask = _resolver->Resolve(boost::asio::ip::tcp::v4(), localSubmaskString, "");
|
||||
if (!localSubmask)
|
||||
{
|
||||
LOG_ERROR("server.authserver", "Could not resolve localSubnetMask {} for realm \"{}\" id {}", localSubmaskString, name, realmId);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8 icon = fields[6].Get<uint8>();
|
||||
|
||||
if (icon == REALM_TYPE_FFA_PVP)
|
||||
{
|
||||
icon = REALM_TYPE_PVP;
|
||||
}
|
||||
|
||||
if (icon >= MAX_CLIENT_REALM_TYPE)
|
||||
{
|
||||
icon = REALM_TYPE_NORMAL;
|
||||
}
|
||||
|
||||
auto flag = RealmFlags(fields[7].Get<uint8>());
|
||||
uint8 realmTimezone = fields[8].Get<uint8>();
|
||||
uint8 allowedSecurityLevel = fields[9].Get<uint8>();
|
||||
float pop = fields[10].Get<float>();
|
||||
uint32 build = fields[11].Get<uint32>();
|
||||
|
||||
RealmHandle id{ realmId };
|
||||
|
||||
UpdateRealm(id, build, name, externalAddress->address(), localAddress->address(), localSubmask->address(), port, icon, flag,
|
||||
realmTimezone, (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), pop);
|
||||
|
||||
if (!existingRealms.count(id))
|
||||
{
|
||||
LOG_INFO("server.authserver", "Added realm \"{}\" at {}:{}.", name, externalAddressString, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG("server.authserver", "Updating realm \"{}\" at {}:{}.", name, externalAddressString, port);
|
||||
}
|
||||
|
||||
existingRealms.erase(id);
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
LOG_ERROR("server.authserver", "Realmlist::UpdateRealms has thrown an exception: {}", ex.what());
|
||||
ABORT();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto itr = existingRealms.begin(); itr != existingRealms.end(); ++itr)
|
||||
LOG_INFO("server.authserver", "Removed realm \"{}\".", itr->second);
|
||||
|
||||
if (_updateInterval)
|
||||
{
|
||||
_updateTimer->expires_at(Acore::Asio::SteadyTimer::GetExpirationTime(_updateInterval));
|
||||
_updateTimer->async_wait([this](boost::system::error_code const& errorCode){ UpdateRealms(errorCode); });
|
||||
}
|
||||
}
|
||||
|
||||
Realm const* RealmList::GetRealm(RealmHandle const& id) const
|
||||
{
|
||||
auto itr = _realms.find(id);
|
||||
if (itr != _realms.end())
|
||||
{
|
||||
return &itr->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RealmBuildInfo const* RealmList::GetBuildInfo(uint32 build) const
|
||||
{
|
||||
for (RealmBuildInfo const& clientBuild : _builds)
|
||||
{
|
||||
if (clientBuild.Build == build)
|
||||
{
|
||||
return &clientBuild;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _REALMLIST_H
|
||||
#define _REALMLIST_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Realm.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory> // NOTE: this import is NEEDED (even though some IDEs report it as unused)
|
||||
#include <vector>
|
||||
|
||||
namespace Acore::Asio
|
||||
{
|
||||
class IoContext;
|
||||
}
|
||||
|
||||
namespace boost::system
|
||||
{
|
||||
class error_code;
|
||||
}
|
||||
|
||||
struct RealmBuildInfo
|
||||
{
|
||||
uint32 Build;
|
||||
uint32 MajorVersion;
|
||||
uint32 MinorVersion;
|
||||
uint32 BugfixVersion;
|
||||
std::array<char, 4> HotfixVersion;
|
||||
std::array<uint8, 20> WindowsHash;
|
||||
std::array<uint8, 20> MacHash;
|
||||
};
|
||||
|
||||
/// Storage object for the list of realms on the server
|
||||
class AC_SHARED_API RealmList
|
||||
{
|
||||
public:
|
||||
typedef std::map<RealmHandle, Realm> RealmMap;
|
||||
|
||||
static RealmList* Instance();
|
||||
|
||||
void Initialize(Acore::Asio::IoContext& ioContext, uint32 updateInterval);
|
||||
void Close();
|
||||
|
||||
[[nodiscard]] RealmMap const& GetRealms() const { return _realms; }
|
||||
[[nodiscard]] Realm const* GetRealm(RealmHandle const& id) const;
|
||||
|
||||
[[nodiscard]] RealmBuildInfo const* GetBuildInfo(uint32 build) const;
|
||||
|
||||
private:
|
||||
RealmList();
|
||||
~RealmList() = default;
|
||||
|
||||
void LoadBuildInfo();
|
||||
void UpdateRealms(boost::system::error_code const& error);
|
||||
void UpdateRealm(RealmHandle const& id, uint32 build, std::string const& name,
|
||||
boost::asio::ip::address&& address, boost::asio::ip::address&& localAddr, boost::asio::ip::address&& localSubmask,
|
||||
uint16 port, uint8 icon, RealmFlags flag, uint8 realmTimezone, AccountTypes allowedSecurityLevel, float population);
|
||||
|
||||
std::vector<RealmBuildInfo> _builds;
|
||||
RealmMap _realms;
|
||||
uint32 _updateInterval{0};
|
||||
std::unique_ptr<boost::asio::steady_timer> _updateTimer;
|
||||
std::unique_ptr<Acore::Asio::Resolver> _resolver;
|
||||
};
|
||||
|
||||
#define sRealmList RealmList::Instance()
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SecretMgr.h"
|
||||
#include "AES.h"
|
||||
#include "Argon2.h"
|
||||
#include "Config.h"
|
||||
#include "CryptoGenerics.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "QueryResult.h"
|
||||
#include "SharedDefines.h"
|
||||
|
||||
#define SECRET_FLAG_FOR(key, val, server) server ## _ ## key = (val ## ull << (16*SERVER_PROCESS_ ## server))
|
||||
#define SECRET_FLAG(key, val) SECRET_FLAG_ ## key = val, SECRET_FLAG_FOR(key, val, AUTHSERVER), SECRET_FLAG_FOR(key, val, WORLDSERVER)
|
||||
enum SecretFlags : uint64
|
||||
{
|
||||
SECRET_FLAG(DEFER_LOAD, 0x1)
|
||||
};
|
||||
#undef SECRET_FLAG_FOR
|
||||
#undef SECRET_FLAG
|
||||
|
||||
struct SecretInfo
|
||||
{
|
||||
char const* configKey;
|
||||
char const* oldKey;
|
||||
int bits;
|
||||
ServerProcessTypes owner;
|
||||
uint64 _flags;
|
||||
[[nodiscard]] uint16 flags() const { return static_cast<uint16>(_flags >> (16*THIS_SERVER_PROCESS)); }
|
||||
};
|
||||
|
||||
static constexpr SecretInfo secret_info[NUM_SECRETS] =
|
||||
{
|
||||
{ "TOTPMasterSecret", "TOTPOldMasterSecret", 128, SERVER_PROCESS_AUTHSERVER, WORLDSERVER_DEFER_LOAD }
|
||||
};
|
||||
|
||||
/*static*/ SecretMgr* SecretMgr::instance()
|
||||
{
|
||||
static SecretMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
static Optional<BigNumber> GetHexFromConfig(char const* configKey, int bits)
|
||||
{
|
||||
ASSERT(bits > 0);
|
||||
std::string str = sConfigMgr->GetOption<std::string>(configKey, "");
|
||||
if (str.empty())
|
||||
return {};
|
||||
|
||||
BigNumber secret;
|
||||
if (!secret.SetHexStr(str.c_str()))
|
||||
{
|
||||
LOG_FATAL("server.loading", "Invalid value for '{}' - specify a hexadecimal integer of up to {} bits with no prefix.", configKey, bits);
|
||||
ABORT();
|
||||
}
|
||||
|
||||
BigNumber threshold(2);
|
||||
threshold <<= bits;
|
||||
if (!((BigNumber(0) <= secret) && (secret < threshold)))
|
||||
{
|
||||
LOG_ERROR("server.loading", "Value for '{}' is out of bounds (should be an integer of up to {} bits with no prefix). Truncated to {} bits.", configKey, bits, bits);
|
||||
secret %= threshold;
|
||||
}
|
||||
ASSERT(((BigNumber(0) <= secret) && (secret < threshold)));
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
void SecretMgr::Initialize()
|
||||
{
|
||||
for (uint32 i = 0; i < NUM_SECRETS; ++i)
|
||||
{
|
||||
if (secret_info[i].flags() & SECRET_FLAG_DEFER_LOAD)
|
||||
continue;
|
||||
std::unique_lock<std::mutex> lock(_secrets[i].lock);
|
||||
AttemptLoad(Secrets(i), LogLevel::LOG_LEVEL_FATAL, lock);
|
||||
if (!_secrets[i].IsAvailable())
|
||||
ABORT(); // load failed
|
||||
}
|
||||
}
|
||||
|
||||
SecretMgr::Secret const& SecretMgr::GetSecret(Secrets i)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_secrets[i].lock);
|
||||
|
||||
if (_secrets[i].state == Secret::NOT_LOADED_YET)
|
||||
AttemptLoad(i, LogLevel::LOG_LEVEL_ERROR, lock);
|
||||
return _secrets[i];
|
||||
}
|
||||
|
||||
void SecretMgr::AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock<std::mutex> const&)
|
||||
{
|
||||
auto const& info = secret_info[i];
|
||||
|
||||
Optional<std::string> oldDigest;
|
||||
{
|
||||
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SECRET_DIGEST);
|
||||
stmt->SetData(0, i);
|
||||
PreparedQueryResult result = LoginDatabase.Query(stmt);
|
||||
if (result)
|
||||
oldDigest = result->Fetch()->Get<std::string>();
|
||||
}
|
||||
|
||||
Optional<BigNumber> currentValue = GetHexFromConfig(info.configKey, info.bits);
|
||||
|
||||
// verify digest
|
||||
if (
|
||||
((!oldDigest) != (!currentValue)) || // there is an old digest, but no current secret (or vice versa)
|
||||
(oldDigest && !Acore::Crypto::Argon2::Verify(currentValue->AsHexStr(), *oldDigest)) // there is an old digest, and the current secret does not match it
|
||||
)
|
||||
{
|
||||
if (info.owner != THIS_SERVER_PROCESS)
|
||||
{
|
||||
if (currentValue)
|
||||
LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '{}' specified - this is not actually the secret being used in your auth DB.", info.configKey);
|
||||
else
|
||||
LOG_MESSAGE_BODY("server.loading", errorLevel, "No value for '{}' specified - please specify the secret currently being used in your auth DB.", info.configKey);
|
||||
_secrets[i].state = Secret::LOAD_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<BigNumber> oldSecret;
|
||||
if (oldDigest && info.oldKey) // there is an old digest, so there might be an old secret (if possible)
|
||||
{
|
||||
oldSecret = GetHexFromConfig(info.oldKey, info.bits);
|
||||
if (oldSecret && !Acore::Crypto::Argon2::Verify(oldSecret->AsHexStr(), *oldDigest))
|
||||
{
|
||||
LOG_MESSAGE_BODY("server.loading", errorLevel, "Invalid value for '{}' specified - this is not actually the secret previously used in your auth DB.", info.oldKey);
|
||||
_secrets[i].state = Secret::LOAD_FAILED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to transition us to the new key, if possible
|
||||
Optional<std::string> error = AttemptTransition(Secrets(i), currentValue, oldSecret, static_cast<bool>(oldDigest));
|
||||
if (error)
|
||||
{
|
||||
LOG_MESSAGE_BODY("server.loading", errorLevel, "Your value of '{}' changed, but we cannot transition your database to the new value:\n{}", info.configKey, error->c_str());
|
||||
_secrets[i].state = Secret::LOAD_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", "Successfully transitioned database to new '{}' value.", info.configKey);
|
||||
}
|
||||
|
||||
if (currentValue)
|
||||
{
|
||||
_secrets[i].state = Secret::PRESENT;
|
||||
_secrets[i].value = *currentValue;
|
||||
}
|
||||
else
|
||||
_secrets[i].state = Secret::NOT_PRESENT;
|
||||
}
|
||||
|
||||
Optional<std::string> SecretMgr::AttemptTransition(Secrets i, Optional<BigNumber> const& newSecret, Optional<BigNumber> const& oldSecret, bool hadOldSecret) const
|
||||
{
|
||||
auto trans = LoginDatabase.BeginTransaction();
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case SECRET_TOTP_MASTER_KEY:
|
||||
{
|
||||
QueryResult result = LoginDatabase.Query("SELECT id, totp_secret FROM account");
|
||||
if (result) do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
if (fields[1].IsNull())
|
||||
continue;
|
||||
|
||||
uint32 id = fields[0].Get<uint32>();
|
||||
std::vector<uint8> totpSecret = fields[1].Get<Binary>();
|
||||
|
||||
if (hadOldSecret)
|
||||
{
|
||||
if (!oldSecret)
|
||||
return Acore::StringFormat("Cannot decrypt old TOTP tokens - add config key '{}' to authserver.conf!", secret_info[i].oldKey);
|
||||
|
||||
bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(totpSecret, oldSecret->ToByteArray<Acore::Crypto::AES::KEY_SIZE_BYTES>());
|
||||
if (!success)
|
||||
return Acore::StringFormat("Cannot decrypt old TOTP tokens - value of '{}' is incorrect for some users!", secret_info[i].oldKey);
|
||||
}
|
||||
|
||||
if (newSecret)
|
||||
Acore::Crypto::AEEncryptWithRandomIV<Acore::Crypto::AES>(totpSecret, newSecret->ToByteArray<Acore::Crypto::AES::KEY_SIZE_BYTES>());
|
||||
|
||||
auto* updateStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_TOTP_SECRET);
|
||||
updateStmt->SetData(0, totpSecret);
|
||||
updateStmt->SetData(1, id);
|
||||
trans->Append(updateStmt);
|
||||
} while (result->NextRow());
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return std::string("Unknown secret index - huh?");
|
||||
}
|
||||
|
||||
if (hadOldSecret)
|
||||
{
|
||||
auto* deleteStmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_SECRET_DIGEST);
|
||||
deleteStmt->SetData(0, i);
|
||||
trans->Append(deleteStmt);
|
||||
}
|
||||
|
||||
if (newSecret)
|
||||
{
|
||||
BigNumber salt;
|
||||
salt.SetRand(128);
|
||||
Optional<std::string> hash = Acore::Crypto::Argon2::Hash(newSecret->AsHexStr(), salt);
|
||||
if (!hash)
|
||||
return std::string("Failed to hash new secret");
|
||||
|
||||
auto* insertStmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_SECRET_DIGEST);
|
||||
insertStmt->SetData(0, i);
|
||||
insertStmt->SetData(1, *hash);
|
||||
trans->Append(insertStmt);
|
||||
}
|
||||
|
||||
LoginDatabase.CommitTransaction(trans);
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ACORE_SECRETMGR_H__
|
||||
#define __ACORE_SECRETMGR_H__
|
||||
|
||||
#include "BigNumber.h"
|
||||
#include "Log.h"
|
||||
#include "Optional.h"
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
enum Secrets : uint32
|
||||
{
|
||||
SECRET_TOTP_MASTER_KEY = 0,
|
||||
|
||||
// only add new indices right above this line
|
||||
NUM_SECRETS
|
||||
};
|
||||
|
||||
class AC_SHARED_API SecretMgr
|
||||
{
|
||||
private:
|
||||
SecretMgr() = default;
|
||||
~SecretMgr() = default;
|
||||
|
||||
public:
|
||||
SecretMgr(SecretMgr const&) = delete;
|
||||
static SecretMgr* instance();
|
||||
|
||||
struct Secret
|
||||
{
|
||||
public:
|
||||
explicit operator bool() const { return (state == PRESENT); }
|
||||
BigNumber const& operator*() const { return value; }
|
||||
BigNumber const* operator->() const { return &value; }
|
||||
[[nodiscard]] bool IsAvailable() const { return (state != NOT_LOADED_YET) && (state != LOAD_FAILED); }
|
||||
|
||||
private:
|
||||
std::mutex lock;
|
||||
enum { NOT_LOADED_YET, LOAD_FAILED, NOT_PRESENT, PRESENT } state = NOT_LOADED_YET;
|
||||
BigNumber value;
|
||||
|
||||
friend class SecretMgr;
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
Secret const& GetSecret(Secrets i);
|
||||
|
||||
private:
|
||||
void AttemptLoad(Secrets i, LogLevel errorLevel, std::unique_lock<std::mutex> const&);
|
||||
[[nodiscard]] Optional<std::string> AttemptTransition(Secrets i, Optional<BigNumber> const& newSecret, Optional<BigNumber> const& oldSecret, bool hadOldSecret) const;
|
||||
|
||||
std::array<Secret, NUM_SECRETS> _secrets;
|
||||
};
|
||||
|
||||
#define sSecretMgr SecretMgr::instance()
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SharedDefines.h"
|
||||
|
||||
ServerProcessTypes Acore::Impl::CurrentServerProcessHolder::_type = NUM_SERVER_PROCESS_TYPES;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user