From a147347393a3fad09f76f7592ed0c9499b25b3af Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 00:16:28 -0700 Subject: [PATCH 01/16] fix(combattext): honor health leech multipliers --- src/game/game_handler.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 74e1782c..fa82fd09 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6503,16 +6503,26 @@ void GameHandler::handlePacket(network::Packet& packet) { : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 8) { packet.setReadPos(packet.getSize()); break; } uint32_t leechAmount = packet.readUInt32(); - /*float leechMult =*/ packet.readFloat(); + float leechMult = packet.readFloat(); if (leechAmount > 0) { - if (leechTarget == playerGuid) + if (leechTarget == playerGuid) { addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast(leechAmount), exeSpellId, false, 0, exeCaster, leechTarget); - else if (isPlayerCaster) - addCombatText(CombatTextEntry::HEAL, static_cast(leechAmount), exeSpellId, true, 0, - exeCaster, exeCaster); + } else if (isPlayerCaster) { + addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast(leechAmount), exeSpellId, true, 0, + exeCaster, leechTarget); + } + if (isPlayerCaster && leechMult > 0.0f && std::isfinite(leechMult)) { + const uint32_t gainedAmount = static_cast( + std::lround(static_cast(leechAmount) * static_cast(leechMult))); + if (gainedAmount > 0) { + addCombatText(CombatTextEntry::HEAL, static_cast(gainedAmount), exeSpellId, true, 0, + exeCaster, exeCaster); + } + } } - LOG_DEBUG("SMSG_SPELLLOGEXECUTE HEALTH_LEECH: spell=", exeSpellId, " amount=", leechAmount); + LOG_DEBUG("SMSG_SPELLLOGEXECUTE HEALTH_LEECH: spell=", exeSpellId, + " amount=", leechAmount, " multiplier=", leechMult); } } else if (effectType == 24 || effectType == 114) { // SPELL_EFFECT_CREATE_ITEM / CREATE_ITEM2: uint32 itemEntry per log entry From ed5134d60115ceab56b5ffbf8682e10c192a8e94 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 00:24:21 -0700 Subject: [PATCH 02/16] fix(combatlog): parse classic spelllogexecute GUIDs as packed --- src/game/game_handler.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index fa82fd09..ca3b453c 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6408,8 +6408,8 @@ void GameHandler::handlePacket(network::Packet& packet) { break; } case Opcode::SMSG_SPELLLOGEXECUTE: { - // WotLK: packed_guid caster + uint32 spellId + uint32 effectCount - // TBC/Classic: uint64 caster + uint32 spellId + uint32 effectCount + // WotLK/Classic/Turtle: packed_guid caster + uint32 spellId + uint32 effectCount + // TBC: uint64 caster + uint32 spellId + uint32 effectCount // Per-effect: uint8 effectType + uint32 effectLogCount + effect-specific data // Effect 10 = POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier // Effect 11 = HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier @@ -6417,7 +6417,7 @@ void GameHandler::handlePacket(network::Packet& packet) { // Effect 26 = INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id // Effect 49 = FEED_PET: uint32 itemEntry // Effect 114= CREATE_ITEM2: uint32 itemEntry (same layout as CREATE_ITEM) - const bool exeTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); + const bool exeUsesFullGuid = isActiveExpansion("tbc"); const auto hasFullPackedGuid = [&packet]() -> bool { if (packet.getReadPos() >= packet.getSize()) { return false; @@ -6432,13 +6432,13 @@ void GameHandler::handlePacket(network::Packet& packet) { } return packet.getSize() - packet.getReadPos() >= guidBytes; }; - if (packet.getSize() - packet.getReadPos() < (exeTbcLike ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - if (!exeTbcLike && !hasFullPackedGuid()) { + if (!exeUsesFullGuid && !hasFullPackedGuid()) { packet.setReadPos(packet.getSize()); break; } - uint64_t exeCaster = exeTbcLike + uint64_t exeCaster = exeUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 8) { packet.setReadPos(packet.getSize()); break; @@ -6456,11 +6456,11 @@ void GameHandler::handlePacket(network::Packet& packet) { if (effectType == 10) { // SPELL_EFFECT_POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier for (uint32_t li = 0; li < effectLogCount; ++li) { - if (packet.getSize() - packet.getReadPos() < (exeTbcLike ? 8u : 1u) - || (!exeTbcLike && !hasFullPackedGuid())) { + if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u) + || (!exeUsesFullGuid && !hasFullPackedGuid())) { packet.setReadPos(packet.getSize()); break; } - uint64_t drainTarget = exeTbcLike + uint64_t drainTarget = exeUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 12) { packet.setReadPos(packet.getSize()); break; } @@ -6494,11 +6494,11 @@ void GameHandler::handlePacket(network::Packet& packet) { } else if (effectType == 11) { // SPELL_EFFECT_HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier for (uint32_t li = 0; li < effectLogCount; ++li) { - if (packet.getSize() - packet.getReadPos() < (exeTbcLike ? 8u : 1u) - || (!exeTbcLike && !hasFullPackedGuid())) { + if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u) + || (!exeUsesFullGuid && !hasFullPackedGuid())) { packet.setReadPos(packet.getSize()); break; } - uint64_t leechTarget = exeTbcLike + uint64_t leechTarget = exeUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 8) { packet.setReadPos(packet.getSize()); break; } @@ -6549,11 +6549,11 @@ void GameHandler::handlePacket(network::Packet& packet) { } else if (effectType == 26) { // SPELL_EFFECT_INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id for (uint32_t li = 0; li < effectLogCount; ++li) { - if (packet.getSize() - packet.getReadPos() < (exeTbcLike ? 8u : 1u) - || (!exeTbcLike && !hasFullPackedGuid())) { + if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u) + || (!exeUsesFullGuid && !hasFullPackedGuid())) { packet.setReadPos(packet.getSize()); break; } - uint64_t icTarget = exeTbcLike + uint64_t icTarget = exeUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 4) { packet.setReadPos(packet.getSize()); break; } From 9c3b5d17cf97aa7eeb540dd939b7b75874aa02f4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 00:31:35 -0700 Subject: [PATCH 03/16] fix(combatlog): parse classic resist log GUIDs as packed --- src/game/game_handler.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index ca3b453c..ff594ad6 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -7002,19 +7002,19 @@ void GameHandler::handlePacket(network::Packet& packet) { // ---- Resistance/combat log ---- case Opcode::SMSG_RESISTLOG: { - // WotLK: uint32 hitInfo + packed_guid attacker + packed_guid victim + uint32 spellId - // + float resistFactor + uint32 targetRes + uint32 resistedValue + ... - // TBC/Classic: same but full uint64 GUIDs + // WotLK/Classic/Turtle: uint32 hitInfo + packed_guid attacker + packed_guid victim + uint32 spellId + // + float resistFactor + uint32 targetRes + uint32 resistedValue + ... + // TBC: same layout but full uint64 GUIDs // Show RESIST combat text when player resists an incoming spell. - const bool rlTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); + const bool rlUsesFullGuid = isActiveExpansion("tbc"); auto rl_rem = [&]() { return packet.getSize() - packet.getReadPos(); }; if (rl_rem() < 4) { packet.setReadPos(packet.getSize()); break; } /*uint32_t hitInfo =*/ packet.readUInt32(); - if (rl_rem() < (rlTbcLike ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t attackerGuid = rlTbcLike + if (rl_rem() < (rlUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + uint64_t attackerGuid = rlUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (rl_rem() < (rlTbcLike ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t victimGuid = rlTbcLike + if (rl_rem() < (rlUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + uint64_t victimGuid = rlUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (rl_rem() < 4) { packet.setReadPos(packet.getSize()); break; } uint32_t spellId = packet.readUInt32(); From 8ba5ca5337b5570ee75b9551277d7070cf3ca57c Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 00:38:22 -0700 Subject: [PATCH 04/16] fix(combatlog): parse classic instakill log GUIDs as packed --- src/game/game_handler.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index ff594ad6..f7937079 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6384,15 +6384,15 @@ void GameHandler::handlePacket(network::Packet& packet) { } case Opcode::SMSG_SPELLINSTAKILLLOG: { // Sent when a unit is killed by a spell with SPELL_ATTR_EX2_INSTAKILL (e.g. Execute, Obliterate, etc.) - // WotLK: packed_guid caster + packed_guid victim + uint32 spellId - // TBC/Classic: full uint64 caster + full uint64 victim + uint32 spellId - const bool ikTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); + // WotLK/Classic/Turtle: packed_guid caster + packed_guid victim + uint32 spellId + // TBC: full uint64 caster + full uint64 victim + uint32 spellId + const bool ikUsesFullGuid = isActiveExpansion("tbc"); auto ik_rem = [&]() { return packet.getSize() - packet.getReadPos(); }; - if (ik_rem() < (ikTbcLike ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t ikCaster = ikTbcLike + if (ik_rem() < (ikUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + uint64_t ikCaster = ikUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (ik_rem() < (ikTbcLike ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t ikVictim = ikTbcLike + if (ik_rem() < (ikUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + uint64_t ikVictim = ikUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); uint32_t ikSpell = (ik_rem() >= 4) ? packet.readUInt32() : 0; // Show kill/death feedback for the local player From fd8ea4e69e026b52b2ae00c19a41d17ff98988cd Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 00:45:50 -0700 Subject: [PATCH 05/16] fix(combatlog): parse classic proc log GUIDs as packed --- src/game/game_handler.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f7937079..beba372d 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2086,17 +2086,17 @@ void GameHandler::handlePacket(network::Packet& packet) { // ---- Spell proc resist log ---- case Opcode::SMSG_PROCRESIST: { - // WotLK: packed_guid caster + packed_guid victim + uint32 spellId + ... - // TBC/Classic: uint64 caster + uint64 victim + uint32 spellId + ... - const bool prTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); + // WotLK/Classic/Turtle: packed_guid caster + packed_guid victim + uint32 spellId + ... + // TBC: uint64 caster + uint64 victim + uint32 spellId + ... + const bool prUsesFullGuid = isActiveExpansion("tbc"); auto readPrGuid = [&]() -> uint64_t { - if (prTbcLike) + if (prUsesFullGuid) return (packet.getSize() - packet.getReadPos() >= 8) ? packet.readUInt64() : 0; return UpdateObjectParser::readPackedGuid(packet); }; - if (packet.getSize() - packet.getReadPos() < (prTbcLike ? 8u : 1u)) break; + if (packet.getSize() - packet.getReadPos() < (prUsesFullGuid ? 8u : 1u)) break; uint64_t caster = readPrGuid(); - if (packet.getSize() - packet.getReadPos() < (prTbcLike ? 8u : 1u)) break; + if (packet.getSize() - packet.getReadPos() < (prUsesFullGuid ? 8u : 1u)) break; uint64_t victim = readPrGuid(); if (packet.getSize() - packet.getReadPos() < 4) break; uint32_t spellId = packet.readUInt32(); @@ -6355,19 +6355,19 @@ void GameHandler::handlePacket(network::Packet& packet) { break; } case Opcode::SMSG_SPELL_CHANCE_PROC_LOG: { - // WotLK: packed_guid target + packed_guid caster + uint32 spellId + ... - // TBC/Classic: uint64 target + uint64 caster + uint32 spellId + ... - const bool procChanceTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); + // WotLK/Classic/Turtle: packed_guid target + packed_guid caster + uint32 spellId + ... + // TBC: uint64 target + uint64 caster + uint32 spellId + ... + const bool procChanceUsesFullGuid = isActiveExpansion("tbc"); auto readProcChanceGuid = [&]() -> uint64_t { - if (procChanceTbcLike) + if (procChanceUsesFullGuid) return (packet.getSize() - packet.getReadPos() >= 8) ? packet.readUInt64() : 0; return UpdateObjectParser::readPackedGuid(packet); }; - if (packet.getSize() - packet.getReadPos() < (procChanceTbcLike ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (procChanceUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } uint64_t procTargetGuid = readProcChanceGuid(); - if (packet.getSize() - packet.getReadPos() < (procChanceTbcLike ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (procChanceUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } uint64_t procCasterGuid = readProcChanceGuid(); From 1fa2cbc64e1713d73387c9b0dd917f72d7cfe16d Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 00:53:42 -0700 Subject: [PATCH 06/16] fix(combatlog): parse classic dispel and spellsteal GUIDs as packed --- src/game/game_handler.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index beba372d..a43e4c94 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6202,17 +6202,17 @@ void GameHandler::handlePacket(network::Packet& packet) { break; } case Opcode::SMSG_SPELLDISPELLOG: { - // WotLK: packed casterGuid + packed victimGuid + uint32 dispelSpell + uint8 isStolen - // TBC/Classic: full uint64 casterGuid + full uint64 victimGuid + ... + // WotLK/Classic/Turtle: packed casterGuid + packed victimGuid + uint32 dispelSpell + uint8 isStolen + // TBC: full uint64 casterGuid + full uint64 victimGuid + ... // + uint32 count + count × (uint32 dispelled_spellId + uint32 unk) - const bool dispelTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); - if (packet.getSize() - packet.getReadPos() < (dispelTbcLike ? 8u : 2u)) { + const bool dispelUsesFullGuid = isActiveExpansion("tbc"); + if (packet.getSize() - packet.getReadPos() < (dispelUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t casterGuid = dispelTbcLike + uint64_t casterGuid = dispelUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < (dispelTbcLike ? 8u : 2u)) break; - uint64_t victimGuid = dispelTbcLike + if (packet.getSize() - packet.getReadPos() < (dispelUsesFullGuid ? 8u : 1u)) break; + uint64_t victimGuid = dispelUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 9) break; /*uint32_t dispelSpell =*/ packet.readUInt32(); @@ -6220,12 +6220,12 @@ void GameHandler::handlePacket(network::Packet& packet) { uint32_t count = packet.readUInt32(); // Preserve every dispelled aura in the combat log instead of collapsing // multi-aura packets down to the first entry only. - const size_t dispelEntrySize = dispelTbcLike ? 8u : 5u; + const size_t dispelEntrySize = dispelUsesFullGuid ? 8u : 5u; std::vector dispelledIds; dispelledIds.reserve(count); for (uint32_t i = 0; i < count && packet.getSize() - packet.getReadPos() >= dispelEntrySize; ++i) { uint32_t dispelledId = packet.readUInt32(); - if (dispelTbcLike) { + if (dispelUsesFullGuid) { /*uint32_t unk =*/ packet.readUInt32(); } else { /*uint8_t isPositive =*/ packet.readUInt8(); @@ -6288,19 +6288,19 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_SPELLSTEALLOG: { // Sent to the CASTER (Mage) when Spellsteal succeeds. // Wire format mirrors SPELLDISPELLOG: - // WotLK: packed victim + packed caster + uint32 spellId + uint8 isStolen + uint32 count - // + count × (uint32 stolenSpellId + uint8 isPositive) - // TBC/Classic: full uint64 victim + full uint64 caster + same tail - const bool stealTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); - if (packet.getSize() - packet.getReadPos() < (stealTbcLike ? 8u : 2u)) { + // WotLK/Classic/Turtle: packed victim + packed caster + uint32 spellId + uint8 isStolen + uint32 count + // + count × (uint32 stolenSpellId + uint8 isPositive) + // TBC: full uint64 victim + full uint64 caster + same tail + const bool stealUsesFullGuid = isActiveExpansion("tbc"); + if (packet.getSize() - packet.getReadPos() < (stealUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t stealVictim = stealTbcLike + uint64_t stealVictim = stealUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < (stealTbcLike ? 8u : 2u)) { + if (packet.getSize() - packet.getReadPos() < (stealUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - uint64_t stealCaster = stealTbcLike + uint64_t stealCaster = stealUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 9) { packet.setReadPos(packet.getSize()); break; @@ -6309,12 +6309,12 @@ void GameHandler::handlePacket(network::Packet& packet) { /*uint8_t isStolen =*/ packet.readUInt8(); uint32_t stealCount = packet.readUInt32(); // Preserve every stolen aura in the combat log instead of only the first. - const size_t stealEntrySize = stealTbcLike ? 8u : 5u; + const size_t stealEntrySize = stealUsesFullGuid ? 8u : 5u; std::vector stolenIds; stolenIds.reserve(stealCount); for (uint32_t i = 0; i < stealCount && packet.getSize() - packet.getReadPos() >= stealEntrySize; ++i) { uint32_t stolenId = packet.readUInt32(); - if (stealTbcLike) { + if (stealUsesFullGuid) { /*uint32_t unk =*/ packet.readUInt32(); } else { /*uint8_t isPos =*/ packet.readUInt8(); From bd8c46fa49c702b4f529fd5e939f81eb76285695 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:00:56 -0700 Subject: [PATCH 07/16] fix(combatlog): parse classic dispel failed GUIDs as packed --- src/game/game_handler.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index a43e4c94..d528eca5 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -3239,12 +3239,14 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_DISPEL_FAILED: { // WotLK: uint32 dispelSpellId + packed_guid caster + packed_guid victim // [+ count × uint32 failedSpellId] - // TBC/Classic: uint64 caster + uint64 victim + uint32 spellId + // Classic: uint32 dispelSpellId + packed_guid caster + packed_guid victim // [+ count × uint32 failedSpellId] - const bool dispelTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); + // TBC: uint64 caster + uint64 victim + uint32 spellId + // [+ count × uint32 failedSpellId] + const bool dispelUsesFullGuid = isActiveExpansion("tbc"); uint32_t dispelSpellId = 0; uint64_t dispelCasterGuid = 0; - if (dispelTbcLike) { + if (dispelUsesFullGuid) { if (packet.getSize() - packet.getReadPos() < 20) break; dispelCasterGuid = packet.readUInt64(); /*uint64_t victim =*/ packet.readUInt64(); From dbdc45a8a9a9034a84920b9080c6924998db191b Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:10:43 -0700 Subject: [PATCH 08/16] fix(combatlog): validate packed dispel-family GUIDs --- src/game/game_handler.cpp | 60 +++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index d528eca5..a3275997 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -100,6 +100,22 @@ bool envFlagEnabled(const char* key, bool defaultValue = false) { raw[0] == 'n' || raw[0] == 'N'); } +bool hasFullPackedGuid(const network::Packet& packet) { + if (packet.getReadPos() >= packet.getSize()) { + return false; + } + + const auto& rawData = packet.getData(); + const uint8_t mask = rawData[packet.getReadPos()]; + size_t guidBytes = 1; + for (int bit = 0; bit < 8; ++bit) { + if ((mask & (1u << bit)) != 0) { + ++guidBytes; + } + } + return packet.getSize() - packet.getReadPos() >= guidBytes; +} + std::string formatCopperAmount(uint32_t amount) { uint32_t gold = amount / 10000; uint32_t silver = (amount / 100) % 100; @@ -3254,9 +3270,13 @@ void GameHandler::handlePacket(network::Packet& packet) { } else { if (packet.getSize() - packet.getReadPos() < 4) break; dispelSpellId = packet.readUInt32(); - if (packet.getSize() - packet.getReadPos() < 1) break; + if (!hasFullPackedGuid(packet)) { + packet.setReadPos(packet.getSize()); break; + } dispelCasterGuid = UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < 1) break; + if (!hasFullPackedGuid(packet)) { + packet.setReadPos(packet.getSize()); break; + } /*uint64_t victim =*/ UpdateObjectParser::readPackedGuid(packet); } // Only show failure to the player who attempted the dispel @@ -6208,12 +6228,16 @@ void GameHandler::handlePacket(network::Packet& packet) { // TBC: full uint64 casterGuid + full uint64 victimGuid + ... // + uint32 count + count × (uint32 dispelled_spellId + uint32 unk) const bool dispelUsesFullGuid = isActiveExpansion("tbc"); - if (packet.getSize() - packet.getReadPos() < (dispelUsesFullGuid ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (dispelUsesFullGuid ? 8u : 1u) + || (!dispelUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t casterGuid = dispelUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < (dispelUsesFullGuid ? 8u : 1u)) break; + if (packet.getSize() - packet.getReadPos() < (dispelUsesFullGuid ? 8u : 1u) + || (!dispelUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t victimGuid = dispelUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 9) break; @@ -6294,12 +6318,14 @@ void GameHandler::handlePacket(network::Packet& packet) { // + count × (uint32 stolenSpellId + uint8 isPositive) // TBC: full uint64 victim + full uint64 caster + same tail const bool stealUsesFullGuid = isActiveExpansion("tbc"); - if (packet.getSize() - packet.getReadPos() < (stealUsesFullGuid ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (stealUsesFullGuid ? 8u : 1u) + || (!stealUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t stealVictim = stealUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < (stealUsesFullGuid ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (stealUsesFullGuid ? 8u : 1u) + || (!stealUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t stealCaster = stealUsesFullGuid @@ -6420,24 +6446,10 @@ void GameHandler::handlePacket(network::Packet& packet) { // Effect 49 = FEED_PET: uint32 itemEntry // Effect 114= CREATE_ITEM2: uint32 itemEntry (same layout as CREATE_ITEM) const bool exeUsesFullGuid = isActiveExpansion("tbc"); - const auto hasFullPackedGuid = [&packet]() -> bool { - if (packet.getReadPos() >= packet.getSize()) { - return false; - } - const auto& rawData = packet.getData(); - const uint8_t mask = rawData[packet.getReadPos()]; - size_t guidBytes = 1; - for (int bit = 0; bit < 8; ++bit) { - if ((mask & (1u << bit)) != 0) { - ++guidBytes; - } - } - return packet.getSize() - packet.getReadPos() >= guidBytes; - }; if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } - if (!exeUsesFullGuid && !hasFullPackedGuid()) { + if (!exeUsesFullGuid && !hasFullPackedGuid(packet)) { packet.setReadPos(packet.getSize()); break; } uint64_t exeCaster = exeUsesFullGuid @@ -6459,7 +6471,7 @@ void GameHandler::handlePacket(network::Packet& packet) { // SPELL_EFFECT_POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier for (uint32_t li = 0; li < effectLogCount; ++li) { if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u) - || (!exeUsesFullGuid && !hasFullPackedGuid())) { + || (!exeUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t drainTarget = exeUsesFullGuid @@ -6497,7 +6509,7 @@ void GameHandler::handlePacket(network::Packet& packet) { // SPELL_EFFECT_HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier for (uint32_t li = 0; li < effectLogCount; ++li) { if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u) - || (!exeUsesFullGuid && !hasFullPackedGuid())) { + || (!exeUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t leechTarget = exeUsesFullGuid @@ -6552,7 +6564,7 @@ void GameHandler::handlePacket(network::Packet& packet) { // SPELL_EFFECT_INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id for (uint32_t li = 0; li < effectLogCount; ++li) { if (packet.getSize() - packet.getReadPos() < (exeUsesFullGuid ? 8u : 1u) - || (!exeUsesFullGuid && !hasFullPackedGuid())) { + || (!exeUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t icTarget = exeUsesFullGuid From 0fc887a3d249b98c1a6cc37c91e6b95ce29b04be Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:18:28 -0700 Subject: [PATCH 09/16] fix(combatlog): validate packed proc log GUIDs --- src/game/game_handler.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index a3275997..b8db9c3a 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2110,9 +2110,15 @@ void GameHandler::handlePacket(network::Packet& packet) { return (packet.getSize() - packet.getReadPos() >= 8) ? packet.readUInt64() : 0; return UpdateObjectParser::readPackedGuid(packet); }; - if (packet.getSize() - packet.getReadPos() < (prUsesFullGuid ? 8u : 1u)) break; + if (packet.getSize() - packet.getReadPos() < (prUsesFullGuid ? 8u : 1u) + || (!prUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t caster = readPrGuid(); - if (packet.getSize() - packet.getReadPos() < (prUsesFullGuid ? 8u : 1u)) break; + if (packet.getSize() - packet.getReadPos() < (prUsesFullGuid ? 8u : 1u) + || (!prUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t victim = readPrGuid(); if (packet.getSize() - packet.getReadPos() < 4) break; uint32_t spellId = packet.readUInt32(); @@ -6391,11 +6397,13 @@ void GameHandler::handlePacket(network::Packet& packet) { return (packet.getSize() - packet.getReadPos() >= 8) ? packet.readUInt64() : 0; return UpdateObjectParser::readPackedGuid(packet); }; - if (packet.getSize() - packet.getReadPos() < (procChanceUsesFullGuid ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (procChanceUsesFullGuid ? 8u : 1u) + || (!procChanceUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t procTargetGuid = readProcChanceGuid(); - if (packet.getSize() - packet.getReadPos() < (procChanceUsesFullGuid ? 8u : 1u)) { + if (packet.getSize() - packet.getReadPos() < (procChanceUsesFullGuid ? 8u : 1u) + || (!procChanceUsesFullGuid && !hasFullPackedGuid(packet))) { packet.setReadPos(packet.getSize()); break; } uint64_t procCasterGuid = readProcChanceGuid(); From a48f6d1044a3bdb8d720e8cf04aa21526bb03175 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:25:47 -0700 Subject: [PATCH 10/16] fix(combatlog): parse classic immune log GUIDs as packed --- src/game/game_handler.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b8db9c3a..fee5dd0c 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6206,17 +6206,23 @@ void GameHandler::handlePacket(network::Packet& packet) { packet.setReadPos(packet.getSize()); break; case Opcode::SMSG_SPELLORDAMAGE_IMMUNE: { - // WotLK: packed casterGuid + packed victimGuid + uint32 spellId + uint8 saveType - // TBC/Classic: full uint64 casterGuid + full uint64 victimGuid + uint32 + uint8 - const bool immuneTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); - const size_t minSz = immuneTbcLike ? 21u : 2u; + // WotLK/Classic/Turtle: packed casterGuid + packed victimGuid + uint32 spellId + uint8 saveType + // TBC: full uint64 casterGuid + full uint64 victimGuid + uint32 + uint8 + const bool immuneUsesFullGuid = isActiveExpansion("tbc"); + const size_t minSz = immuneUsesFullGuid ? 21u : 2u; if (packet.getSize() - packet.getReadPos() < minSz) { packet.setReadPos(packet.getSize()); break; } - uint64_t casterGuid = immuneTbcLike + if (!immuneUsesFullGuid && !hasFullPackedGuid(packet)) { + packet.setReadPos(packet.getSize()); break; + } + uint64_t casterGuid = immuneUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < (immuneTbcLike ? 8u : 2u)) break; - uint64_t victimGuid = immuneTbcLike + if (packet.getSize() - packet.getReadPos() < (immuneUsesFullGuid ? 8u : 2u) + || (!immuneUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } + uint64_t victimGuid = immuneUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 5) break; uint32_t immuneSpellId = packet.readUInt32(); From 0968a11234fa837e1e0403131638162b845662df Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:32:45 -0700 Subject: [PATCH 11/16] fix(combatlog): validate packed instakill GUIDs --- src/game/game_handler.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index fee5dd0c..0b849230 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6430,10 +6430,16 @@ void GameHandler::handlePacket(network::Packet& packet) { // TBC: full uint64 caster + full uint64 victim + uint32 spellId const bool ikUsesFullGuid = isActiveExpansion("tbc"); auto ik_rem = [&]() { return packet.getSize() - packet.getReadPos(); }; - if (ik_rem() < (ikUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + if (ik_rem() < (ikUsesFullGuid ? 8u : 1u) + || (!ikUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t ikCaster = ikUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (ik_rem() < (ikUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + if (ik_rem() < (ikUsesFullGuid ? 8u : 1u) + || (!ikUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t ikVictim = ikUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); uint32_t ikSpell = (ik_rem() >= 4) ? packet.readUInt32() : 0; From 468880e2c89a170d839b44aeba68b5b73473dc3d Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:39:53 -0700 Subject: [PATCH 12/16] fix(combatlog): validate packed resist log GUIDs --- src/game/game_handler.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 0b849230..54ca6119 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -7044,10 +7044,16 @@ void GameHandler::handlePacket(network::Packet& packet) { auto rl_rem = [&]() { return packet.getSize() - packet.getReadPos(); }; if (rl_rem() < 4) { packet.setReadPos(packet.getSize()); break; } /*uint32_t hitInfo =*/ packet.readUInt32(); - if (rl_rem() < (rlUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + if (rl_rem() < (rlUsesFullGuid ? 8u : 1u) + || (!rlUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t attackerGuid = rlUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (rl_rem() < (rlUsesFullGuid ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; } + if (rl_rem() < (rlUsesFullGuid ? 8u : 1u) + || (!rlUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t victimGuid = rlUsesFullGuid ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (rl_rem() < 4) { packet.setReadPos(packet.getSize()); break; } From b059bbcf891b17fdf1d20209465263681bdb2ed7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:47:06 -0700 Subject: [PATCH 13/16] fix(combatlog): parse classic spell damage shield GUIDs as packed --- src/game/game_handler.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 54ca6119..844bafd6 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6170,23 +6170,32 @@ void GameHandler::handlePacket(network::Packet& packet) { // ---- Spell combat logs (consume) ---- case Opcode::SMSG_SPELLDAMAGESHIELD: { - // Classic/TBC: uint64 victim + uint64 caster + spellId(4) + damage(4) + schoolMask(4) - // WotLK: packed_guid victim + packed_guid caster + spellId(4) + damage(4) + absorbed(4) + schoolMask(4) - const bool shieldClassicLike = isClassicLikeExpansion() || isActiveExpansion("tbc"); - const size_t shieldMinSz = shieldClassicLike ? 24u : 2u; + // Classic: packed_guid victim + packed_guid caster + spellId(4) + damage(4) + schoolMask(4) + // TBC: uint64 victim + uint64 caster + spellId(4) + damage(4) + schoolMask(4) + // WotLK: packed_guid victim + packed_guid caster + spellId(4) + damage(4) + absorbed(4) + schoolMask(4) + const bool shieldTbc = isActiveExpansion("tbc"); + const bool shieldWotlkLike = !isClassicLikeExpansion() && !shieldTbc; + const size_t shieldMinSz = shieldTbc ? 24u : 2u; if (packet.getSize() - packet.getReadPos() < shieldMinSz) { packet.setReadPos(packet.getSize()); break; } - uint64_t victimGuid = shieldClassicLike + if (!shieldTbc && (!hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } + uint64_t victimGuid = shieldTbc ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - uint64_t casterGuid = shieldClassicLike + if (packet.getSize() - packet.getReadPos() < (shieldTbc ? 8u : 1u) + || (!shieldTbc && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } + uint64_t casterGuid = shieldTbc ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < 12) { packet.setReadPos(packet.getSize()); break; } uint32_t shieldSpellId = packet.readUInt32(); uint32_t damage = packet.readUInt32(); - if (!shieldClassicLike && packet.getSize() - packet.getReadPos() >= 4) + if (shieldWotlkLike && packet.getSize() - packet.getReadPos() >= 4) /*uint32_t absorbed =*/ packet.readUInt32(); /*uint32_t school =*/ packet.readUInt32(); // Show combat text: damage shield reflect From f6d8c01779d18a2ec7e78fa1068ef15f5cad2aa9 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 01:54:01 -0700 Subject: [PATCH 14/16] fix(combatlog): validate packed spell miss GUIDs --- src/game/game_handler.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 844bafd6..ca2edcd8 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2717,14 +2717,20 @@ void GameHandler::handlePacket(network::Packet& packet) { // spellId prefix present in all expansions if (packet.getSize() - packet.getReadPos() < 4) break; uint32_t spellId = packet.readUInt32(); - if (packet.getSize() - packet.getReadPos() < (spellMissUsesFullGuid ? 8u : 1u)) break; + if (packet.getSize() - packet.getReadPos() < (spellMissUsesFullGuid ? 8u : 1u) + || (!spellMissUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t casterGuid = readSpellMissGuid(); if (packet.getSize() - packet.getReadPos() < 5) break; /*uint8_t unk =*/ packet.readUInt8(); uint32_t count = packet.readUInt32(); count = std::min(count, 32u); for (uint32_t i = 0; i < count; ++i) { - if (packet.getSize() - packet.getReadPos() < (spellMissUsesFullGuid ? 9u : 2u)) break; + if (packet.getSize() - packet.getReadPos() < (spellMissUsesFullGuid ? 9u : 2u) + || (!spellMissUsesFullGuid && !hasFullPackedGuid(packet))) { + packet.setReadPos(packet.getSize()); break; + } uint64_t victimGuid = readSpellMissGuid(); if (packet.getSize() - packet.getReadPos() < 1) break; uint8_t missInfo = packet.readUInt8(); From 011a148105123da253e45a1133596d6ae00c68da Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 02:01:07 -0700 Subject: [PATCH 15/16] fix(combatlog): validate packed damage shield GUIDs --- src/game/game_handler.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index ca2edcd8..30e91ccb 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6181,6 +6181,7 @@ void GameHandler::handlePacket(network::Packet& packet) { // WotLK: packed_guid victim + packed_guid caster + spellId(4) + damage(4) + absorbed(4) + schoolMask(4) const bool shieldTbc = isActiveExpansion("tbc"); const bool shieldWotlkLike = !isClassicLikeExpansion() && !shieldTbc; + const auto shieldRem = [&]() { return packet.getSize() - packet.getReadPos(); }; const size_t shieldMinSz = shieldTbc ? 24u : 2u; if (packet.getSize() - packet.getReadPos() < shieldMinSz) { packet.setReadPos(packet.getSize()); break; @@ -6196,12 +6197,13 @@ void GameHandler::handlePacket(network::Packet& packet) { } uint64_t casterGuid = shieldTbc ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); - if (packet.getSize() - packet.getReadPos() < 12) { + const size_t shieldTailSize = shieldWotlkLike ? 16u : 12u; + if (shieldRem() < shieldTailSize) { packet.setReadPos(packet.getSize()); break; } uint32_t shieldSpellId = packet.readUInt32(); uint32_t damage = packet.readUInt32(); - if (shieldWotlkLike && packet.getSize() - packet.getReadPos() >= 4) + if (shieldWotlkLike) /*uint32_t absorbed =*/ packet.readUInt32(); /*uint32_t school =*/ packet.readUInt32(); // Show combat text: damage shield reflect From 6a7071fd64963e25dd5032236dfbdd75a42ca7aa Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 02:10:14 -0700 Subject: [PATCH 16/16] fix(combatlog): validate classic spell damage and heal GUIDs --- src/game/packet_parsers_classic.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index 7077d0ab..046839d0 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -4,6 +4,26 @@ namespace wowee { namespace game { +namespace { + +bool hasFullPackedGuid(const network::Packet& packet) { + if (packet.getReadPos() >= packet.getSize()) { + return false; + } + + const auto& rawData = packet.getData(); + const uint8_t mask = rawData[packet.getReadPos()]; + size_t guidBytes = 1; + for (int bit = 0; bit < 8; ++bit) { + if ((mask & (1u << bit)) != 0) { + ++guidBytes; + } + } + return packet.getSize() - packet.getReadPos() >= guidBytes; +} + +} // namespace + // ============================================================================ // Classic 1.12.1 movement flag constants // Key differences from TBC: @@ -497,10 +517,10 @@ bool ClassicPacketParsers::parseAttackerStateUpdate(network::Packet& packet, Att // ============================================================================ bool ClassicPacketParsers::parseSpellDamageLog(network::Packet& packet, SpellDamageLogData& data) { auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; - if (rem() < 2) return false; + if (rem() < 2 || !hasFullPackedGuid(packet)) return false; data.targetGuid = UpdateObjectParser::readPackedGuid(packet); // PackedGuid in Vanilla - if (rem() < 1) return false; + if (rem() < 1 || !hasFullPackedGuid(packet)) return false; data.attackerGuid = UpdateObjectParser::readPackedGuid(packet); // PackedGuid in Vanilla // uint32(spellId) + uint32(damage) + uint8(schoolMask) + uint32(absorbed) @@ -532,10 +552,10 @@ bool ClassicPacketParsers::parseSpellDamageLog(network::Packet& packet, SpellDam // ============================================================================ bool ClassicPacketParsers::parseSpellHealLog(network::Packet& packet, SpellHealLogData& data) { auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; - if (rem() < 2) return false; + if (rem() < 2 || !hasFullPackedGuid(packet)) return false; data.targetGuid = UpdateObjectParser::readPackedGuid(packet); // PackedGuid in Vanilla - if (rem() < 1) return false; + if (rem() < 1 || !hasFullPackedGuid(packet)) return false; data.casterGuid = UpdateObjectParser::readPackedGuid(packet); // PackedGuid in Vanilla if (rem() < 13) return false; // uint32 + uint32 + uint32 + uint8 = 13 bytes