From 38333df2604a97e9e19588ee1e744d744fed1af4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 21:14:06 -0700 Subject: [PATCH] tbc: fix spell cast format and NPC movement parsing for TBC 2.4.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CMSG_CAST_SPELL: WotLK adds a castFlags(u8) byte after spellId that TBC 2.4.3 does not have. Add TbcPacketParsers::buildCastSpell() to omit it, preventing every spell cast from being rejected by TBC servers. CMSG_USE_ITEM: WotLK adds a glyphIndex(u32) field between itemGuid and castFlags that TBC 2.4.3 does not have. Add buildUseItem() override. SMSG_MONSTER_MOVE: WotLK adds a uint8 unk byte after the packed GUID (MOVEMENTFLAG2_UNK7 toggle) that TBC 2.4.3 does not have. Add parseMonsterMove() override to fix NPC movement parsing — without this, all NPC positions, durations, and waypoints parse from the wrong byte offset, making all NPC movement appear broken on TBC servers. --- include/game/packet_parsers.hpp | 6 ++ src/game/packet_parsers_tbc.cpp | 146 ++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/include/game/packet_parsers.hpp b/include/game/packet_parsers.hpp index f1466a1d..fea1f88a 100644 --- a/include/game/packet_parsers.hpp +++ b/include/game/packet_parsers.hpp @@ -287,6 +287,12 @@ public: bool parseNameQueryResponse(network::Packet& packet, NameQueryResponseData& data) override; bool parseItemQueryResponse(network::Packet& packet, ItemQueryResponseData& data) override; network::Packet buildAcceptQuestPacket(uint64_t npcGuid, uint32_t questId) override; + // TBC 2.4.3 CMSG_CAST_SPELL has no castFlags byte (WotLK added it) + network::Packet buildCastSpell(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) override; + // TBC 2.4.3 CMSG_USE_ITEM has no glyphIndex field (WotLK added it) + network::Packet buildUseItem(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid, uint32_t spellId = 0) override; + // TBC 2.4.3 SMSG_MONSTER_MOVE has no unk byte after packed GUID (WotLK added it) + bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override; }; /** diff --git a/src/game/packet_parsers_tbc.cpp b/src/game/packet_parsers_tbc.cpp index e4275640..956c5af0 100644 --- a/src/game/packet_parsers_tbc.cpp +++ b/src/game/packet_parsers_tbc.cpp @@ -497,6 +497,152 @@ bool TbcPacketParsers::parseUpdateObject(network::Packet& packet, UpdateObjectDa return false; } +// ============================================================================ +// TBC 2.4.3 SMSG_MONSTER_MOVE +// Identical to WotLK except WotLK added a uint8 unk byte immediately after the +// packed GUID (toggles MOVEMENTFLAG2_UNK7). TBC does NOT have this byte. +// Without this override, all NPC movement positions/durations are offset by 1 +// byte and parse as garbage. +// ============================================================================ +bool TbcPacketParsers::parseMonsterMove(network::Packet& packet, MonsterMoveData& data) { + data.guid = UpdateObjectParser::readPackedGuid(packet); + if (data.guid == 0) return false; + // No unk byte here in TBC 2.4.3 + + if (packet.getReadPos() + 12 > packet.getSize()) return false; + data.x = packet.readFloat(); + data.y = packet.readFloat(); + data.z = packet.readFloat(); + + if (packet.getReadPos() + 4 > packet.getSize()) return false; + packet.readUInt32(); // splineId + + if (packet.getReadPos() >= packet.getSize()) return false; + data.moveType = packet.readUInt8(); + + if (data.moveType == 1) { + data.destX = data.x; + data.destY = data.y; + data.destZ = data.z; + data.hasDest = false; + return true; + } + + if (data.moveType == 2) { + if (packet.getReadPos() + 12 > packet.getSize()) return false; + packet.readFloat(); packet.readFloat(); packet.readFloat(); + } else if (data.moveType == 3) { + if (packet.getReadPos() + 8 > packet.getSize()) return false; + data.facingTarget = packet.readUInt64(); + } else if (data.moveType == 4) { + if (packet.getReadPos() + 4 > packet.getSize()) return false; + data.facingAngle = packet.readFloat(); + } + + if (packet.getReadPos() + 4 > packet.getSize()) return false; + data.splineFlags = packet.readUInt32(); + + // TBC 2.4.3 SplineFlags animation bit is same as WotLK: 0x00400000 + if (data.splineFlags & 0x00400000) { + if (packet.getReadPos() + 5 > packet.getSize()) return false; + packet.readUInt8(); // animationType + packet.readUInt32(); // effectStartTime + } + + if (packet.getReadPos() + 4 > packet.getSize()) return false; + data.duration = packet.readUInt32(); + + if (data.splineFlags & 0x00000800) { + if (packet.getReadPos() + 8 > packet.getSize()) return false; + packet.readFloat(); // verticalAcceleration + packet.readUInt32(); // effectStartTime + } + + if (packet.getReadPos() + 4 > packet.getSize()) return false; + uint32_t pointCount = packet.readUInt32(); + if (pointCount == 0) return true; + if (pointCount > 16384) return false; + + bool uncompressed = (data.splineFlags & (0x00080000 | 0x00002000)) != 0; + if (uncompressed) { + for (uint32_t i = 0; i < pointCount - 1; i++) { + if (packet.getReadPos() + 12 > packet.getSize()) return true; + packet.readFloat(); packet.readFloat(); packet.readFloat(); + } + if (packet.getReadPos() + 12 > packet.getSize()) return true; + data.destX = packet.readFloat(); + data.destY = packet.readFloat(); + data.destZ = packet.readFloat(); + data.hasDest = true; + } else { + if (packet.getReadPos() + 12 > packet.getSize()) return true; + data.destX = packet.readFloat(); + data.destY = packet.readFloat(); + data.destZ = packet.readFloat(); + data.hasDest = true; + } + + LOG_DEBUG("[TBC] MonsterMove: guid=0x", std::hex, data.guid, std::dec, + " type=", (int)data.moveType, " dur=", data.duration, "ms", + " dest=(", data.destX, ",", data.destY, ",", data.destZ, ")"); + return true; +} + +// ============================================================================ +// TBC 2.4.3 CMSG_CAST_SPELL +// Format: castCount(u8) + spellId(u32) + SpellCastTargets +// WotLK 3.3.5a adds castFlags(u8) between spellId and targets — TBC does NOT. +// ============================================================================ +network::Packet TbcPacketParsers::buildCastSpell(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) { + network::Packet packet(wireOpcode(LogicalOpcode::CMSG_CAST_SPELL)); + packet.writeUInt8(castCount); + packet.writeUInt32(spellId); + // No castFlags byte in TBC 2.4.3 + + if (targetGuid != 0) { + packet.writeUInt32(0x02); // TARGET_FLAG_UNIT + // Write packed GUID + uint8_t mask = 0; + uint8_t bytes[8]; + int byteCount = 0; + uint64_t g = targetGuid; + for (int i = 0; i < 8; ++i) { + uint8_t b = g & 0xFF; + if (b != 0) { + mask |= (1 << i); + bytes[byteCount++] = b; + } + g >>= 8; + } + packet.writeUInt8(mask); + for (int i = 0; i < byteCount; ++i) + packet.writeUInt8(bytes[i]); + } else { + packet.writeUInt32(0x00); // TARGET_FLAG_SELF + } + + return packet; +} + +// ============================================================================ +// TBC 2.4.3 CMSG_USE_ITEM +// Format: bag(u8) + slot(u8) + castCount(u8) + spellId(u32) + itemGuid(u64) + +// castFlags(u8) + SpellCastTargets +// WotLK 3.3.5a adds glyphIndex(u32) between itemGuid and castFlags — TBC does NOT. +// ============================================================================ +network::Packet TbcPacketParsers::buildUseItem(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid, uint32_t spellId) { + network::Packet packet(wireOpcode(LogicalOpcode::CMSG_USE_ITEM)); + packet.writeUInt8(bagIndex); + packet.writeUInt8(slotIndex); + packet.writeUInt8(0); // cast count + packet.writeUInt32(spellId); // on-use spell id + packet.writeUInt64(itemGuid); // full 8-byte GUID + // No glyph index field in TBC 2.4.3 + packet.writeUInt8(0); // cast flags + packet.writeUInt32(0x00); // SpellCastTargets: TARGET_FLAG_SELF + return packet; +} + network::Packet TbcPacketParsers::buildAcceptQuestPacket(uint64_t npcGuid, uint32_t questId) { network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_ACCEPT_QUEST)); packet.writeUInt64(npcGuid);