From ed48a3c425e72f0ae875eac83031a61957f8df4e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 04:38:30 -0700 Subject: [PATCH] fix: replace fragile heuristic in SMSG_INITIAL_SPELLS with explicit Classic format flag Classic 1.12 uses uint16 spellId + uint16 slot (4 bytes/spell); TBC and WotLK use uint32 spellId + uint16 unknown (6 bytes/spell). The old size-based heuristic could misdetect TBC packets that happened to fit both layouts. Add a vanillaFormat parameter to InitialSpellsParser::parse and override parseInitialSpells in ClassicPacketParsers to always pass true, eliminating the ambiguity. --- include/game/packet_parsers.hpp | 4 ++++ include/game/world_packets.hpp | 5 ++++- src/game/world_packets.cpp | 13 ++++--------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/game/packet_parsers.hpp b/include/game/packet_parsers.hpp index 9fb0f166..40655045 100644 --- a/include/game/packet_parsers.hpp +++ b/include/game/packet_parsers.hpp @@ -419,6 +419,10 @@ public: bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override { return MonsterMoveParser::parseVanilla(packet, data); } + // Classic 1.12 SMSG_INITIAL_SPELLS: uint16 spellId + uint16 slot per entry (not uint32 + uint16) + bool parseInitialSpells(network::Packet& packet, InitialSpellsData& data) override { + return InitialSpellsParser::parse(packet, data, /*vanillaFormat=*/true); + } // Classic 1.12 uses PackedGuid (not full uint64) and uint16 castFlags (not uint32) bool parseSpellStart(network::Packet& packet, SpellStartData& data) override; bool parseSpellGo(network::Packet& packet, SpellGoData& data) override; diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 539d3b8a..6e5721fd 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1758,7 +1758,10 @@ struct InitialSpellsData { class InitialSpellsParser { public: - static bool parse(network::Packet& packet, InitialSpellsData& data); + // vanillaFormat=true: Classic 1.12 uint16 spellId + uint16 slot (4 bytes/spell) + // vanillaFormat=false: TBC/WotLK uint32 spellId + uint16 unk (6 bytes/spell) + static bool parse(network::Packet& packet, InitialSpellsData& data, + bool vanillaFormat = false); }; /** CMSG_CAST_SPELL packet builder */ diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 8c7c5ec9..b7d8075b 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2901,18 +2901,13 @@ bool XpGainParser::parse(network::Packet& packet, XpGainData& data) { // Phase 3: Spells, Action Bar, Auras // ============================================================ -bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data) { - size_t packetSize = packet.getSize(); +bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data, + bool vanillaFormat) { data.talentSpec = packet.readUInt8(); uint16_t spellCount = packet.readUInt16(); - // Detect vanilla (uint16 spellId) vs WotLK (uint32 spellId) format - // Vanilla: 4 bytes/spell (uint16 id + uint16 slot), WotLK: 6 bytes/spell (uint32 id + uint16 unk) - size_t remainingAfterHeader = packetSize - 3; // subtract talentSpec(1) + spellCount(2) - bool vanillaFormat = remainingAfterHeader < static_cast(spellCount) * 6 + 2; - - LOG_DEBUG("SMSG_INITIAL_SPELLS: packetSize=", packetSize, " bytes, spellCount=", spellCount, - vanillaFormat ? " (vanilla uint16 format)" : " (WotLK uint32 format)"); + LOG_DEBUG("SMSG_INITIAL_SPELLS: spellCount=", spellCount, + vanillaFormat ? " (vanilla uint16 format)" : " (TBC/WotLK uint32 format)"); data.spellIds.reserve(spellCount); for (uint16_t i = 0; i < spellCount; ++i) {