#include "game/packet_parsers.hpp" #include "core/logger.hpp" namespace wowee { namespace game { // ============================================================================ // TBC 2.4.3 movement flag constants (shifted relative to WotLK 3.3.5a) // ============================================================================ namespace TbcMoveFlags { constexpr uint32_t ON_TRANSPORT = 0x00000200; // Gates transport data (same as WotLK) constexpr uint32_t JUMPING = 0x00002000; // Gates jump data (WotLK: FALLING=0x1000) constexpr uint32_t SWIMMING = 0x00200000; // Same as WotLK constexpr uint32_t FLYING = 0x01000000; // WotLK: 0x02000000 constexpr uint32_t ONTRANSPORT = 0x02000000; // Secondary pitch check constexpr uint32_t SPLINE_ELEVATION = 0x04000000; // Same as WotLK constexpr uint32_t SPLINE_ENABLED = 0x08000000; // Same as WotLK } // ============================================================================ // TBC parseMovementBlock // Key differences from WotLK: // - UpdateFlags is uint8 (not uint16) // - No VEHICLE (0x0080), POSITION (0x0100), ROTATION (0x0200) flags // - moveFlags2 is uint8 (not uint16) // - No transport seat byte // - No interpolated movement (flags2 & 0x0200) check // - Pitch check: SWIMMING, else ONTRANSPORT(0x02000000) // - Spline data: has splineId, no durationMod/durationModNext/verticalAccel/effectStartTime/splineMode // - Flag 0x08 (HIGH_GUID) reads 2 u32s (Classic: 1 u32) // ============================================================================ bool TbcPacketParsers::parseMovementBlock(network::Packet& packet, UpdateBlock& block) { // TBC 2.4.3: UpdateFlags is uint8 (1 byte) uint8_t updateFlags = packet.readUInt8(); block.updateFlags = static_cast(updateFlags); LOG_DEBUG(" [TBC] UpdateFlags: 0x", std::hex, (int)updateFlags, std::dec); // TBC UpdateFlag bit values (same as lower byte of WotLK): // 0x01 = SELF // 0x02 = TRANSPORT // 0x04 = HAS_TARGET // 0x08 = LOWGUID // 0x10 = HIGHGUID // 0x20 = LIVING // 0x40 = HAS_POSITION (stationary) const uint8_t UPDATEFLAG_LIVING = 0x20; const uint8_t UPDATEFLAG_HAS_POSITION = 0x40; const uint8_t UPDATEFLAG_HAS_TARGET = 0x04; const uint8_t UPDATEFLAG_TRANSPORT = 0x02; const uint8_t UPDATEFLAG_LOWGUID = 0x08; const uint8_t UPDATEFLAG_HIGHGUID = 0x10; if (updateFlags & UPDATEFLAG_LIVING) { // Full movement block for living units uint32_t moveFlags = packet.readUInt32(); uint8_t moveFlags2 = packet.readUInt8(); // TBC: uint8, not uint16 (void)moveFlags2; /*uint32_t time =*/ packet.readUInt32(); // Position block.x = packet.readFloat(); block.y = packet.readFloat(); block.z = packet.readFloat(); block.orientation = packet.readFloat(); block.hasMovement = true; LOG_DEBUG(" [TBC] LIVING: (", block.x, ", ", block.y, ", ", block.z, "), o=", block.orientation, " moveFlags=0x", std::hex, moveFlags, std::dec); // Transport data if (moveFlags & TbcMoveFlags::ON_TRANSPORT) { block.onTransport = true; block.transportGuid = UpdateObjectParser::readPackedGuid(packet); block.transportX = packet.readFloat(); block.transportY = packet.readFloat(); block.transportZ = packet.readFloat(); block.transportO = packet.readFloat(); /*uint32_t tTime =*/ packet.readUInt32(); // TBC: NO transport seat byte // TBC: NO interpolated movement check } // Pitch: SWIMMING, or else ONTRANSPORT (TBC-specific secondary pitch) if (moveFlags & TbcMoveFlags::SWIMMING) { /*float pitch =*/ packet.readFloat(); } else if (moveFlags & TbcMoveFlags::ONTRANSPORT) { /*float pitch =*/ packet.readFloat(); } // Fall time (always present) /*uint32_t fallTime =*/ packet.readUInt32(); // Jumping (TBC: JUMPING=0x2000, WotLK: FALLING=0x1000) if (moveFlags & TbcMoveFlags::JUMPING) { /*float jumpVelocity =*/ packet.readFloat(); /*float jumpSinAngle =*/ packet.readFloat(); /*float jumpCosAngle =*/ packet.readFloat(); /*float jumpXYSpeed =*/ packet.readFloat(); } // Spline elevation (TBC: 0x02000000, WotLK: 0x04000000) if (moveFlags & TbcMoveFlags::SPLINE_ELEVATION) { /*float splineElevation =*/ packet.readFloat(); } // Speeds (TBC: 8 values — walk, run, runBack, swim, fly, flyBack, swimBack, turn) // WotLK adds pitchRate (9 total) /*float walkSpeed =*/ packet.readFloat(); float runSpeed = packet.readFloat(); /*float runBackSpeed =*/ packet.readFloat(); /*float swimSpeed =*/ packet.readFloat(); /*float flySpeed =*/ packet.readFloat(); /*float flyBackSpeed =*/ packet.readFloat(); /*float swimBackSpeed =*/ packet.readFloat(); /*float turnRate =*/ packet.readFloat(); block.runSpeed = runSpeed; // Spline data (TBC/WotLK: SPLINE_ENABLED = 0x08000000) if (moveFlags & TbcMoveFlags::SPLINE_ENABLED) { uint32_t splineFlags = packet.readUInt32(); LOG_DEBUG(" [TBC] Spline: flags=0x", std::hex, splineFlags, std::dec); if (splineFlags & 0x00010000) { // FINAL_POINT /*float finalX =*/ packet.readFloat(); /*float finalY =*/ packet.readFloat(); /*float finalZ =*/ packet.readFloat(); } else if (splineFlags & 0x00020000) { // FINAL_TARGET /*uint64_t finalTarget =*/ packet.readUInt64(); } else if (splineFlags & 0x00040000) { // FINAL_ANGLE /*float finalAngle =*/ packet.readFloat(); } // TBC spline: timePassed, duration, id, nodes, finalNode // (no durationMod, durationModNext, verticalAccel, effectStartTime, splineMode) /*uint32_t timePassed =*/ packet.readUInt32(); /*uint32_t duration =*/ packet.readUInt32(); /*uint32_t splineId =*/ packet.readUInt32(); uint32_t pointCount = packet.readUInt32(); if (pointCount > 256) { static uint32_t badTbcSplineCount = 0; ++badTbcSplineCount; if (badTbcSplineCount <= 5 || (badTbcSplineCount % 100) == 0) { LOG_WARNING(" [TBC] Spline pointCount=", pointCount, " exceeds max, capping (occurrence=", badTbcSplineCount, ")"); } pointCount = 0; } for (uint32_t i = 0; i < pointCount; i++) { /*float px =*/ packet.readFloat(); /*float py =*/ packet.readFloat(); /*float pz =*/ packet.readFloat(); } // TBC: NO splineMode byte (WotLK adds it) /*float endPointX =*/ packet.readFloat(); /*float endPointY =*/ packet.readFloat(); /*float endPointZ =*/ packet.readFloat(); } } else if (updateFlags & UPDATEFLAG_HAS_POSITION) { // TBC: Simple stationary position (same as WotLK STATIONARY) block.x = packet.readFloat(); block.y = packet.readFloat(); block.z = packet.readFloat(); block.orientation = packet.readFloat(); block.hasMovement = true; LOG_DEBUG(" [TBC] STATIONARY: (", block.x, ", ", block.y, ", ", block.z, ")"); } // TBC: No UPDATEFLAG_POSITION (0x0100) code path // Target GUID if (updateFlags & UPDATEFLAG_HAS_TARGET) { /*uint64_t targetGuid =*/ UpdateObjectParser::readPackedGuid(packet); } // Transport time if (updateFlags & UPDATEFLAG_TRANSPORT) { /*uint32_t transportTime =*/ packet.readUInt32(); } // TBC: No VEHICLE flag (WotLK 0x0080) // TBC: No ROTATION flag (WotLK 0x0200) // HIGH_GUID (0x08) — TBC has 2 u32s, Classic has 1 u32 if (updateFlags & UPDATEFLAG_LOWGUID) { /*uint32_t unknown0 =*/ packet.readUInt32(); /*uint32_t unknown1 =*/ packet.readUInt32(); } // ALL (0x10) if (updateFlags & UPDATEFLAG_HIGHGUID) { /*uint32_t unknown2 =*/ packet.readUInt32(); } return true; } // ============================================================================ // TBC writeMovementPayload // Key differences from WotLK: // - flags2 is uint8 (not uint16) // - No transport seat byte // - No interpolated movement (flags2 & 0x0200) write // - Pitch check uses TBC flag positions // ============================================================================ void TbcPacketParsers::writeMovementPayload(network::Packet& packet, const MovementInfo& info) { // Movement flags (uint32, same as WotLK) packet.writeUInt32(info.flags); // TBC: flags2 is uint8 (WotLK: uint16) packet.writeUInt8(static_cast(info.flags2 & 0xFF)); // Timestamp packet.writeUInt32(info.time); // Position packet.writeBytes(reinterpret_cast(&info.x), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.y), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.z), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.orientation), sizeof(float)); // Transport data (TBC ON_TRANSPORT = 0x200, same bit as WotLK) if (info.flags & TbcMoveFlags::ON_TRANSPORT) { // Packed transport GUID uint8_t transMask = 0; uint8_t transGuidBytes[8]; int transGuidByteCount = 0; for (int i = 0; i < 8; i++) { uint8_t byte = static_cast((info.transportGuid >> (i * 8)) & 0xFF); if (byte != 0) { transMask |= (1 << i); transGuidBytes[transGuidByteCount++] = byte; } } packet.writeUInt8(transMask); for (int i = 0; i < transGuidByteCount; i++) { packet.writeUInt8(transGuidBytes[i]); } // Transport local position packet.writeBytes(reinterpret_cast(&info.transportX), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.transportY), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.transportZ), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.transportO), sizeof(float)); // Transport time packet.writeUInt32(info.transportTime); // TBC: NO transport seat byte // TBC: NO interpolated movement time } // Pitch: SWIMMING or else ONTRANSPORT (TBC flag positions) if (info.flags & TbcMoveFlags::SWIMMING) { packet.writeBytes(reinterpret_cast(&info.pitch), sizeof(float)); } else if (info.flags & TbcMoveFlags::ONTRANSPORT) { packet.writeBytes(reinterpret_cast(&info.pitch), sizeof(float)); } // Fall time (always present) packet.writeUInt32(info.fallTime); // Jump data (TBC JUMPING = 0x2000, WotLK FALLING = 0x1000) if (info.flags & TbcMoveFlags::JUMPING) { packet.writeBytes(reinterpret_cast(&info.jumpVelocity), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.jumpSinAngle), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.jumpCosAngle), sizeof(float)); packet.writeBytes(reinterpret_cast(&info.jumpXYSpeed), sizeof(float)); } } // ============================================================================ // TBC buildMovementPacket // Classic/TBC: client movement packets do NOT include PackedGuid prefix // (WotLK added PackedGuid to client packets) // ============================================================================ network::Packet TbcPacketParsers::buildMovementPacket(LogicalOpcode opcode, const MovementInfo& info, uint64_t /*playerGuid*/) { network::Packet packet(wireOpcode(opcode)); // TBC: NO PackedGuid prefix for client packets writeMovementPayload(packet, info); return packet; } // ============================================================================ // TBC parseCharEnum // Differences from WotLK: // - After flags: uint8 firstLogin (not uint32 customization + uint8 unknown) // - Equipment: 20 items (not 23) // ============================================================================ bool TbcPacketParsers::parseCharEnum(network::Packet& packet, CharEnumResponse& response) { uint8_t count = packet.readUInt8(); LOG_INFO("[TBC] Parsing SMSG_CHAR_ENUM: ", (int)count, " characters"); response.characters.clear(); response.characters.reserve(count); for (uint8_t i = 0; i < count; ++i) { Character character; // GUID (8 bytes) character.guid = packet.readUInt64(); // Name (null-terminated string) character.name = packet.readString(); // Race, class, gender character.race = static_cast(packet.readUInt8()); character.characterClass = static_cast(packet.readUInt8()); character.gender = static_cast(packet.readUInt8()); // Appearance (5 bytes: skin, face, hairStyle, hairColor packed + facialFeatures) character.appearanceBytes = packet.readUInt32(); character.facialFeatures = packet.readUInt8(); // Level character.level = packet.readUInt8(); // Location character.zoneId = packet.readUInt32(); character.mapId = packet.readUInt32(); character.x = packet.readFloat(); character.y = packet.readFloat(); character.z = packet.readFloat(); // Guild ID character.guildId = packet.readUInt32(); // Flags character.flags = packet.readUInt32(); // TBC: uint8 firstLogin (WotLK: uint32 customization + uint8 unknown) /*uint8_t firstLogin =*/ packet.readUInt8(); // Pet data (always present) character.pet.displayModel = packet.readUInt32(); character.pet.level = packet.readUInt32(); character.pet.family = packet.readUInt32(); // Equipment (TBC: 20 items, WotLK: 23 items) character.equipment.reserve(20); for (int j = 0; j < 20; ++j) { EquipmentItem item; item.displayModel = packet.readUInt32(); item.inventoryType = packet.readUInt8(); item.enchantment = packet.readUInt32(); character.equipment.push_back(item); } LOG_INFO(" Character ", (int)(i + 1), ": ", character.name); LOG_INFO(" GUID: 0x", std::hex, character.guid, std::dec); LOG_INFO(" ", getRaceName(character.race), " ", getClassName(character.characterClass), " (", getGenderName(character.gender), ")"); LOG_INFO(" Level: ", (int)character.level); LOG_INFO(" Location: Zone ", character.zoneId, ", Map ", character.mapId); response.characters.push_back(character); } LOG_INFO("[TBC] Parsed ", response.characters.size(), " characters"); return true; } // ============================================================================ // TBC parseUpdateObject // Key difference from WotLK: u8 has_transport byte after blockCount // (WotLK removed this field) // ============================================================================ bool TbcPacketParsers::parseUpdateObject(network::Packet& packet, UpdateObjectData& data) { constexpr uint32_t kMaxReasonableUpdateBlocks = 4096; auto parseWithLayout = [&](bool withHasTransportByte, UpdateObjectData& out) -> bool { out = UpdateObjectData{}; size_t start = packet.getReadPos(); if (packet.getSize() - start < 4) return false; out.blockCount = packet.readUInt32(); if (out.blockCount > kMaxReasonableUpdateBlocks) { packet.setReadPos(start); return false; } if (withHasTransportByte) { if (packet.getReadPos() >= packet.getSize()) { packet.setReadPos(start); return false; } /*uint8_t hasTransport =*/ packet.readUInt8(); } if (packet.getReadPos() + 1 <= packet.getSize()) { uint8_t firstByte = packet.readUInt8(); if (firstByte == static_cast(UpdateType::OUT_OF_RANGE_OBJECTS)) { if (packet.getReadPos() + 4 > packet.getSize()) { packet.setReadPos(start); return false; } uint32_t count = packet.readUInt32(); if (count > kMaxReasonableUpdateBlocks) { packet.setReadPos(start); return false; } for (uint32_t i = 0; i < count; ++i) { if (packet.getReadPos() >= packet.getSize()) { packet.setReadPos(start); return false; } uint64_t guid = UpdateObjectParser::readPackedGuid(packet); out.outOfRangeGuids.push_back(guid); } } else { packet.setReadPos(packet.getReadPos() - 1); } } out.blocks.reserve(out.blockCount); for (uint32_t i = 0; i < out.blockCount; ++i) { if (packet.getReadPos() >= packet.getSize()) { packet.setReadPos(start); return false; } UpdateBlock block; uint8_t updateTypeVal = packet.readUInt8(); if (updateTypeVal > static_cast(UpdateType::NEAR_OBJECTS)) { packet.setReadPos(start); return false; } block.updateType = static_cast(updateTypeVal); bool ok = false; switch (block.updateType) { case UpdateType::VALUES: { block.guid = UpdateObjectParser::readPackedGuid(packet); ok = UpdateObjectParser::parseUpdateFields(packet, block); break; } case UpdateType::MOVEMENT: { block.guid = UpdateObjectParser::readPackedGuid(packet); ok = this->parseMovementBlock(packet, block); break; } case UpdateType::CREATE_OBJECT: case UpdateType::CREATE_OBJECT2: { block.guid = UpdateObjectParser::readPackedGuid(packet); if (packet.getReadPos() >= packet.getSize()) { ok = false; break; } uint8_t objectTypeVal = packet.readUInt8(); block.objectType = static_cast(objectTypeVal); ok = this->parseMovementBlock(packet, block); if (ok) ok = UpdateObjectParser::parseUpdateFields(packet, block); break; } case UpdateType::OUT_OF_RANGE_OBJECTS: case UpdateType::NEAR_OBJECTS: ok = true; break; default: ok = false; break; } if (!ok) { packet.setReadPos(start); return false; } out.blocks.push_back(block); } return true; }; size_t startPos = packet.getReadPos(); UpdateObjectData parsed; if (parseWithLayout(true, parsed)) { data = std::move(parsed); return true; } packet.setReadPos(startPos); if (parseWithLayout(false, parsed)) { LOG_WARNING("[TBC] SMSG_UPDATE_OBJECT parsed without has_transport byte fallback"); data = std::move(parsed); return true; } packet.setReadPos(startPos); return false; } network::Packet TbcPacketParsers::buildAcceptQuestPacket(uint64_t npcGuid, uint32_t questId) { network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_ACCEPT_QUEST)); packet.writeUInt64(npcGuid); packet.writeUInt32(questId); // TBC servers generally expect guid + questId only. return packet; } // ============================================================================ // TBC parseAuraUpdate - SMSG_AURA_UPDATE doesn't exist in TBC // TBC uses inline aura update fields + SMSG_INIT_EXTRA_AURA_INFO_OBSOLETE (0x3A3) / // SMSG_SET_EXTRA_AURA_INFO_OBSOLETE (0x3A4) instead // ============================================================================ bool TbcPacketParsers::parseAuraUpdate(network::Packet& /*packet*/, AuraUpdateData& /*data*/, bool /*isAll*/) { LOG_WARNING("[TBC] parseAuraUpdate called but SMSG_AURA_UPDATE does not exist in TBC 2.4.3"); return false; } // ============================================================================ // TBC/Classic parseNameQueryResponse // // WotLK uses: packedGuid + uint8 found + name + realmName + u8 race + u8 gender + u8 class // Classic/TBC commonly use: uint64 guid + [optional uint8 found] + CString name + uint32 race + uint32 gender + uint32 class // // Implement a robust parser that handles both classic-era variants. // ============================================================================ static bool hasNullWithin(const network::Packet& p, size_t start, size_t maxLen) { const auto& d = p.getData(); size_t end = std::min(d.size(), start + maxLen); for (size_t i = start; i < end; i++) { if (d[i] == 0) return true; } return false; } bool TbcPacketParsers::parseNameQueryResponse(network::Packet& packet, NameQueryResponseData& data) { // Default all fields data = NameQueryResponseData{}; size_t start = packet.getReadPos(); if (packet.getSize() - start < 8) return false; // Variant A: guid(u64) + name + race(u32) + gender(u32) + class(u32) { packet.setReadPos(start); data.guid = packet.readUInt64(); data.found = 0; data.name = packet.readString(); if (!data.name.empty() && (packet.getSize() - packet.getReadPos()) >= 12) { uint32_t race = packet.readUInt32(); uint32_t gender = packet.readUInt32(); uint32_t cls = packet.readUInt32(); data.race = static_cast(race & 0xFF); data.gender = static_cast(gender & 0xFF); data.classId = static_cast(cls & 0xFF); data.realmName.clear(); return true; } } // Variant B: guid(u64) + found(u8) + [if found==0: name + race(u32)+gender(u32)+class(u32)] { packet.setReadPos(start); data.guid = packet.readUInt64(); if (packet.getSize() - packet.getReadPos() < 1) { packet.setReadPos(start); return false; } uint8_t found = packet.readUInt8(); // Guard: only treat it as a found flag if a CString likely follows. if ((found == 0 || found == 1) && hasNullWithin(packet, packet.getReadPos(), 64)) { data.found = found; if (data.found != 0) return true; data.name = packet.readString(); if (!data.name.empty() && (packet.getSize() - packet.getReadPos()) >= 12) { uint32_t race = packet.readUInt32(); uint32_t gender = packet.readUInt32(); uint32_t cls = packet.readUInt32(); data.race = static_cast(race & 0xFF); data.gender = static_cast(gender & 0xFF); data.classId = static_cast(cls & 0xFF); data.realmName.clear(); return true; } } } packet.setReadPos(start); return false; } // ============================================================================ // TBC parseItemQueryResponse - SMSG_ITEM_QUERY_SINGLE_RESPONSE (2.4.3 format) // // Differences from WotLK (handled by base class ItemQueryResponseParser::parse): // - No Flags2 field (WotLK added a second flags uint32 after Flags) // - No BuyCount field (WotLK added this between Flags2 and BuyPrice) // - Stats: sends exactly statsCount pairs (WotLK always sends 10) // - No ScalingStatDistribution / ScalingStatValue (WotLK-only heirloom scaling) // // Differences from Classic (ClassicPacketParsers::parseItemQueryResponse): // - Has SoundOverrideSubclass (int32) after subClass (Classic lacks it) // - Has statsCount prefix (Classic reads 10 pairs with no prefix) // ============================================================================ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQueryResponseData& data) { data.entry = packet.readUInt32(); if (data.entry & 0x80000000) { data.entry &= ~0x80000000; return true; } uint32_t itemClass = packet.readUInt32(); uint32_t subClass = packet.readUInt32(); data.itemClass = itemClass; data.subClass = subClass; packet.readUInt32(); // SoundOverrideSubclass (int32, -1 = no override) data.subclassName = ""; // Name strings data.name = packet.readString(); packet.readString(); // name2 packet.readString(); // name3 packet.readString(); // name4 data.displayInfoId = packet.readUInt32(); data.quality = packet.readUInt32(); packet.readUInt32(); // Flags (TBC: 1 flags field only — no Flags2) // TBC: NO Flags2, NO BuyCount packet.readUInt32(); // BuyPrice data.sellPrice = packet.readUInt32(); data.inventoryType = packet.readUInt32(); packet.readUInt32(); // AllowableClass packet.readUInt32(); // AllowableRace packet.readUInt32(); // ItemLevel packet.readUInt32(); // RequiredLevel packet.readUInt32(); // RequiredSkill packet.readUInt32(); // RequiredSkillRank packet.readUInt32(); // RequiredSpell packet.readUInt32(); // RequiredHonorRank packet.readUInt32(); // RequiredCityRank packet.readUInt32(); // RequiredReputationFaction packet.readUInt32(); // RequiredReputationRank packet.readUInt32(); // MaxCount data.maxStack = static_cast(packet.readUInt32()); // Stackable data.containerSlots = packet.readUInt32(); // TBC: statsCount prefix + exactly statsCount pairs (WotLK always sends 10) uint32_t statsCount = packet.readUInt32(); if (statsCount > 10) statsCount = 10; // sanity cap for (uint32_t i = 0; i < statsCount; i++) { uint32_t statType = packet.readUInt32(); int32_t statValue = static_cast(packet.readUInt32()); switch (statType) { case 3: data.agility = statValue; break; case 4: data.strength = statValue; break; case 5: data.intellect = statValue; break; case 6: data.spirit = statValue; break; case 7: data.stamina = statValue; break; default: break; } } // TBC: NO ScalingStatDistribution, NO ScalingStatValue (WotLK-only) // 5 damage entries bool haveWeaponDamage = false; for (int i = 0; i < 5; i++) { float dmgMin = packet.readFloat(); float dmgMax = packet.readFloat(); uint32_t damageType = packet.readUInt32(); if (!haveWeaponDamage && dmgMax > 0.0f) { if (damageType == 0 || data.damageMax <= 0.0f) { data.damageMin = dmgMin; data.damageMax = dmgMax; haveWeaponDamage = (damageType == 0); } } } data.armor = static_cast(packet.readUInt32()); if (packet.getSize() - packet.getReadPos() >= 28) { packet.readUInt32(); // HolyRes packet.readUInt32(); // FireRes packet.readUInt32(); // NatureRes packet.readUInt32(); // FrostRes packet.readUInt32(); // ShadowRes packet.readUInt32(); // ArcaneRes data.delayMs = packet.readUInt32(); } data.valid = !data.name.empty(); LOG_DEBUG("[TBC] Item query: ", data.name, " quality=", data.quality, " invType=", data.inventoryType, " armor=", data.armor); return true; } } // namespace game } // namespace wowee