#pragma once #include "game/world_packets.hpp" #include #include namespace wowee { namespace game { /** * PacketParsers - Polymorphic interface for expansion-specific packet parsing. * * Binary packet formats differ significantly between WoW expansions * (movement flags, update fields, character enum layout, etc.). * Each expansion implements this interface with its specific parsing logic. * * The base PacketParsers delegates to the existing static parser classes * in world_packets.hpp. Expansion subclasses override the methods that * differ from WotLK. */ class PacketParsers { public: virtual ~PacketParsers() = default; // Size of MovementInfo.flags2 in bytes for MSG_MOVE_* payloads. // Classic: none, TBC: u8, WotLK: u16. virtual uint8_t movementFlags2Size() const { return 2; } // --- Movement --- /** Parse movement block from SMSG_UPDATE_OBJECT */ virtual bool parseMovementBlock(network::Packet& packet, UpdateBlock& block) { return UpdateObjectParser::parseMovementBlock(packet, block); } /** Write movement payload for CMSG_MOVE_* packets */ virtual void writeMovementPayload(network::Packet& packet, const MovementInfo& info) { MovementPacket::writeMovementPayload(packet, info); } /** Build a complete movement packet with packed GUID + payload */ virtual network::Packet buildMovementPacket(LogicalOpcode opcode, const MovementInfo& info, uint64_t playerGuid = 0) { return MovementPacket::build(opcode, info, playerGuid); } /** Build CMSG_CAST_SPELL (WotLK default: castCount + spellId + castFlags + targets) */ virtual network::Packet buildCastSpell(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) { return CastSpellPacket::build(spellId, targetGuid, castCount); } /** Build CMSG_USE_ITEM (WotLK default: bag + slot + castCount + spellId + itemGuid + glyphIndex + castFlags + targets) */ virtual network::Packet buildUseItem(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid, uint32_t spellId = 0) { return UseItemPacket::build(bagIndex, slotIndex, itemGuid, spellId); } // --- Character Enumeration --- /** Parse SMSG_CHAR_ENUM */ virtual bool parseCharEnum(network::Packet& packet, CharEnumResponse& response) { return CharEnumParser::parse(packet, response); } // --- Update Object --- /** Parse a full SMSG_UPDATE_OBJECT packet */ virtual bool parseUpdateObject(network::Packet& packet, UpdateObjectData& data) { return UpdateObjectParser::parse(packet, data); } /** Parse update fields block (value mask + field values) */ virtual bool parseUpdateFields(network::Packet& packet, UpdateBlock& block) { return UpdateObjectParser::parseUpdateFields(packet, block); } // --- Monster Movement --- /** Parse SMSG_MONSTER_MOVE */ virtual bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) { return MonsterMoveParser::parse(packet, data); } // --- Combat --- /** Parse SMSG_ATTACKERSTATEUPDATE */ virtual bool parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) { return AttackerStateUpdateParser::parse(packet, data); } /** Parse SMSG_SPELLNONMELEEDAMAGELOG */ virtual bool parseSpellDamageLog(network::Packet& packet, SpellDamageLogData& data) { return SpellDamageLogParser::parse(packet, data); } /** Parse SMSG_SPELLHEALLOG */ virtual bool parseSpellHealLog(network::Packet& packet, SpellHealLogData& data) { return SpellHealLogParser::parse(packet, data); } // --- Spells --- /** Parse SMSG_INITIAL_SPELLS */ virtual bool parseInitialSpells(network::Packet& packet, InitialSpellsData& data) { return InitialSpellsParser::parse(packet, data); } /** Parse SMSG_SPELL_START */ virtual bool parseSpellStart(network::Packet& packet, SpellStartData& data) { return SpellStartParser::parse(packet, data); } /** Parse SMSG_SPELL_GO */ virtual bool parseSpellGo(network::Packet& packet, SpellGoData& data) { return SpellGoParser::parse(packet, data); } /** Parse SMSG_CAST_FAILED */ virtual bool parseCastFailed(network::Packet& packet, CastFailedData& data) { return CastFailedParser::parse(packet, data); } /** Parse SMSG_CAST_RESULT header (spellId + result), expansion-aware. * WotLK: castCount(u8) + spellId(u32) + result(u8) * TBC/Classic: spellId(u32) + result(u8) (no castCount prefix) */ virtual bool parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) { // WotLK default: skip castCount, read spellId + result if (packet.getSize() - packet.getReadPos() < 6) return false; packet.readUInt8(); // castCount spellId = packet.readUInt32(); result = packet.readUInt8(); return true; } /** Parse SMSG_AURA_UPDATE / SMSG_AURA_UPDATE_ALL */ virtual bool parseAuraUpdate(network::Packet& packet, AuraUpdateData& data, bool isAll = false) { return AuraUpdateParser::parse(packet, data, isAll); } // --- Chat --- /** Parse SMSG_MESSAGECHAT */ virtual bool parseMessageChat(network::Packet& packet, MessageChatData& data) { return MessageChatParser::parse(packet, data); } /** Parse SMSG_NAME_QUERY_RESPONSE */ virtual bool parseNameQueryResponse(network::Packet& packet, NameQueryResponseData& data) { return NameQueryResponseParser::parse(packet, data); } // --- Creature Query --- /** Parse SMSG_CREATURE_QUERY_RESPONSE */ virtual bool parseCreatureQueryResponse(network::Packet& packet, CreatureQueryResponseData& data) { return CreatureQueryResponseParser::parse(packet, data); } // --- Item Query --- /** Build CMSG_ITEM_QUERY_SINGLE */ virtual network::Packet buildItemQuery(uint32_t entry, uint64_t guid) { return ItemQueryPacket::build(entry, guid); } /** Parse SMSG_ITEM_QUERY_SINGLE_RESPONSE */ virtual bool parseItemQueryResponse(network::Packet& packet, ItemQueryResponseData& data) { return ItemQueryResponseParser::parse(packet, data); } // --- GameObject Query --- /** Parse SMSG_GAMEOBJECT_QUERY_RESPONSE */ virtual bool parseGameObjectQueryResponse(network::Packet& packet, GameObjectQueryResponseData& data) { return GameObjectQueryResponseParser::parse(packet, data); } // --- Gossip --- /** Parse SMSG_GOSSIP_MESSAGE */ virtual bool parseGossipMessage(network::Packet& packet, GossipMessageData& data) { return GossipMessageParser::parse(packet, data); } // --- Quest details --- /** Build CMSG_QUESTGIVER_QUERY_QUEST. * WotLK appends a trailing unk1 byte; Vanilla/Classic do not. */ virtual network::Packet buildQueryQuestPacket(uint64_t npcGuid, uint32_t questId) { return QuestgiverQueryQuestPacket::build(npcGuid, questId); // includes unk1 } /** Build CMSG_QUESTGIVER_ACCEPT_QUEST. * WotLK/AzerothCore expects trailing unk1 uint32; older expansions may not. */ virtual network::Packet buildAcceptQuestPacket(uint64_t npcGuid, uint32_t questId) { return QuestgiverAcceptQuestPacket::build(npcGuid, questId); } /** Parse SMSG_QUESTGIVER_QUEST_DETAILS. * WotLK has an extra informUnit GUID before questId; Vanilla/Classic do not. */ virtual bool parseQuestDetails(network::Packet& packet, QuestDetailsData& data) { return QuestDetailsParser::parse(packet, data); // WotLK auto-detect } /** Stride of PLAYER_QUEST_LOG fields in update-object blocks. * WotLK: 5 fields per slot, Classic/Vanilla: 3. */ virtual uint8_t questLogStride() const { return 5; } /** Number of PLAYER_EXPLORED_ZONES uint32 fields in update-object blocks. * Classic/Vanilla/Turtle: 64 (bit-packs up to zone ID 2047). * TBC/WotLK: 128 (covers Outland/Northrend zone IDs up to 4095). */ virtual uint8_t exploredZonesCount() const { return 128; } // --- Quest Giver Status --- /** Read quest giver status from packet. * WotLK: uint8, vanilla/classic: uint32 with different enum values. * Returns the status value normalized to WotLK enum values. */ virtual uint8_t readQuestGiverStatus(network::Packet& packet) { return packet.readUInt8(); } // --- Destroy Object --- /** Parse SMSG_DESTROY_OBJECT */ virtual bool parseDestroyObject(network::Packet& packet, DestroyObjectData& data) { return DestroyObjectParser::parse(packet, data); } // --- Guild --- /** Parse SMSG_GUILD_ROSTER */ virtual bool parseGuildRoster(network::Packet& packet, GuildRosterData& data) { return GuildRosterParser::parse(packet, data); } /** Parse SMSG_GUILD_QUERY_RESPONSE */ virtual bool parseGuildQueryResponse(network::Packet& packet, GuildQueryResponseData& data) { return GuildQueryResponseParser::parse(packet, data); } // --- Channels --- /** Build CMSG_JOIN_CHANNEL */ virtual network::Packet buildJoinChannel(const std::string& channelName, const std::string& password) { return JoinChannelPacket::build(channelName, password); } /** Build CMSG_LEAVE_CHANNEL */ virtual network::Packet buildLeaveChannel(const std::string& channelName) { return LeaveChannelPacket::build(channelName); } // --- Mail --- /** Build CMSG_SEND_MAIL */ virtual network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient, const std::string& subject, const std::string& body, uint32_t money, uint32_t cod, const std::vector& itemGuids = {}) { return SendMailPacket::build(mailboxGuid, recipient, subject, body, money, cod, itemGuids); } /** Parse SMSG_MAIL_LIST_RESULT into a vector of MailMessage */ virtual bool parseMailList(network::Packet& packet, std::vector& inbox); /** Build CMSG_MAIL_TAKE_ITEM */ virtual network::Packet buildMailTakeItem(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemSlot) { return MailTakeItemPacket::build(mailboxGuid, mailId, itemSlot); } /** Build CMSG_MAIL_DELETE */ virtual network::Packet buildMailDelete(uint64_t mailboxGuid, uint32_t mailId, uint32_t mailTemplateId) { return MailDeletePacket::build(mailboxGuid, mailId, mailTemplateId); } // --- Utility --- /** Read a packed GUID from the packet */ virtual uint64_t readPackedGuid(network::Packet& packet) { return UpdateObjectParser::readPackedGuid(packet); } /** Write a packed GUID to the packet */ virtual void writePackedGuid(network::Packet& packet, uint64_t guid) { MovementPacket::writePackedGuid(packet, guid); } }; /** * WotLK 3.3.5a packet parsers. * * Uses the default implementations which delegate to the existing * static parser classes. All current parsing code is WotLK-specific, * so no overrides are needed. */ class WotlkPacketParsers : public PacketParsers { // All methods use the defaults from PacketParsers base class, // which delegate to the existing WotLK static parsers. }; /** * TBC 2.4.3 packet parsers. * * Overrides methods where TBC binary format differs from WotLK: * - SMSG_UPDATE_OBJECT: u8 has_transport after blockCount (WotLK removed it) * - UpdateFlags is u8 (not u16), no VEHICLE/ROTATION/POSITION flags * - Movement flags2 is u8 (not u16), no transport seat byte * - Movement flags: JUMPING=0x2000 gates jump data (WotLK: FALLING=0x1000) * - SPLINE_ENABLED=0x08000000, SPLINE_ELEVATION=0x04000000 (same as WotLK) * - Pitch: SWIMMING or else ONTRANSPORT(0x02000000) * - CharEnum: uint8 firstLogin (not uint32+uint8), 20 equipment items (not 23) * - Aura updates use inline update fields, not SMSG_AURA_UPDATE */ class TbcPacketParsers : public PacketParsers { public: uint8_t movementFlags2Size() const override { return 1; } bool parseMovementBlock(network::Packet& packet, UpdateBlock& block) override; void writeMovementPayload(network::Packet& packet, const MovementInfo& info) override; network::Packet buildMovementPacket(LogicalOpcode opcode, const MovementInfo& info, uint64_t playerGuid = 0) override; bool parseUpdateObject(network::Packet& packet, UpdateObjectData& data) override; bool parseCharEnum(network::Packet& packet, CharEnumResponse& response) override; bool parseAuraUpdate(network::Packet& packet, AuraUpdateData& data, bool isAll = false) override; 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; // TBC 2.4.3 SMSG_GOSSIP_MESSAGE quests lack questFlags(u32)+isRepeatable(u8) (WotLK added them) bool parseGossipMessage(network::Packet& packet, GossipMessageData& data) override; // TBC 2.4.3 SMSG_CAST_RESULT: spellId(u32) + result(u8) — WotLK added castCount(u8) prefix bool parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) override; // TBC 2.4.3 SMSG_CAST_FAILED: spellId(u32) + result(u8) — WotLK added castCount(u8) prefix bool parseCastFailed(network::Packet& packet, CastFailedData& data) override; // TBC 2.4.3 SMSG_SPELL_START: full uint64 GUIDs (WotLK uses packed GUIDs) bool parseSpellStart(network::Packet& packet, SpellStartData& data) override; // TBC 2.4.3 SMSG_SPELL_GO: full uint64 GUIDs, no timestamp field (WotLK added one) bool parseSpellGo(network::Packet& packet, SpellGoData& data) override; // TBC 2.4.3 SMSG_MAIL_LIST_RESULT: uint8 count (not uint32+uint8), no body field, // attachment uses uint64 itemGuid (not uint32), enchants are 7×u32 id-only (not 7×{id+dur+charges}) bool parseMailList(network::Packet& packet, std::vector& inbox) override; // TBC 2.4.3 SMSG_ATTACKERSTATEUPDATE uses full uint64 GUIDs (WotLK uses packed GUIDs) bool parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) override; // TBC 2.4.3 SMSG_SPELLNONMELEEDAMAGELOG uses full uint64 GUIDs (WotLK uses packed GUIDs) bool parseSpellDamageLog(network::Packet& packet, SpellDamageLogData& data) override; // TBC 2.4.3 SMSG_SPELLHEALLOG uses full uint64 GUIDs (WotLK uses packed GUIDs) bool parseSpellHealLog(network::Packet& packet, SpellHealLogData& data) override; // TBC 2.4.3 quest log has 4 update fields per slot (questId, state, counts, timer) // WotLK expands this to 5 (splits counts into two fields). uint8_t questLogStride() const override { return 4; } // 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; }; /** * Classic 1.12.1 packet parsers. * * Inherits from TBC (shared: u8 UpdateFlags, has_transport byte). * * Differences from TBC: * - No moveFlags2 byte (TBC has u8, Classic has none) * - Only 6 speed fields (no flight speeds — flying added in TBC) * - SPLINE_ENABLED at 0x00400000 (TBC/WotLK: 0x08000000) * - Transport data has no timestamp (TBC adds u32 timestamp) * - Pitch: only SWIMMING (no ONTRANSPORT secondary pitch) * - CharEnum: no enchantment field per equipment slot * - No SMSG_AURA_UPDATE (uses update fields, same as TBC) */ class ClassicPacketParsers : public TbcPacketParsers { public: uint8_t movementFlags2Size() const override { return 0; } bool parseCharEnum(network::Packet& packet, CharEnumResponse& response) override; bool parseMovementBlock(network::Packet& packet, UpdateBlock& block) override; void writeMovementPayload(network::Packet& packet, const MovementInfo& info) override; network::Packet buildMovementPacket(LogicalOpcode opcode, const MovementInfo& info, uint64_t playerGuid = 0) override; network::Packet buildCastSpell(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) override; network::Packet buildUseItem(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid, uint32_t spellId = 0) override; bool parseCastFailed(network::Packet& packet, CastFailedData& data) override; bool parseCastResult(network::Packet& packet, uint32_t& spellId, uint8_t& result) override; bool parseMessageChat(network::Packet& packet, MessageChatData& data) override; bool parseGameObjectQueryResponse(network::Packet& packet, GameObjectQueryResponseData& data) override; // Classic 1.12 SMSG_CREATURE_QUERY_RESPONSE lacks the iconName string that TBC/WotLK include bool parseCreatureQueryResponse(network::Packet& packet, CreatureQueryResponseData& 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; network::Packet buildLeaveChannel(const std::string& channelName) override; network::Packet buildSendMail(uint64_t mailboxGuid, const std::string& recipient, const std::string& subject, const std::string& body, uint32_t money, uint32_t cod, const std::vector& itemGuids = {}) override; bool parseMailList(network::Packet& packet, std::vector& inbox) override; network::Packet buildMailTakeItem(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemSlot) override; network::Packet buildMailDelete(uint64_t mailboxGuid, uint32_t mailId, uint32_t mailTemplateId) override; network::Packet buildItemQuery(uint32_t entry, uint64_t guid) override; bool parseItemQueryResponse(network::Packet& packet, ItemQueryResponseData& data) override; 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; // parseQuestDetails inherited from TbcPacketParsers (same format as TBC 2.4.3) uint8_t questLogStride() const override { return 3; } // Classic 1.12 has 64 explored-zone uint32 fields (zone IDs fit in 2048 bits). // TBC/WotLK use 128 (needed for Outland/Northrend zone IDs up to 4095). uint8_t exploredZonesCount() const override { return 64; } 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; // Classic 1.12 melee/spell log packets use PackedGuid (not full uint64) bool parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) override; bool parseSpellDamageLog(network::Packet& packet, SpellDamageLogData& data) override; bool parseSpellHealLog(network::Packet& packet, SpellHealLogData& data) override; // Classic 1.12 has SMSG_AURA_UPDATE (unlike TBC which doesn't); // format differs from WotLK: no caster GUID, DURATION flag is 0x10 not 0x20 bool parseAuraUpdate(network::Packet& packet, AuraUpdateData& data, bool isAll = false) override; // Classic 1.12 SMSG_NAME_QUERY_RESPONSE: full uint64 guid + name + realmName CString + // uint32 race + uint32 gender + uint32 class (TBC Variant A skips the realmName CString) bool parseNameQueryResponse(network::Packet& packet, NameQueryResponseData& data) override; }; /** * Turtle WoW (build 7234) packet parsers. * * Turtle WoW is a heavily modified vanilla server that sends TBC-style * movement blocks (moveFlags2, transport timestamps, 8 speeds including flight) * while keeping all other Classic packet formats. * * Inherits all Classic overrides (charEnum, chat, gossip, mail, items, etc.) * but delegates movement block parsing to TBC format. */ class TurtlePacketParsers : public ClassicPacketParsers { public: uint8_t movementFlags2Size() const override { return 0; } bool parseMovementBlock(network::Packet& packet, UpdateBlock& block) override; bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override; }; /** * Factory function to create the right parser set for an expansion. */ inline std::unique_ptr createPacketParsers(const std::string& expansionId) { if (expansionId == "classic") return std::make_unique(); if (expansionId == "turtle") return std::make_unique(); if (expansionId == "tbc") return std::make_unique(); return std::make_unique(); } } // namespace game } // namespace wowee