From 8493729a1073d37cca5f0ab27a5a4c0cecc7fd06 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 04:08:16 -0700 Subject: [PATCH] fix: use uint16 spellId in Classic 1.12 SMSG_LEARNED/REMOVED/SUPERCEDED_SPELL Classic 1.12 (vmangos/cmangos) sends uint16 spellIds in SMSG_LEARNED_SPELL, SMSG_REMOVED_SPELL, and SMSG_SUPERCEDED_SPELL. TBC 2.4.3 and WotLK 3.3.5a use uint32. The handlers were unconditionally reading uint32, causing the first byte of the next field to be consumed as part of the spellId on Classic, producing garbage spell IDs and breaking known-spell tracking. Apply the same Classic/TBC+WotLK gate used by the SMSG_INITIAL_SPELLS heuristic: read uint16 for Classic, uint32 for all others. --- src/game/game_handler.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f7f42b59..06e4c057 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -14174,8 +14174,11 @@ void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) { } void GameHandler::handleLearnedSpell(network::Packet& packet) { - if (packet.getSize() - packet.getReadPos() < 4) return; - uint32_t spellId = packet.readUInt32(); + // Classic 1.12: uint16 spellId; TBC 2.4.3 / WotLK 3.3.5a: uint32 spellId + const bool classicSpellId = isClassicLikeExpansion(); + const size_t minSz = classicSpellId ? 2u : 4u; + if (packet.getSize() - packet.getReadPos() < minSz) return; + uint32_t spellId = classicSpellId ? packet.readUInt16() : packet.readUInt32(); knownSpells.insert(spellId); LOG_INFO("Learned spell: ", spellId); @@ -14203,17 +14206,24 @@ void GameHandler::handleLearnedSpell(network::Packet& packet) { } void GameHandler::handleRemovedSpell(network::Packet& packet) { - if (packet.getSize() - packet.getReadPos() < 4) return; - uint32_t spellId = packet.readUInt32(); + // Classic 1.12: uint16 spellId; TBC 2.4.3 / WotLK 3.3.5a: uint32 spellId + const bool classicSpellId = isClassicLikeExpansion(); + const size_t minSz = classicSpellId ? 2u : 4u; + if (packet.getSize() - packet.getReadPos() < minSz) return; + uint32_t spellId = classicSpellId ? packet.readUInt16() : packet.readUInt32(); knownSpells.erase(spellId); LOG_INFO("Removed spell: ", spellId); } void GameHandler::handleSupercededSpell(network::Packet& packet) { // Old spell replaced by new rank (e.g., Fireball Rank 1 -> Fireball Rank 2) - if (packet.getSize() - packet.getReadPos() < 8) return; - uint32_t oldSpellId = packet.readUInt32(); - uint32_t newSpellId = packet.readUInt32(); + // Classic 1.12: uint16 oldSpellId + uint16 newSpellId (4 bytes total) + // TBC 2.4.3 / WotLK 3.3.5a: uint32 oldSpellId + uint32 newSpellId (8 bytes total) + const bool classicSpellId = isClassicLikeExpansion(); + const size_t minSz = classicSpellId ? 4u : 8u; + if (packet.getSize() - packet.getReadPos() < minSz) return; + uint32_t oldSpellId = classicSpellId ? packet.readUInt16() : packet.readUInt32(); + uint32_t newSpellId = classicSpellId ? packet.readUInt16() : packet.readUInt32(); // Remove old spell knownSpells.erase(oldSpellId);