From 6f936f258f478094dc83afca16f94d1e9bd34a6d Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 17 Mar 2026 22:26:05 -0700 Subject: [PATCH] fix: consume all SpellCastTargets bytes in WotLK SpellGoParser Applied the same SpellCastTargets fix from SpellStartParser (dd64724) to SpellGoParser: after parsing hit/miss target lists, now reads the full target section (UNIT/UNIT_MINIPET/CORPSE/GAMEOBJECT packed GUID, ITEM/TRADE_ITEM packed GUID, SOURCE/DEST PackedGuid+3floats, null- terminated STRING). Also adds targetGuid field to SpellGoData so callers can read the primary target. Prevents stream misalignment on ground-targeted AoE spells (e.g. Blizzard, Rain of Fire). --- include/game/world_packets.hpp | 1 + src/game/world_packets.cpp | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index a30194f4..c7fc0ef4 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1875,6 +1875,7 @@ struct SpellGoData { std::vector hitTargets; uint8_t missCount = 0; std::vector missTargets; + uint64_t targetGuid = 0; ///< Primary target GUID from SpellCastTargets (0 = none/AoE) bool isValid() const { return spellId != 0; } }; diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 511f2544..aaf18ca2 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -3931,6 +3931,50 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) { } data.missCount = static_cast(data.missTargets.size()); + // WotLK 3.3.5a SpellCastTargets — consume ALL target payload bytes so that + // any trailing fields after the target section are not misaligned for + // ground-targeted or AoE spells. Same layout as SpellStartParser. + if (packet.getReadPos() < packet.getSize()) { + if (packet.getSize() - packet.getReadPos() >= 4) { + uint32_t targetFlags = packet.readUInt32(); + + auto readPackedTarget = [&](uint64_t* out) -> bool { + if (!hasFullPackedGuid(packet)) return false; + uint64_t g = UpdateObjectParser::readPackedGuid(packet); + if (out) *out = g; + return true; + }; + auto skipPackedAndFloats3 = [&]() -> bool { + if (!hasFullPackedGuid(packet)) return false; + UpdateObjectParser::readPackedGuid(packet); // transport GUID + if (packet.getSize() - packet.getReadPos() < 12) return false; + packet.readFloat(); packet.readFloat(); packet.readFloat(); + return true; + }; + + // UNIT/UNIT_MINIPET/CORPSE_ALLY/GAMEOBJECT share one object target GUID + if (targetFlags & (0x0002u | 0x0004u | 0x0400u | 0x0800u)) { + readPackedTarget(&data.targetGuid); + } + // ITEM/TRADE_ITEM share one item target GUID + if (targetFlags & (0x0010u | 0x0100u)) { + readPackedTarget(nullptr); + } + // SOURCE_LOCATION: PackedGuid (transport) + float x,y,z + if (targetFlags & 0x0020u) { + skipPackedAndFloats3(); + } + // DEST_LOCATION: PackedGuid (transport) + float x,y,z + if (targetFlags & 0x0040u) { + skipPackedAndFloats3(); + } + // STRING: null-terminated + if (targetFlags & 0x0200u) { + while (packet.getReadPos() < packet.getSize() && packet.readUInt8() != 0) {} + } + } + } + LOG_DEBUG("Spell go: spell=", data.spellId, " hits=", (int)data.hitCount, " misses=", (int)data.missCount); return true;