From 31ae689b2ce1809b6a8081bb8bb478b7558849bc Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 04:37:03 -0700 Subject: [PATCH] game: fix TBC SMSG_QUESTGIVER_QUEST_DETAILS parsing by promoting Classic override to TbcPacketParsers TBC 2.4.3 and Classic 1.12 share the same SMSG_QUESTGIVER_QUEST_DETAILS format. WotLK 3.3.5a adds three extra fields (informUnit u64, flags u32, isFinished u8) that the base QuestDetailsParser::parse handles. TBC had no override, so it fell through to the WotLK heuristic which read flags+isFinished as if they were TBC fields, misaligning choiceCount, rewardMoney, and rewardXp. Fix: move parseQuestDetails from ClassicPacketParsers to TbcPacketParsers. Classic inherits it unchanged (formats are identical). Both expansions now correctly parse: no informUnit, activateAccept(u8), suggestedPlayers(u32), emote section, variable choice/reward item counts, rewardMoney, and rewardXp. --- include/game/packet_parsers.hpp | 5 ++- src/game/packet_parsers_classic.cpp | 63 -------------------------- src/game/packet_parsers_tbc.cpp | 69 +++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/include/game/packet_parsers.hpp b/include/game/packet_parsers.hpp index 03fc502e..d2556e7b 100644 --- a/include/game/packet_parsers.hpp +++ b/include/game/packet_parsers.hpp @@ -353,6 +353,9 @@ public: // TBC 2.4.3 CMSG_QUESTGIVER_QUERY_QUEST: guid(8) + questId(4) — no trailing // isDialogContinued byte that WotLK added network::Packet buildQueryQuestPacket(uint64_t npcGuid, uint32_t questId) override; + // TBC/Classic SMSG_QUESTGIVER_QUEST_DETAILS lacks informUnit(u64), flags(u32), + // isFinished(u8) that WotLK added; uses variable item counts + emote section. + bool parseQuestDetails(network::Packet& packet, QuestDetailsData& data) override; }; /** @@ -402,7 +405,7 @@ public: uint8_t readQuestGiverStatus(network::Packet& packet) override; network::Packet buildQueryQuestPacket(uint64_t npcGuid, uint32_t questId) override; network::Packet buildAcceptQuestPacket(uint64_t npcGuid, uint32_t questId) override; - bool parseQuestDetails(network::Packet& packet, QuestDetailsData& data) override; + // parseQuestDetails inherited from TbcPacketParsers (same format as TBC 2.4.3) uint8_t questLogStride() const override { return 3; } bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override { return MonsterMoveParser::parseVanilla(packet, data); diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index eeebe520..946b9acc 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -1557,69 +1557,6 @@ network::Packet ClassicPacketParsers::buildAcceptQuestPacket(uint64_t npcGuid, u return packet; } -// ============================================================================ -// Classic SMSG_QUESTGIVER_QUEST_DETAILS — Vanilla 1.12 format -// WotLK inserts an informUnit GUID (8 bytes) between npcGuid and questId. -// Vanilla has: npcGuid(8) + questId(4) + title + details + objectives + ... -// ============================================================================ -bool ClassicPacketParsers::parseQuestDetails(network::Packet& packet, QuestDetailsData& data) { - if (packet.getSize() < 16) return false; - - data.npcGuid = packet.readUInt64(); - // Vanilla: questId follows immediately — no informUnit GUID - data.questId = packet.readUInt32(); - data.title = normalizeWowTextTokens(packet.readString()); - data.details = normalizeWowTextTokens(packet.readString()); - data.objectives = normalizeWowTextTokens(packet.readString()); - - if (packet.getReadPos() + 5 > packet.getSize()) { - LOG_INFO("Quest details classic (short): id=", data.questId, " title='", data.title, "'"); - return !data.title.empty() || data.questId != 0; - } - - /*activateAccept*/ packet.readUInt8(); - data.suggestedPlayers = packet.readUInt32(); - - // Vanilla 1.12: emote section before reward items - // Format: emoteCount(u32) + [delay(u32) + type(u32)] × emoteCount - if (packet.getReadPos() + 4 <= packet.getSize()) { - uint32_t emoteCount = packet.readUInt32(); - for (uint32_t i = 0; i < emoteCount && packet.getReadPos() + 8 <= packet.getSize(); ++i) { - packet.readUInt32(); // delay - packet.readUInt32(); // type - } - } - - // Choice reward items: variable count + 3 uint32s each - if (packet.getReadPos() + 4 <= packet.getSize()) { - uint32_t choiceCount = packet.readUInt32(); - for (uint32_t i = 0; i < choiceCount && packet.getReadPos() + 12 <= packet.getSize(); ++i) { - packet.readUInt32(); // itemId - packet.readUInt32(); // count - packet.readUInt32(); // displayInfo - } - } - - // Fixed reward items: variable count + 3 uint32s each - if (packet.getReadPos() + 4 <= packet.getSize()) { - uint32_t rewardCount = packet.readUInt32(); - for (uint32_t i = 0; i < rewardCount && packet.getReadPos() + 12 <= packet.getSize(); ++i) { - packet.readUInt32(); // itemId - packet.readUInt32(); // count - packet.readUInt32(); // displayInfo - } - } - - if (packet.getReadPos() + 4 <= packet.getSize()) - data.rewardMoney = packet.readUInt32(); - // Vanilla 1.12 includes rewardXp after rewardMoney (same as WotLK) - if (packet.getReadPos() + 4 <= packet.getSize()) - data.rewardXp = packet.readUInt32(); - - LOG_INFO("Quest details classic: id=", data.questId, " title='", data.title, "'"); - return true; -} - // ============================================================================ // ClassicPacketParsers::parseCreatureQueryResponse // diff --git a/src/game/packet_parsers_tbc.cpp b/src/game/packet_parsers_tbc.cpp index 7851007e..5232ba33 100644 --- a/src/game/packet_parsers_tbc.cpp +++ b/src/game/packet_parsers_tbc.cpp @@ -694,6 +694,75 @@ network::Packet TbcPacketParsers::buildAcceptQuestPacket(uint64_t npcGuid, uint3 return packet; } +// ============================================================================ +// TBC 2.4.3 SMSG_QUESTGIVER_QUEST_DETAILS +// +// TBC and Classic share the same format — neither has the WotLK-specific fields +// (informUnit GUID, flags uint32, isFinished uint8) that were added in 3.x. +// +// Format: +// npcGuid(8) + questId(4) + title + details + objectives +// + activateAccept(1) + suggestedPlayers(4) +// + emoteCount(4) + [delay(4)+type(4)] × emoteCount +// + choiceCount(4) + [itemId(4)+count(4)+displayInfo(4)] × choiceCount +// + rewardCount(4) + [itemId(4)+count(4)+displayInfo(4)] × rewardCount +// + rewardMoney(4) + rewardXp(4) +// ============================================================================ +bool TbcPacketParsers::parseQuestDetails(network::Packet& packet, QuestDetailsData& data) { + if (packet.getSize() < 16) return false; + + data.npcGuid = packet.readUInt64(); + data.questId = packet.readUInt32(); + data.title = normalizeWowTextTokens(packet.readString()); + data.details = normalizeWowTextTokens(packet.readString()); + data.objectives = normalizeWowTextTokens(packet.readString()); + + if (packet.getReadPos() + 5 > packet.getSize()) { + LOG_INFO("Quest details tbc/classic (short): id=", data.questId, " title='", data.title, "'"); + return !data.title.empty() || data.questId != 0; + } + + /*activateAccept*/ packet.readUInt8(); + data.suggestedPlayers = packet.readUInt32(); + + // TBC/Classic: emote section before reward items + if (packet.getReadPos() + 4 <= packet.getSize()) { + uint32_t emoteCount = packet.readUInt32(); + for (uint32_t i = 0; i < emoteCount && packet.getReadPos() + 8 <= packet.getSize(); ++i) { + packet.readUInt32(); // delay + packet.readUInt32(); // type + } + } + + // Choice reward items (variable count, up to QUEST_REWARD_CHOICES_COUNT) + if (packet.getReadPos() + 4 <= packet.getSize()) { + uint32_t choiceCount = packet.readUInt32(); + for (uint32_t i = 0; i < choiceCount && packet.getReadPos() + 12 <= packet.getSize(); ++i) { + packet.readUInt32(); // itemId + packet.readUInt32(); // count + packet.readUInt32(); // displayInfo + } + } + + // Fixed reward items (variable count, up to QUEST_REWARDS_COUNT) + if (packet.getReadPos() + 4 <= packet.getSize()) { + uint32_t rewardCount = packet.readUInt32(); + for (uint32_t i = 0; i < rewardCount && packet.getReadPos() + 12 <= packet.getSize(); ++i) { + packet.readUInt32(); // itemId + packet.readUInt32(); // count + packet.readUInt32(); // displayInfo + } + } + + if (packet.getReadPos() + 4 <= packet.getSize()) + data.rewardMoney = packet.readUInt32(); + if (packet.getReadPos() + 4 <= packet.getSize()) + data.rewardXp = packet.readUInt32(); + + LOG_INFO("Quest details tbc/classic: id=", data.questId, " title='", data.title, "'"); + return true; +} + // ============================================================================ // TBC 2.4.3 CMSG_QUESTGIVER_QUERY_QUEST //