Kelsidavis-WoWee/include/game/packet_parsers.hpp

473 lines
22 KiB
C++
Raw Normal View History

#pragma once
#include "game/world_packets.hpp"
#include <memory>
#include <string>
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);
}
2026-02-13 19:52:49 -08:00
/** 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<uint64_t>& 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<MailMessage>& inbox);
/** Build CMSG_MAIL_TAKE_ITEM */
virtual network::Packet buildMailTakeItem(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemGuidLow) {
return MailTakeItemPacket::build(mailboxGuid, mailId, itemGuidLow);
}
/** 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;
2026-02-13 19:52:49 -08:00
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<MailMessage>& 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<uint64_t>& itemGuids = {}) override;
bool parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) override;
network::Packet buildMailTakeItem(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemGuidLow) 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 packet parsers.
*
* Turtle is Classic-based but not wire-identical to vanilla MaNGOS. It keeps
* most Classic packet formats, while overriding the movement-bearing paths that
* have proven to vary in live traffic:
* - update-object movement blocks use a Turtle-specific hybrid layout
* - update-object parsing falls back through Classic/TBC/WotLK movement layouts
* - monster-move parsing falls back through Vanilla, TBC, and guarded WotLK layouts
*
* Everything else inherits the Classic parser behavior.
*/
class TurtlePacketParsers : public ClassicPacketParsers {
public:
uint8_t movementFlags2Size() const override { return 0; }
bool parseUpdateObject(network::Packet& packet, UpdateObjectData& data) override;
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<PacketParsers> createPacketParsers(const std::string& expansionId) {
if (expansionId == "classic") return std::make_unique<ClassicPacketParsers>();
if (expansionId == "turtle") return std::make_unique<TurtlePacketParsers>();
if (expansionId == "tbc") return std::make_unique<TbcPacketParsers>();
return std::make_unique<WotlkPacketParsers>();
}
} // namespace game
} // namespace wowee