Merge pull request #13862 from ariel-/pr_tmp
Implement CharSections.dbc and Serverside Checks.
This commit is contained in:
commit
bdc9beacbd
7 files changed
+172
-7
No files matched your search
@@ -48,6 +48,7 @@ struct WMOAreaTableTripple
|
||||
};
|
||||
|
||||
typedef std::map<WMOAreaTableTripple, WMOAreaTableEntry const*> WMOAreaInfoByTripple;
|
||||
typedef std::multimap<uint32, CharSectionsEntry const*> CharSectionsMap;
|
||||
|
||||
DBCStorage <AreaTableEntry> sAreaStore(AreaTableEntryfmt);
|
||||
DBCStorage <AreaGroupEntry> sAreaGroupStore(AreaGroupEntryfmt);
|
||||
@@ -67,6 +68,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);
|
||||
@@ -305,6 +308,12 @@ void LoadDBCStores(const std::string& dataPath)
|
||||
if (CharStartOutfitEntry const* outfit = sCharStartOutfitStore.LookupEntry(i))
|
||||
sCharStartOutfitMap[outfit->Race | (outfit->Class << 8) | (outfit->Gender << 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");
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sChatChannelsStore, dbcPath, "ChatChannels.dbc");
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sChrClassesStore, dbcPath, "ChrClasses.dbc");
|
||||
@@ -945,6 +954,18 @@ CharStartOutfitEntry const* GetCharStartOutfitEntry(uint8 race, uint8 class_, ui
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -69,6 +69,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);
|
||||
|
||||
LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty);
|
||||
|
||||
@@ -91,6 +92,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;
|
||||
|
||||
@@ -647,6 +647,33 @@ struct CharStartOutfitEntry
|
||||
//int32 ItemInventorySlot[MAX_OUTFIT_ITEMS]; // 53-76 not required at server side
|
||||
};
|
||||
|
||||
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()
|
||||
|
||||
@@ -33,6 +33,7 @@ char const BannedAddOnsfmt[] = "nxxxxxxxxxx";
|
||||
char const BarberShopStyleEntryfmt[] = "nixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxiii";
|
||||
char const BattlemasterListEntryfmt[] = "niiiiiiiiixssssssssssssssssxiixx";
|
||||
char const CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
char const CharSectionsEntryfmt[] = "diiixxxiii";
|
||||
char const CharTitlesEntryfmt[] = "nxssssssssssssssssxssssssssssssssssxi";
|
||||
char const ChatChannelsEntryfmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxx";
|
||||
char const ChrClassesEntryfmt[] = "nxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixii";
|
||||
|
||||
@@ -964,8 +964,6 @@ bool Player::Create(uint32 guidlow, CharacterCreateInfo* createInfo)
|
||||
{
|
||||
//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(guidlow, 0, HIGHGUID_PLAYER);
|
||||
|
||||
@@ -1007,6 +1005,13 @@ bool Player::Create(uint32 guidlow, CharacterCreateInfo* createInfo)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateAppearance(createInfo->Race, createInfo->Class, createInfo->Gender, createInfo->HairStyle, createInfo->HairColor, createInfo->Face, createInfo->FacialHair, 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;
|
||||
}
|
||||
|
||||
uint32 RaceClassGender = (createInfo->Race) | (createInfo->Class << 8) | (createInfo->Gender << 16);
|
||||
|
||||
SetUInt32Value(UNIT_FIELD_BYTES_0, (RaceClassGender | (powertype << 24)));
|
||||
@@ -1979,12 +1984,29 @@ bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
|
||||
*data << uint8(gender); // gender
|
||||
|
||||
uint32 playerBytes = fields[5].GetUInt32();
|
||||
uint32 playerBytes2 = fields[6].GetUInt32();
|
||||
|
||||
uint16 atLoginFlags = fields[15].GetUInt16();
|
||||
|
||||
if (!ValidateAppearance(uint8(plrRace), uint8(plrClass), gender, uint8(playerBytes >> 16), uint8(playerBytes >> 24), uint8(playerBytes >> 8), uint8(playerBytes2), uint8(playerBytes)))
|
||||
{
|
||||
TC_LOG_ERROR("entities.player.loading", "Player %u has wrong Appearance values (Hair/Skin/Color), forcing recustomize", guid);
|
||||
|
||||
if (!(atLoginFlags & AT_LOGIN_CUSTOMIZE))
|
||||
{
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG);
|
||||
stmt->setUInt16(0, uint16(AT_LOGIN_CUSTOMIZE));
|
||||
stmt->setUInt32(1, guid);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
atLoginFlags |= AT_LOGIN_CUSTOMIZE;
|
||||
}
|
||||
}
|
||||
|
||||
*data << uint8(playerBytes); // skin
|
||||
*data << uint8(playerBytes >> 8); // face
|
||||
*data << uint8(playerBytes >> 16); // hair style
|
||||
*data << uint8(playerBytes >> 24); // hair color
|
||||
|
||||
uint32 playerBytes2 = fields[6].GetUInt32();
|
||||
*data << uint8(playerBytes2 & 0xFF); // facial hair
|
||||
|
||||
*data << uint8(fields[7].GetUInt8()); // level
|
||||
@@ -1999,7 +2021,6 @@ bool Player::BuildEnumData(PreparedQueryResult result, WorldPacket* data)
|
||||
|
||||
uint32 charFlags = 0;
|
||||
uint32 playerFlags = fields[14].GetUInt32();
|
||||
uint16 atLoginFlags = fields[15].GetUInt16();
|
||||
if (playerFlags & PLAYER_FLAGS_HIDE_HELM)
|
||||
charFlags |= CHARACTER_FLAG_HIDE_HELM;
|
||||
if (playerFlags & PLAYER_FLAGS_HIDE_CLOAK)
|
||||
@@ -17292,6 +17313,20 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
|
||||
SetUInt32Value(PLAYER_BYTES_2, fields[10].GetUInt32());
|
||||
SetByteValue(PLAYER_BYTES_3, 0, fields[5].GetUInt8());
|
||||
SetByteValue(PLAYER_BYTES_3, 1, fields[49].GetUInt8());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SetUInt32Value(PLAYER_FLAGS, fields[11].GetUInt32());
|
||||
SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, fields[48].GetUInt32());
|
||||
|
||||
@@ -26947,3 +26982,57 @@ void Player::SendSupercededSpell(uint32 oldSpell, uint32 newSpell)
|
||||
GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1499,6 +1499,7 @@ class Player : public Unit, public GridObject<Player>
|
||||
static bool LoadPositionFromDB(uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight, ObjectGuid guid);
|
||||
|
||||
static bool IsValidGender(uint8 Gender) { return Gender <= GENDER_FEMALE; }
|
||||
static bool ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 hairID, uint8 hairColor, uint8 faceID, uint8 facialHair, uint8 skinColor, bool create = false);
|
||||
|
||||
/*********************************************************/
|
||||
/*** SAVE SYSTEM ***/
|
||||
|
||||
@@ -1253,6 +1253,9 @@ void WorldSession::HandleAlterAppearance(WorldPacket& recvData)
|
||||
if (bs_skinColor && (bs_skinColor->type != 3 || bs_skinColor->race != _player->getRace() || bs_skinColor->gender != _player->getGender()))
|
||||
return;
|
||||
|
||||
if (!Player::ValidateAppearance(_player->getRace(), _player->getClass(), _player->getGender(), bs_hair->hair_id, Color, uint8(_player->GetUInt32Value(PLAYER_FLAGS) >> 8), bs_facialHair->hair_id, bs_skinColor ? bs_skinColor->hair_id : 0))
|
||||
return;
|
||||
|
||||
GameObject* go = _player->FindNearestGameObjectOfType(GAMEOBJECT_TYPE_BARBER_CHAIR, 5.0f);
|
||||
if (!go)
|
||||
{
|
||||
@@ -1337,10 +1340,8 @@ void WorldSession::HandleCharCustomize(WorldPacket& recvData)
|
||||
>> customizeInfo.FacialHair
|
||||
>> customizeInfo.Face;
|
||||
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_AT_LOGIN);
|
||||
|
||||
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_NAME_DATA);
|
||||
stmt->setUInt32(0, customizeInfo.Guid.GetCounter());
|
||||
// TODO: Make async with callback
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
@@ -1350,6 +1351,29 @@ void WorldSession::HandleCharCustomize(WorldPacket& recvData)
|
||||
}
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
uint8 plrRace = fields[0].GetUInt8();
|
||||
uint8 plrClass = fields[1].GetUInt8();
|
||||
uint8 plrGender = fields[2].GetUInt8();
|
||||
|
||||
if (!Player::ValidateAppearance(plrRace, plrClass, plrGender, customizeInfo.HairStyle, customizeInfo.HairColor, customizeInfo.Face, customizeInfo.FacialHair, customizeInfo.Skin, true))
|
||||
{
|
||||
SendCharCustomize(CHAR_CREATE_ERROR, customizeInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_AT_LOGIN);
|
||||
|
||||
stmt->setUInt32(0, customizeInfo.Guid.GetCounter());
|
||||
// TODO: Make async with callback
|
||||
result = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
SendCharCustomize(CHAR_CREATE_ERROR, customizeInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
fields = result->Fetch();
|
||||
uint32 at_loginFlags = fields[0].GetUInt16();
|
||||
|
||||
if (!(at_loginFlags & AT_LOGIN_CUSTOMIZE))
|
||||
|
||||
Reference in new issue
Block a user