mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33:51 +00:00
refactor: migrate 521 getRemainingSize() comparisons to hasRemaining()
Replace getRemainingSize()>=N with hasRemaining(N) and getRemainingSize()<N with !hasRemaining(N) across all 4 packet files. hasRemaining() is now the canonical bounds-check idiom with 680+ uses.
This commit is contained in:
parent
ca08d4313a
commit
618b479818
4 changed files with 520 additions and 520 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -29,7 +29,7 @@ std::string formatPacketBytes(const network::Packet& packet, size_t startPos) {
|
|||
}
|
||||
|
||||
bool skipClassicSpellCastTargets(network::Packet& packet, uint64_t* primaryTargetGuid = nullptr) {
|
||||
if (packet.getRemainingSize() < 2) {
|
||||
if (!packet.hasRemaining(2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ bool skipClassicSpellCastTargets(network::Packet& packet, uint64_t* primaryTarge
|
|||
}
|
||||
|
||||
if ((targetFlags & 0x0020) != 0) { // SOURCE_LOCATION
|
||||
if (packet.getRemainingSize() < 12) {
|
||||
if (!packet.hasRemaining(12)) {
|
||||
return false;
|
||||
}
|
||||
(void)packet.readFloat();
|
||||
|
|
@ -72,7 +72,7 @@ bool skipClassicSpellCastTargets(network::Packet& packet, uint64_t* primaryTarge
|
|||
(void)packet.readFloat();
|
||||
}
|
||||
if ((targetFlags & 0x0040) != 0) { // DEST_LOCATION
|
||||
if (packet.getRemainingSize() < 12) {
|
||||
if (!packet.hasRemaining(12)) {
|
||||
return false;
|
||||
}
|
||||
(void)packet.readFloat();
|
||||
|
|
@ -81,7 +81,7 @@ bool skipClassicSpellCastTargets(network::Packet& packet, uint64_t* primaryTarge
|
|||
}
|
||||
|
||||
if ((targetFlags & 0x1000) != 0) { // TRADE_ITEM
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
return false;
|
||||
}
|
||||
(void)packet.readUInt8();
|
||||
|
|
@ -1023,7 +1023,7 @@ bool ClassicPacketParsers::parseCastFailed(network::Packet& packet, CastFailedDa
|
|||
// align with WotLK's getSpellCastResultString table.
|
||||
// ============================================================================
|
||||
bool ClassicPacketParsers::parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) {
|
||||
if (packet.getRemainingSize() < 5) return false;
|
||||
if (!packet.hasRemaining(5)) return false;
|
||||
spellId = packet.readUInt32();
|
||||
uint8_t vanillaResult = packet.readUInt8();
|
||||
// Shift +1: Vanilla result 0=AFFECTING_COMBAT maps to WotLK result 1=AFFECTING_COMBAT
|
||||
|
|
@ -1372,7 +1372,7 @@ bool ClassicPacketParsers::parseGameObjectQueryResponse(network::Packet& packet,
|
|||
}
|
||||
|
||||
// Validate minimum size for fixed fields: type(4) + displayId(4)
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_ERROR("Classic SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated before names (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1677,7 +1677,7 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
}
|
||||
|
||||
// Validate minimum size for fixed fields: itemClass(4) + subClass(4) + 4 name strings + displayInfoId(4) + quality(4)
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_ERROR("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before names (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1731,7 +1731,7 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
data.quality = packet.readUInt32();
|
||||
|
||||
// Validate minimum size for fixed fields: Flags(4) + BuyPrice(4) + SellPrice(4) + inventoryType(4)
|
||||
if (packet.getRemainingSize() < 16) {
|
||||
if (!packet.hasRemaining(16)) {
|
||||
LOG_ERROR("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before inventoryType (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1744,7 +1744,7 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
data.inventoryType = packet.readUInt32();
|
||||
|
||||
// Validate minimum size for remaining fixed fields: 13×4 = 52 bytes
|
||||
if (packet.getRemainingSize() < 52) {
|
||||
if (!packet.hasRemaining(52)) {
|
||||
LOG_ERROR("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before stats (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1765,12 +1765,12 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
data.containerSlots = packet.readUInt32();
|
||||
|
||||
// Vanilla: 10 stat pairs, NO statsCount prefix (10×8 = 80 bytes)
|
||||
if (packet.getRemainingSize() < 80) {
|
||||
if (!packet.hasRemaining(80)) {
|
||||
LOG_WARNING("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated in stats section (entry=", data.entry, ")");
|
||||
// Read what we can
|
||||
}
|
||||
for (uint32_t i = 0; i < 10; i++) {
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: stat ", i, " truncated (entry=", data.entry, ")");
|
||||
break;
|
||||
}
|
||||
|
|
@ -1797,7 +1797,7 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
bool haveWeaponDamage = false;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// Each damage entry is dmgMin(4) + dmgMax(4) + damageType(4) = 12 bytes
|
||||
if (packet.getRemainingSize() < 12) {
|
||||
if (!packet.hasRemaining(12)) {
|
||||
LOG_WARNING("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: damage ", i, " truncated (entry=", data.entry, ")");
|
||||
break;
|
||||
}
|
||||
|
|
@ -1815,14 +1815,14 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
}
|
||||
|
||||
// Validate minimum size for armor field (4 bytes)
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
LOG_WARNING("Classic SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before armor (entry=", data.entry, ")");
|
||||
return true; // Have core fields; armor is important but optional
|
||||
}
|
||||
data.armor = static_cast<int32_t>(packet.readUInt32());
|
||||
|
||||
// Remaining tail can vary by core. Read resistances + delay when present.
|
||||
if (packet.getRemainingSize() >= 28) {
|
||||
if (packet.hasRemaining(28)) {
|
||||
data.holyRes = static_cast<int32_t>(packet.readUInt32()); // HolyRes
|
||||
data.fireRes = static_cast<int32_t>(packet.readUInt32()); // FireRes
|
||||
data.natureRes = static_cast<int32_t>(packet.readUInt32()); // NatureRes
|
||||
|
|
@ -1833,7 +1833,7 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
}
|
||||
|
||||
// AmmoType + RangedModRange (2 fields, 8 bytes)
|
||||
if (packet.getRemainingSize() >= 8) {
|
||||
if (packet.hasRemaining(8)) {
|
||||
packet.readUInt32(); // AmmoType
|
||||
packet.readFloat(); // RangedModRange
|
||||
}
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ bool TbcPacketParsers::parseUpdateObject(network::Packet& packet, UpdateObjectDa
|
|||
// reads those 5 bytes as part of the quest title, corrupting all gossip quests.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseGossipMessage(network::Packet& packet, GossipMessageData& data) {
|
||||
if (packet.getRemainingSize() < 16) return false;
|
||||
if (!packet.hasRemaining(16)) return false;
|
||||
|
||||
data.npcGuid = packet.readUInt64();
|
||||
data.menuId = packet.readUInt32(); // TBC added menuId (Classic doesn't have it)
|
||||
|
|
@ -928,7 +928,7 @@ bool TbcPacketParsers::parseNameQueryResponse(network::Packet& packet, NameQuery
|
|||
{
|
||||
packet.setReadPos(start);
|
||||
data.guid = packet.readUInt64();
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -982,7 +982,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
}
|
||||
|
||||
// Validate minimum size for fixed fields: itemClass(4) + subClass(4) + soundOverride(4) + 4 name strings + displayInfoId(4) + quality(4)
|
||||
if (packet.getRemainingSize() < 12) {
|
||||
if (!packet.hasRemaining(12)) {
|
||||
LOG_ERROR("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before names (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1004,7 +1004,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
data.quality = packet.readUInt32();
|
||||
|
||||
// Validate minimum size for fixed fields: Flags(4) + BuyPrice(4) + SellPrice(4) + inventoryType(4)
|
||||
if (packet.getRemainingSize() < 16) {
|
||||
if (!packet.hasRemaining(16)) {
|
||||
LOG_ERROR("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before inventoryType (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1017,7 +1017,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
data.inventoryType = packet.readUInt32();
|
||||
|
||||
// Validate minimum size for remaining fixed fields: 13×4 = 52 bytes
|
||||
if (packet.getRemainingSize() < 52) {
|
||||
if (!packet.hasRemaining(52)) {
|
||||
LOG_ERROR("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before statsCount (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1038,7 +1038,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
data.containerSlots = packet.readUInt32();
|
||||
|
||||
// TBC: statsCount prefix + exactly statsCount pairs (WotLK always sends 10)
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
LOG_WARNING("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated at statsCount (entry=", data.entry, ")");
|
||||
return true; // Have core fields; stats are optional
|
||||
}
|
||||
|
|
@ -1050,7 +1050,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
}
|
||||
for (uint32_t i = 0; i < statsCount; i++) {
|
||||
// Each stat is 2 uint32s = 8 bytes
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: stat ", i, " truncated (entry=", data.entry, ")");
|
||||
break;
|
||||
}
|
||||
|
|
@ -1074,7 +1074,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
bool haveWeaponDamage = false;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// Each damage entry is dmgMin(4) + dmgMax(4) + damageType(4) = 12 bytes
|
||||
if (packet.getRemainingSize() < 12) {
|
||||
if (!packet.hasRemaining(12)) {
|
||||
LOG_WARNING("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: damage ", i, " truncated (entry=", data.entry, ")");
|
||||
break;
|
||||
}
|
||||
|
|
@ -1091,13 +1091,13 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
}
|
||||
|
||||
// Validate minimum size for armor (4 bytes)
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
LOG_WARNING("TBC SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before armor (entry=", data.entry, ")");
|
||||
return true; // Have core fields; armor is important but optional
|
||||
}
|
||||
data.armor = static_cast<int32_t>(packet.readUInt32());
|
||||
|
||||
if (packet.getRemainingSize() >= 28) {
|
||||
if (packet.hasRemaining(28)) {
|
||||
data.holyRes = static_cast<int32_t>(packet.readUInt32()); // HolyRes
|
||||
data.fireRes = static_cast<int32_t>(packet.readUInt32()); // FireRes
|
||||
data.natureRes = static_cast<int32_t>(packet.readUInt32()); // NatureRes
|
||||
|
|
@ -1108,7 +1108,7 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
}
|
||||
|
||||
// AmmoType + RangedModRange
|
||||
if (packet.getRemainingSize() >= 8) {
|
||||
if (packet.hasRemaining(8)) {
|
||||
packet.readUInt32(); // AmmoType
|
||||
packet.readFloat(); // RangedModRange
|
||||
}
|
||||
|
|
@ -1247,7 +1247,7 @@ bool TbcPacketParsers::parseMailList(network::Packet& packet, std::vector<MailMe
|
|||
// caller after spell targets) are not corrupted.
|
||||
// ---------------------------------------------------------------------------
|
||||
static bool skipTbcSpellCastTargets(network::Packet& packet, uint64_t* primaryTargetGuid = nullptr) {
|
||||
if (packet.getRemainingSize() < 4) return false;
|
||||
if (!packet.hasRemaining(4)) return false;
|
||||
|
||||
const uint32_t targetFlags = packet.readUInt32();
|
||||
|
||||
|
|
@ -1259,14 +1259,14 @@ static bool skipTbcSpellCastTargets(network::Packet& packet, uint64_t* primaryTa
|
|||
uint8_t mask = packet.getData()[packet.getReadPos()];
|
||||
size_t needed = 1;
|
||||
for (int b = 0; b < 8; ++b) if (mask & (1u << b)) ++needed;
|
||||
if (packet.getRemainingSize() < needed) return false;
|
||||
if (!packet.hasRemaining(needed)) return false;
|
||||
uint64_t g = packet.readPackedGuid();
|
||||
if (capture && primaryTargetGuid && *primaryTargetGuid == 0) *primaryTargetGuid = g;
|
||||
return true;
|
||||
};
|
||||
auto skipFloats3 = [&](uint32_t flag) -> bool {
|
||||
if (!(targetFlags & flag)) return true;
|
||||
if (packet.getRemainingSize() < 12) return false;
|
||||
if (!packet.hasRemaining(12)) return false;
|
||||
(void)packet.readFloat(); (void)packet.readFloat(); (void)packet.readFloat();
|
||||
return true;
|
||||
};
|
||||
|
|
@ -1306,7 +1306,7 @@ static bool skipTbcSpellCastTargets(network::Packet& packet, uint64_t* primaryTa
|
|||
// ============================================================================
|
||||
bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData& data) {
|
||||
data = SpellStartData{};
|
||||
if (packet.getRemainingSize() < 22) return false;
|
||||
if (!packet.hasRemaining(22)) return false;
|
||||
|
||||
data.casterGuid = packet.readUInt64(); // full GUID (object)
|
||||
data.casterUnit = packet.readUInt64(); // full GUID (caster unit)
|
||||
|
|
@ -1344,7 +1344,7 @@ bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data)
|
|||
const size_t startPos = packet.getReadPos();
|
||||
// Fixed header before hit/miss lists:
|
||||
// casterGuid(u64) + casterUnit(u64) + castCount(u8) + spellId(u32) + castFlags(u32)
|
||||
if (packet.getRemainingSize() < 25) return false;
|
||||
if (!packet.hasRemaining(25)) return false;
|
||||
|
||||
data.casterGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.casterUnit = packet.readUInt64(); // full GUID in TBC
|
||||
|
|
@ -1443,7 +1443,7 @@ bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data)
|
|||
// then the remaining 4 bytes as spellId (off by one), producing wrong result.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) {
|
||||
if (packet.getRemainingSize() < 5) return false;
|
||||
if (!packet.hasRemaining(5)) return false;
|
||||
spellId = packet.readUInt32(); // No castCount prefix in TBC
|
||||
result = packet.readUInt8();
|
||||
return true;
|
||||
|
|
@ -1459,7 +1459,7 @@ bool TbcPacketParsers::parseCastResult(network::Packet& packet, uint32_t& spellI
|
|||
// TBC uses the same result values as WotLK so no offset is needed.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseCastFailed(network::Packet& packet, CastFailedData& data) {
|
||||
if (packet.getRemainingSize() < 5) return false;
|
||||
if (!packet.hasRemaining(5)) return false;
|
||||
data.castCount = 0; // not present in TBC
|
||||
data.spellId = packet.readUInt32();
|
||||
data.result = packet.readUInt8(); // same enum as WotLK
|
||||
|
|
@ -1543,7 +1543,7 @@ bool TbcPacketParsers::parseSpellDamageLog(network::Packet& packet, SpellDamageL
|
|||
// = 43 bytes
|
||||
// Some servers append additional trailing fields; consume the canonical minimum
|
||||
// and leave any extension bytes unread.
|
||||
if (packet.getRemainingSize() < 43) return false;
|
||||
if (!packet.hasRemaining(43)) return false;
|
||||
|
||||
data = SpellDamageLogData{};
|
||||
|
||||
|
|
@ -1578,7 +1578,7 @@ bool TbcPacketParsers::parseSpellDamageLog(network::Packet& packet, SpellDamageL
|
|||
bool TbcPacketParsers::parseSpellHealLog(network::Packet& packet, SpellHealLogData& data) {
|
||||
// Fixed payload is 28 bytes; many cores append crit flag (1 byte).
|
||||
// targetGuid(8) + casterGuid(8) + spellId(4) + heal(4) + overheal(4)
|
||||
if (packet.getRemainingSize() < 28) return false;
|
||||
if (!packet.hasRemaining(28)) return false;
|
||||
|
||||
data = SpellHealLogData{};
|
||||
|
||||
|
|
@ -1761,7 +1761,7 @@ bool TbcPacketParsers::parseGameObjectQueryResponse(network::Packet& packet, Gam
|
|||
return true;
|
||||
}
|
||||
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_ERROR("TBC SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated before names (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace {
|
|||
++guidBytes;
|
||||
}
|
||||
}
|
||||
return packet.getRemainingSize() >= guidBytes;
|
||||
return packet.hasRemaining(guidBytes);
|
||||
}
|
||||
|
||||
const char* updateTypeName(wowee::game::UpdateType type) {
|
||||
|
|
@ -402,7 +402,7 @@ network::Packet CharCreatePacket::build(const CharCreateData& data) {
|
|||
|
||||
bool CharCreateResponseParser::parse(network::Packet& packet, CharCreateResponseData& data) {
|
||||
// Validate minimum packet size: result(1)
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
LOG_WARNING("SMSG_CHAR_CREATE: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -423,7 +423,7 @@ network::Packet CharEnumPacket::build() {
|
|||
|
||||
bool CharEnumParser::parse(network::Packet& packet, CharEnumResponse& response) {
|
||||
// Upfront validation: count(1) + at least minimal character data
|
||||
if (packet.getRemainingSize() < 1) return false;
|
||||
if (!packet.hasRemaining(1)) return false;
|
||||
|
||||
// Read character count
|
||||
uint8_t count = packet.readUInt8();
|
||||
|
|
@ -1824,7 +1824,7 @@ network::Packet QueryTimePacket::build() {
|
|||
|
||||
bool QueryTimeResponseParser::parse(network::Packet& packet, QueryTimeResponseData& data) {
|
||||
// Validate minimum packet size: serverTime(4) + timeOffset(4)
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("SMSG_QUERY_TIME_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1845,14 +1845,14 @@ network::Packet RequestPlayedTimePacket::build(bool sendToChat) {
|
|||
bool PlayedTimeParser::parse(network::Packet& packet, PlayedTimeData& data) {
|
||||
// Classic/Turtle may omit the trailing trigger-message byte and send only
|
||||
// totalTime(4) + levelTime(4). Later expansions append triggerMsg(1).
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("SMSG_PLAYED_TIME: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
||||
data.totalTimePlayed = packet.readUInt32();
|
||||
data.levelTimePlayed = packet.readUInt32();
|
||||
data.triggerMessage = (packet.getRemainingSize() >= 1) && (packet.readUInt8() != 0);
|
||||
data.triggerMessage = (packet.hasRemaining(1)) && (packet.readUInt8() != 0);
|
||||
LOG_DEBUG("Parsed SMSG_PLAYED_TIME: total=", data.totalTimePlayed, " level=", data.levelTimePlayed);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1906,7 +1906,7 @@ network::Packet SetContactNotesPacket::build(uint64_t friendGuid, const std::str
|
|||
|
||||
bool FriendStatusParser::parse(network::Packet& packet, FriendStatusData& data) {
|
||||
// Validate minimum packet size: status(1) + guid(8)
|
||||
if (packet.getRemainingSize() < 9) {
|
||||
if (!packet.hasRemaining(9)) {
|
||||
LOG_WARNING("SMSG_FRIEND_STATUS: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1958,7 +1958,7 @@ network::Packet LogoutCancelPacket::build() {
|
|||
|
||||
bool LogoutResponseParser::parse(network::Packet& packet, LogoutResponseData& data) {
|
||||
// Validate minimum packet size: result(4) + instant(1)
|
||||
if (packet.getRemainingSize() < 5) {
|
||||
if (!packet.hasRemaining(5)) {
|
||||
LOG_WARNING("SMSG_LOGOUT_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2620,7 +2620,7 @@ network::Packet RandomRollPacket::build(uint32_t minRoll, uint32_t maxRoll) {
|
|||
|
||||
bool RandomRollParser::parse(network::Packet& packet, RandomRollData& data) {
|
||||
// Validate minimum packet size: rollerGuid(8) + targetGuid(8) + minRoll(4) + maxRoll(4) + result(4)
|
||||
if (packet.getRemainingSize() < 28) {
|
||||
if (!packet.hasRemaining(28)) {
|
||||
LOG_WARNING("SMSG_RANDOM_ROLL: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2646,13 +2646,13 @@ bool NameQueryResponseParser::parse(network::Packet& packet, NameQueryResponseDa
|
|||
// 3.3.5a: packedGuid, uint8 found
|
||||
// If found==0: CString name, CString realmName, uint8 race, uint8 gender, uint8 classId
|
||||
// Validation: packed GUID (1-8 bytes) + found flag (1 byte minimum)
|
||||
if (packet.getRemainingSize() < 2) return false; // At least 1 for packed GUID + 1 for found
|
||||
if (!packet.hasRemaining(2)) return false; // At least 1 for packed GUID + 1 for found
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
data.guid = packet.readPackedGuid();
|
||||
|
||||
// Validate found flag read
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2664,7 +2664,7 @@ bool NameQueryResponseParser::parse(network::Packet& packet, NameQueryResponseDa
|
|||
}
|
||||
|
||||
// Validate strings: need at least 2 null terminators for empty strings
|
||||
if (packet.getRemainingSize() < 2) {
|
||||
if (!packet.hasRemaining(2)) {
|
||||
data.name.clear();
|
||||
data.realmName.clear();
|
||||
return !data.name.empty(); // Fail if name was required
|
||||
|
|
@ -2674,7 +2674,7 @@ bool NameQueryResponseParser::parse(network::Packet& packet, NameQueryResponseDa
|
|||
data.realmName = packet.readString();
|
||||
|
||||
// Validate final 3 uint8 fields (race, gender, classId)
|
||||
if (packet.getRemainingSize() < 3) {
|
||||
if (!packet.hasRemaining(3)) {
|
||||
LOG_WARNING("Name query: truncated fields after realmName, expected 3 uint8s");
|
||||
data.race = 0;
|
||||
data.gender = 0;
|
||||
|
|
@ -2726,7 +2726,7 @@ bool CreatureQueryResponseParser::parse(network::Packet& packet, CreatureQueryRe
|
|||
|
||||
// WotLK: 4 fixed fields after iconName (typeFlags, creatureType, family, rank)
|
||||
// Validate minimum size for these fields: 4×4 = 16 bytes
|
||||
if (packet.getRemainingSize() < 16) {
|
||||
if (!packet.hasRemaining(16)) {
|
||||
LOG_WARNING("SMSG_CREATURE_QUERY_RESPONSE: truncated before typeFlags (entry=", data.entry, ")");
|
||||
data.typeFlags = 0;
|
||||
data.creatureType = 0;
|
||||
|
|
@ -2776,7 +2776,7 @@ bool GameObjectQueryResponseParser::parse(network::Packet& packet, GameObjectQue
|
|||
}
|
||||
|
||||
// Validate minimum size for fixed fields: type(4) + displayId(4)
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_ERROR("SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated before names (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2826,10 +2826,10 @@ network::Packet PageTextQueryPacket::build(uint32_t pageId, uint64_t guid) {
|
|||
}
|
||||
|
||||
bool PageTextQueryResponseParser::parse(network::Packet& packet, PageTextQueryResponseData& data) {
|
||||
if (packet.getRemainingSize() < 4) return false;
|
||||
if (!packet.hasRemaining(4)) return false;
|
||||
data.pageId = packet.readUInt32();
|
||||
data.text = normalizeWowTextTokens(packet.readString());
|
||||
if (packet.getRemainingSize() >= 4) {
|
||||
if (packet.hasRemaining(4)) {
|
||||
data.nextPageId = packet.readUInt32();
|
||||
} else {
|
||||
data.nextPageId = 0;
|
||||
|
|
@ -2891,7 +2891,7 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
|
||||
// Validate minimum size for fixed fields before reading: itemClass(4) + subClass(4) + soundOverride(4)
|
||||
// + 4 name strings + displayInfoId(4) + quality(4) = at least 24 bytes more
|
||||
if (packet.getRemainingSize() < 24) {
|
||||
if (!packet.hasRemaining(24)) {
|
||||
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before displayInfoId (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2917,7 +2917,7 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
// Some server variants omit BuyCount (4 fields instead of 5).
|
||||
// Read 5 fields and validate InventoryType; if it looks implausible, rewind and try 4.
|
||||
const size_t postQualityPos = packet.getReadPos();
|
||||
if (packet.getRemainingSize() < 24) {
|
||||
if (!packet.hasRemaining(24)) {
|
||||
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before flags (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2939,7 +2939,7 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
}
|
||||
|
||||
// Validate minimum size for remaining fixed fields before inventoryType through containerSlots: 13×4 = 52 bytes
|
||||
if (packet.getRemainingSize() < 52) {
|
||||
if (!packet.hasRemaining(52)) {
|
||||
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before statsCount (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2959,7 +2959,7 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
data.containerSlots = packet.readUInt32();
|
||||
|
||||
// Read statsCount with bounds validation
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated at statsCount (entry=", data.entry, ")");
|
||||
return true; // Have enough for core fields; stats are optional
|
||||
}
|
||||
|
|
@ -2977,7 +2977,7 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
uint32_t statsToRead = std::min(statsCount, 10u);
|
||||
for (uint32_t i = 0; i < statsToRead; i++) {
|
||||
// Each stat is 2 uint32s (type + value) = 8 bytes
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: stat ", i, " truncated (entry=", data.entry, ")");
|
||||
break;
|
||||
}
|
||||
|
|
@ -2997,7 +2997,7 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
}
|
||||
|
||||
// ScalingStatDistribution and ScalingStatValue
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before scaling stats (entry=", data.entry, ")");
|
||||
return true; // Have core fields; scaling is optional
|
||||
}
|
||||
|
|
@ -3337,7 +3337,7 @@ bool AttackStopParser::parse(network::Packet& packet, AttackStopData& data) {
|
|||
|
||||
bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpdateData& data) {
|
||||
// Upfront validation: hitInfo(4) + packed GUIDs(1-8 each) + totalDamage(4) + subDamageCount(1) = 13 bytes minimum
|
||||
if (packet.getRemainingSize() < 13) return false;
|
||||
if (!packet.hasRemaining(13)) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
data.hitInfo = packet.readUInt32();
|
||||
|
|
@ -3353,7 +3353,7 @@ bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpda
|
|||
data.targetGuid = packet.readPackedGuid();
|
||||
|
||||
// Validate totalDamage + subDamageCount can be read (5 bytes)
|
||||
if (packet.getRemainingSize() < 5) {
|
||||
if (!packet.hasRemaining(5)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3379,7 +3379,7 @@ bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpda
|
|||
data.subDamages.reserve(data.subDamageCount);
|
||||
for (uint8_t i = 0; i < data.subDamageCount; ++i) {
|
||||
// Each sub-damage entry needs 20 bytes: schoolMask(4) + damage(4) + intDamage(4) + absorbed(4) + resisted(4)
|
||||
if (packet.getRemainingSize() < 20) {
|
||||
if (!packet.hasRemaining(20)) {
|
||||
data.subDamageCount = i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -3393,7 +3393,7 @@ bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpda
|
|||
}
|
||||
|
||||
// Validate victimState + overkill fields (8 bytes)
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
data.victimState = 0;
|
||||
data.overkill = 0;
|
||||
return !data.subDamages.empty();
|
||||
|
|
@ -3425,7 +3425,7 @@ bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& da
|
|||
// packed GUIDs(1-8 each) + spellId(4) + damage(4) + overkill(4) + schoolMask(1)
|
||||
// + absorbed(4) + resisted(4) + periodicLog(1) + unused(1) + blocked(4) + flags(4)
|
||||
// = 33 bytes minimum.
|
||||
if (packet.getRemainingSize() < 33) return false;
|
||||
if (!packet.hasRemaining(33)) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!packet.hasFullPackedGuid()) {
|
||||
|
|
@ -3440,7 +3440,7 @@ bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& da
|
|||
data.attackerGuid = packet.readPackedGuid();
|
||||
|
||||
// Validate core fields (spellId + damage + overkill + schoolMask + absorbed + resisted = 21 bytes)
|
||||
if (packet.getRemainingSize() < 21) {
|
||||
if (!packet.hasRemaining(21)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3454,7 +3454,7 @@ bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& da
|
|||
|
||||
// Remaining fields are required for a complete event.
|
||||
// Reject truncated packets so we do not emit partial/incorrect combat entries.
|
||||
if (packet.getRemainingSize() < 10) {
|
||||
if (!packet.hasRemaining(10)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3475,7 +3475,7 @@ bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& da
|
|||
|
||||
bool SpellHealLogParser::parse(network::Packet& packet, SpellHealLogData& data) {
|
||||
// Upfront validation: packed GUIDs(1-8 each) + spellId(4) + heal(4) + overheal(4) + absorbed(4) + critFlag(1) = 21 bytes minimum
|
||||
if (packet.getRemainingSize() < 21) return false;
|
||||
if (!packet.hasRemaining(21)) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!packet.hasFullPackedGuid()) {
|
||||
|
|
@ -3490,7 +3490,7 @@ bool SpellHealLogParser::parse(network::Packet& packet, SpellHealLogData& data)
|
|||
data.casterGuid = packet.readPackedGuid();
|
||||
|
||||
// Validate remaining fields (spellId + heal + overheal + absorbed + critFlag = 17 bytes)
|
||||
if (packet.getRemainingSize() < 17) {
|
||||
if (!packet.hasRemaining(17)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3513,7 +3513,7 @@ bool SpellHealLogParser::parse(network::Packet& packet, SpellHealLogData& data)
|
|||
|
||||
bool XpGainParser::parse(network::Packet& packet, XpGainData& data) {
|
||||
// Validate minimum packet size: victimGuid(8) + totalXp(4) + type(1)
|
||||
if (packet.getRemainingSize() < 13) {
|
||||
if (!packet.hasRemaining(13)) {
|
||||
LOG_WARNING("SMSG_LOG_XPGAIN: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3544,7 +3544,7 @@ bool XpGainParser::parse(network::Packet& packet, XpGainData& data) {
|
|||
bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data,
|
||||
bool vanillaFormat) {
|
||||
// Validate minimum packet size for header: talentSpec(1) + spellCount(2)
|
||||
if (packet.getRemainingSize() < 3) {
|
||||
if (!packet.hasRemaining(3)) {
|
||||
LOG_ERROR("SMSG_INITIAL_SPELLS: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3570,7 +3570,7 @@ bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data
|
|||
// Vanilla spell: spellId(2) + slot(2) = 4 bytes
|
||||
// TBC/WotLK spell: spellId(4) + unknown(2) = 6 bytes
|
||||
size_t spellEntrySize = vanillaFormat ? 4 : 6;
|
||||
if (packet.getRemainingSize() < spellEntrySize) {
|
||||
if (!packet.hasRemaining(spellEntrySize)) {
|
||||
LOG_WARNING("SMSG_INITIAL_SPELLS: spell ", i, " truncated (", spellCount, " expected)");
|
||||
break;
|
||||
}
|
||||
|
|
@ -3589,7 +3589,7 @@ bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data
|
|||
}
|
||||
|
||||
// Validate minimum packet size for cooldownCount (2 bytes)
|
||||
if (packet.getRemainingSize() < 2) {
|
||||
if (!packet.hasRemaining(2)) {
|
||||
LOG_WARNING("SMSG_INITIAL_SPELLS: truncated before cooldownCount (parsed ", data.spellIds.size(),
|
||||
" spells)");
|
||||
return true; // Have spells; cooldowns are optional
|
||||
|
|
@ -3612,7 +3612,7 @@ bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data
|
|||
// Vanilla cooldown: spellId(2) + itemId(2) + categoryId(2) + cooldownMs(4) + categoryCooldownMs(4) = 14 bytes
|
||||
// TBC/WotLK cooldown: spellId(4) + itemId(2) + categoryId(2) + cooldownMs(4) + categoryCooldownMs(4) = 16 bytes
|
||||
size_t cooldownEntrySize = vanillaFormat ? 14 : 16;
|
||||
if (packet.getRemainingSize() < cooldownEntrySize) {
|
||||
if (!packet.hasRemaining(cooldownEntrySize)) {
|
||||
LOG_WARNING("SMSG_INITIAL_SPELLS: cooldown ", i, " truncated (", cooldownCount, " expected)");
|
||||
break;
|
||||
}
|
||||
|
|
@ -3698,7 +3698,7 @@ network::Packet PetActionPacket::build(uint64_t petGuid, uint32_t action, uint64
|
|||
|
||||
bool CastFailedParser::parse(network::Packet& packet, CastFailedData& data) {
|
||||
// WotLK format: castCount(1) + spellId(4) + result(1) = 6 bytes minimum
|
||||
if (packet.getRemainingSize() < 6) return false;
|
||||
if (!packet.hasRemaining(6)) return false;
|
||||
|
||||
data.castCount = packet.readUInt8();
|
||||
data.spellId = packet.readUInt32();
|
||||
|
|
@ -3712,7 +3712,7 @@ bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
|||
|
||||
// Packed GUIDs are variable-length; only require minimal packet shape up front:
|
||||
// two GUID masks + castCount(1) + spellId(4) + castFlags(4) + castTime(4).
|
||||
if (packet.getRemainingSize() < 15) return false;
|
||||
if (!packet.hasRemaining(15)) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!packet.hasFullPackedGuid()) {
|
||||
|
|
@ -3726,7 +3726,7 @@ bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
|||
data.casterUnit = packet.readPackedGuid();
|
||||
|
||||
// Validate remaining fixed fields (castCount + spellId + castFlags + castTime = 13 bytes)
|
||||
if (packet.getRemainingSize() < 13) {
|
||||
if (!packet.hasRemaining(13)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3737,7 +3737,7 @@ bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
|||
data.castTime = packet.readUInt32();
|
||||
|
||||
// SpellCastTargets starts with target flags and is mandatory.
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
LOG_WARNING("Spell start: missing targetFlags");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
|
|
@ -3757,7 +3757,7 @@ bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
|||
auto skipPackedAndFloats3 = [&]() -> bool {
|
||||
if (!packet.hasFullPackedGuid()) return false;
|
||||
packet.readPackedGuid(); // transport GUID (may be zero)
|
||||
if (packet.getRemainingSize() < 12) return false;
|
||||
if (!packet.hasRemaining(12)) return false;
|
||||
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
||||
return true;
|
||||
};
|
||||
|
|
@ -3793,7 +3793,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
|
||||
// Packed GUIDs are variable-length, so only require the smallest possible
|
||||
// shape up front: 2 GUID masks + fixed fields through hitCount.
|
||||
if (packet.getRemainingSize() < 16) return false;
|
||||
if (!packet.hasRemaining(16)) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!packet.hasFullPackedGuid()) {
|
||||
|
|
@ -3807,7 +3807,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
data.casterUnit = packet.readPackedGuid();
|
||||
|
||||
// Validate remaining fixed fields up to hitCount/missCount
|
||||
if (packet.getRemainingSize() < 14) { // castCount(1) + spellId(4) + castFlags(4) + timestamp(4) + hitCount(1)
|
||||
if (!packet.hasRemaining(14)) { // castCount(1) + spellId(4) + castFlags(4) + timestamp(4) + hitCount(1)
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3829,7 +3829,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
data.hitTargets.reserve(storedHitLimit);
|
||||
for (uint16_t i = 0; i < rawHitCount; ++i) {
|
||||
// WotLK 3.3.5a hit targets are full uint64 GUIDs (not PackedGuid).
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("Spell go: truncated hit targets at index ", i, "/", static_cast<int>(rawHitCount));
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
|
|
@ -3846,7 +3846,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
data.hitCount = static_cast<uint8_t>(data.hitTargets.size());
|
||||
|
||||
// missCount is mandatory in SMSG_SPELL_GO. Missing byte means truncation.
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
LOG_WARNING("Spell go: missing missCount after hit target list");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
|
|
@ -3884,7 +3884,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
for (uint16_t i = 0; i < rawMissCount; ++i) {
|
||||
// WotLK 3.3.5a miss targets are full uint64 GUIDs + uint8 missType.
|
||||
// REFLECT additionally appends uint8 reflectResult.
|
||||
if (packet.getRemainingSize() < 9) { // 8 GUID + 1 missType
|
||||
if (!packet.hasRemaining(9)) { // 8 GUID + 1 missType
|
||||
LOG_WARNING("Spell go: truncated miss targets at index ", i, "/", static_cast<int>(rawMissCount),
|
||||
" spell=", data.spellId, " hits=", static_cast<int>(data.hitCount));
|
||||
truncatedTargets = true;
|
||||
|
|
@ -3894,7 +3894,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
m.targetGuid = packet.readUInt64();
|
||||
m.missType = packet.readUInt8();
|
||||
if (m.missType == 11) { // SPELL_MISS_REFLECT
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
LOG_WARNING("Spell go: truncated reflect payload at miss index ", i, "/", static_cast<int>(rawMissCount));
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
|
|
@ -3920,7 +3920,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
// any trailing fields after the target section are not misaligned for
|
||||
// ground-targeted or AoE spells. Same layout as SpellStartParser.
|
||||
if (packet.hasData()) {
|
||||
if (packet.getRemainingSize() >= 4) {
|
||||
if (packet.hasRemaining(4)) {
|
||||
uint32_t targetFlags = packet.readUInt32();
|
||||
|
||||
auto readPackedTarget = [&](uint64_t* out) -> bool {
|
||||
|
|
@ -3932,7 +3932,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
auto skipPackedAndFloats3 = [&]() -> bool {
|
||||
if (!packet.hasFullPackedGuid()) return false;
|
||||
packet.readPackedGuid(); // transport GUID
|
||||
if (packet.getRemainingSize() < 12) return false;
|
||||
if (!packet.hasRemaining(12)) return false;
|
||||
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
||||
return true;
|
||||
};
|
||||
|
|
@ -3967,7 +3967,7 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
|
||||
bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool isAll) {
|
||||
// Validation: packed GUID (1-8 bytes minimum for reading)
|
||||
if (packet.getRemainingSize() < 1) return false;
|
||||
if (!packet.hasRemaining(1)) return false;
|
||||
|
||||
data.guid = packet.readPackedGuid();
|
||||
|
||||
|
|
@ -3977,7 +3977,7 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool
|
|||
|
||||
while (packet.hasData() && auraCount < maxAuras) {
|
||||
// Validate we can read slot (1) + spellId (4) = 5 bytes minimum
|
||||
if (packet.getRemainingSize() < 5) {
|
||||
if (!packet.hasRemaining(5)) {
|
||||
LOG_DEBUG("Aura update: truncated entry at position ", auraCount);
|
||||
break;
|
||||
}
|
||||
|
|
@ -3991,7 +3991,7 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool
|
|||
aura.spellId = spellId;
|
||||
|
||||
// Validate flags + level + charges (3 bytes)
|
||||
if (packet.getRemainingSize() < 3) {
|
||||
if (!packet.hasRemaining(3)) {
|
||||
LOG_WARNING("Aura update: truncated flags/level/charges at entry ", auraCount);
|
||||
aura.flags = 0;
|
||||
aura.level = 0;
|
||||
|
|
@ -4004,7 +4004,7 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool
|
|||
|
||||
if (!(aura.flags & 0x08)) { // NOT_CASTER flag
|
||||
// Validate space for packed GUID read (minimum 1 byte)
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
aura.casterGuid = 0;
|
||||
} else {
|
||||
aura.casterGuid = packet.readPackedGuid();
|
||||
|
|
@ -4012,7 +4012,7 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool
|
|||
}
|
||||
|
||||
if (aura.flags & 0x20) { // DURATION - need 8 bytes (two uint32s)
|
||||
if (packet.getRemainingSize() < 8) {
|
||||
if (!packet.hasRemaining(8)) {
|
||||
LOG_WARNING("Aura update: truncated duration fields at entry ", auraCount);
|
||||
aura.maxDurationMs = 0;
|
||||
aura.durationMs = 0;
|
||||
|
|
@ -4026,7 +4026,7 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool
|
|||
// Only read amounts for active effect indices (flags 0x01, 0x02, 0x04)
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (aura.flags & (1 << i)) {
|
||||
if (packet.getRemainingSize() >= 4) {
|
||||
if (packet.hasRemaining(4)) {
|
||||
packet.readUInt32();
|
||||
} else {
|
||||
LOG_WARNING("Aura update: truncated effect amount ", i, " at entry ", auraCount);
|
||||
|
|
@ -4054,7 +4054,7 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool
|
|||
|
||||
bool SpellCooldownParser::parse(network::Packet& packet, SpellCooldownData& data) {
|
||||
// Upfront validation: guid(8) + flags(1) = 9 bytes minimum
|
||||
if (packet.getRemainingSize() < 9) return false;
|
||||
if (!packet.hasRemaining(9)) return false;
|
||||
|
||||
data.guid = packet.readUInt64();
|
||||
data.flags = packet.readUInt8();
|
||||
|
|
@ -4092,7 +4092,7 @@ network::Packet GroupInvitePacket::build(const std::string& playerName) {
|
|||
|
||||
bool GroupInviteResponseParser::parse(network::Packet& packet, GroupInviteResponseData& data) {
|
||||
// Validate minimum packet size: canAccept(1)
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
LOG_WARNING("SMSG_GROUP_INVITE: packet too small (", packet.getSize(), " bytes)");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -4200,13 +4200,13 @@ bool GroupListParser::parse(network::Packet& packet, GroupListData& data, bool h
|
|||
|
||||
bool PartyCommandResultParser::parse(network::Packet& packet, PartyCommandResultData& data) {
|
||||
// Upfront validation: command(4) + name(var) + result(4) = 8 bytes minimum (plus name string)
|
||||
if (packet.getRemainingSize() < 8) return false;
|
||||
if (!packet.hasRemaining(8)) return false;
|
||||
|
||||
data.command = static_cast<PartyCommand>(packet.readUInt32());
|
||||
data.name = packet.readString();
|
||||
|
||||
// Validate result field exists (4 bytes)
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
data.result = static_cast<PartyResult>(0);
|
||||
return true; // Partial read is acceptable
|
||||
}
|
||||
|
|
@ -4218,7 +4218,7 @@ bool PartyCommandResultParser::parse(network::Packet& packet, PartyCommandResult
|
|||
|
||||
bool GroupDeclineResponseParser::parse(network::Packet& packet, GroupDeclineData& data) {
|
||||
// Upfront validation: playerName is a CString (minimum 1 null terminator)
|
||||
if (packet.getRemainingSize() < 1) return false;
|
||||
if (!packet.hasRemaining(1)) return false;
|
||||
|
||||
data.playerName = packet.readString();
|
||||
LOG_INFO("Group decline from: ", data.playerName);
|
||||
|
|
@ -4367,7 +4367,7 @@ bool LootResponseParser::parse(network::Packet& packet, LootResponseData& data,
|
|||
|
||||
// Quest item section only present in WotLK 3.3.5a
|
||||
uint8_t questItemCount = 0;
|
||||
if (isWotlkFormat && packet.getRemainingSize() >= 1) {
|
||||
if (isWotlkFormat && packet.hasRemaining(1)) {
|
||||
questItemCount = packet.readUInt8();
|
||||
data.items.reserve(data.items.size() + questItemCount);
|
||||
if (!parseLootItemList(questItemCount, true)) {
|
||||
|
|
@ -4499,7 +4499,7 @@ bool QuestDetailsParser::parse(network::Packet& packet, QuestDetailsData& data)
|
|||
|
||||
bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data) {
|
||||
// Upfront validation: npcGuid(8) + menuId(4) + titleTextId(4) + optionCount(4) = 20 bytes minimum
|
||||
if (packet.getRemainingSize() < 20) return false;
|
||||
if (!packet.hasRemaining(20)) return false;
|
||||
|
||||
data.npcGuid = packet.readUInt64();
|
||||
data.menuId = packet.readUInt32();
|
||||
|
|
@ -4518,7 +4518,7 @@ bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data
|
|||
for (uint32_t i = 0; i < optionCount; ++i) {
|
||||
// Each option: id(4) + icon(1) + isCoded(1) + boxMoney(4) + text(var) + boxText(var)
|
||||
// Minimum: 10 bytes + 2 empty strings (2 null terminators) = 12 bytes
|
||||
if (packet.getRemainingSize() < 12) {
|
||||
if (!packet.hasRemaining(12)) {
|
||||
LOG_WARNING("GossipMessageParser: truncated options at index ", i, "/", optionCount);
|
||||
break;
|
||||
}
|
||||
|
|
@ -4533,7 +4533,7 @@ bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data
|
|||
}
|
||||
|
||||
// Validate questCount field exists (4 bytes)
|
||||
if (packet.getRemainingSize() < 4) {
|
||||
if (!packet.hasRemaining(4)) {
|
||||
LOG_DEBUG("Gossip: ", data.options.size(), " options (no quest data)");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -4551,7 +4551,7 @@ bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data
|
|||
for (uint32_t i = 0; i < questCount; ++i) {
|
||||
// Each quest: questId(4) + questIcon(4) + questLevel(4) + questFlags(4) + isRepeatable(1) + title(var)
|
||||
// Minimum: 17 bytes + empty string (1 null terminator) = 18 bytes
|
||||
if (packet.getRemainingSize() < 18) {
|
||||
if (!packet.hasRemaining(18)) {
|
||||
LOG_WARNING("GossipMessageParser: truncated quests at index ", i, "/", questCount);
|
||||
break;
|
||||
}
|
||||
|
|
@ -4590,7 +4590,7 @@ bool BindPointUpdateParser::parse(network::Packet& packet, BindPointUpdateData&
|
|||
}
|
||||
|
||||
bool QuestRequestItemsParser::parse(network::Packet& packet, QuestRequestItemsData& data) {
|
||||
if (packet.getRemainingSize() < 20) return false;
|
||||
if (!packet.hasRemaining(20)) return false;
|
||||
data.npcGuid = packet.readUInt64();
|
||||
data.questId = packet.readUInt32();
|
||||
data.title = normalizeWowTextTokens(packet.readString());
|
||||
|
|
@ -4679,7 +4679,7 @@ bool QuestRequestItemsParser::parse(network::Packet& packet, QuestRequestItemsDa
|
|||
}
|
||||
|
||||
bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData& data) {
|
||||
if (packet.getRemainingSize() < 20) return false;
|
||||
if (!packet.hasRemaining(20)) return false;
|
||||
data.npcGuid = packet.readUInt64();
|
||||
data.questId = packet.readUInt32();
|
||||
data.title = normalizeWowTextTokens(packet.readString());
|
||||
|
|
@ -4887,7 +4887,7 @@ network::Packet BuybackItemPacket::build(uint64_t vendorGuid, uint32_t slot) {
|
|||
|
||||
bool ListInventoryParser::parse(network::Packet& packet, ListInventoryData& data) {
|
||||
data = ListInventoryData{};
|
||||
if (packet.getRemainingSize() < 9) {
|
||||
if (!packet.hasRemaining(9)) {
|
||||
LOG_WARNING("ListInventoryParser: packet too short");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -4919,7 +4919,7 @@ bool ListInventoryParser::parse(network::Packet& packet, ListInventoryData& data
|
|||
data.items.reserve(itemCount);
|
||||
for (uint8_t i = 0; i < itemCount; ++i) {
|
||||
const size_t perItemBytes = hasExtendedCost ? bytesPerItemWithExt : bytesPerItemNoExt;
|
||||
if (packet.getRemainingSize() < perItemBytes) {
|
||||
if (!packet.hasRemaining(perItemBytes)) {
|
||||
LOG_WARNING("ListInventoryParser: item ", static_cast<int>(i), " truncated");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -4949,7 +4949,7 @@ bool TrainerListParser::parse(network::Packet& packet, TrainerListData& data, bo
|
|||
// Classic per-entry: spellId(4) + state(1) + cost(4) + reqLevel(1) +
|
||||
// reqSkill(4) + reqSkillValue(4) + chain×3(12) + unk(4) = 34 bytes
|
||||
data = TrainerListData{};
|
||||
if (packet.getRemainingSize() < 16) return false; // guid(8) + type(4) + count(4)
|
||||
if (!packet.hasRemaining(16)) return false; // guid(8) + type(4) + count(4)
|
||||
|
||||
data.trainerGuid = packet.readUInt64();
|
||||
data.trainerType = packet.readUInt32();
|
||||
|
|
@ -5067,7 +5067,7 @@ bool TalentsInfoParser::parse(network::Packet& packet, TalentsInfoData& data) {
|
|||
data.talents.reserve(entryCount);
|
||||
|
||||
for (uint16_t i = 0; i < entryCount; ++i) {
|
||||
if (packet.getRemainingSize() < 5) {
|
||||
if (!packet.hasRemaining(5)) {
|
||||
LOG_ERROR("SMSG_TALENTS_INFO: truncated entry list at i=", i);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -5079,7 +5079,7 @@ bool TalentsInfoParser::parse(network::Packet& packet, TalentsInfoData& data) {
|
|||
}
|
||||
|
||||
// Parse glyph tail: glyphSlots + glyphIds[]
|
||||
if (packet.getRemainingSize() < 1) {
|
||||
if (!packet.hasRemaining(1)) {
|
||||
LOG_WARNING("SMSG_TALENTS_INFO: no glyph tail data");
|
||||
return true; // Not fatal, older formats may not have glyphs
|
||||
}
|
||||
|
|
@ -5098,7 +5098,7 @@ bool TalentsInfoParser::parse(network::Packet& packet, TalentsInfoData& data) {
|
|||
data.glyphs.reserve(glyphSlots);
|
||||
|
||||
for (uint8_t i = 0; i < glyphSlots; ++i) {
|
||||
if (packet.getRemainingSize() < 2) {
|
||||
if (!packet.hasRemaining(2)) {
|
||||
LOG_ERROR("SMSG_TALENTS_INFO: truncated glyph list at i=", i);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -5496,7 +5496,7 @@ network::Packet GuildBankSwapItemsPacket::buildInventoryToBank(
|
|||
}
|
||||
|
||||
bool GuildBankListParser::parse(network::Packet& packet, GuildBankData& data) {
|
||||
if (packet.getRemainingSize() < 14) return false;
|
||||
if (!packet.hasRemaining(14)) return false;
|
||||
|
||||
data.money = packet.readUInt64();
|
||||
data.tabId = packet.readUInt8();
|
||||
|
|
@ -5698,7 +5698,7 @@ bool AuctionListResultParser::parse(network::Packet& packet, AuctionListResult&
|
|||
// bidderGuid(8) + curBid(4)
|
||||
// Classic: numEnchantSlots=1 → 80 bytes/entry
|
||||
// TBC/WotLK: numEnchantSlots=3 → 104 bytes/entry
|
||||
if (packet.getRemainingSize() < 4) return false;
|
||||
if (!packet.hasRemaining(4)) return false;
|
||||
|
||||
uint32_t count = packet.readUInt32();
|
||||
// Cap auction count to prevent unbounded memory allocation
|
||||
|
|
@ -5742,7 +5742,7 @@ bool AuctionListResultParser::parse(network::Packet& packet, AuctionListResult&
|
|||
data.auctions.push_back(e);
|
||||
}
|
||||
|
||||
if (packet.getRemainingSize() >= 8) {
|
||||
if (packet.hasRemaining(8)) {
|
||||
data.totalCount = packet.readUInt32();
|
||||
data.searchDelay = packet.readUInt32();
|
||||
}
|
||||
|
|
@ -5750,7 +5750,7 @@ bool AuctionListResultParser::parse(network::Packet& packet, AuctionListResult&
|
|||
}
|
||||
|
||||
bool AuctionCommandResultParser::parse(network::Packet& packet, AuctionCommandResult& data) {
|
||||
if (packet.getRemainingSize() < 12) return false;
|
||||
if (!packet.hasRemaining(12)) return false;
|
||||
data.auctionId = packet.readUInt32();
|
||||
data.action = packet.readUInt32();
|
||||
data.errorCode = packet.readUInt32();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue