aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarbenium <keresztesschmidt@gmail.com>2015-04-05 00:30:02 +0200
committerCarbenium <keresztesschmidt@gmail.com>2015-04-05 01:48:45 +0200
commitd1902b40957c4ce6eb0f20a48d6bb86822d96bd0 (patch)
tree227e36b7f16fde19d3cb8a576464a578a3a268fa /src
parent0878a42b7e57c62505b4cd62d9b79b9f767345c6 (diff)
Core/Player: Implement CharSections.dbc and serverside checks
Ported from 6d4c672fb1b799e1888881282e40992a9e15a006 Original work by @ariel-
Diffstat (limited to 'src')
-rw-r--r--src/server/game/DataStores/DBCStores.cpp21
-rw-r--r--src/server/game/DataStores/DBCStores.h2
-rw-r--r--src/server/game/DataStores/DBCStructure.h28
-rw-r--r--src/server/game/DataStores/DBCfmt.h1
-rw-r--r--src/server/game/Entities/Player/Player.cpp77
-rw-r--r--src/server/game/Entities/Player/Player.h1
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp39
-rw-r--r--src/server/shared/Database/Implementation/CharacterDatabase.cpp2
8 files changed, 166 insertions, 5 deletions
diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp
index 4794120d188..e83e9d2f5d4 100644
--- a/src/server/game/DataStores/DBCStores.cpp
+++ b/src/server/game/DataStores/DBCStores.cpp
@@ -51,6 +51,7 @@ struct WMOAreaTableTripple
};
typedef std::map<WMOAreaTableTripple, WMOAreaTableEntry const*> WMOAreaInfoByTripple;
+typedef std::multimap<uint32, CharSectionsEntry const*> CharSectionsMap;
DBCStorage <AreaTableEntry> sAreaStore(AreaTableEntryfmt);
static AreaFlagByAreaID sAreaFlagByAreaID;
@@ -68,6 +69,8 @@ DBCStorage <BattlemasterListEntry> sBattlemasterListStore(BattlemasterListEntryf
DBCStorage <BarberShopStyleEntry> sBarberShopStyleStore(BarberShopStyleEntryfmt);
DBCStorage <CharStartOutfitEntry> sCharStartOutfitStore(CharStartOutfitEntryfmt);
std::map<uint32, CharStartOutfitEntry const*> sCharStartOutfitMap;
+DBCStorage <CharSectionsEntry> sCharSectionsStore(CharSectionsEntryfmt);
+CharSectionsMap sCharSectionMap;
DBCStorage <CharTitlesEntry> sCharTitlesStore(CharTitlesEntryfmt);
DBCStorage <ChatChannelsEntry> sChatChannelsStore(ChatChannelsEntryfmt);
DBCStorage <ChrClassesEntry> sChrClassesStore(ChrClassesEntryfmt);
@@ -375,6 +378,12 @@ void LoadDBCStores(const std::string& dataPath)
if (CharStartOutfitEntry const* outfit = sCharStartOutfitStore.LookupEntry(i))
sCharStartOutfitMap[outfit->RaceID | (outfit->ClassID << 8) | (outfit->GenderID << 16)] = outfit;
+ LoadDBC(availableDbcLocales, bad_dbc_files, sCharSectionsStore, dbcPath, "CharSections.dbc");
+ for (uint32 i = 0; i < sCharSectionsStore.GetNumRows(); ++i)
+ if (CharSectionsEntry const* entry = sCharSectionsStore.LookupEntry(i))
+ if (entry->Race && ((1 << (entry->Race - 1)) & RACEMASK_ALL_PLAYABLE) != 0) //ignore Nonplayable races
+ sCharSectionMap.emplace(uint8(entry->GenType) | (uint8(entry->Gender) << 8) | (uint8(entry->Race) << 16), entry);
+
LoadDBC(availableDbcLocales, bad_dbc_files, sCharTitlesStore, dbcPath, "CharTitles.dbc");//19116
LoadDBC(availableDbcLocales, bad_dbc_files, sChatChannelsStore, dbcPath, "ChatChannels.dbc");//19116
LoadDBC(availableDbcLocales, bad_dbc_files, sChrClassesStore, dbcPath, "ChrClasses.dbc");//19116
@@ -979,6 +988,18 @@ uint32 GetPowerIndexByClass(uint32 powerType, uint32 classId)
return PowersByClass[classId][powerType];
}
+CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color)
+{
+ std::pair<CharSectionsMap::const_iterator, CharSectionsMap::const_iterator> eqr = sCharSectionMap.equal_range(uint32(genType) | uint32(gender << 8) | uint32(race << 16));
+ for (CharSectionsMap::const_iterator itr = eqr.first; itr != eqr.second; ++itr)
+ {
+ if (itr->second->Type == type && itr->second->Color == color)
+ return itr->second;
+ }
+
+ return NULL;
+}
+
/// Returns LFGDungeonEntry for a specific map and difficulty. Will return first found entry if multiple dungeons use the same map (such as Scarlet Monastery)
LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty)
{
diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h
index fcb7e0ccc68..f16b56609dd 100644
--- a/src/server/game/DataStores/DBCStores.h
+++ b/src/server/game/DataStores/DBCStores.h
@@ -77,6 +77,7 @@ PvPDifficultyEntry const* GetBattlegroundBracketByLevel(uint32 mapid, uint32 lev
PvPDifficultyEntry const* GetBattlegroundBracketById(uint32 mapid, BattlegroundBracketId id);
CharStartOutfitEntry const* GetCharStartOutfitEntry(uint8 race, uint8 class_, uint8 gender);
+CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color);
uint32 GetPowerIndexByClass(uint32 powerType, uint32 classId);
LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty);
@@ -133,6 +134,7 @@ extern DBCStorage <BarberShopStyleEntry> sBarberShopStyleStore;
extern DBCStorage <BattlemasterListEntry> sBattlemasterListStore;
extern DBCStorage <ChatChannelsEntry> sChatChannelsStore;
extern DBCStorage <CharStartOutfitEntry> sCharStartOutfitStore;
+extern DBCStorage <CharSectionsEntry> sCharSectionsStore;
extern DBCStorage <CharTitlesEntry> sCharTitlesStore;
extern DBCStorage <ChrClassesEntry> sChrClassesStore;
extern DBCStorage <ChrRacesEntry> sChrRacesStore;
diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h
index f997aaf776f..0d4ada1ab39 100644
--- a/src/server/game/DataStores/DBCStructure.h
+++ b/src/server/game/DataStores/DBCStructure.h
@@ -199,6 +199,34 @@ struct CharStartOutfitEntry
uint32 PetFamilyID; // 30 Pet Family Entry for starting pet
};
+enum CharSectionFlags
+{
+ SECTION_FLAG_PLAYER = 0x01,
+ SECTION_FLAG_DEATH_KNIGHT = 0x04
+};
+
+enum CharSectionType
+{
+ SECTION_TYPE_SKIN = 0,
+ SECTION_TYPE_FACE = 1,
+ SECTION_TYPE_FACIAL_HAIR = 2,
+ SECTION_TYPE_HAIR = 3,
+ SECTION_TYPE_UNDERWEAR = 4
+};
+
+struct CharSectionsEntry
+{
+ //uint32 Id;
+ uint32 Race;
+ uint32 Gender;
+ uint32 GenType;
+ //char* TexturePath[3];
+ uint32 Flags;
+ uint32 Type;
+ uint32 Color;
+};
+
+
struct CharTitlesEntry
{
uint32 ID; // 0, title ids, for example in Quest::GetCharTitleId()
diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h
index 338a06cb3f8..98677746c3c 100644
--- a/src/server/game/DataStores/DBCfmt.h
+++ b/src/server/game/DataStores/DBCfmt.h
@@ -34,6 +34,7 @@ char const BannedAddOnsfmt[] = "nxxxxxxxxxx";
char const BarberShopStyleEntryfmt[] = "nixxxiii";
char const BattlemasterListEntryfmt[] = "niiiiiiiiiiiiiiiiixsiiiixxxxxxx";
char const CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiiii";
+char const CharSectionsEntryfmt[] = "diiixxxiii";
char const CharTitlesEntryfmt[] = "nxssix";
char const ChatChannelsEntryfmt[] = "nixsx";
char const ChrClassesEntryfmt[] = "nixsxxxixiiiixxxxxx";
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 80990c1ed01..6e3f0841e77 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -948,8 +948,6 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac
{
//FIXME: outfitId not used in player creating
/// @todo need more checks against packet modifications
- // should check that skin, face, hair* are valid via DBC per race/class
- // also do it in Player::BuildEnumData, Player::LoadFromDB
Object::_Create(ObjectGuid::Create<HighGuid::Player>(guidlow));
@@ -976,6 +974,13 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac
return false;
}
+ if (!ValidateAppearance(createInfo->Race, createInfo->Class, createInfo->Sex, createInfo->HairStyle, createInfo->HairColor, createInfo->Face, createInfo->FacialHairStyle, createInfo->Skin, true))
+ {
+ TC_LOG_ERROR("entities.player", "Player::Create: Possible hacking-attempt: Account %u tried creating a character named '%s' with invalid appearance attributes - refusing to do so",
+ GetSession()->GetAccountId(), m_name.c_str());
+ return false;
+ }
+
SetMap(sMapMgr->CreateMap(info->mapId, this));
uint8 powertype = cEntry->PowerType;
@@ -16649,6 +16654,19 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
SetUInt32Value(PLAYER_FLAGS, fields[11].GetUInt32());
SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[44].GetUInt32());
+ if (!ValidateAppearance(
+ fields[3].GetUInt8(), // race
+ fields[4].GetUInt8(), // class
+ gender, GetByteValue(PLAYER_BYTES, 2), // hair type
+ GetByteValue(PLAYER_BYTES, 3), //hair color
+ uint8(fields[9].GetUInt32() >> 8), // face
+ GetByteValue(PLAYER_BYTES_2, 0), // facial hair
+ GetByteValue(PLAYER_BYTES, 0))) // skin color
+ {
+ TC_LOG_ERROR("entities.player", "Player %s has wrong Appearance values (Hair/Skin/Color), can't be loaded.", guid.ToString().c_str());
+ return false;
+ }
+
// set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise)
SetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES, fields[58].GetUInt8());
@@ -26525,3 +26543,58 @@ void Player::RemoveSocial()
sSocialMgr->RemovePlayerSocial(GetGUID());
m_social = nullptr;
}
+
+bool Player::ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 hairID, uint8 hairColor, uint8 faceID, uint8 facialHair, uint8 skinColor, bool create /*=false*/)
+{
+ // Check skin color
+ // For Skin type is always 0
+ if (CharSectionsEntry const* entry = GetCharSectionEntry(race, SECTION_TYPE_SKIN, gender, 0, skinColor))
+ { // Skin Color defined as Face color, too, we check skin & face in one pass
+ if (CharSectionsEntry const* entry2 = GetCharSectionEntry(race, SECTION_TYPE_FACE, gender, faceID, skinColor))
+ {
+ // Check DeathKnight exclusive
+ if (((entry->Flags & SECTION_FLAG_DEATH_KNIGHT) || (entry2->Flags & SECTION_FLAG_DEATH_KNIGHT)) && class_ != CLASS_DEATH_KNIGHT)
+ return false;
+ if (create && !((entry->Flags & SECTION_FLAG_PLAYER) && (entry2->Flags & SECTION_FLAG_PLAYER)))
+ return false;
+ }
+ else
+ return false;
+ }
+ else
+ return false;
+
+ // These combinations don't have an entry of Type SECTION_TYPE_FACIAL_HAIR, exclude them from that check
+ bool excludeCheck = (race == RACE_TAUREN) || (race == RACE_DRAENEI) || (gender == GENDER_FEMALE && race != RACE_NIGHTELF && race != RACE_UNDEAD_PLAYER);
+
+ // Check Hair
+ if (CharSectionsEntry const* entry = GetCharSectionEntry(race, SECTION_TYPE_HAIR, gender, hairID, hairColor))
+ {
+ if ((entry->Flags & SECTION_FLAG_DEATH_KNIGHT) && class_ != CLASS_DEATH_KNIGHT)
+ return false;
+ if (create && !(entry->Flags & SECTION_FLAG_PLAYER))
+ return false;
+
+ if (!excludeCheck)
+ {
+ if (CharSectionsEntry const* entry2 = GetCharSectionEntry(race, SECTION_TYPE_FACIAL_HAIR, gender, facialHair, hairColor))
+ {
+ if ((entry2->Flags & SECTION_FLAG_DEATH_KNIGHT) && class_ != CLASS_DEATH_KNIGHT)
+ return false;
+ if (create && !(entry2->Flags & SECTION_FLAG_PLAYER))
+ return false;
+ }
+ else
+ return false;
+ }
+ else
+ {
+ // @TODO: Bound checking for facialHair ID (used clientside for markings, tauren beard, etc.)
+ // Not present in DBC
+ }
+ }
+ else
+ return false;
+
+ return true;
+} \ No newline at end of file
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 098b653574b..1a650c509d6 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1757,6 +1757,7 @@ class Player : public Unit, public GridObject<Player>
static bool IsValidGender(uint8 Gender) { return Gender <= GENDER_FEMALE; }
static bool IsValidClass(uint8 Class) { return ((1 << (Class - 1)) & CLASSMASK_ALL_PLAYABLE) != 0; }
static bool IsValidRace(uint8 Race) { return ((1 << (Race - 1)) & RACEMASK_ALL_PLAYABLE) != 0; }
+ static bool ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 hairID, uint8 hairColor, uint8 faceID, uint8 facialHair, uint8 skinColor, bool create = false);
/*********************************************************/
/*** SAVE SYSTEM ***/
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index 881326f3b19..5fe6be24c51 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -257,6 +257,29 @@ void WorldSession::HandleCharEnum(PreparedQueryResult result)
TC_LOG_INFO("network", "Loading char guid %s from account %u.", charInfo.Guid.ToString().c_str(), GetAccountId());
+ uint8 plrRace = fields[2].GetUInt8();
+ uint8 plrClass = fields[3].GetUInt8();
+ uint8 plrGender = fields[4].GetUInt8();
+ uint8 plrSkin = uint8(fields[5].GetUInt32() & 0xFF);
+ uint8 plrFace = uint8((fields[5].GetUInt32() >> 8) & 0xFF);
+ uint8 plrHairStyle = uint8((fields[5].GetUInt32() >> 16) & 0xFF);
+ uint8 plrHairColor = uint8((fields[5].GetUInt32() >> 24) & 0xFF);
+ uint8 plrFacialHair = uint8(fields[6].GetUInt32() & 0xFF);
+
+ if (!Player::ValidateAppearance(charInfo.Race, charInfo.Class, charInfo.Sex, charInfo.HairStyle, charInfo.HairColor, charInfo.Face, charInfo.FacialHair, charInfo.Skin))
+ {
+ TC_LOG_ERROR("entities.player.loading", "Player %s has wrong Appearance values (Hair/Skin/Color), forcing recustomize", charInfo.Guid.ToString().c_str());
+
+ if (!(charInfo.CustomizationFlag == CHAR_CUSTOMIZE_FLAG_CUSTOMIZE))
+ {
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG);
+ stmt->setUInt16(0, uint16(AT_LOGIN_CUSTOMIZE));
+ stmt->setUInt64(1, charInfo.Guid.GetCounter());
+ CharacterDatabase.Execute(stmt);
+ charInfo.CustomizationFlag = CHAR_CUSTOMIZE_FLAG_CUSTOMIZE;
+ }
+ }
+
// Do not allow locked characters to login
if (!(charInfo.Flags & (CHARACTER_FLAG_LOCKED_FOR_TRANSFER | CHARACTER_FLAG_LOCKED_BY_BILLING)))
_legitCharacters.insert(charInfo.Guid);
@@ -1385,6 +1408,9 @@ void WorldSession::HandleAlterAppearance(WorldPackets::Character::AlterApperance
if (bs_skinColor && (bs_skinColor->Type != 3 || bs_skinColor->Race != _player->getRace() || bs_skinColor->Sex != _player->getGender()))
return;
+ if (!Player::ValidateAppearance(_player->getRace(), _player->getClass(), _player->getGender(), bs_hair->ID, packet.NewHairColor, uint8(_player->GetUInt32Value(PLAYER_FLAGS) >> 8), bs_facialHair->ID, bs_skinColor ? bs_skinColor->ID : 0))
+ return;
+
GameObject* go = _player->FindNearestGameObjectOfType(GAMEOBJECT_TYPE_BARBER_CHAIR, 5.0f);
if (!go)
{
@@ -1477,8 +1503,17 @@ void WorldSession::HandleCharCustomizeCallback(PreparedQueryResult result, World
Field* fields = result->Fetch();
std::string oldName = fields[0].GetString();
- uint16 atLoginFlags = fields[1].GetUInt16();
- uint32 playerBytes2 = fields[2].GetUInt32();
+ uint8 plrRace = fields[1].GetUInt8();
+ uint8 plrClass = fields[2].GetUInt8();
+ uint8 plrGender = fields[3].GetUInt8();
+ uint16 atLoginFlags = fields[4].GetUInt16();
+ uint32 playerBytes2 = fields[5].GetUInt32();
+
+ if (!Player::ValidateAppearance(plrRace, plrClass, plrGender, customizeInfo->HairStyleID, customizeInfo->HairColorID, customizeInfo->FaceID, customizeInfo->FacialHairStyleID, customizeInfo->SkinID, true))
+ {
+ SendCharCustomize(CHAR_CREATE_ERROR, customizeInfo);
+ return;
+ }
if (!(atLoginFlags & AT_LOGIN_CUSTOMIZE))
{
diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.cpp b/src/server/shared/Database/Implementation/CharacterDatabase.cpp
index 352854636dc..cf871ab79a4 100644
--- a/src/server/shared/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/shared/Database/Implementation/CharacterDatabase.cpp
@@ -477,7 +477,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_SEL_CHAR_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_GUID_NAME_BY_ACC, "SELECT guid, name FROM characters WHERE account = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_POOL_QUEST_SAVE, "SELECT quest_id FROM pool_quest_save WHERE pool_id = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_CUSTOMIZE_INFO, "SELECT name, at_login, playerBytes2 FROM characters WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_CHAR_CUSTOMIZE_INFO, "SELECT name, race, class, gender, at_login, playerBytes2 FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_RACE_OR_FACTION_CHANGE_INFOS, "SELECT at_login, knownTitles, playerBytes, playerBytes2 FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_INSTANCE, "SELECT data, completedEncounters FROM instance WHERE map = ? AND id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0", CONNECTION_SYNCH);