From cbb3035313f4aa987b412905e840b338c8b98ff1 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Feb 2026 19:38:11 -0800 Subject: [PATCH] Fix gossip message parsing for Vanilla/Turtle (flight master hang) Classic gossip packets lack the menuId field and quest items don't have questFlags/isRepeatable, causing the WotLK parser to read garbage counts (541M quests) and hang. Added ClassicPacketParsers::parseGossipMessage override with the correct vanilla format. --- include/game/packet_parsers.hpp | 8 +++++ src/game/game_handler.cpp | 4 ++- src/game/packet_parsers_classic.cpp | 49 +++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/include/game/packet_parsers.hpp b/include/game/packet_parsers.hpp index 989144ea..3a0d46af 100644 --- a/include/game/packet_parsers.hpp +++ b/include/game/packet_parsers.hpp @@ -117,6 +117,13 @@ public: return NameQueryResponseParser::parse(packet, data); } + // --- Gossip --- + + /** Parse SMSG_GOSSIP_MESSAGE */ + virtual bool parseGossipMessage(network::Packet& packet, GossipMessageData& data) { + return GossipMessageParser::parse(packet, data); + } + // --- Destroy Object --- /** Parse SMSG_DESTROY_OBJECT */ @@ -226,6 +233,7 @@ public: network::Packet buildCastSpell(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) override; bool parseCastFailed(network::Packet& packet, CastFailedData& data) override; bool parseMessageChat(network::Packet& packet, MessageChatData& data) override; + bool parseGossipMessage(network::Packet& packet, GossipMessageData& data) override; bool parseGuildRoster(network::Packet& packet, GuildRosterData& data) override; bool parseGuildQueryResponse(network::Packet& packet, GuildQueryResponseData& data) override; network::Packet buildJoinChannel(const std::string& channelName, const std::string& password) override; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b3e6250d..43db7a92 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -7655,7 +7655,9 @@ void GameHandler::handleLootRemoved(network::Packet& packet) { } void GameHandler::handleGossipMessage(network::Packet& packet) { - if (!GossipMessageParser::parse(packet, currentGossip)) return; + bool ok = packetParsers_ ? packetParsers_->parseGossipMessage(packet, currentGossip) + : GossipMessageParser::parse(packet, currentGossip); + if (!ok) return; if (questDetailsOpen) return; // Don't reopen gossip while viewing quest gossipWindowOpen = true; vendorWindowOpen = false; // Close vendor if gossip opens diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index 6c618f4e..23326398 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -580,5 +580,54 @@ bool ClassicPacketParsers::parseGuildQueryResponse(network::Packet& packet, Guil return true; } +// ============================================================================ +// Gossip — Classic has no menuId, and quest items lack questFlags + isRepeatable +// ============================================================================ + +bool ClassicPacketParsers::parseGossipMessage(network::Packet& packet, GossipMessageData& data) { + size_t remaining = packet.getSize() - packet.getReadPos(); + if (remaining < 8 + 4 + 4) { + LOG_ERROR("Classic SMSG_GOSSIP_MESSAGE too small: ", remaining, " bytes"); + return false; + } + + data.npcGuid = packet.readUInt64(); + // Classic: NO menuId field (WotLK adds uint32 menuId here) + data.menuId = 0; + data.titleTextId = packet.readUInt32(); + uint32_t optionCount = packet.readUInt32(); + + data.options.clear(); + data.options.reserve(optionCount); + for (uint32_t i = 0; i < optionCount; ++i) { + GossipOption opt; + opt.id = packet.readUInt32(); + opt.icon = packet.readUInt8(); + opt.isCoded = (packet.readUInt8() != 0); + opt.boxMoney = packet.readUInt32(); + opt.text = packet.readString(); + opt.boxText = packet.readString(); + data.options.push_back(opt); + } + + uint32_t questCount = packet.readUInt32(); + data.quests.clear(); + data.quests.reserve(questCount); + for (uint32_t i = 0; i < questCount; ++i) { + GossipQuestItem quest; + quest.questId = packet.readUInt32(); + quest.questIcon = packet.readUInt32(); + quest.questLevel = static_cast(packet.readUInt32()); + // Classic: NO questFlags, NO isRepeatable + quest.questFlags = 0; + quest.isRepeatable = 0; + quest.title = packet.readString(); + data.quests.push_back(quest); + } + + LOG_INFO("Classic Gossip: ", optionCount, " options, ", questCount, " quests"); + return true; +} + } // namespace game } // namespace wowee