2026-02-02 12:24:50 -08:00
|
|
|
|
#include "game/world_packets.hpp"
|
2026-02-16 18:46:44 -08:00
|
|
|
|
#include "game/packet_parsers.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
|
#include "game/opcodes.hpp"
|
|
|
|
|
|
#include "game/character.hpp"
|
|
|
|
|
|
#include "auth/crypto.hpp"
|
|
|
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
#include <cctype>
|
|
|
|
|
|
#include <cstring>
|
2026-02-10 13:16:38 -08:00
|
|
|
|
#include <sstream>
|
|
|
|
|
|
#include <iomanip>
|
2026-02-05 21:03:11 -08:00
|
|
|
|
#include <zlib.h>
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-23 16:30:49 +01:00
|
|
|
|
namespace {
|
|
|
|
|
|
inline uint32_t bswap32(uint32_t v) {
|
|
|
|
|
|
return ((v & 0xFF000000u) >> 24) | ((v & 0x00FF0000u) >> 8)
|
|
|
|
|
|
| ((v & 0x0000FF00u) << 8) | ((v & 0x000000FFu) << 24);
|
|
|
|
|
|
}
|
|
|
|
|
|
inline uint16_t bswap16(uint16_t v) {
|
|
|
|
|
|
return static_cast<uint16_t>(((v & 0xFF00u) >> 8) | ((v & 0x00FFu) << 8));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
namespace wowee {
|
|
|
|
|
|
namespace game {
|
|
|
|
|
|
|
2026-02-20 23:20:02 -08:00
|
|
|
|
std::string normalizeWowTextTokens(std::string text) {
|
|
|
|
|
|
if (text.empty()) return text;
|
|
|
|
|
|
|
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
while ((pos = text.find('$', pos)) != std::string::npos) {
|
|
|
|
|
|
if (pos + 1 >= text.size()) break;
|
|
|
|
|
|
const char code = text[pos + 1];
|
|
|
|
|
|
if (code == 'b' || code == 'B') {
|
|
|
|
|
|
text.replace(pos, 2, "\n");
|
|
|
|
|
|
++pos;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
++pos;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pos = 0;
|
|
|
|
|
|
while ((pos = text.find("|n", pos)) != std::string::npos) {
|
|
|
|
|
|
text.replace(pos, 2, "\n");
|
|
|
|
|
|
++pos;
|
|
|
|
|
|
}
|
|
|
|
|
|
pos = 0;
|
|
|
|
|
|
while ((pos = text.find("|N", pos)) != std::string::npos) {
|
|
|
|
|
|
text.replace(pos, 2, "\n");
|
|
|
|
|
|
++pos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
network::Packet AuthSessionPacket::build(uint32_t build,
|
|
|
|
|
|
const std::string& accountName,
|
|
|
|
|
|
uint32_t clientSeed,
|
|
|
|
|
|
const std::vector<uint8_t>& sessionKey,
|
2026-02-05 21:03:11 -08:00
|
|
|
|
uint32_t serverSeed,
|
|
|
|
|
|
uint32_t realmId) {
|
2026-02-02 12:24:50 -08:00
|
|
|
|
if (sessionKey.size() != 40) {
|
|
|
|
|
|
LOG_ERROR("Invalid session key size: ", sessionKey.size(), " (expected 40)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert account name to uppercase
|
|
|
|
|
|
std::string upperAccount = accountName;
|
|
|
|
|
|
std::transform(upperAccount.begin(), upperAccount.end(),
|
|
|
|
|
|
upperAccount.begin(), ::toupper);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Building CMSG_AUTH_SESSION for account: ", upperAccount);
|
|
|
|
|
|
|
|
|
|
|
|
// Compute authentication hash
|
|
|
|
|
|
auto authHash = computeAuthHash(upperAccount, clientSeed, serverSeed, sessionKey);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG(" Build: ", build);
|
|
|
|
|
|
LOG_DEBUG(" Client seed: 0x", std::hex, clientSeed, std::dec);
|
|
|
|
|
|
LOG_DEBUG(" Server seed: 0x", std::hex, serverSeed, std::dec);
|
|
|
|
|
|
LOG_DEBUG(" Auth hash: ", authHash.size(), " bytes");
|
|
|
|
|
|
|
|
|
|
|
|
// Create packet (opcode will be added by WorldSocket)
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_AUTH_SESSION));
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
|
bool isTbc = (build <= 8606); // TBC 2.4.3 = 8606, WotLK starts at 11159+
|
2026-02-05 21:03:11 -08:00
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
|
if (isTbc) {
|
|
|
|
|
|
// TBC 2.4.3 format (6 fields):
|
|
|
|
|
|
// Build, ServerID, Account, ClientSeed, Digest, AddonInfo
|
|
|
|
|
|
packet.writeUInt32(build);
|
|
|
|
|
|
packet.writeUInt32(realmId); // server_id
|
|
|
|
|
|
packet.writeString(upperAccount);
|
|
|
|
|
|
packet.writeUInt32(clientSeed);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// WotLK 3.3.5a format (11 fields):
|
|
|
|
|
|
// Build, LoginServerID, Account, LoginServerType, LocalChallenge,
|
|
|
|
|
|
// RegionID, BattlegroupID, RealmID, DosResponse, Digest, AddonInfo
|
|
|
|
|
|
packet.writeUInt32(build);
|
|
|
|
|
|
packet.writeUInt32(0); // LoginServerID
|
|
|
|
|
|
packet.writeString(upperAccount);
|
|
|
|
|
|
packet.writeUInt32(0); // LoginServerType
|
|
|
|
|
|
packet.writeUInt32(clientSeed);
|
2026-02-13 16:53:28 -08:00
|
|
|
|
// AzerothCore ignores these fields; other cores may validate them.
|
|
|
|
|
|
// Use 0 for maximum compatibility.
|
|
|
|
|
|
packet.writeUInt32(0); // RegionID
|
|
|
|
|
|
packet.writeUInt32(0); // BattlegroupID
|
|
|
|
|
|
packet.writeUInt32(realmId); // RealmID
|
2026-02-12 22:56:36 -08:00
|
|
|
|
LOG_DEBUG(" Realm ID: ", realmId);
|
|
|
|
|
|
packet.writeUInt32(0); // DOS response (uint64)
|
|
|
|
|
|
packet.writeUInt32(0);
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:03:11 -08:00
|
|
|
|
// Authentication hash/digest (20 bytes)
|
|
|
|
|
|
packet.writeBytes(authHash.data(), authHash.size());
|
|
|
|
|
|
|
Fix Classic field extraction, Warden PE patches, and auth/opcode corrections
Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.
Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
2026-02-20 00:18:03 -08:00
|
|
|
|
// Addon info - compressed block
|
|
|
|
|
|
// Format differs between expansions:
|
|
|
|
|
|
// Vanilla/TBC (CMaNGOS): while-loop of {string name, uint8 flags, uint32 modulusCRC, uint32 urlCRC}
|
|
|
|
|
|
// WotLK (AzerothCore): uint32 addonCount + {string name, uint8 enabled, uint32 crc, uint32 unk} + uint32 clientTime
|
|
|
|
|
|
std::vector<uint8_t> addonData;
|
|
|
|
|
|
if (isTbc) {
|
|
|
|
|
|
// Vanilla/TBC: each addon entry = null-terminated name + uint8 flags + uint32 modulusCRC + uint32 urlCRC
|
|
|
|
|
|
// Send standard Blizzard addons that CMaNGOS anticheat expects for fingerprinting
|
|
|
|
|
|
static const char* vanillaAddons[] = {
|
|
|
|
|
|
"Blizzard_AuctionUI", "Blizzard_BattlefieldMinimap", "Blizzard_BindingUI",
|
|
|
|
|
|
"Blizzard_CombatText", "Blizzard_CraftUI", "Blizzard_GMSurveyUI",
|
|
|
|
|
|
"Blizzard_InspectUI", "Blizzard_MacroUI", "Blizzard_RaidUI",
|
|
|
|
|
|
"Blizzard_TalentUI", "Blizzard_TradeSkillUI", "Blizzard_TrainerUI"
|
|
|
|
|
|
};
|
|
|
|
|
|
static const uint32_t standardModulusCRC = 0x4C1C776D;
|
|
|
|
|
|
for (const char* name : vanillaAddons) {
|
|
|
|
|
|
// string (null-terminated)
|
|
|
|
|
|
size_t len = strlen(name);
|
|
|
|
|
|
addonData.insert(addonData.end(), reinterpret_cast<const uint8_t*>(name),
|
|
|
|
|
|
reinterpret_cast<const uint8_t*>(name) + len + 1);
|
|
|
|
|
|
// uint8 flags = 1 (enabled)
|
|
|
|
|
|
addonData.push_back(0x01);
|
|
|
|
|
|
// uint32 modulusCRC (little-endian)
|
|
|
|
|
|
addonData.push_back(static_cast<uint8_t>(standardModulusCRC & 0xFF));
|
|
|
|
|
|
addonData.push_back(static_cast<uint8_t>((standardModulusCRC >> 8) & 0xFF));
|
|
|
|
|
|
addonData.push_back(static_cast<uint8_t>((standardModulusCRC >> 16) & 0xFF));
|
|
|
|
|
|
addonData.push_back(static_cast<uint8_t>((standardModulusCRC >> 24) & 0xFF));
|
|
|
|
|
|
// uint32 urlCRC = 0
|
|
|
|
|
|
addonData.push_back(0); addonData.push_back(0);
|
|
|
|
|
|
addonData.push_back(0); addonData.push_back(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// WotLK: uint32 addonCount + entries + uint32 clientTime
|
|
|
|
|
|
// Send 0 addons
|
|
|
|
|
|
addonData = { 0, 0, 0, 0, // addonCount = 0
|
|
|
|
|
|
0, 0, 0, 0 }; // clientTime = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t decompressedSize = static_cast<uint32_t>(addonData.size());
|
2026-02-05 21:03:11 -08:00
|
|
|
|
|
|
|
|
|
|
// Compress with zlib
|
|
|
|
|
|
uLongf compressedSize = compressBound(decompressedSize);
|
|
|
|
|
|
std::vector<uint8_t> compressed(compressedSize);
|
Fix Classic field extraction, Warden PE patches, and auth/opcode corrections
Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.
Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
2026-02-20 00:18:03 -08:00
|
|
|
|
int ret = compress(compressed.data(), &compressedSize, addonData.data(), decompressedSize);
|
2026-02-05 21:03:11 -08:00
|
|
|
|
if (ret == Z_OK) {
|
|
|
|
|
|
compressed.resize(compressedSize);
|
|
|
|
|
|
// Write decompressedSize, then compressed bytes
|
|
|
|
|
|
packet.writeUInt32(decompressedSize);
|
|
|
|
|
|
packet.writeBytes(compressed.data(), compressed.size());
|
|
|
|
|
|
LOG_DEBUG("Addon info: decompressedSize=", decompressedSize,
|
Fix Classic field extraction, Warden PE patches, and auth/opcode corrections
Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.
Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
2026-02-20 00:18:03 -08:00
|
|
|
|
" compressedSize=", compressedSize, " addons=",
|
|
|
|
|
|
isTbc ? "12 vanilla" : "0 wotlk");
|
2026-02-05 21:03:11 -08:00
|
|
|
|
} else {
|
|
|
|
|
|
LOG_ERROR("zlib compress failed with code: ", ret);
|
|
|
|
|
|
packet.writeUInt32(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
LOG_INFO("CMSG_AUTH_SESSION packet built: ", packet.getSize(), " bytes");
|
|
|
|
|
|
|
2026-02-05 21:03:11 -08:00
|
|
|
|
// Dump full packet for protocol debugging
|
|
|
|
|
|
const auto& data = packet.getData();
|
|
|
|
|
|
std::string hexDump;
|
|
|
|
|
|
for (size_t i = 0; i < data.size(); ++i) {
|
|
|
|
|
|
char buf[4];
|
|
|
|
|
|
snprintf(buf, sizeof(buf), "%02x ", data[i]);
|
|
|
|
|
|
hexDump += buf;
|
|
|
|
|
|
if ((i + 1) % 16 == 0) hexDump += "\n";
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_DEBUG("CMSG_AUTH_SESSION full dump:\n", hexDump);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> AuthSessionPacket::computeAuthHash(
|
|
|
|
|
|
const std::string& accountName,
|
|
|
|
|
|
uint32_t clientSeed,
|
|
|
|
|
|
uint32_t serverSeed,
|
|
|
|
|
|
const std::vector<uint8_t>& sessionKey) {
|
|
|
|
|
|
|
|
|
|
|
|
// Build hash input:
|
|
|
|
|
|
// account_name + [0,0,0,0] + client_seed + server_seed + session_key
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> hashInput;
|
|
|
|
|
|
hashInput.reserve(accountName.size() + 4 + 4 + 4 + sessionKey.size());
|
|
|
|
|
|
|
|
|
|
|
|
// Account name (as bytes)
|
|
|
|
|
|
hashInput.insert(hashInput.end(), accountName.begin(), accountName.end());
|
|
|
|
|
|
|
|
|
|
|
|
// 4 null bytes
|
|
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
|
|
hashInput.push_back(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Client seed (little-endian)
|
|
|
|
|
|
hashInput.push_back(clientSeed & 0xFF);
|
|
|
|
|
|
hashInput.push_back((clientSeed >> 8) & 0xFF);
|
|
|
|
|
|
hashInput.push_back((clientSeed >> 16) & 0xFF);
|
|
|
|
|
|
hashInput.push_back((clientSeed >> 24) & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
// Server seed (little-endian)
|
|
|
|
|
|
hashInput.push_back(serverSeed & 0xFF);
|
|
|
|
|
|
hashInput.push_back((serverSeed >> 8) & 0xFF);
|
|
|
|
|
|
hashInput.push_back((serverSeed >> 16) & 0xFF);
|
|
|
|
|
|
hashInput.push_back((serverSeed >> 24) & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
// Session key (40 bytes)
|
|
|
|
|
|
hashInput.insert(hashInput.end(), sessionKey.begin(), sessionKey.end());
|
|
|
|
|
|
|
2026-02-13 16:53:28 -08:00
|
|
|
|
// Diagnostic: dump auth hash inputs for debugging AUTH_REJECT
|
|
|
|
|
|
{
|
|
|
|
|
|
auto toHex = [](const uint8_t* data, size_t len) {
|
|
|
|
|
|
std::string s;
|
|
|
|
|
|
for (size_t i = 0; i < len; ++i) {
|
|
|
|
|
|
char buf[4]; snprintf(buf, sizeof(buf), "%02x", data[i]); s += buf;
|
|
|
|
|
|
}
|
|
|
|
|
|
return s;
|
|
|
|
|
|
};
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_DEBUG("AUTH HASH: account='", accountName, "' clientSeed=0x", std::hex, clientSeed,
|
|
|
|
|
|
" serverSeed=0x", serverSeed, std::dec);
|
|
|
|
|
|
LOG_DEBUG("AUTH HASH: sessionKey=", toHex(sessionKey.data(), sessionKey.size()));
|
|
|
|
|
|
LOG_DEBUG("AUTH HASH: input(", hashInput.size(), ")=", toHex(hashInput.data(), hashInput.size()));
|
2026-02-13 16:53:28 -08:00
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Compute SHA1 hash
|
2026-02-13 16:53:28 -08:00
|
|
|
|
auto result = auth::Crypto::sha1(hashInput);
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
auto toHex = [](const uint8_t* data, size_t len) {
|
|
|
|
|
|
std::string s;
|
|
|
|
|
|
for (size_t i = 0; i < len; ++i) {
|
|
|
|
|
|
char buf[4]; snprintf(buf, sizeof(buf), "%02x", data[i]); s += buf;
|
|
|
|
|
|
}
|
|
|
|
|
|
return s;
|
|
|
|
|
|
};
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_DEBUG("AUTH HASH: digest=", toHex(result.data(), result.size()));
|
2026-02-13 16:53:28 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AuthChallengeParser::parse(network::Packet& packet, AuthChallengeData& data) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
// SMSG_AUTH_CHALLENGE format varies by expansion:
|
|
|
|
|
|
// TBC 2.4.3: uint32 serverSeed (4 bytes)
|
|
|
|
|
|
// WotLK 3.3.5a: uint32 one + uint32 serverSeed + seeds (40 bytes)
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
|
if (packet.getSize() < 4) {
|
2026-02-02 12:24:50 -08:00
|
|
|
|
LOG_ERROR("SMSG_AUTH_CHALLENGE packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Fix Classic field extraction, Warden PE patches, and auth/opcode corrections
Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.
Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
2026-02-20 00:18:03 -08:00
|
|
|
|
if (packet.getSize() <= 4) {
|
|
|
|
|
|
// Original vanilla/TBC format: just the server seed (4 bytes)
|
|
|
|
|
|
data.unknown1 = 0;
|
|
|
|
|
|
data.serverSeed = packet.readUInt32();
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_INFO("SMSG_AUTH_CHALLENGE: TBC format (", packet.getSize(), " bytes)");
|
Fix Classic field extraction, Warden PE patches, and auth/opcode corrections
Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.
Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
2026-02-20 00:18:03 -08:00
|
|
|
|
} else if (packet.getSize() < 40) {
|
|
|
|
|
|
// Vanilla with encryption seeds (36 bytes): serverSeed + 32 bytes seeds
|
|
|
|
|
|
// No "unknown1" prefix — first uint32 IS the server seed
|
2026-02-12 22:56:36 -08:00
|
|
|
|
data.unknown1 = 0;
|
|
|
|
|
|
data.serverSeed = packet.readUInt32();
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_INFO("SMSG_AUTH_CHALLENGE: Classic+seeds format (", packet.getSize(), " bytes)");
|
2026-02-12 22:56:36 -08:00
|
|
|
|
} else {
|
Fix Classic field extraction, Warden PE patches, and auth/opcode corrections
Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.
Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
2026-02-20 00:18:03 -08:00
|
|
|
|
// WotLK format (40+ bytes): unknown1 + serverSeed + 32 bytes encryption seeds
|
2026-02-12 22:56:36 -08:00
|
|
|
|
data.unknown1 = packet.readUInt32();
|
|
|
|
|
|
data.serverSeed = packet.readUInt32();
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_INFO("SMSG_AUTH_CHALLENGE: WotLK format (", packet.getSize(), " bytes)");
|
|
|
|
|
|
LOG_DEBUG(" Unknown1: 0x", std::hex, data.unknown1, std::dec);
|
2026-02-12 22:56:36 -08:00
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_DEBUG(" Server seed: 0x", std::hex, data.serverSeed, std::dec);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AuthResponseParser::parse(network::Packet& packet, AuthResponseData& response) {
|
|
|
|
|
|
// SMSG_AUTH_RESPONSE format:
|
|
|
|
|
|
// uint8 result
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getSize() < 1) {
|
|
|
|
|
|
LOG_ERROR("SMSG_AUTH_RESPONSE packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t resultCode = packet.readUInt8();
|
|
|
|
|
|
response.result = static_cast<AuthResult>(resultCode);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_AUTH_RESPONSE: ", getAuthResultString(response.result));
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* getAuthResultString(AuthResult result) {
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
|
case AuthResult::OK:
|
|
|
|
|
|
return "OK - Authentication successful";
|
|
|
|
|
|
case AuthResult::FAILED:
|
|
|
|
|
|
return "FAILED - Authentication failed";
|
|
|
|
|
|
case AuthResult::REJECT:
|
|
|
|
|
|
return "REJECT - Connection rejected";
|
|
|
|
|
|
case AuthResult::BAD_SERVER_PROOF:
|
|
|
|
|
|
return "BAD_SERVER_PROOF - Invalid server proof";
|
|
|
|
|
|
case AuthResult::UNAVAILABLE:
|
|
|
|
|
|
return "UNAVAILABLE - Server unavailable";
|
|
|
|
|
|
case AuthResult::SYSTEM_ERROR:
|
|
|
|
|
|
return "SYSTEM_ERROR - System error occurred";
|
|
|
|
|
|
case AuthResult::BILLING_ERROR:
|
|
|
|
|
|
return "BILLING_ERROR - Billing error";
|
|
|
|
|
|
case AuthResult::BILLING_EXPIRED:
|
|
|
|
|
|
return "BILLING_EXPIRED - Subscription expired";
|
|
|
|
|
|
case AuthResult::VERSION_MISMATCH:
|
|
|
|
|
|
return "VERSION_MISMATCH - Client version mismatch";
|
|
|
|
|
|
case AuthResult::UNKNOWN_ACCOUNT:
|
|
|
|
|
|
return "UNKNOWN_ACCOUNT - Account not found";
|
|
|
|
|
|
case AuthResult::INCORRECT_PASSWORD:
|
|
|
|
|
|
return "INCORRECT_PASSWORD - Wrong password";
|
|
|
|
|
|
case AuthResult::SESSION_EXPIRED:
|
|
|
|
|
|
return "SESSION_EXPIRED - Session has expired";
|
|
|
|
|
|
case AuthResult::SERVER_SHUTTING_DOWN:
|
|
|
|
|
|
return "SERVER_SHUTTING_DOWN - Server is shutting down";
|
|
|
|
|
|
case AuthResult::ALREADY_LOGGING_IN:
|
|
|
|
|
|
return "ALREADY_LOGGING_IN - Already logging in";
|
|
|
|
|
|
case AuthResult::LOGIN_SERVER_NOT_FOUND:
|
|
|
|
|
|
return "LOGIN_SERVER_NOT_FOUND - Can't contact login server";
|
|
|
|
|
|
case AuthResult::WAIT_QUEUE:
|
|
|
|
|
|
return "WAIT_QUEUE - Waiting in queue";
|
|
|
|
|
|
case AuthResult::BANNED:
|
|
|
|
|
|
return "BANNED - Account is banned";
|
|
|
|
|
|
case AuthResult::ALREADY_ONLINE:
|
|
|
|
|
|
return "ALREADY_ONLINE - Character already logged in";
|
|
|
|
|
|
case AuthResult::NO_TIME:
|
|
|
|
|
|
return "NO_TIME - No game time remaining";
|
|
|
|
|
|
case AuthResult::DB_BUSY:
|
|
|
|
|
|
return "DB_BUSY - Database is busy";
|
|
|
|
|
|
case AuthResult::SUSPENDED:
|
|
|
|
|
|
return "SUSPENDED - Account is suspended";
|
|
|
|
|
|
case AuthResult::PARENTAL_CONTROL:
|
|
|
|
|
|
return "PARENTAL_CONTROL - Parental controls active";
|
|
|
|
|
|
case AuthResult::LOCKED_ENFORCED:
|
|
|
|
|
|
return "LOCKED_ENFORCED - Account is locked";
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "UNKNOWN - Unknown result code";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 14:13:48 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Character Creation
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet CharCreatePacket::build(const CharCreateData& data) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CHAR_CREATE));
|
2026-02-05 14:13:48 -08:00
|
|
|
|
|
2026-02-09 17:39:21 -08:00
|
|
|
|
// Convert nonbinary gender to server-compatible value (servers only support male/female)
|
|
|
|
|
|
Gender serverGender = toServerGender(data.gender);
|
|
|
|
|
|
|
2026-02-05 14:13:48 -08:00
|
|
|
|
packet.writeString(data.name); // null-terminated name
|
|
|
|
|
|
packet.writeUInt8(static_cast<uint8_t>(data.race));
|
|
|
|
|
|
packet.writeUInt8(static_cast<uint8_t>(data.characterClass));
|
2026-02-09 17:39:21 -08:00
|
|
|
|
packet.writeUInt8(static_cast<uint8_t>(serverGender));
|
2026-02-05 14:13:48 -08:00
|
|
|
|
packet.writeUInt8(data.skin);
|
|
|
|
|
|
packet.writeUInt8(data.face);
|
|
|
|
|
|
packet.writeUInt8(data.hairStyle);
|
|
|
|
|
|
packet.writeUInt8(data.hairColor);
|
|
|
|
|
|
packet.writeUInt8(data.facialHair);
|
|
|
|
|
|
packet.writeUInt8(0); // outfitId, always 0
|
2026-02-17 04:16:27 -08:00
|
|
|
|
// Turtle WoW / 1.12.1 clients send 4 extra zero bytes after outfitId.
|
|
|
|
|
|
// Servers may validate packet length and silently drop undersized packets.
|
|
|
|
|
|
packet.writeUInt32(0);
|
2026-02-05 14:13:48 -08:00
|
|
|
|
|
2026-02-05 21:03:11 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_CHAR_CREATE: name=", data.name,
|
|
|
|
|
|
" race=", static_cast<int>(data.race),
|
|
|
|
|
|
" class=", static_cast<int>(data.characterClass),
|
|
|
|
|
|
" gender=", static_cast<int>(data.gender),
|
2026-02-09 17:39:21 -08:00
|
|
|
|
" (server gender=", static_cast<int>(serverGender), ")",
|
2026-02-05 21:03:11 -08:00
|
|
|
|
" skin=", static_cast<int>(data.skin),
|
|
|
|
|
|
" face=", static_cast<int>(data.face),
|
|
|
|
|
|
" hair=", static_cast<int>(data.hairStyle),
|
|
|
|
|
|
" hairColor=", static_cast<int>(data.hairColor),
|
|
|
|
|
|
" facial=", static_cast<int>(data.facialHair));
|
|
|
|
|
|
|
|
|
|
|
|
// Dump full packet for protocol debugging
|
|
|
|
|
|
const auto& pktData = packet.getData();
|
|
|
|
|
|
std::string hexDump;
|
|
|
|
|
|
for (size_t i = 0; i < pktData.size(); ++i) {
|
|
|
|
|
|
char buf[4];
|
|
|
|
|
|
snprintf(buf, sizeof(buf), "%02x ", pktData[i]);
|
|
|
|
|
|
hexDump += buf;
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_DEBUG("CMSG_CHAR_CREATE full dump: ", hexDump);
|
2026-02-05 14:13:48 -08:00
|
|
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CharCreateResponseParser::parse(network::Packet& packet, CharCreateResponseData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: result(1)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) {
|
|
|
|
|
|
LOG_WARNING("SMSG_CHAR_CREATE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 14:13:48 -08:00
|
|
|
|
data.result = static_cast<CharCreateResult>(packet.readUInt8());
|
|
|
|
|
|
LOG_INFO("SMSG_CHAR_CREATE result: ", static_cast<int>(data.result));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
network::Packet CharEnumPacket::build() {
|
|
|
|
|
|
// CMSG_CHAR_ENUM has no body - just the opcode
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CHAR_ENUM));
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_CHAR_ENUM packet (no body)");
|
|
|
|
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CharEnumParser::parse(network::Packet& packet, CharEnumResponse& response) {
|
2026-03-11 14:40:07 -07:00
|
|
|
|
// Upfront validation: count(1) + at least minimal character data
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) return false;
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Read character count
|
|
|
|
|
|
uint8_t count = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Parsing SMSG_CHAR_ENUM: ", (int)count, " characters");
|
|
|
|
|
|
|
|
|
|
|
|
response.characters.clear();
|
|
|
|
|
|
response.characters.reserve(count);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < count; ++i) {
|
|
|
|
|
|
Character character;
|
|
|
|
|
|
|
2026-03-11 14:40:07 -07:00
|
|
|
|
// Validate minimum bytes for this character entry before reading:
|
|
|
|
|
|
// GUID(8) + name(>=1 for empty string) + race(1) + class(1) + gender(1) +
|
|
|
|
|
|
// appearanceBytes(4) + facialFeatures(1) + level(1) + zoneId(4) + mapId(4) +
|
|
|
|
|
|
// x(4) + y(4) + z(4) + guildId(4) + flags(4) + customization(4) + unknown(1) +
|
|
|
|
|
|
// petDisplayModel(4) + petLevel(4) + petFamily(4) + 23items*(dispModel(4)+invType(1)+enchant(4)) = 207 bytes
|
|
|
|
|
|
const size_t minCharacterSize = 8 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 1 + 4 + 4 + 4 + (23 * 9);
|
|
|
|
|
|
if (packet.getReadPos() + minCharacterSize > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("CharEnumParser: truncated character at index ", (int)i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Read GUID (8 bytes, little-endian)
|
|
|
|
|
|
character.guid = packet.readUInt64();
|
|
|
|
|
|
|
2026-03-11 14:40:07 -07:00
|
|
|
|
// Read name (null-terminated string) - validate before reading
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("CharEnumParser: no bytes for name at index ", (int)i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
character.name = packet.readString();
|
|
|
|
|
|
|
2026-03-11 14:40:07 -07:00
|
|
|
|
// Validate remaining bytes before reading fixed-size fields
|
|
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("CharEnumParser: truncated before race/class/gender at index ", (int)i);
|
|
|
|
|
|
character.race = Race::HUMAN;
|
|
|
|
|
|
character.characterClass = Class::WARRIOR;
|
|
|
|
|
|
character.gender = Gender::MALE;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Read race, class, gender
|
|
|
|
|
|
character.race = static_cast<Race>(packet.readUInt8());
|
|
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
character.characterClass = Class::WARRIOR;
|
|
|
|
|
|
character.gender = Gender::MALE;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.characterClass = static_cast<Class>(packet.readUInt8());
|
|
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
character.gender = Gender::MALE;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.gender = static_cast<Gender>(packet.readUInt8());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-03-11 14:40:07 -07:00
|
|
|
|
// Validate before reading appearance data
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
character.appearanceBytes = 0;
|
|
|
|
|
|
character.facialFeatures = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Read appearance data
|
|
|
|
|
|
character.appearanceBytes = packet.readUInt32();
|
|
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
character.facialFeatures = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.facialFeatures = packet.readUInt8();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read level
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
character.level = 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.level = packet.readUInt8();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read location
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) {
|
|
|
|
|
|
character.zoneId = 0;
|
|
|
|
|
|
character.mapId = 0;
|
|
|
|
|
|
character.x = 0.0f;
|
|
|
|
|
|
character.y = 0.0f;
|
|
|
|
|
|
character.z = 0.0f;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.zoneId = packet.readUInt32();
|
|
|
|
|
|
character.mapId = packet.readUInt32();
|
|
|
|
|
|
character.x = packet.readFloat();
|
|
|
|
|
|
character.y = packet.readFloat();
|
|
|
|
|
|
character.z = packet.readFloat();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read affiliations
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
character.guildId = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.guildId = packet.readUInt32();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read flags
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
character.flags = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.flags = packet.readUInt32();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Skip customization flag (uint32) and unknown byte
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
// Customization missing, skip unknown
|
|
|
|
|
|
} else {
|
|
|
|
|
|
packet.readUInt32(); // Customization
|
|
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
// Unknown missing
|
|
|
|
|
|
} else {
|
|
|
|
|
|
packet.readUInt8(); // Unknown
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read pet data (always present, even if no pet)
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) {
|
|
|
|
|
|
character.pet.displayModel = 0;
|
|
|
|
|
|
character.pet.level = 0;
|
|
|
|
|
|
character.pet.family = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
character.pet.displayModel = packet.readUInt32();
|
|
|
|
|
|
character.pet.level = packet.readUInt32();
|
|
|
|
|
|
character.pet.family = packet.readUInt32();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read equipment (23 items)
|
|
|
|
|
|
character.equipment.reserve(23);
|
|
|
|
|
|
for (int j = 0; j < 23; ++j) {
|
2026-03-11 14:40:07 -07:00
|
|
|
|
if (packet.getReadPos() + 9 > packet.getSize()) break;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
EquipmentItem item;
|
|
|
|
|
|
item.displayModel = packet.readUInt32();
|
|
|
|
|
|
item.inventoryType = packet.readUInt8();
|
|
|
|
|
|
item.enchantment = packet.readUInt32();
|
|
|
|
|
|
character.equipment.push_back(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 04:25:45 -07:00
|
|
|
|
LOG_DEBUG(" Character ", (int)(i + 1), ": ", character.name,
|
|
|
|
|
|
" (", getRaceName(character.race), " ", getClassName(character.characterClass),
|
|
|
|
|
|
" level ", (int)character.level, " zone ", character.zoneId, ")");
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
response.characters.push_back(character);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Successfully parsed ", response.characters.size(), " characters");
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet PlayerLoginPacket::build(uint64_t characterGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PLAYER_LOGIN));
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Write character GUID (8 bytes, little-endian)
|
|
|
|
|
|
packet.writeUInt64(characterGuid);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Built CMSG_PLAYER_LOGIN packet");
|
|
|
|
|
|
LOG_INFO(" Character GUID: 0x", std::hex, characterGuid, std::dec);
|
|
|
|
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LoginVerifyWorldParser::parse(network::Packet& packet, LoginVerifyWorldData& data) {
|
|
|
|
|
|
// SMSG_LOGIN_VERIFY_WORLD format (WoW 3.3.5a):
|
|
|
|
|
|
// uint32 mapId
|
|
|
|
|
|
// float x, y, z (position)
|
|
|
|
|
|
// float orientation
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getSize() < 20) {
|
|
|
|
|
|
LOG_ERROR("SMSG_LOGIN_VERIFY_WORLD packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.mapId = packet.readUInt32();
|
|
|
|
|
|
data.x = packet.readFloat();
|
|
|
|
|
|
data.y = packet.readFloat();
|
|
|
|
|
|
data.z = packet.readFloat();
|
|
|
|
|
|
data.orientation = packet.readFloat();
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_LOGIN_VERIFY_WORLD:");
|
|
|
|
|
|
LOG_INFO(" Map ID: ", data.mapId);
|
|
|
|
|
|
LOG_INFO(" Position: (", data.x, ", ", data.y, ", ", data.z, ")");
|
|
|
|
|
|
LOG_INFO(" Orientation: ", data.orientation, " radians");
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AccountDataTimesParser::parse(network::Packet& packet, AccountDataTimesData& data) {
|
2026-02-22 07:44:32 -08:00
|
|
|
|
// Common layouts seen in the wild:
|
|
|
|
|
|
// - WotLK-like: uint32 serverTime, uint8 unk, uint32 mask, uint32[up to 8] slotTimes
|
|
|
|
|
|
// - Older/variant: uint32 serverTime, uint8 unk, uint32[up to 8] slotTimes
|
|
|
|
|
|
// Some servers only send a subset of slots.
|
|
|
|
|
|
if (packet.getSize() < 5) {
|
|
|
|
|
|
LOG_ERROR("SMSG_ACCOUNT_DATA_TIMES packet too small: ", packet.getSize(),
|
|
|
|
|
|
" bytes (need at least 5)");
|
2026-02-02 12:24:50 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 07:44:32 -08:00
|
|
|
|
for (uint32_t& t : data.accountDataTimes) {
|
|
|
|
|
|
t = 0;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
data.serverTime = packet.readUInt32();
|
|
|
|
|
|
data.unknown = packet.readUInt8();
|
|
|
|
|
|
|
2026-02-22 07:44:32 -08:00
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
uint32_t mask = 0xFF;
|
|
|
|
|
|
if (remaining >= 4 && ((remaining - 4) % 4) == 0) {
|
|
|
|
|
|
// Treat first dword as slot mask when payload shape matches.
|
|
|
|
|
|
mask = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
size_t slotWords = std::min<size_t>(8, remaining / 4);
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
LOG_DEBUG("Parsed SMSG_ACCOUNT_DATA_TIMES:");
|
|
|
|
|
|
LOG_DEBUG(" Server time: ", data.serverTime);
|
|
|
|
|
|
LOG_DEBUG(" Unknown: ", (int)data.unknown);
|
2026-02-22 07:44:32 -08:00
|
|
|
|
LOG_DEBUG(" Mask: 0x", std::hex, mask, std::dec, " slotsInPacket=", slotWords);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-22 07:44:32 -08:00
|
|
|
|
for (size_t i = 0; i < slotWords; ++i) {
|
2026-02-02 12:24:50 -08:00
|
|
|
|
data.accountDataTimes[i] = packet.readUInt32();
|
2026-02-22 07:44:32 -08:00
|
|
|
|
if (data.accountDataTimes[i] != 0 || ((mask & (1u << i)) != 0)) {
|
2026-02-02 12:24:50 -08:00
|
|
|
|
LOG_DEBUG(" Data slot ", i, ": ", data.accountDataTimes[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-22 07:44:32 -08:00
|
|
|
|
if (packet.getReadPos() != packet.getSize()) {
|
|
|
|
|
|
LOG_DEBUG(" AccountDataTimes trailing bytes: ", packet.getSize() - packet.getReadPos());
|
|
|
|
|
|
packet.setReadPos(packet.getSize());
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool MotdParser::parse(network::Packet& packet, MotdData& data) {
|
|
|
|
|
|
// SMSG_MOTD format (WoW 3.3.5a):
|
|
|
|
|
|
// uint32 lineCount
|
|
|
|
|
|
// string[lineCount] lines (null-terminated strings)
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_MOTD packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t lineCount = packet.readUInt32();
|
|
|
|
|
|
|
2026-03-11 14:41:25 -07:00
|
|
|
|
// Cap lineCount to prevent unbounded memory allocation
|
|
|
|
|
|
const uint32_t MAX_MOTD_LINES = 64;
|
|
|
|
|
|
if (lineCount > MAX_MOTD_LINES) {
|
|
|
|
|
|
LOG_WARNING("MotdParser: lineCount capped (requested=", lineCount, ")");
|
|
|
|
|
|
lineCount = MAX_MOTD_LINES;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_INFO("Parsed SMSG_MOTD: ", lineCount, " line(s)");
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
data.lines.clear();
|
|
|
|
|
|
data.lines.reserve(lineCount);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < lineCount; ++i) {
|
2026-03-11 14:41:25 -07:00
|
|
|
|
// Validate at least 1 byte available for the string
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("MotdParser: truncated at line ", i + 1);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
std::string line = packet.readString();
|
|
|
|
|
|
data.lines.push_back(line);
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_DEBUG(" MOTD[", i + 1, "]: ", line);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet PingPacket::build(uint32_t sequence, uint32_t latency) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PING));
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Write sequence number (uint32, little-endian)
|
|
|
|
|
|
packet.writeUInt32(sequence);
|
|
|
|
|
|
|
|
|
|
|
|
// Write latency (uint32, little-endian, in milliseconds)
|
|
|
|
|
|
packet.writeUInt32(latency);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_PING packet");
|
|
|
|
|
|
LOG_DEBUG(" Sequence: ", sequence);
|
|
|
|
|
|
LOG_DEBUG(" Latency: ", latency, " ms");
|
|
|
|
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PongParser::parse(network::Packet& packet, PongData& data) {
|
|
|
|
|
|
// SMSG_PONG format (WoW 3.3.5a):
|
|
|
|
|
|
// uint32 sequence (echoed from CMSG_PING)
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_PONG packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.sequence = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_PONG:");
|
|
|
|
|
|
LOG_DEBUG(" Sequence: ", data.sequence);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 21:14:35 -08:00
|
|
|
|
void MovementPacket::writePackedGuid(network::Packet& packet, uint64_t guid) {
|
2026-02-06 03:24:46 -08:00
|
|
|
|
uint8_t mask = 0;
|
|
|
|
|
|
uint8_t guidBytes[8];
|
|
|
|
|
|
int guidByteCount = 0;
|
|
|
|
|
|
for (int i = 0; i < 8; i++) {
|
2026-02-11 21:14:35 -08:00
|
|
|
|
uint8_t byte = static_cast<uint8_t>((guid >> (i * 8)) & 0xFF);
|
2026-02-06 03:24:46 -08:00
|
|
|
|
if (byte != 0) {
|
|
|
|
|
|
mask |= (1 << i);
|
|
|
|
|
|
guidBytes[guidByteCount++] = byte;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
packet.writeUInt8(mask);
|
|
|
|
|
|
for (int i = 0; i < guidByteCount; i++) {
|
|
|
|
|
|
packet.writeUInt8(guidBytes[i]);
|
|
|
|
|
|
}
|
2026-02-11 21:14:35 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MovementPacket::writeMovementPayload(network::Packet& packet, const MovementInfo& info) {
|
|
|
|
|
|
// Movement packet format (WoW 3.3.5a) payload:
|
|
|
|
|
|
// uint32 flags
|
|
|
|
|
|
// uint16 flags2
|
|
|
|
|
|
// uint32 time
|
|
|
|
|
|
// float x, y, z
|
|
|
|
|
|
// float orientation
|
2026-02-06 03:24:46 -08:00
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Write movement flags
|
|
|
|
|
|
packet.writeUInt32(info.flags);
|
|
|
|
|
|
packet.writeUInt16(info.flags2);
|
|
|
|
|
|
|
|
|
|
|
|
// Write timestamp
|
|
|
|
|
|
packet.writeUInt32(info.time);
|
|
|
|
|
|
|
|
|
|
|
|
// Write position
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.x), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.y), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.z), sizeof(float));
|
|
|
|
|
|
|
|
|
|
|
|
// Write orientation
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.orientation), sizeof(float));
|
|
|
|
|
|
|
2026-02-11 15:24:05 -08:00
|
|
|
|
// Write transport data if on transport.
|
|
|
|
|
|
// 3.3.5a ordering: transport block appears before pitch/fall/jump.
|
2026-02-11 02:23:37 -08:00
|
|
|
|
if (info.hasFlag(MovementFlags::ONTRANSPORT)) {
|
|
|
|
|
|
// Write 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<uint8_t>((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]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Write transport local position
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportX), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportY), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportZ), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportO), sizeof(float));
|
|
|
|
|
|
|
|
|
|
|
|
// Write transport time
|
|
|
|
|
|
packet.writeUInt32(info.transportTime);
|
2026-02-11 15:24:05 -08:00
|
|
|
|
|
|
|
|
|
|
// Transport seat is always present in ONTRANSPORT movement info.
|
|
|
|
|
|
packet.writeUInt8(static_cast<uint8_t>(info.transportSeat));
|
|
|
|
|
|
|
|
|
|
|
|
// Optional second transport time for interpolated movement.
|
|
|
|
|
|
if (info.flags2 & 0x0200) {
|
|
|
|
|
|
packet.writeUInt32(info.transportTime2);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Write pitch if swimming/flying
|
|
|
|
|
|
if (info.hasFlag(MovementFlags::SWIMMING) || info.hasFlag(MovementFlags::FLYING)) {
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.pitch), sizeof(float));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fall time is ALWAYS present in the packet (server reads it unconditionally).
|
|
|
|
|
|
// Jump velocity/angle data is only present when FALLING flag is set.
|
|
|
|
|
|
packet.writeUInt32(info.fallTime);
|
|
|
|
|
|
|
|
|
|
|
|
if (info.hasFlag(MovementFlags::FALLING)) {
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpVelocity), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpSinAngle), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpCosAngle), sizeof(float));
|
|
|
|
|
|
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpXYSpeed), sizeof(float));
|
2026-02-11 02:23:37 -08:00
|
|
|
|
}
|
2026-02-11 21:14:35 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet MovementPacket::build(Opcode opcode, const MovementInfo& info, uint64_t playerGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(opcode));
|
2026-02-11 21:14:35 -08:00
|
|
|
|
|
|
|
|
|
|
// Movement packet format (WoW 3.3.5a):
|
|
|
|
|
|
// packed GUID + movement payload
|
|
|
|
|
|
writePackedGuid(packet, playerGuid);
|
|
|
|
|
|
writeMovementPayload(packet, info);
|
2026-02-11 02:23:37 -08:00
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
|
// Detailed hex dump for debugging
|
|
|
|
|
|
static int mvLog = 5;
|
2026-02-06 09:14:22 -08:00
|
|
|
|
if (mvLog-- > 0) {
|
2026-02-06 13:47:03 -08:00
|
|
|
|
const auto& raw = packet.getData();
|
|
|
|
|
|
std::string hex;
|
|
|
|
|
|
for (size_t i = 0; i < raw.size(); i++) {
|
|
|
|
|
|
char b[4]; snprintf(b, sizeof(b), "%02x ", raw[i]);
|
|
|
|
|
|
hex += b;
|
|
|
|
|
|
}
|
2026-02-21 01:26:16 -08:00
|
|
|
|
LOG_DEBUG("MOVEPKT opcode=0x", std::hex, wireOpcode(opcode), std::dec,
|
2026-02-06 13:47:03 -08:00
|
|
|
|
" guid=0x", std::hex, playerGuid, std::dec,
|
|
|
|
|
|
" payload=", raw.size(), " bytes",
|
|
|
|
|
|
" flags=0x", std::hex, info.flags, std::dec,
|
|
|
|
|
|
" flags2=0x", std::hex, info.flags2, std::dec,
|
|
|
|
|
|
" pos=(", info.x, ",", info.y, ",", info.z, ",", info.orientation, ")",
|
2026-02-11 02:23:37 -08:00
|
|
|
|
" fallTime=", info.fallTime,
|
|
|
|
|
|
(info.hasFlag(MovementFlags::ONTRANSPORT) ?
|
|
|
|
|
|
" ONTRANSPORT guid=0x" + std::to_string(info.transportGuid) +
|
|
|
|
|
|
" localPos=(" + std::to_string(info.transportX) + "," +
|
|
|
|
|
|
std::to_string(info.transportY) + "," + std::to_string(info.transportZ) + ")" : ""));
|
2026-02-21 01:26:16 -08:00
|
|
|
|
LOG_DEBUG("MOVEPKT hex: ", hex);
|
2026-02-06 09:14:22 -08:00
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t UpdateObjectParser::readPackedGuid(network::Packet& packet) {
|
|
|
|
|
|
// Read packed GUID format:
|
|
|
|
|
|
// First byte is a mask indicating which bytes are present
|
|
|
|
|
|
uint8_t mask = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (mask == 0) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t guid = 0;
|
|
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
|
|
if (mask & (1 << i)) {
|
|
|
|
|
|
uint8_t byte = packet.readUInt8();
|
|
|
|
|
|
guid |= (static_cast<uint64_t>(byte) << (i * 8));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return guid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock& block) {
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// WoW 3.3.5a UPDATE_OBJECT movement block structure:
|
|
|
|
|
|
// 1. UpdateFlags (1 byte, sometimes 2)
|
|
|
|
|
|
// 2. Movement data depends on update flags
|
|
|
|
|
|
|
|
|
|
|
|
// Update flags (3.3.5a uses 2 bytes for flags)
|
|
|
|
|
|
uint16_t updateFlags = packet.readUInt16();
|
2026-02-08 00:59:40 -08:00
|
|
|
|
block.updateFlags = updateFlags;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG(" UpdateFlags: 0x", std::hex, updateFlags, std::dec);
|
|
|
|
|
|
|
2026-02-11 00:54:38 -08:00
|
|
|
|
// Log transport-related flag combinations
|
|
|
|
|
|
if (updateFlags & 0x0002) { // UPDATEFLAG_TRANSPORT
|
2026-02-21 01:26:16 -08:00
|
|
|
|
static int transportFlagLogCount = 0;
|
|
|
|
|
|
if (transportFlagLogCount < 12) {
|
|
|
|
|
|
LOG_INFO(" Transport flags detected: 0x", std::hex, updateFlags, std::dec,
|
|
|
|
|
|
" (TRANSPORT=", !!(updateFlags & 0x0002),
|
|
|
|
|
|
", POSITION=", !!(updateFlags & 0x0100),
|
|
|
|
|
|
", ROTATION=", !!(updateFlags & 0x0200),
|
|
|
|
|
|
", STATIONARY=", !!(updateFlags & 0x0040), ")");
|
|
|
|
|
|
transportFlagLogCount++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
LOG_DEBUG(" Transport flags detected: 0x", std::hex, updateFlags, std::dec);
|
|
|
|
|
|
}
|
2026-02-11 00:54:38 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// UpdateFlags bit meanings:
|
|
|
|
|
|
// 0x0001 = UPDATEFLAG_SELF
|
|
|
|
|
|
// 0x0002 = UPDATEFLAG_TRANSPORT
|
|
|
|
|
|
// 0x0004 = UPDATEFLAG_HAS_TARGET
|
|
|
|
|
|
// 0x0008 = UPDATEFLAG_LOWGUID
|
|
|
|
|
|
// 0x0010 = UPDATEFLAG_HIGHGUID
|
|
|
|
|
|
// 0x0020 = UPDATEFLAG_LIVING
|
|
|
|
|
|
// 0x0040 = UPDATEFLAG_STATIONARY_POSITION
|
|
|
|
|
|
// 0x0080 = UPDATEFLAG_VEHICLE
|
|
|
|
|
|
// 0x0100 = UPDATEFLAG_POSITION (transport)
|
|
|
|
|
|
// 0x0200 = UPDATEFLAG_ROTATION
|
|
|
|
|
|
|
|
|
|
|
|
const uint16_t UPDATEFLAG_LIVING = 0x0020;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_STATIONARY_POSITION = 0x0040;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_HAS_TARGET = 0x0004;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_TRANSPORT = 0x0002;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_POSITION = 0x0100;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_VEHICLE = 0x0080;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_ROTATION = 0x0200;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_LOWGUID = 0x0008;
|
|
|
|
|
|
const uint16_t UPDATEFLAG_HIGHGUID = 0x0010;
|
|
|
|
|
|
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_LIVING) {
|
|
|
|
|
|
// Full movement block for living units
|
|
|
|
|
|
uint32_t moveFlags = packet.readUInt32();
|
|
|
|
|
|
uint16_t moveFlags2 = packet.readUInt16();
|
|
|
|
|
|
/*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(" LIVING movement: (", block.x, ", ", block.y, ", ", block.z,
|
|
|
|
|
|
"), o=", block.orientation, " moveFlags=0x", std::hex, moveFlags, std::dec);
|
|
|
|
|
|
|
|
|
|
|
|
// Transport data (if on transport)
|
|
|
|
|
|
if (moveFlags & 0x00000200) { // MOVEMENTFLAG_ONTRANSPORT
|
2026-02-08 00:59:40 -08:00
|
|
|
|
block.onTransport = true;
|
|
|
|
|
|
block.transportGuid = readPackedGuid(packet);
|
|
|
|
|
|
block.transportX = packet.readFloat();
|
|
|
|
|
|
block.transportY = packet.readFloat();
|
|
|
|
|
|
block.transportZ = packet.readFloat();
|
|
|
|
|
|
block.transportO = packet.readFloat();
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*uint32_t tTime =*/ packet.readUInt32();
|
|
|
|
|
|
/*int8_t tSeat =*/ packet.readUInt8();
|
|
|
|
|
|
|
2026-02-08 00:59:40 -08:00
|
|
|
|
LOG_DEBUG(" OnTransport: guid=0x", std::hex, block.transportGuid, std::dec,
|
|
|
|
|
|
" offset=(", block.transportX, ", ", block.transportY, ", ", block.transportZ, ")");
|
|
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
if (moveFlags2 & 0x0200) { // MOVEMENTFLAG2_INTERPOLATED_MOVEMENT
|
|
|
|
|
|
/*uint32_t tTime2 =*/ packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Swimming/flying pitch
|
2026-03-10 11:16:40 -07:00
|
|
|
|
// WotLK 3.3.5a movement flags relevant here:
|
|
|
|
|
|
// SWIMMING = 0x00200000
|
|
|
|
|
|
// FLYING = 0x01000000 (player/creature actively flying)
|
|
|
|
|
|
// SPLINE_ELEVATION = 0x02000000 (smooth vertical spline offset — no pitch field)
|
|
|
|
|
|
// MovementFlags2:
|
|
|
|
|
|
// MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING = 0x0010
|
|
|
|
|
|
//
|
|
|
|
|
|
// Pitch is present when SWIMMING or FLYING are set, or the always-allow flag is set.
|
|
|
|
|
|
// The original code checked 0x02000000 (SPLINE_ELEVATION) which neither covers SWIMMING
|
|
|
|
|
|
// nor FLYING, causing misaligned reads for swimming/flying entities in SMSG_UPDATE_OBJECT.
|
2026-03-10 11:03:33 -07:00
|
|
|
|
if ((moveFlags & 0x00200000) /* SWIMMING */ ||
|
2026-03-10 11:16:40 -07:00
|
|
|
|
(moveFlags & 0x01000000) /* FLYING */ ||
|
2026-03-10 11:03:33 -07:00
|
|
|
|
(moveFlags2 & 0x0010) /* MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING */) {
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*float pitch =*/ packet.readFloat();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Fall time
|
|
|
|
|
|
/*uint32_t fallTime =*/ packet.readUInt32();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Jumping
|
|
|
|
|
|
if (moveFlags & 0x00001000) { // MOVEMENTFLAG_FALLING
|
|
|
|
|
|
/*float jumpVelocity =*/ packet.readFloat();
|
|
|
|
|
|
/*float jumpSinAngle =*/ packet.readFloat();
|
|
|
|
|
|
/*float jumpCosAngle =*/ packet.readFloat();
|
|
|
|
|
|
/*float jumpXYSpeed =*/ packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Spline elevation
|
|
|
|
|
|
if (moveFlags & 0x04000000) { // MOVEMENTFLAG_SPLINE_ELEVATION
|
|
|
|
|
|
/*float splineElevation =*/ packet.readFloat();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Speeds (7 speed values)
|
|
|
|
|
|
/*float walkSpeed =*/ packet.readFloat();
|
2026-02-07 20:24:25 -08:00
|
|
|
|
float runSpeed = packet.readFloat();
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*float runBackSpeed =*/ packet.readFloat();
|
|
|
|
|
|
/*float swimSpeed =*/ packet.readFloat();
|
|
|
|
|
|
/*float swimBackSpeed =*/ packet.readFloat();
|
|
|
|
|
|
/*float flightSpeed =*/ packet.readFloat();
|
|
|
|
|
|
/*float flightBackSpeed =*/ packet.readFloat();
|
|
|
|
|
|
/*float turnRate =*/ packet.readFloat();
|
|
|
|
|
|
/*float pitchRate =*/ packet.readFloat();
|
|
|
|
|
|
|
2026-02-07 20:24:25 -08:00
|
|
|
|
block.runSpeed = runSpeed;
|
2026-03-10 11:14:58 -07:00
|
|
|
|
block.moveFlags = moveFlags;
|
2026-02-07 20:24:25 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Spline data
|
|
|
|
|
|
if (moveFlags & 0x08000000) { // MOVEMENTFLAG_SPLINE_ENABLED
|
2026-02-22 08:27:17 -08:00
|
|
|
|
auto bytesAvailable = [&](size_t n) -> bool { return packet.getReadPos() + n <= packet.getSize(); };
|
|
|
|
|
|
if (!bytesAvailable(4)) return false;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
uint32_t splineFlags = packet.readUInt32();
|
2026-02-06 15:18:50 -08:00
|
|
|
|
LOG_DEBUG(" Spline: flags=0x", std::hex, splineFlags, std::dec);
|
2026-02-05 21:55:52 -08:00
|
|
|
|
|
2026-02-06 15:18:50 -08:00
|
|
|
|
if (splineFlags & 0x00010000) { // SPLINEFLAG_FINAL_POINT
|
2026-02-22 08:27:17 -08:00
|
|
|
|
if (!bytesAvailable(12)) return false;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*float finalX =*/ packet.readFloat();
|
|
|
|
|
|
/*float finalY =*/ packet.readFloat();
|
|
|
|
|
|
/*float finalZ =*/ packet.readFloat();
|
2026-02-06 15:18:50 -08:00
|
|
|
|
} else if (splineFlags & 0x00020000) { // SPLINEFLAG_FINAL_TARGET
|
2026-02-22 08:27:17 -08:00
|
|
|
|
if (!bytesAvailable(8)) return false;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*uint64_t finalTarget =*/ packet.readUInt64();
|
2026-02-06 15:18:50 -08:00
|
|
|
|
} else if (splineFlags & 0x00040000) { // SPLINEFLAG_FINAL_ANGLE
|
2026-02-22 08:27:17 -08:00
|
|
|
|
if (!bytesAvailable(4)) return false;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*float finalAngle =*/ packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 08:27:17 -08:00
|
|
|
|
// Legacy UPDATE_OBJECT spline layout used by many servers:
|
|
|
|
|
|
// timePassed, duration, splineId, durationMod, durationModNext,
|
|
|
|
|
|
// verticalAccel, effectStartTime, pointCount, points, splineMode, endPoint.
|
|
|
|
|
|
const size_t legacyStart = packet.getReadPos();
|
|
|
|
|
|
if (!bytesAvailable(12 + 8 + 8 + 4)) return false;
|
2026-02-05 21:55:52 -08:00
|
|
|
|
/*uint32_t timePassed =*/ packet.readUInt32();
|
|
|
|
|
|
/*uint32_t duration =*/ packet.readUInt32();
|
|
|
|
|
|
/*uint32_t splineId =*/ packet.readUInt32();
|
|
|
|
|
|
/*float durationMod =*/ packet.readFloat();
|
|
|
|
|
|
/*float durationModNext =*/ packet.readFloat();
|
|
|
|
|
|
/*float verticalAccel =*/ packet.readFloat();
|
|
|
|
|
|
/*uint32_t effectStartTime =*/ packet.readUInt32();
|
|
|
|
|
|
uint32_t pointCount = packet.readUInt32();
|
2026-02-22 08:27:17 -08:00
|
|
|
|
|
|
|
|
|
|
const size_t remainingAfterCount = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
const bool legacyCountLooksValid = (pointCount <= 256);
|
|
|
|
|
|
const size_t legacyPointsBytes = static_cast<size_t>(pointCount) * 12ull;
|
|
|
|
|
|
const bool legacyPayloadFits = (legacyPointsBytes + 13ull) <= remainingAfterCount;
|
|
|
|
|
|
|
|
|
|
|
|
if (legacyCountLooksValid && legacyPayloadFits) {
|
|
|
|
|
|
for (uint32_t i = 0; i < pointCount; i++) {
|
|
|
|
|
|
/*float px =*/ packet.readFloat();
|
|
|
|
|
|
/*float py =*/ packet.readFloat();
|
|
|
|
|
|
/*float pz =*/ packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
/*uint8_t splineMode =*/ packet.readUInt8();
|
|
|
|
|
|
/*float endPointX =*/ packet.readFloat();
|
|
|
|
|
|
/*float endPointY =*/ packet.readFloat();
|
|
|
|
|
|
/*float endPointZ =*/ packet.readFloat();
|
2026-02-23 05:13:26 -08:00
|
|
|
|
LOG_DEBUG(" Spline pointCount=", pointCount, " (legacy)");
|
|
|
|
|
|
} else {
|
2026-02-22 08:27:17 -08:00
|
|
|
|
// Legacy pointCount looks invalid; try compact WotLK layout as recovery.
|
|
|
|
|
|
// This keeps malformed/variant packets from desyncing the whole update block.
|
|
|
|
|
|
packet.setReadPos(legacyStart);
|
|
|
|
|
|
const size_t afterFinalFacingPos = packet.getReadPos();
|
|
|
|
|
|
if (splineFlags & 0x00400000) { // Animation
|
|
|
|
|
|
if (!bytesAvailable(5)) return false;
|
|
|
|
|
|
/*uint8_t animType =*/ packet.readUInt8();
|
|
|
|
|
|
/*uint32_t animStart =*/ packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bytesAvailable(4)) return false;
|
|
|
|
|
|
/*uint32_t duration =*/ packet.readUInt32();
|
|
|
|
|
|
if (splineFlags & 0x00000800) { // Parabolic
|
|
|
|
|
|
if (!bytesAvailable(8)) return false;
|
|
|
|
|
|
/*float verticalAccel =*/ packet.readFloat();
|
|
|
|
|
|
/*uint32_t effectStartTime =*/ packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bytesAvailable(4)) return false;
|
|
|
|
|
|
const uint32_t compactPointCount = packet.readUInt32();
|
|
|
|
|
|
if (compactPointCount > 16384) {
|
2026-02-21 04:05:53 -08:00
|
|
|
|
static uint32_t badSplineCount = 0;
|
|
|
|
|
|
++badSplineCount;
|
|
|
|
|
|
if (badSplineCount <= 5 || (badSplineCount % 100) == 0) {
|
|
|
|
|
|
LOG_WARNING(" Spline pointCount=", pointCount,
|
2026-02-22 08:27:17 -08:00
|
|
|
|
" invalid (legacy+compact) at readPos=",
|
|
|
|
|
|
afterFinalFacingPos, "/", packet.getSize(),
|
|
|
|
|
|
", occurrence=", badSplineCount);
|
2026-02-21 04:05:53 -08:00
|
|
|
|
}
|
2026-02-22 08:27:17 -08:00
|
|
|
|
return false;
|
2026-02-06 15:18:50 -08:00
|
|
|
|
}
|
2026-02-22 08:27:17 -08:00
|
|
|
|
const bool uncompressed = (splineFlags & (0x00080000 | 0x00002000)) != 0;
|
|
|
|
|
|
size_t compactPayloadBytes = 0;
|
|
|
|
|
|
if (compactPointCount > 0) {
|
|
|
|
|
|
if (uncompressed) {
|
|
|
|
|
|
compactPayloadBytes = static_cast<size_t>(compactPointCount) * 12ull;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
compactPayloadBytes = 12ull;
|
|
|
|
|
|
if (compactPointCount > 1) {
|
|
|
|
|
|
compactPayloadBytes += static_cast<size_t>(compactPointCount - 1) * 4ull;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!bytesAvailable(compactPayloadBytes)) return false;
|
|
|
|
|
|
packet.setReadPos(packet.getReadPos() + compactPayloadBytes);
|
2026-02-05 21:55:52 -08:00
|
|
|
|
}
|
2026-02-23 05:13:26 -08:00
|
|
|
|
} // end else (compact fallback)
|
2026-02-05 21:55:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (updateFlags & UPDATEFLAG_POSITION) {
|
2026-02-11 00:54:38 -08:00
|
|
|
|
// Transport position update (UPDATEFLAG_POSITION = 0x0100)
|
|
|
|
|
|
uint64_t transportGuid = readPackedGuid(packet);
|
2026-02-05 21:55:52 -08:00
|
|
|
|
block.x = packet.readFloat();
|
|
|
|
|
|
block.y = packet.readFloat();
|
|
|
|
|
|
block.z = packet.readFloat();
|
2026-02-11 15:24:05 -08:00
|
|
|
|
block.onTransport = (transportGuid != 0);
|
|
|
|
|
|
block.transportGuid = transportGuid;
|
2026-02-11 21:14:35 -08:00
|
|
|
|
float tx = packet.readFloat();
|
|
|
|
|
|
float ty = packet.readFloat();
|
|
|
|
|
|
float tz = packet.readFloat();
|
|
|
|
|
|
if (block.onTransport) {
|
|
|
|
|
|
block.transportX = tx;
|
|
|
|
|
|
block.transportY = ty;
|
|
|
|
|
|
block.transportZ = tz;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
block.transportX = 0.0f;
|
|
|
|
|
|
block.transportY = 0.0f;
|
|
|
|
|
|
block.transportZ = 0.0f;
|
|
|
|
|
|
}
|
2026-02-05 21:55:52 -08:00
|
|
|
|
block.orientation = packet.readFloat();
|
|
|
|
|
|
/*float corpseOrientation =*/ packet.readFloat();
|
|
|
|
|
|
block.hasMovement = true;
|
|
|
|
|
|
|
2026-02-11 21:14:35 -08:00
|
|
|
|
if (block.onTransport) {
|
2026-03-10 04:51:01 -07:00
|
|
|
|
LOG_DEBUG(" TRANSPORT POSITION UPDATE: guid=0x", std::hex, transportGuid, std::dec,
|
|
|
|
|
|
" pos=(", block.x, ", ", block.y, ", ", block.z, "), o=", block.orientation,
|
|
|
|
|
|
" offset=(", block.transportX, ", ", block.transportY, ", ", block.transportZ, ")");
|
2026-02-11 21:14:35 -08:00
|
|
|
|
}
|
2026-02-05 21:55:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
else if (updateFlags & UPDATEFLAG_STATIONARY_POSITION) {
|
|
|
|
|
|
// Simple stationary position (4 floats)
|
|
|
|
|
|
block.x = packet.readFloat();
|
|
|
|
|
|
block.y = packet.readFloat();
|
|
|
|
|
|
block.z = packet.readFloat();
|
|
|
|
|
|
block.orientation = packet.readFloat();
|
|
|
|
|
|
block.hasMovement = true;
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG(" STATIONARY: (", block.x, ", ", block.y, ", ", block.z, "), o=", block.orientation);
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Target GUID (for units with target)
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_HAS_TARGET) {
|
|
|
|
|
|
/*uint64_t targetGuid =*/ readPackedGuid(packet);
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-05 21:55:52 -08:00
|
|
|
|
// Transport time
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_TRANSPORT) {
|
|
|
|
|
|
/*uint32_t transportTime =*/ packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Vehicle
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_VEHICLE) {
|
|
|
|
|
|
/*uint32_t vehicleId =*/ packet.readUInt32();
|
|
|
|
|
|
/*float vehicleOrientation =*/ packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Rotation (GameObjects)
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_ROTATION) {
|
|
|
|
|
|
/*int64_t rotation =*/ packet.readUInt64();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Low GUID
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_LOWGUID) {
|
|
|
|
|
|
/*uint32_t lowGuid =*/ packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// High GUID
|
|
|
|
|
|
if (updateFlags & UPDATEFLAG_HIGHGUID) {
|
|
|
|
|
|
/*uint32_t highGuid =*/ packet.readUInt32();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UpdateObjectParser::parseUpdateFields(network::Packet& packet, UpdateBlock& block) {
|
2026-02-10 01:24:37 -08:00
|
|
|
|
size_t startPos = packet.getReadPos();
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Read number of blocks (each block is 32 fields = 32 bits)
|
|
|
|
|
|
uint8_t blockCount = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (blockCount == 0) {
|
|
|
|
|
|
return true; // No fields to update
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 01:24:37 -08:00
|
|
|
|
uint32_t fieldsCapacity = blockCount * 32;
|
2026-02-11 21:14:35 -08:00
|
|
|
|
LOG_DEBUG(" UPDATE MASK PARSE:");
|
|
|
|
|
|
LOG_DEBUG(" maskBlockCount = ", (int)blockCount);
|
|
|
|
|
|
LOG_DEBUG(" fieldsCapacity (blocks * 32) = ", fieldsCapacity);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-22 08:30:18 -08:00
|
|
|
|
// Read update mask into a reused scratch buffer to avoid per-block allocations.
|
|
|
|
|
|
static thread_local std::vector<uint32_t> updateMask;
|
|
|
|
|
|
updateMask.resize(blockCount);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
for (int i = 0; i < blockCount; ++i) {
|
2026-03-11 14:41:25 -07:00
|
|
|
|
// Validate 4 bytes available before each block read
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("UpdateObjectParser: truncated update mask at block ", i);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
updateMask[i] = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 01:24:37 -08:00
|
|
|
|
// Find highest set bit
|
|
|
|
|
|
uint16_t highestSetBit = 0;
|
|
|
|
|
|
uint32_t valuesReadCount = 0;
|
|
|
|
|
|
|
2026-02-22 08:30:18 -08:00
|
|
|
|
// Read only set bits in each mask block (faster than scanning all 32 bits).
|
2026-02-02 12:24:50 -08:00
|
|
|
|
for (int blockIdx = 0; blockIdx < blockCount; ++blockIdx) {
|
|
|
|
|
|
uint32_t mask = updateMask[blockIdx];
|
2026-02-22 08:30:18 -08:00
|
|
|
|
while (mask != 0) {
|
|
|
|
|
|
const uint16_t fieldIndex =
|
|
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
|
|
|
|
static_cast<uint16_t>(blockIdx * 32 + __builtin_ctz(mask));
|
|
|
|
|
|
#else
|
|
|
|
|
|
static_cast<uint16_t>(blockIdx * 32 + [] (uint32_t v) -> uint16_t {
|
|
|
|
|
|
uint16_t b = 0;
|
|
|
|
|
|
while ((v & 1u) == 0u) { v >>= 1u; ++b; }
|
|
|
|
|
|
return b;
|
|
|
|
|
|
}(mask));
|
|
|
|
|
|
#endif
|
|
|
|
|
|
if (fieldIndex > highestSetBit) {
|
|
|
|
|
|
highestSetBit = fieldIndex;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
2026-03-11 14:41:25 -07:00
|
|
|
|
// Validate 4 bytes available before reading field value
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("UpdateObjectParser: truncated field value at field ", fieldIndex);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-22 08:30:18 -08:00
|
|
|
|
uint32_t value = packet.readUInt32();
|
2026-02-22 08:37:02 -08:00
|
|
|
|
// fieldIndex is monotonically increasing here, so end() is a good insertion hint.
|
|
|
|
|
|
block.fields.emplace_hint(block.fields.end(), fieldIndex, value);
|
2026-02-22 08:30:18 -08:00
|
|
|
|
valuesReadCount++;
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG(" Field[", fieldIndex, "] = 0x", std::hex, value, std::dec);
|
|
|
|
|
|
mask &= (mask - 1u);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 01:24:37 -08:00
|
|
|
|
size_t endPos = packet.getReadPos();
|
|
|
|
|
|
size_t bytesUsed = endPos - startPos;
|
|
|
|
|
|
size_t bytesRemaining = packet.getSize() - endPos;
|
|
|
|
|
|
|
2026-02-11 21:14:35 -08:00
|
|
|
|
LOG_DEBUG(" highestSetBitIndex = ", highestSetBit);
|
|
|
|
|
|
LOG_DEBUG(" valuesReadCount = ", valuesReadCount);
|
|
|
|
|
|
LOG_DEBUG(" bytesUsedForFields = ", bytesUsed);
|
|
|
|
|
|
LOG_DEBUG(" bytesRemainingInPacket = ", bytesRemaining);
|
|
|
|
|
|
LOG_DEBUG(" Parsed ", block.fields.size(), " fields");
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UpdateObjectParser::parseUpdateBlock(network::Packet& packet, UpdateBlock& block) {
|
|
|
|
|
|
// Read update type
|
|
|
|
|
|
uint8_t updateTypeVal = packet.readUInt8();
|
|
|
|
|
|
block.updateType = static_cast<UpdateType>(updateTypeVal);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Update block: type=", (int)updateTypeVal);
|
|
|
|
|
|
|
|
|
|
|
|
switch (block.updateType) {
|
|
|
|
|
|
case UpdateType::VALUES: {
|
|
|
|
|
|
// Partial update - changed fields only
|
|
|
|
|
|
block.guid = readPackedGuid(packet);
|
|
|
|
|
|
LOG_DEBUG(" VALUES update for GUID: 0x", std::hex, block.guid, std::dec);
|
|
|
|
|
|
|
|
|
|
|
|
return parseUpdateFields(packet, block);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case UpdateType::MOVEMENT: {
|
|
|
|
|
|
// Movement update
|
|
|
|
|
|
block.guid = readPackedGuid(packet);
|
|
|
|
|
|
LOG_DEBUG(" MOVEMENT update for GUID: 0x", std::hex, block.guid, std::dec);
|
|
|
|
|
|
|
|
|
|
|
|
return parseMovementBlock(packet, block);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case UpdateType::CREATE_OBJECT:
|
|
|
|
|
|
case UpdateType::CREATE_OBJECT2: {
|
|
|
|
|
|
// Create new object with full data
|
|
|
|
|
|
block.guid = readPackedGuid(packet);
|
|
|
|
|
|
LOG_DEBUG(" CREATE_OBJECT for GUID: 0x", std::hex, block.guid, std::dec);
|
|
|
|
|
|
|
|
|
|
|
|
// Read object type
|
|
|
|
|
|
uint8_t objectTypeVal = packet.readUInt8();
|
|
|
|
|
|
block.objectType = static_cast<ObjectType>(objectTypeVal);
|
|
|
|
|
|
LOG_DEBUG(" Object type: ", (int)objectTypeVal);
|
|
|
|
|
|
|
|
|
|
|
|
// Parse movement if present
|
|
|
|
|
|
bool hasMovement = parseMovementBlock(packet, block);
|
|
|
|
|
|
if (!hasMovement) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse update fields
|
|
|
|
|
|
return parseUpdateFields(packet, block);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case UpdateType::OUT_OF_RANGE_OBJECTS: {
|
|
|
|
|
|
// Objects leaving view range - handled differently
|
|
|
|
|
|
LOG_DEBUG(" OUT_OF_RANGE_OBJECTS (skipping in block parser)");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case UpdateType::NEAR_OBJECTS: {
|
|
|
|
|
|
// Objects entering view range - handled differently
|
|
|
|
|
|
LOG_DEBUG(" NEAR_OBJECTS (skipping in block parser)");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
LOG_WARNING("Unknown update type: ", (int)updateTypeVal);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data) {
|
2026-02-22 07:26:54 -08:00
|
|
|
|
constexpr uint32_t kMaxReasonableUpdateBlocks = 4096;
|
|
|
|
|
|
constexpr uint32_t kMaxReasonableOutOfRangeGuids = 16384;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Read block count
|
|
|
|
|
|
data.blockCount = packet.readUInt32();
|
2026-02-22 07:26:54 -08:00
|
|
|
|
if (data.blockCount > kMaxReasonableUpdateBlocks) {
|
|
|
|
|
|
LOG_ERROR("SMSG_UPDATE_OBJECT rejected: unreasonable blockCount=", data.blockCount,
|
|
|
|
|
|
" packetSize=", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-11 21:14:35 -08:00
|
|
|
|
LOG_DEBUG("SMSG_UPDATE_OBJECT:");
|
|
|
|
|
|
LOG_DEBUG(" objectCount = ", data.blockCount);
|
|
|
|
|
|
LOG_DEBUG(" packetSize = ", packet.getSize());
|
2026-02-10 01:24:37 -08:00
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
// Check for out-of-range objects first
|
|
|
|
|
|
if (packet.getReadPos() + 1 <= packet.getSize()) {
|
|
|
|
|
|
uint8_t firstByte = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (firstByte == static_cast<uint8_t>(UpdateType::OUT_OF_RANGE_OBJECTS)) {
|
|
|
|
|
|
// Read out-of-range GUID count
|
|
|
|
|
|
uint32_t count = packet.readUInt32();
|
2026-02-22 07:26:54 -08:00
|
|
|
|
if (count > kMaxReasonableOutOfRangeGuids) {
|
|
|
|
|
|
LOG_ERROR("SMSG_UPDATE_OBJECT rejected: unreasonable outOfRange count=", count,
|
|
|
|
|
|
" packetSize=", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
|
|
|
|
uint64_t guid = readPackedGuid(packet);
|
|
|
|
|
|
data.outOfRangeGuids.push_back(guid);
|
|
|
|
|
|
LOG_DEBUG(" Out of range: 0x", std::hex, guid, std::dec);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Done - packet may have more blocks after this
|
|
|
|
|
|
// Reset read position to after the first byte if needed
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Not out-of-range, rewind
|
|
|
|
|
|
packet.setReadPos(packet.getReadPos() - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse update blocks
|
|
|
|
|
|
data.blocks.reserve(data.blockCount);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < data.blockCount; ++i) {
|
|
|
|
|
|
LOG_DEBUG("Parsing block ", i + 1, " / ", data.blockCount);
|
|
|
|
|
|
|
|
|
|
|
|
UpdateBlock block;
|
|
|
|
|
|
if (!parseUpdateBlock(packet, block)) {
|
2026-02-23 04:32:58 -08:00
|
|
|
|
static int parseBlockErrors = 0;
|
|
|
|
|
|
if (++parseBlockErrors <= 5) {
|
2026-03-10 08:38:39 -07:00
|
|
|
|
LOG_ERROR("Failed to parse update block ", i + 1, " of ", data.blockCount,
|
|
|
|
|
|
" (", i, " blocks parsed successfully before failure)");
|
2026-02-23 04:32:58 -08:00
|
|
|
|
if (parseBlockErrors == 5)
|
|
|
|
|
|
LOG_ERROR("(suppressing further update block parse errors)");
|
|
|
|
|
|
}
|
2026-03-10 08:38:39 -07:00
|
|
|
|
// Cannot reliably re-sync to the next block after a parse failure,
|
|
|
|
|
|
// but still return true so the blocks already parsed are processed.
|
|
|
|
|
|
break;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 08:37:02 -08:00
|
|
|
|
data.blocks.emplace_back(std::move(block));
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DestroyObjectParser::parse(network::Packet& packet, DestroyObjectData& data) {
|
|
|
|
|
|
// SMSG_DESTROY_OBJECT format:
|
|
|
|
|
|
// uint64 guid
|
2026-02-13 18:59:09 -08:00
|
|
|
|
// uint8 isDeath (0 = despawn, 1 = death) — WotLK only; vanilla/TBC omit this
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-13 18:59:09 -08:00
|
|
|
|
if (packet.getSize() < 8) {
|
2026-02-02 12:24:50 -08:00
|
|
|
|
LOG_ERROR("SMSG_DESTROY_OBJECT packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.guid = packet.readUInt64();
|
2026-02-13 18:59:09 -08:00
|
|
|
|
// WotLK adds isDeath byte; vanilla/TBC packets are exactly 8 bytes
|
|
|
|
|
|
if (packet.getReadPos() < packet.getSize()) {
|
|
|
|
|
|
data.isDeath = (packet.readUInt8() != 0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.isDeath = false;
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
LOG_DEBUG("Parsed SMSG_DESTROY_OBJECT:");
|
|
|
|
|
|
LOG_DEBUG(" GUID: 0x", std::hex, data.guid, std::dec);
|
|
|
|
|
|
LOG_DEBUG(" Is death: ", data.isDeath ? "yes" : "no");
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet MessageChatPacket::build(ChatType type,
|
|
|
|
|
|
ChatLanguage language,
|
|
|
|
|
|
const std::string& message,
|
|
|
|
|
|
const std::string& target) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_MESSAGECHAT));
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
|
|
// Write chat type
|
|
|
|
|
|
packet.writeUInt32(static_cast<uint32_t>(type));
|
|
|
|
|
|
|
|
|
|
|
|
// Write language
|
|
|
|
|
|
packet.writeUInt32(static_cast<uint32_t>(language));
|
|
|
|
|
|
|
|
|
|
|
|
// Write target (for whispers) or channel name
|
|
|
|
|
|
if (type == ChatType::WHISPER) {
|
|
|
|
|
|
packet.writeString(target);
|
|
|
|
|
|
} else if (type == ChatType::CHANNEL) {
|
|
|
|
|
|
packet.writeString(target); // Channel name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Write message
|
|
|
|
|
|
packet.writeString(message);
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_MESSAGECHAT packet");
|
|
|
|
|
|
LOG_DEBUG(" Type: ", static_cast<int>(type));
|
|
|
|
|
|
LOG_DEBUG(" Language: ", static_cast<int>(language));
|
|
|
|
|
|
LOG_DEBUG(" Message: ", message);
|
|
|
|
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool MessageChatParser::parse(network::Packet& packet, MessageChatData& data) {
|
|
|
|
|
|
// SMSG_MESSAGECHAT format (WoW 3.3.5a):
|
|
|
|
|
|
// uint8 type
|
|
|
|
|
|
// uint32 language
|
|
|
|
|
|
// uint64 senderGuid
|
|
|
|
|
|
// uint32 unknown (always 0)
|
|
|
|
|
|
// [type-specific data]
|
|
|
|
|
|
// uint32 messageLength
|
|
|
|
|
|
// string message
|
|
|
|
|
|
// uint8 chatTag
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getSize() < 15) {
|
|
|
|
|
|
LOG_ERROR("SMSG_MESSAGECHAT packet too small: ", packet.getSize(), " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read chat type
|
|
|
|
|
|
uint8_t typeVal = packet.readUInt8();
|
|
|
|
|
|
data.type = static_cast<ChatType>(typeVal);
|
|
|
|
|
|
|
|
|
|
|
|
// Read language
|
|
|
|
|
|
uint32_t langVal = packet.readUInt32();
|
|
|
|
|
|
data.language = static_cast<ChatLanguage>(langVal);
|
|
|
|
|
|
|
|
|
|
|
|
// Read sender GUID
|
|
|
|
|
|
data.senderGuid = packet.readUInt64();
|
|
|
|
|
|
|
|
|
|
|
|
// Read unknown field
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Type-specific data
|
2026-02-14 14:37:53 -08:00
|
|
|
|
// WoW 3.3.5 SMSG_MESSAGECHAT format: after senderGuid+unk, most types
|
|
|
|
|
|
// have a receiverGuid (uint64). Some types have extra fields before it.
|
2026-02-02 12:24:50 -08:00
|
|
|
|
switch (data.type) {
|
|
|
|
|
|
case ChatType::MONSTER_SAY:
|
|
|
|
|
|
case ChatType::MONSTER_YELL:
|
2026-03-02 08:31:34 -08:00
|
|
|
|
case ChatType::MONSTER_EMOTE:
|
|
|
|
|
|
case ChatType::MONSTER_WHISPER:
|
|
|
|
|
|
case ChatType::MONSTER_PARTY:
|
|
|
|
|
|
case ChatType::RAID_BOSS_EMOTE:
|
|
|
|
|
|
case ChatType::RAID_BOSS_WHISPER: {
|
|
|
|
|
|
// Read sender name (SizedCString: uint32 len including null + chars)
|
2026-02-02 12:24:50 -08:00
|
|
|
|
uint32_t nameLen = packet.readUInt32();
|
|
|
|
|
|
if (nameLen > 0 && nameLen < 256) {
|
2026-02-04 11:31:08 -08:00
|
|
|
|
data.senderName.resize(nameLen);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
for (uint32_t i = 0; i < nameLen; ++i) {
|
2026-02-04 11:31:08 -08:00
|
|
|
|
data.senderName[i] = static_cast<char>(packet.readUInt8());
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
2026-03-02 08:31:34 -08:00
|
|
|
|
// Strip trailing null (server includes it in nameLen)
|
|
|
|
|
|
if (!data.senderName.empty() && data.senderName.back() == '\0') {
|
|
|
|
|
|
data.senderName.pop_back();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
2026-03-02 08:31:34 -08:00
|
|
|
|
// Read receiver GUID (NamedGuid: guid + optional name for non-player targets)
|
2026-02-02 12:24:50 -08:00
|
|
|
|
data.receiverGuid = packet.readUInt64();
|
2026-03-02 08:31:34 -08:00
|
|
|
|
if (data.receiverGuid != 0) {
|
|
|
|
|
|
// Non-player, non-pet GUIDs have high type bits set (0xF1xx/0xF0xx range)
|
|
|
|
|
|
uint16_t highGuid = static_cast<uint16_t>(data.receiverGuid >> 48);
|
|
|
|
|
|
bool isPlayer = (highGuid == 0x0000);
|
|
|
|
|
|
bool isPet = ((highGuid & 0xF0FF) == 0xF040) || ((highGuid & 0xF0FF) == 0xF014);
|
|
|
|
|
|
if (!isPlayer && !isPet) {
|
|
|
|
|
|
// Read receiver name (SizedCString)
|
|
|
|
|
|
uint32_t recvNameLen = packet.readUInt32();
|
|
|
|
|
|
if (recvNameLen > 0 && recvNameLen < 256) {
|
|
|
|
|
|
packet.setReadPos(packet.getReadPos() + recvNameLen);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case ChatType::CHANNEL: {
|
2026-02-14 14:37:53 -08:00
|
|
|
|
// Read channel name, then receiver GUID
|
2026-02-02 12:24:50 -08:00
|
|
|
|
data.channelName = packet.readString();
|
2026-02-14 14:37:53 -08:00
|
|
|
|
data.receiverGuid = packet.readUInt64();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case ChatType::ACHIEVEMENT:
|
|
|
|
|
|
case ChatType::GUILD_ACHIEVEMENT: {
|
2026-02-14 14:37:53 -08:00
|
|
|
|
// Read target GUID
|
|
|
|
|
|
data.receiverGuid = packet.readUInt64();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 15:32:04 -07:00
|
|
|
|
case ChatType::BG_SYSTEM_NEUTRAL:
|
|
|
|
|
|
case ChatType::BG_SYSTEM_ALLIANCE:
|
|
|
|
|
|
case ChatType::BG_SYSTEM_HORDE:
|
|
|
|
|
|
// BG/Arena system messages — no sender GUID or name field, just message.
|
|
|
|
|
|
// Reclassify as SYSTEM for consistent display.
|
|
|
|
|
|
data.type = ChatType::SYSTEM;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
default:
|
2026-02-14 14:37:53 -08:00
|
|
|
|
// SAY, GUILD, PARTY, YELL, WHISPER, WHISPER_INFORM, RAID, etc.
|
|
|
|
|
|
// All have receiverGuid (typically senderGuid repeated)
|
|
|
|
|
|
data.receiverGuid = packet.readUInt64();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read message length
|
|
|
|
|
|
uint32_t messageLen = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Read message
|
|
|
|
|
|
if (messageLen > 0 && messageLen < 8192) {
|
2026-02-04 11:31:08 -08:00
|
|
|
|
data.message.resize(messageLen);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
for (uint32_t i = 0; i < messageLen; ++i) {
|
2026-02-04 11:31:08 -08:00
|
|
|
|
data.message[i] = static_cast<char>(packet.readUInt8());
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
2026-02-14 18:27:59 -08:00
|
|
|
|
// Strip trailing null terminator (servers include it in messageLen)
|
|
|
|
|
|
if (!data.message.empty() && data.message.back() == '\0') {
|
|
|
|
|
|
data.message.pop_back();
|
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read chat tag
|
|
|
|
|
|
data.chatTag = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_MESSAGECHAT:");
|
|
|
|
|
|
LOG_DEBUG(" Type: ", getChatTypeString(data.type));
|
|
|
|
|
|
LOG_DEBUG(" Language: ", static_cast<int>(data.language));
|
|
|
|
|
|
LOG_DEBUG(" Sender GUID: 0x", std::hex, data.senderGuid, std::dec);
|
|
|
|
|
|
if (!data.senderName.empty()) {
|
|
|
|
|
|
LOG_DEBUG(" Sender name: ", data.senderName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!data.channelName.empty()) {
|
|
|
|
|
|
LOG_DEBUG(" Channel: ", data.channelName);
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_DEBUG(" Message: ", data.message);
|
|
|
|
|
|
LOG_DEBUG(" Chat tag: 0x", std::hex, (int)data.chatTag, std::dec);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* getChatTypeString(ChatType type) {
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case ChatType::SAY: return "SAY";
|
|
|
|
|
|
case ChatType::PARTY: return "PARTY";
|
|
|
|
|
|
case ChatType::RAID: return "RAID";
|
|
|
|
|
|
case ChatType::GUILD: return "GUILD";
|
|
|
|
|
|
case ChatType::OFFICER: return "OFFICER";
|
|
|
|
|
|
case ChatType::YELL: return "YELL";
|
|
|
|
|
|
case ChatType::WHISPER: return "WHISPER";
|
|
|
|
|
|
case ChatType::WHISPER_INFORM: return "WHISPER_INFORM";
|
|
|
|
|
|
case ChatType::EMOTE: return "EMOTE";
|
|
|
|
|
|
case ChatType::TEXT_EMOTE: return "TEXT_EMOTE";
|
|
|
|
|
|
case ChatType::SYSTEM: return "SYSTEM";
|
|
|
|
|
|
case ChatType::MONSTER_SAY: return "MONSTER_SAY";
|
|
|
|
|
|
case ChatType::MONSTER_YELL: return "MONSTER_YELL";
|
|
|
|
|
|
case ChatType::MONSTER_EMOTE: return "MONSTER_EMOTE";
|
|
|
|
|
|
case ChatType::CHANNEL: return "CHANNEL";
|
|
|
|
|
|
case ChatType::CHANNEL_JOIN: return "CHANNEL_JOIN";
|
|
|
|
|
|
case ChatType::CHANNEL_LEAVE: return "CHANNEL_LEAVE";
|
|
|
|
|
|
case ChatType::CHANNEL_LIST: return "CHANNEL_LIST";
|
|
|
|
|
|
case ChatType::CHANNEL_NOTICE: return "CHANNEL_NOTICE";
|
|
|
|
|
|
case ChatType::CHANNEL_NOTICE_USER: return "CHANNEL_NOTICE_USER";
|
|
|
|
|
|
case ChatType::AFK: return "AFK";
|
|
|
|
|
|
case ChatType::DND: return "DND";
|
|
|
|
|
|
case ChatType::IGNORED: return "IGNORED";
|
|
|
|
|
|
case ChatType::SKILL: return "SKILL";
|
|
|
|
|
|
case ChatType::LOOT: return "LOOT";
|
|
|
|
|
|
case ChatType::BATTLEGROUND: return "BATTLEGROUND";
|
|
|
|
|
|
case ChatType::BATTLEGROUND_LEADER: return "BATTLEGROUND_LEADER";
|
|
|
|
|
|
case ChatType::RAID_LEADER: return "RAID_LEADER";
|
|
|
|
|
|
case ChatType::RAID_WARNING: return "RAID_WARNING";
|
|
|
|
|
|
case ChatType::ACHIEVEMENT: return "ACHIEVEMENT";
|
|
|
|
|
|
case ChatType::GUILD_ACHIEVEMENT: return "GUILD_ACHIEVEMENT";
|
|
|
|
|
|
default: return "UNKNOWN";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 14:30:09 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Text Emotes
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet TextEmotePacket::build(uint32_t textEmoteId, uint64_t targetGuid) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_TEXT_EMOTE));
|
|
|
|
|
|
packet.writeUInt32(textEmoteId);
|
|
|
|
|
|
packet.writeUInt32(0); // emoteNum (unused)
|
|
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_TEXT_EMOTE: emoteId=", textEmoteId, " target=0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 01:05:23 -07:00
|
|
|
|
bool TextEmoteParser::parse(network::Packet& packet, TextEmoteData& data, bool legacyFormat) {
|
2026-02-14 14:30:09 -08:00
|
|
|
|
size_t bytesLeft = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (bytesLeft < 20) {
|
|
|
|
|
|
LOG_WARNING("SMSG_TEXT_EMOTE too short: ", bytesLeft, " bytes");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-03-10 01:05:23 -07:00
|
|
|
|
|
|
|
|
|
|
if (legacyFormat) {
|
|
|
|
|
|
// Classic 1.12 / TBC 2.4.3: textEmoteId(u32) + emoteNum(u32) + senderGuid(u64)
|
|
|
|
|
|
data.textEmoteId = packet.readUInt32();
|
|
|
|
|
|
data.emoteNum = packet.readUInt32();
|
|
|
|
|
|
data.senderGuid = packet.readUInt64();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// WotLK 3.3.5a: senderGuid(u64) + textEmoteId(u32) + emoteNum(u32)
|
|
|
|
|
|
data.senderGuid = packet.readUInt64();
|
|
|
|
|
|
data.textEmoteId = packet.readUInt32();
|
|
|
|
|
|
data.emoteNum = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 14:30:09 -08:00
|
|
|
|
uint32_t nameLen = packet.readUInt32();
|
|
|
|
|
|
if (nameLen > 0 && nameLen <= 256) {
|
|
|
|
|
|
data.targetName = packet.readString();
|
|
|
|
|
|
} else if (nameLen > 0) {
|
2026-03-10 01:05:23 -07:00
|
|
|
|
// Implausible name length — misaligned read
|
2026-02-14 14:30:09 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Channel System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet JoinChannelPacket::build(const std::string& channelName, const std::string& password) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_JOIN_CHANNEL));
|
|
|
|
|
|
packet.writeUInt32(0); // channelId (unused)
|
|
|
|
|
|
packet.writeUInt8(0); // hasVoice
|
|
|
|
|
|
packet.writeUInt8(0); // joinedByZone
|
|
|
|
|
|
packet.writeString(channelName);
|
|
|
|
|
|
packet.writeString(password);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_JOIN_CHANNEL: channel=", channelName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet LeaveChannelPacket::build(const std::string& channelName) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LEAVE_CHANNEL));
|
|
|
|
|
|
packet.writeUInt32(0); // channelId (unused)
|
|
|
|
|
|
packet.writeString(channelName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_LEAVE_CHANNEL: channel=", channelName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ChannelNotifyParser::parse(network::Packet& packet, ChannelNotifyData& data) {
|
|
|
|
|
|
size_t bytesLeft = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (bytesLeft < 2) {
|
|
|
|
|
|
LOG_WARNING("SMSG_CHANNEL_NOTIFY too short");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.notifyType = static_cast<ChannelNotifyType>(packet.readUInt8());
|
|
|
|
|
|
data.channelName = packet.readString();
|
|
|
|
|
|
// Some notification types have additional fields (guid, etc.)
|
|
|
|
|
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (bytesLeft >= 8) {
|
|
|
|
|
|
data.senderGuid = packet.readUInt64();
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 1: Foundation — Targeting, Name Queries
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet SetSelectionPacket::build(uint64_t targetGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SET_SELECTION));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SET_SELECTION: target=0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet SetActiveMoverPacket::build(uint64_t guid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SET_ACTIVE_MOVER));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(guid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SET_ACTIVE_MOVER: guid=0x", std::hex, guid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:37:13 -08:00
|
|
|
|
network::Packet InspectPacket::build(uint64_t targetGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_INSPECT));
|
2026-02-07 12:37:13 -08:00
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_INSPECT: target=0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:43:32 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Server Info Commands
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet QueryTimePacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUERY_TIME));
|
2026-02-07 12:43:32 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_QUERY_TIME");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool QueryTimeResponseParser::parse(network::Packet& packet, QueryTimeResponseData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: serverTime(4) + timeOffset(4)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_WARNING("SMSG_QUERY_TIME_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:43:32 -08:00
|
|
|
|
data.serverTime = packet.readUInt32();
|
|
|
|
|
|
data.timeOffset = packet.readUInt32();
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_QUERY_TIME_RESPONSE: time=", data.serverTime, " offset=", data.timeOffset);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet RequestPlayedTimePacket::build(bool sendToChat) {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PLAYED_TIME));
|
2026-02-07 12:43:32 -08:00
|
|
|
|
packet.writeUInt8(sendToChat ? 1 : 0);
|
2026-02-20 02:50:59 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_PLAYED_TIME: sendToChat=", sendToChat);
|
2026-02-07 12:43:32 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PlayedTimeParser::parse(network::Packet& packet, PlayedTimeData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: totalTime(4) + levelTime(4) + triggerMsg(1)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 9) {
|
|
|
|
|
|
LOG_WARNING("SMSG_PLAYED_TIME: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:43:32 -08:00
|
|
|
|
data.totalTimePlayed = packet.readUInt32();
|
|
|
|
|
|
data.levelTimePlayed = packet.readUInt32();
|
|
|
|
|
|
data.triggerMessage = packet.readUInt8() != 0;
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_PLAYED_TIME: total=", data.totalTimePlayed, " level=", data.levelTimePlayed);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet WhoPacket::build(uint32_t minLevel, uint32_t maxLevel,
|
|
|
|
|
|
const std::string& playerName,
|
|
|
|
|
|
const std::string& guildName,
|
|
|
|
|
|
uint32_t raceMask, uint32_t classMask,
|
|
|
|
|
|
uint32_t zones) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_WHO));
|
2026-02-07 12:43:32 -08:00
|
|
|
|
packet.writeUInt32(minLevel);
|
|
|
|
|
|
packet.writeUInt32(maxLevel);
|
|
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
packet.writeString(guildName);
|
|
|
|
|
|
packet.writeUInt32(raceMask);
|
|
|
|
|
|
packet.writeUInt32(classMask);
|
2026-02-14 15:05:18 -08:00
|
|
|
|
packet.writeUInt32(zones); // Number of zone IDs (0 = no zone filter)
|
|
|
|
|
|
// Zone ID array would go here if zones > 0
|
|
|
|
|
|
packet.writeUInt32(0); // stringCount (number of search strings)
|
|
|
|
|
|
// String array would go here if stringCount > 0
|
2026-02-07 12:43:32 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_WHO: player=", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Social Commands
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AddFriendPacket::build(const std::string& playerName, const std::string& note) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ADD_FRIEND));
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
packet.writeString(note);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_ADD_FRIEND: player=", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet DelFriendPacket::build(uint64_t friendGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_DEL_FRIEND));
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
packet.writeUInt64(friendGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_DEL_FRIEND: guid=0x", std::hex, friendGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet SetContactNotesPacket::build(uint64_t friendGuid, const std::string& note) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SET_CONTACT_NOTES));
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
packet.writeUInt64(friendGuid);
|
|
|
|
|
|
packet.writeString(note);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SET_CONTACT_NOTES: guid=0x", std::hex, friendGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool FriendStatusParser::parse(network::Packet& packet, FriendStatusData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: status(1) + guid(8)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 9) {
|
|
|
|
|
|
LOG_WARNING("SMSG_FRIEND_STATUS: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
data.status = packet.readUInt8();
|
|
|
|
|
|
data.guid = packet.readUInt64();
|
|
|
|
|
|
if (data.status == 1) { // Online
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Conditional: note (string) + chatFlag (1)
|
|
|
|
|
|
if (packet.getReadPos() < packet.getSize()) {
|
|
|
|
|
|
data.note = packet.readString();
|
|
|
|
|
|
if (packet.getReadPos() + 1 <= packet.getSize()) {
|
|
|
|
|
|
data.chatFlag = packet.readUInt8();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
}
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_FRIEND_STATUS: status=", (int)data.status, " guid=0x", std::hex, data.guid, std::dec);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:58:11 -08:00
|
|
|
|
network::Packet AddIgnorePacket::build(const std::string& playerName) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ADD_IGNORE));
|
2026-02-07 12:58:11 -08:00
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_ADD_IGNORE: player=", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet DelIgnorePacket::build(uint64_t ignoreGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_DEL_IGNORE));
|
2026-02-07 12:58:11 -08:00
|
|
|
|
packet.writeUInt64(ignoreGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_DEL_IGNORE: guid=0x", std::hex, ignoreGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Logout Commands
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet LogoutRequestPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LOGOUT_REQUEST));
|
2026-02-07 12:58:11 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_LOGOUT_REQUEST");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet LogoutCancelPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LOGOUT_CANCEL));
|
2026-02-07 12:58:11 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_LOGOUT_CANCEL");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LogoutResponseParser::parse(network::Packet& packet, LogoutResponseData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: result(4) + instant(1)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 5) {
|
|
|
|
|
|
LOG_WARNING("SMSG_LOGOUT_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 12:58:11 -08:00
|
|
|
|
data.result = packet.readUInt32();
|
|
|
|
|
|
data.instant = packet.readUInt8();
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_LOGOUT_RESPONSE: result=", data.result, " instant=", (int)data.instant);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Stand State
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet StandStateChangePacket::build(uint8_t state) {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_STANDSTATECHANGE));
|
2026-02-07 12:58:11 -08:00
|
|
|
|
packet.writeUInt32(state);
|
2026-02-20 02:50:59 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_STANDSTATECHANGE: state=", (int)state);
|
2026-02-07 12:58:11 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 13:03:21 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Display Toggles
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ShowingHelmPacket::build(bool show) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SHOWING_HELM));
|
2026-02-07 13:03:21 -08:00
|
|
|
|
packet.writeUInt8(show ? 1 : 0);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SHOWING_HELM: show=", show);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ShowingCloakPacket::build(bool show) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SHOWING_CLOAK));
|
2026-02-07 13:03:21 -08:00
|
|
|
|
packet.writeUInt8(show ? 1 : 0);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SHOWING_CLOAK: show=", show);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// PvP
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet TogglePvpPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_TOGGLE_PVP));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_TOGGLE_PVP");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Guild Commands
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildInfoPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_INFO));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_INFO");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildRosterPacket::build() {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_ROSTER));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_ROSTER");
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildMotdPacket::build(const std::string& motd) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_MOTD));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
packet.writeString(motd);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_MOTD: ", motd);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildPromotePacket::build(const std::string& playerName) {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_PROMOTE));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
packet.writeString(playerName);
|
2026-02-20 02:50:59 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_PROMOTE: ", playerName);
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildDemotePacket::build(const std::string& playerName) {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_DEMOTE));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
packet.writeString(playerName);
|
2026-02-20 02:50:59 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_DEMOTE: ", playerName);
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildLeavePacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_LEAVE));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_LEAVE");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildInvitePacket::build(const std::string& playerName) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_INVITE));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_INVITE: ", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
network::Packet GuildQueryPacket::build(uint32_t guildId) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_QUERY));
|
|
|
|
|
|
packet.writeUInt32(guildId);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_QUERY: guildId=", guildId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildRemovePacket::build(const std::string& playerName) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_REMOVE));
|
|
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_REMOVE: ", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-16 20:16:14 -08:00
|
|
|
|
network::Packet GuildDisbandPacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_DISBAND));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_DISBAND");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildLeaderPacket::build(const std::string& playerName) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_LEADER));
|
|
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_LEADER: ", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildSetPublicNotePacket::build(const std::string& playerName, const std::string& note) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_SET_PUBLIC_NOTE));
|
|
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
packet.writeString(note);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_SET_PUBLIC_NOTE: ", playerName, " -> ", note);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildSetOfficerNotePacket::build(const std::string& playerName, const std::string& note) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_SET_OFFICER_NOTE));
|
|
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
packet.writeString(note);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_SET_OFFICER_NOTE: ", playerName, " -> ", note);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
network::Packet GuildAcceptPacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_ACCEPT));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_ACCEPT");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildDeclineInvitationPacket::build() {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_DECLINE));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_DECLINE");
|
2026-02-13 21:39:48 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-25 14:44:44 -08:00
|
|
|
|
network::Packet GuildCreatePacket::build(const std::string& guildName) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_CREATE));
|
|
|
|
|
|
packet.writeString(guildName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_CREATE: ", guildName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildAddRankPacket::build(const std::string& rankName) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_ADD_RANK));
|
|
|
|
|
|
packet.writeString(rankName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_ADD_RANK: ", rankName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildDelRankPacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_DEL_RANK));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GUILD_DEL_RANK");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet PetitionShowlistPacket::build(uint64_t npcGuid) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PETITION_SHOWLIST));
|
|
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_PETITION_SHOWLIST: guid=", npcGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet PetitionBuyPacket::build(uint64_t npcGuid, const std::string& guildName) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PETITION_BUY));
|
|
|
|
|
|
packet.writeUInt64(npcGuid); // NPC GUID
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
packet.writeUInt64(0); // unk
|
|
|
|
|
|
packet.writeString(guildName); // guild name
|
|
|
|
|
|
packet.writeUInt32(0); // body text (empty)
|
|
|
|
|
|
packet.writeUInt32(0); // min sigs
|
|
|
|
|
|
packet.writeUInt32(0); // max sigs
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
packet.writeUInt16(0); // unk
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
packet.writeUInt32(0); // unk index
|
|
|
|
|
|
packet.writeUInt32(0); // unk
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_PETITION_BUY: npcGuid=", npcGuid, " name=", guildName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PetitionShowlistParser::parse(network::Packet& packet, PetitionShowlistData& data) {
|
|
|
|
|
|
if (packet.getSize() < 12) {
|
|
|
|
|
|
LOG_ERROR("SMSG_PETITION_SHOWLIST too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.npcGuid = packet.readUInt64();
|
|
|
|
|
|
uint32_t count = packet.readUInt32();
|
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
|
data.itemId = packet.readUInt32();
|
|
|
|
|
|
data.displayId = packet.readUInt32();
|
|
|
|
|
|
data.cost = packet.readUInt32();
|
|
|
|
|
|
// Skip unused fields if present
|
|
|
|
|
|
if ((packet.getSize() - packet.getReadPos()) >= 8) {
|
|
|
|
|
|
data.charterType = packet.readUInt32();
|
|
|
|
|
|
data.requiredSigs = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_PETITION_SHOWLIST: npcGuid=", data.npcGuid, " cost=", data.cost);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool TurnInPetitionResultsParser::parse(network::Packet& packet, uint32_t& result) {
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_TURN_IN_PETITION_RESULTS too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
result = packet.readUInt32();
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_TURN_IN_PETITION_RESULTS: result=", result);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
bool GuildQueryResponseParser::parse(network::Packet& packet, GuildQueryResponseData& data) {
|
|
|
|
|
|
if (packet.getSize() < 8) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GUILD_QUERY_RESPONSE too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.guildId = packet.readUInt32();
|
2026-03-11 14:46:44 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate before reading guild name
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildQueryResponseParser: truncated before guild name");
|
|
|
|
|
|
data.guildName.clear();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-02-13 21:39:48 -08:00
|
|
|
|
data.guildName = packet.readString();
|
2026-03-11 14:46:44 -07:00
|
|
|
|
|
|
|
|
|
|
// Read 10 rank names with validation
|
2026-02-13 21:39:48 -08:00
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
2026-03-11 14:46:44 -07:00
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildQueryResponseParser: truncated at rank name ", i);
|
|
|
|
|
|
data.rankNames[i].clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.rankNames[i] = packet.readString();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate before reading emblem fields (5 uint32s = 20 bytes)
|
|
|
|
|
|
if (packet.getReadPos() + 20 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildQueryResponseParser: truncated before emblem data");
|
|
|
|
|
|
data.emblemStyle = 0;
|
|
|
|
|
|
data.emblemColor = 0;
|
|
|
|
|
|
data.borderStyle = 0;
|
|
|
|
|
|
data.borderColor = 0;
|
|
|
|
|
|
data.backgroundColor = 0;
|
|
|
|
|
|
return true;
|
2026-02-13 21:39:48 -08:00
|
|
|
|
}
|
2026-03-11 14:46:44 -07:00
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
data.emblemStyle = packet.readUInt32();
|
|
|
|
|
|
data.emblemColor = packet.readUInt32();
|
|
|
|
|
|
data.borderStyle = packet.readUInt32();
|
|
|
|
|
|
data.borderColor = packet.readUInt32();
|
|
|
|
|
|
data.backgroundColor = packet.readUInt32();
|
2026-03-11 14:46:44 -07:00
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
if ((packet.getSize() - packet.getReadPos()) >= 4) {
|
|
|
|
|
|
data.rankCount = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_GUILD_QUERY_RESPONSE: guild=", data.guildName, " id=", data.guildId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuildInfoParser::parse(network::Packet& packet, GuildInfoData& data) {
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GUILD_INFO too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.guildName = packet.readString();
|
|
|
|
|
|
data.creationDay = packet.readUInt32();
|
|
|
|
|
|
data.creationMonth = packet.readUInt32();
|
|
|
|
|
|
data.creationYear = packet.readUInt32();
|
|
|
|
|
|
data.numMembers = packet.readUInt32();
|
|
|
|
|
|
data.numAccounts = packet.readUInt32();
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_GUILD_INFO: ", data.guildName, " members=", data.numMembers);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuildRosterParser::parse(network::Packet& packet, GuildRosterData& data) {
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GUILD_ROSTER too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t numMembers = packet.readUInt32();
|
2026-03-11 14:42:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Cap members and ranks to prevent unbounded memory allocation
|
|
|
|
|
|
const uint32_t MAX_GUILD_MEMBERS = 1000;
|
|
|
|
|
|
if (numMembers > MAX_GUILD_MEMBERS) {
|
|
|
|
|
|
LOG_WARNING("GuildRosterParser: numMembers capped (requested=", numMembers, ")");
|
|
|
|
|
|
numMembers = MAX_GUILD_MEMBERS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
data.motd = packet.readString();
|
|
|
|
|
|
data.guildInfo = packet.readString();
|
|
|
|
|
|
|
2026-03-11 14:42:09 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildRosterParser: truncated before rankCount");
|
|
|
|
|
|
data.ranks.clear();
|
|
|
|
|
|
data.members.clear();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
uint32_t rankCount = packet.readUInt32();
|
2026-03-11 14:42:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Cap rank count to prevent unbounded allocation
|
|
|
|
|
|
const uint32_t MAX_GUILD_RANKS = 20;
|
|
|
|
|
|
if (rankCount > MAX_GUILD_RANKS) {
|
|
|
|
|
|
LOG_WARNING("GuildRosterParser: rankCount capped (requested=", rankCount, ")");
|
|
|
|
|
|
rankCount = MAX_GUILD_RANKS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 21:39:48 -08:00
|
|
|
|
data.ranks.resize(rankCount);
|
|
|
|
|
|
for (uint32_t i = 0; i < rankCount; ++i) {
|
2026-03-11 14:42:09 -07:00
|
|
|
|
// Validate 4 bytes before each rank rights read
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildRosterParser: truncated rank at index ", i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-02-13 21:39:48 -08:00
|
|
|
|
data.ranks[i].rights = packet.readUInt32();
|
2026-03-11 14:42:09 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
data.ranks[i].goldLimit = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.ranks[i].goldLimit = packet.readUInt32();
|
|
|
|
|
|
}
|
2026-02-13 21:39:48 -08:00
|
|
|
|
// 6 bank tab flags + 6 bank tab items per day
|
|
|
|
|
|
for (int t = 0; t < 6; ++t) {
|
2026-03-11 14:42:09 -07:00
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) break;
|
2026-02-13 21:39:48 -08:00
|
|
|
|
packet.readUInt32(); // tabFlags
|
|
|
|
|
|
packet.readUInt32(); // tabItemsPerDay
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.members.resize(numMembers);
|
|
|
|
|
|
for (uint32_t i = 0; i < numMembers; ++i) {
|
2026-03-11 14:42:09 -07:00
|
|
|
|
// Validate minimum bytes before reading member (guid+online+name at minimum is 9+ bytes)
|
|
|
|
|
|
if (packet.getReadPos() + 9 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildRosterParser: truncated member at index ", i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-02-13 21:39:48 -08:00
|
|
|
|
auto& m = data.members[i];
|
|
|
|
|
|
m.guid = packet.readUInt64();
|
|
|
|
|
|
m.online = (packet.readUInt8() != 0);
|
2026-03-11 14:42:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate before reading name string
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
m.name.clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.name = packet.readString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate before reading rank/level/class/gender/zone
|
|
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
m.rankIndex = 0;
|
|
|
|
|
|
m.level = 1;
|
|
|
|
|
|
m.classId = 0;
|
|
|
|
|
|
m.gender = 0;
|
|
|
|
|
|
m.zoneId = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.rankIndex = packet.readUInt32();
|
|
|
|
|
|
if (packet.getReadPos() + 3 > packet.getSize()) {
|
|
|
|
|
|
m.level = 1;
|
|
|
|
|
|
m.classId = 0;
|
|
|
|
|
|
m.gender = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.level = packet.readUInt8();
|
|
|
|
|
|
m.classId = packet.readUInt8();
|
|
|
|
|
|
m.gender = packet.readUInt8();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
m.zoneId = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.zoneId = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Online status affects next fields
|
2026-02-13 21:39:48 -08:00
|
|
|
|
if (!m.online) {
|
2026-03-11 14:42:09 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
m.lastOnline = 0.0f;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.lastOnline = packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read notes
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
m.publicNote.clear();
|
|
|
|
|
|
m.officerNote.clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.publicNote = packet.readString();
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
m.officerNote.clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m.officerNote = packet.readString();
|
|
|
|
|
|
}
|
2026-02-13 21:39:48 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_GUILD_ROSTER: ", numMembers, " members, motd=", data.motd);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuildEventParser::parse(network::Packet& packet, GuildEventData& data) {
|
|
|
|
|
|
if (packet.getSize() < 2) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GUILD_EVENT too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.eventType = packet.readUInt8();
|
|
|
|
|
|
data.numStrings = packet.readUInt8();
|
|
|
|
|
|
for (uint8_t i = 0; i < data.numStrings && i < 3; ++i) {
|
|
|
|
|
|
data.strings[i] = packet.readString();
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((packet.getSize() - packet.getReadPos()) >= 8) {
|
|
|
|
|
|
data.guid = packet.readUInt64();
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_GUILD_EVENT: type=", (int)data.eventType, " strings=", (int)data.numStrings);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuildInviteResponseParser::parse(network::Packet& packet, GuildInviteResponseData& data) {
|
|
|
|
|
|
if (packet.getSize() < 2) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GUILD_INVITE too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.inviterName = packet.readString();
|
|
|
|
|
|
data.guildName = packet.readString();
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_GUILD_INVITE: from=", data.inviterName, " guild=", data.guildName);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuildCommandResultParser::parse(network::Packet& packet, GuildCommandResultData& data) {
|
|
|
|
|
|
if (packet.getSize() < 8) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GUILD_COMMAND_RESULT too small: ", packet.getSize());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.command = packet.readUInt32();
|
|
|
|
|
|
data.name = packet.readString();
|
|
|
|
|
|
data.errorCode = packet.readUInt32();
|
|
|
|
|
|
LOG_INFO("Parsed SMSG_GUILD_COMMAND_RESULT: cmd=", data.command, " error=", data.errorCode);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Ready Check
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ReadyCheckPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::MSG_RAID_READY_CHECK));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
LOG_DEBUG("Built MSG_RAID_READY_CHECK");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ReadyCheckConfirmPacket::build(bool ready) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::MSG_RAID_READY_CHECK_CONFIRM));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
packet.writeUInt8(ready ? 1 : 0);
|
|
|
|
|
|
LOG_DEBUG("Built MSG_RAID_READY_CHECK_CONFIRM: ready=", ready);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Duel
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
2026-03-09 13:58:02 -07:00
|
|
|
|
network::Packet DuelAcceptPacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_ACCEPTED));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_DUEL_ACCEPTED");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
network::Packet DuelCancelPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
Add Tier 3 commands: guild management, PvP, ready check, and duel forfeit
- Guild commands: /ginfo, /groster, /gmotd, /gpromote, /gdemote, /gquit, /ginvite
- PvP toggle: /pvp to toggle PvP flag
- Ready check system: /readycheck, /ready, /notready for group coordination
- Duel forfeit: /yield, /forfeit, /surrender to cancel active duels
2026-02-07 13:09:12 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 13:28:46 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Party/Raid Management
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GroupUninvitePacket::build(const std::string& playerName) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_UNINVITE_GUID));
|
2026-02-07 13:28:46 -08:00
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GROUP_UNINVITE_GUID for player: ", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GroupDisbandPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_DISBAND));
|
2026-02-07 13:28:46 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_GROUP_DISBAND");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet RaidTargetUpdatePacket::build(uint8_t targetIndex, uint64_t targetGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::MSG_RAID_TARGET_UPDATE));
|
2026-02-07 13:28:46 -08:00
|
|
|
|
packet.writeUInt8(targetIndex);
|
|
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built MSG_RAID_TARGET_UPDATE, index: ", (uint32_t)targetIndex, ", guid: 0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet RequestRaidInfoPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_REQUEST_RAID_INFO));
|
2026-02-07 13:28:46 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_REQUEST_RAID_INFO");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 13:36:50 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Combat and Trade
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet DuelProposedPacket::build(uint64_t targetGuid) {
|
2026-03-09 16:30:42 -07:00
|
|
|
|
// Duels are initiated via CMSG_CAST_SPELL with spell 7266 (Duel) targeted at the opponent.
|
|
|
|
|
|
// There is no separate CMSG_DUEL_PROPOSED opcode in WoW.
|
2026-02-20 01:13:01 -08:00
|
|
|
|
auto packet = CastSpellPacket::build(7266, targetGuid, 0);
|
|
|
|
|
|
LOG_DEBUG("Built duel request (spell 7266) for target: 0x", std::hex, targetGuid, std::dec);
|
2026-02-07 13:36:50 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request,
open/cancel/complete/accept notifications, error conditions (too far,
wrong faction, stunned, dead, trial account, etc.)
- SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item
window yet; state tracking sufficient for accept/decline flow)
- Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(),
acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE)
- Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders
- Add renderTradeRequestPopup(): shows "X wants to trade" with
Accept/Decline buttons when tradeStatus_ == PendingIncoming
- TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
2026-03-09 14:05:42 -07:00
|
|
|
|
network::Packet BeginTradePacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_BEGIN_TRADE));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_BEGIN_TRADE");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet CancelTradePacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_TRADE));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_CANCEL_TRADE");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AcceptTradePacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ACCEPT_TRADE));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_ACCEPT_TRADE");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 00:44:07 -07:00
|
|
|
|
network::Packet SetTradeItemPacket::build(uint8_t tradeSlot, uint8_t bag, uint8_t bagSlot) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SET_TRADE_ITEM));
|
|
|
|
|
|
packet.writeUInt8(tradeSlot);
|
|
|
|
|
|
packet.writeUInt8(bag);
|
|
|
|
|
|
packet.writeUInt8(bagSlot);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SET_TRADE_ITEM slot=", (int)tradeSlot, " bag=", (int)bag, " bagSlot=", (int)bagSlot);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ClearTradeItemPacket::build(uint8_t tradeSlot) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CLEAR_TRADE_ITEM));
|
|
|
|
|
|
packet.writeUInt8(tradeSlot);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_CLEAR_TRADE_ITEM slot=", (int)tradeSlot);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet SetTradeGoldPacket::build(uint64_t copper) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SET_TRADE_GOLD));
|
|
|
|
|
|
packet.writeUInt64(copper);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_SET_TRADE_GOLD copper=", copper);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet UnacceptTradePacket::build() {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_UNACCEPT_TRADE));
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_UNACCEPT_TRADE");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 13:36:50 -08:00
|
|
|
|
network::Packet InitiateTradePacket::build(uint64_t targetGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_INITIATE_TRADE));
|
2026-02-07 13:36:50 -08:00
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_INITIATE_TRADE for target: 0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AttackSwingPacket::build(uint64_t targetGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ATTACKSWING));
|
2026-02-07 13:36:50 -08:00
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_ATTACKSWING for target: 0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AttackStopPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ATTACKSTOP));
|
2026-02-07 13:36:50 -08:00
|
|
|
|
LOG_DEBUG("Built CMSG_ATTACKSTOP");
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet CancelCastPacket::build(uint32_t spellId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_CAST));
|
2026-02-07 13:36:50 -08:00
|
|
|
|
packet.writeUInt32(0); // cast count/sequence
|
|
|
|
|
|
packet.writeUInt32(spellId);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_CANCEL_CAST for spell: ", spellId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Random Roll
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet RandomRollPacket::build(uint32_t minRoll, uint32_t maxRoll) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::MSG_RANDOM_ROLL));
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
packet.writeUInt32(minRoll);
|
|
|
|
|
|
packet.writeUInt32(maxRoll);
|
|
|
|
|
|
LOG_DEBUG("Built MSG_RANDOM_ROLL: ", minRoll, "-", maxRoll);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool RandomRollParser::parse(network::Packet& packet, RandomRollData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: rollerGuid(8) + targetGuid(8) + minRoll(4) + maxRoll(4) + result(4)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 28) {
|
|
|
|
|
|
LOG_WARNING("SMSG_RANDOM_ROLL: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add /roll and friend management commands
Roll Command:
- Add /roll, /random, /rnd commands for random number generation
- Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50
- Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format
- Cap max roll at 10,000 to prevent abuse
- Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode
Friend Commands:
- Add /friend add <name>, /addfriend <name> to add friends
- Add /friend remove <name>, /removefriend <name> to remove friends
- Support aliases: /delfriend, /remfriend
- Maintain local friends cache mapping names to GUIDs for lookups
- Display status messages for all friend actions:
- Friend added/removed confirmations
- Friend online/offline notifications
- Error messages (not found, already friends, list full, ignoring)
Social Opcodes:
- Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68)
- Add CMSG_DEL_FRIEND (0x6A) for friend removal
- Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use)
- Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use)
Implementation:
- Add RandomRollPacket builder and RandomRollParser for roll data
- Add AddFriendPacket and DelFriendPacket builders
- Add FriendStatusParser to handle server friend status updates
- Add friendsCache map to store friend name-to-GUID mappings
- Add handleRandomRoll() and handleFriendStatus() packet handlers
- Comprehensive slash command parsing with multiple formats and aliases
2026-02-07 12:51:30 -08:00
|
|
|
|
data.rollerGuid = packet.readUInt64();
|
|
|
|
|
|
data.targetGuid = packet.readUInt64();
|
|
|
|
|
|
data.minRoll = packet.readUInt32();
|
|
|
|
|
|
data.maxRoll = packet.readUInt32();
|
|
|
|
|
|
data.result = packet.readUInt32();
|
|
|
|
|
|
LOG_DEBUG("Parsed SMSG_RANDOM_ROLL: roller=0x", std::hex, data.rollerGuid, std::dec,
|
|
|
|
|
|
" result=", data.result, " (", data.minRoll, "-", data.maxRoll, ")");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
network::Packet NameQueryPacket::build(uint64_t playerGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_NAME_QUERY));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(playerGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_NAME_QUERY: guid=0x", std::hex, playerGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool NameQueryResponseParser::parse(network::Packet& packet, NameQueryResponseData& data) {
|
|
|
|
|
|
// 3.3.5a: packedGuid, uint8 found
|
|
|
|
|
|
// If found==0: CString name, CString realmName, uint8 race, uint8 gender, uint8 classId
|
2026-03-11 14:27:39 -07:00
|
|
|
|
// Validation: packed GUID (1-8 bytes) + found flag (1 byte minimum)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 2) return false; // At least 1 for packed GUID + 1 for found
|
|
|
|
|
|
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.guid = UpdateObjectParser::readPackedGuid(packet);
|
2026-03-11 14:27:39 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate found flag read
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) {
|
|
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.found = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.found != 0) {
|
|
|
|
|
|
LOG_DEBUG("Name query: player not found for GUID 0x", std::hex, data.guid, std::dec);
|
|
|
|
|
|
return true; // Valid response, just not found
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:27:39 -07:00
|
|
|
|
// Validate strings: need at least 2 null terminators for empty strings
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 2) {
|
|
|
|
|
|
data.name.clear();
|
|
|
|
|
|
data.realmName.clear();
|
|
|
|
|
|
return !data.name.empty(); // Fail if name was required
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.name = packet.readString();
|
|
|
|
|
|
data.realmName = packet.readString();
|
2026-03-11 14:27:39 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate final 3 uint8 fields (race, gender, classId)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 3) {
|
|
|
|
|
|
LOG_WARNING("Name query: truncated fields after realmName, expected 3 uint8s");
|
|
|
|
|
|
data.race = 0;
|
|
|
|
|
|
data.gender = 0;
|
|
|
|
|
|
data.classId = 0;
|
|
|
|
|
|
return !data.name.empty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.race = packet.readUInt8();
|
|
|
|
|
|
data.gender = packet.readUInt8();
|
|
|
|
|
|
data.classId = packet.readUInt8();
|
|
|
|
|
|
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Name query response: ", data.name, " (race=", (int)data.race,
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
" class=", (int)data.classId, ")");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet CreatureQueryPacket::build(uint32_t entry, uint64_t guid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CREATURE_QUERY));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt32(entry);
|
|
|
|
|
|
packet.writeUInt64(guid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_CREATURE_QUERY: entry=", entry, " guid=0x", std::hex, guid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CreatureQueryResponseParser::parse(network::Packet& packet, CreatureQueryResponseData& data) {
|
2026-03-11 14:19:58 -07:00
|
|
|
|
// Validate minimum packet size: entry(4)
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_CREATURE_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.entry = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// High bit set means creature not found
|
|
|
|
|
|
if (data.entry & 0x80000000) {
|
|
|
|
|
|
data.entry &= ~0x80000000;
|
|
|
|
|
|
LOG_DEBUG("Creature query: entry ", data.entry, " not found");
|
|
|
|
|
|
data.name = "";
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4 name strings (only first is usually populated)
|
|
|
|
|
|
data.name = packet.readString();
|
|
|
|
|
|
packet.readString(); // name2
|
|
|
|
|
|
packet.readString(); // name3
|
|
|
|
|
|
packet.readString(); // name4
|
|
|
|
|
|
data.subName = packet.readString();
|
|
|
|
|
|
data.iconName = packet.readString();
|
2026-03-11 14:19:58 -07:00
|
|
|
|
|
|
|
|
|
|
// WotLK: 4 fixed fields after iconName (typeFlags, creatureType, family, rank)
|
|
|
|
|
|
// Validate minimum size for these fields: 4×4 = 16 bytes
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 16) {
|
|
|
|
|
|
LOG_WARNING("SMSG_CREATURE_QUERY_RESPONSE: truncated before typeFlags (entry=", data.entry, ")");
|
|
|
|
|
|
data.typeFlags = 0;
|
|
|
|
|
|
data.creatureType = 0;
|
|
|
|
|
|
data.family = 0;
|
|
|
|
|
|
data.rank = 0;
|
|
|
|
|
|
return true; // Have name/sub/icon; base fields are important but optional
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.typeFlags = packet.readUInt32();
|
|
|
|
|
|
data.creatureType = packet.readUInt32();
|
|
|
|
|
|
data.family = packet.readUInt32();
|
|
|
|
|
|
data.rank = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Skip remaining fields (kill credits, display IDs, modifiers, quest items, etc.)
|
|
|
|
|
|
// We've got what we need for display purposes
|
|
|
|
|
|
|
2026-02-11 22:27:02 -08:00
|
|
|
|
LOG_DEBUG("Creature query response: ", data.name, " (type=", data.creatureType,
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
" rank=", data.rank, ")");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 00:59:40 -08:00
|
|
|
|
// ---- GameObject Query ----
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GameObjectQueryPacket::build(uint32_t entry, uint64_t guid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GAMEOBJECT_QUERY));
|
2026-02-08 00:59:40 -08:00
|
|
|
|
packet.writeUInt32(entry);
|
|
|
|
|
|
packet.writeUInt64(guid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GAMEOBJECT_QUERY: entry=", entry, " guid=0x", std::hex, guid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GameObjectQueryResponseParser::parse(network::Packet& packet, GameObjectQueryResponseData& data) {
|
2026-03-11 14:11:45 -07:00
|
|
|
|
// Validate minimum packet size: entry(4)
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GAMEOBJECT_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 00:59:40 -08:00
|
|
|
|
data.entry = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// High bit set means gameobject not found
|
|
|
|
|
|
if (data.entry & 0x80000000) {
|
|
|
|
|
|
data.entry &= ~0x80000000;
|
|
|
|
|
|
LOG_DEBUG("GameObject query: entry ", data.entry, " not found");
|
|
|
|
|
|
data.name = "";
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:11:45 -07:00
|
|
|
|
// Validate minimum size for fixed fields: type(4) + displayId(4)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_ERROR("SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated before names (entry=", data.entry, ")");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 00:59:40 -08:00
|
|
|
|
data.type = packet.readUInt32(); // GameObjectType
|
2026-02-14 20:20:43 -08:00
|
|
|
|
data.displayId = packet.readUInt32();
|
2026-02-08 00:59:40 -08:00
|
|
|
|
// 4 name strings (only first is usually populated)
|
|
|
|
|
|
data.name = packet.readString();
|
|
|
|
|
|
// name2, name3, name4
|
|
|
|
|
|
packet.readString();
|
|
|
|
|
|
packet.readString();
|
|
|
|
|
|
packet.readString();
|
|
|
|
|
|
|
2026-02-14 20:20:43 -08:00
|
|
|
|
// WotLK: 3 extra strings before data[] (iconName, castBarCaption, unk1)
|
|
|
|
|
|
packet.readString(); // iconName
|
|
|
|
|
|
packet.readString(); // castBarCaption
|
|
|
|
|
|
packet.readString(); // unk1
|
|
|
|
|
|
|
|
|
|
|
|
// Read 24 type-specific data fields
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining >= 24 * 4) {
|
|
|
|
|
|
for (int i = 0; i < 24; i++) {
|
|
|
|
|
|
data.data[i] = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
data.hasData = true;
|
2026-03-11 14:11:45 -07:00
|
|
|
|
} else if (remaining > 0) {
|
|
|
|
|
|
// Partial data field; read what we can
|
|
|
|
|
|
uint32_t fieldsToRead = remaining / 4;
|
|
|
|
|
|
for (uint32_t i = 0; i < fieldsToRead && i < 24; i++) {
|
|
|
|
|
|
data.data[i] = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fieldsToRead < 24) {
|
|
|
|
|
|
LOG_WARNING("SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated in data fields (", fieldsToRead,
|
|
|
|
|
|
" of 24 read, entry=", data.entry, ")");
|
|
|
|
|
|
}
|
2026-02-14 20:20:43 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 22:27:02 -08:00
|
|
|
|
LOG_DEBUG("GameObject query response: ", data.name, " (type=", data.type, " entry=", data.entry, ")");
|
2026-02-08 00:59:40 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-20 23:31:30 -08:00
|
|
|
|
network::Packet PageTextQueryPacket::build(uint32_t pageId, uint64_t guid) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PAGE_TEXT_QUERY));
|
|
|
|
|
|
packet.writeUInt32(pageId);
|
|
|
|
|
|
packet.writeUInt64(guid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PageTextQueryResponseParser::parse(network::Packet& packet, PageTextQueryResponseData& data) {
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 4) return false;
|
|
|
|
|
|
data.pageId = packet.readUInt32();
|
|
|
|
|
|
data.text = normalizeWowTextTokens(packet.readString());
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() >= 4) {
|
|
|
|
|
|
data.nextPageId = packet.readUInt32();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.nextPageId = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return data.isValid();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 03:11:43 -08:00
|
|
|
|
// ---- Item Query ----
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ItemQueryPacket::build(uint32_t entry, uint64_t guid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ITEM_QUERY_SINGLE));
|
2026-02-06 03:11:43 -08:00
|
|
|
|
packet.writeUInt32(entry);
|
|
|
|
|
|
packet.writeUInt64(guid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_ITEM_QUERY_SINGLE: entry=", entry, " guid=0x", std::hex, guid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const char* getItemSubclassName(uint32_t itemClass, uint32_t subClass) {
|
|
|
|
|
|
if (itemClass == 2) { // Weapon
|
|
|
|
|
|
switch (subClass) {
|
|
|
|
|
|
case 0: return "Axe"; case 1: return "Axe";
|
|
|
|
|
|
case 2: return "Bow"; case 3: return "Gun";
|
|
|
|
|
|
case 4: return "Mace"; case 5: return "Mace";
|
|
|
|
|
|
case 6: return "Polearm"; case 7: return "Sword";
|
|
|
|
|
|
case 8: return "Sword"; case 9: return "Obsolete";
|
|
|
|
|
|
case 10: return "Staff"; case 13: return "Fist Weapon";
|
|
|
|
|
|
case 15: return "Dagger"; case 16: return "Thrown";
|
|
|
|
|
|
case 18: return "Crossbow"; case 19: return "Wand";
|
|
|
|
|
|
case 20: return "Fishing Pole";
|
|
|
|
|
|
default: return "Weapon";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (itemClass == 4) { // Armor
|
|
|
|
|
|
switch (subClass) {
|
|
|
|
|
|
case 0: return "Miscellaneous"; case 1: return "Cloth";
|
|
|
|
|
|
case 2: return "Leather"; case 3: return "Mail";
|
|
|
|
|
|
case 4: return "Plate"; case 6: return "Shield";
|
|
|
|
|
|
default: return "Armor";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseData& data) {
|
2026-03-11 14:08:59 -07:00
|
|
|
|
// Validate minimum packet size: entry(4) + item not found check
|
|
|
|
|
|
if (packet.getSize() < 4) {
|
|
|
|
|
|
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 03:11:43 -08:00
|
|
|
|
data.entry = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// High bit set means item not found
|
|
|
|
|
|
if (data.entry & 0x80000000) {
|
|
|
|
|
|
data.entry &= ~0x80000000;
|
|
|
|
|
|
LOG_DEBUG("Item query: entry ", data.entry, " not found");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:08:59 -07:00
|
|
|
|
// Validate minimum size for fixed fields before reading: itemClass(4) + subClass(4) + soundOverride(4)
|
|
|
|
|
|
// + 4 name strings + displayInfoId(4) + quality(4) = at least 24 bytes more
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 24) {
|
|
|
|
|
|
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before displayInfoId (entry=", data.entry, ")");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 03:11:43 -08:00
|
|
|
|
uint32_t itemClass = packet.readUInt32();
|
|
|
|
|
|
uint32_t subClass = packet.readUInt32();
|
2026-02-18 03:46:03 -08:00
|
|
|
|
data.itemClass = itemClass;
|
|
|
|
|
|
data.subClass = subClass;
|
2026-02-06 03:11:43 -08:00
|
|
|
|
packet.readUInt32(); // SoundOverrideSubclass
|
|
|
|
|
|
|
|
|
|
|
|
data.subclassName = getItemSubclassName(itemClass, subClass);
|
|
|
|
|
|
|
|
|
|
|
|
// 4 name strings
|
|
|
|
|
|
data.name = packet.readString();
|
|
|
|
|
|
packet.readString(); // name2
|
|
|
|
|
|
packet.readString(); // name3
|
|
|
|
|
|
packet.readString(); // name4
|
|
|
|
|
|
|
|
|
|
|
|
data.displayInfoId = packet.readUInt32();
|
|
|
|
|
|
data.quality = packet.readUInt32();
|
|
|
|
|
|
|
2026-02-19 17:54:32 -08:00
|
|
|
|
// WotLK 3.3.5a (TrinityCore/AzerothCore): Flags, Flags2, BuyCount, BuyPrice, SellPrice
|
|
|
|
|
|
// Some server variants omit BuyCount (4 fields instead of 5).
|
|
|
|
|
|
// Read 5 fields and validate InventoryType; if it looks implausible, rewind and try 4.
|
|
|
|
|
|
const size_t postQualityPos = packet.getReadPos();
|
2026-03-11 14:08:59 -07:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 24) {
|
|
|
|
|
|
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before flags (entry=", data.entry, ")");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
packet.readUInt32(); // Flags
|
|
|
|
|
|
packet.readUInt32(); // Flags2
|
2026-02-19 17:54:32 -08:00
|
|
|
|
packet.readUInt32(); // BuyCount
|
2026-02-06 03:11:43 -08:00
|
|
|
|
packet.readUInt32(); // BuyPrice
|
2026-02-06 13:47:03 -08:00
|
|
|
|
data.sellPrice = packet.readUInt32(); // SellPrice
|
2026-02-06 03:11:43 -08:00
|
|
|
|
data.inventoryType = packet.readUInt32();
|
|
|
|
|
|
|
2026-02-19 17:54:32 -08:00
|
|
|
|
if (data.inventoryType > 28) {
|
|
|
|
|
|
// inventoryType out of range — BuyCount probably not present; rewind and try 4 fields
|
|
|
|
|
|
packet.setReadPos(postQualityPos);
|
|
|
|
|
|
packet.readUInt32(); // Flags
|
|
|
|
|
|
packet.readUInt32(); // Flags2
|
|
|
|
|
|
packet.readUInt32(); // BuyPrice
|
|
|
|
|
|
data.sellPrice = packet.readUInt32(); // SellPrice
|
|
|
|
|
|
data.inventoryType = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:08:59 -07:00
|
|
|
|
// Validate minimum size for remaining fixed fields before inventoryType through containerSlots: 13×4 = 52 bytes
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 52) {
|
|
|
|
|
|
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before statsCount (entry=", data.entry, ")");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
packet.readUInt32(); // AllowableClass
|
|
|
|
|
|
packet.readUInt32(); // AllowableRace
|
2026-03-10 16:26:20 -07:00
|
|
|
|
data.itemLevel = packet.readUInt32();
|
|
|
|
|
|
data.requiredLevel = packet.readUInt32();
|
2026-02-06 03:11:43 -08:00
|
|
|
|
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<int32_t>(packet.readUInt32()); // Stackable
|
|
|
|
|
|
data.containerSlots = packet.readUInt32();
|
|
|
|
|
|
|
2026-03-11 14:08:59 -07:00
|
|
|
|
// Read statsCount with bounds validation
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 4) {
|
|
|
|
|
|
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated at statsCount (entry=", data.entry, ")");
|
|
|
|
|
|
return true; // Have enough for core fields; stats are optional
|
|
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
uint32_t statsCount = packet.readUInt32();
|
2026-03-11 14:08:59 -07:00
|
|
|
|
|
|
|
|
|
|
// Cap statsCount to prevent excessive iteration
|
|
|
|
|
|
constexpr uint32_t kMaxItemStats = 10;
|
|
|
|
|
|
if (statsCount > kMaxItemStats) {
|
|
|
|
|
|
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: statsCount=", statsCount, " exceeds max ",
|
|
|
|
|
|
kMaxItemStats, " (entry=", data.entry, "), capping");
|
|
|
|
|
|
statsCount = kMaxItemStats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 00:59:07 -08:00
|
|
|
|
// Server sends exactly statsCount stat pairs (not always 10).
|
|
|
|
|
|
uint32_t statsToRead = std::min(statsCount, 10u);
|
|
|
|
|
|
for (uint32_t i = 0; i < statsToRead; i++) {
|
2026-03-11 14:08:59 -07:00
|
|
|
|
// Each stat is 2 uint32s (type + value) = 8 bytes
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: stat ", i, " truncated (entry=", data.entry, ")");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
uint32_t statType = packet.readUInt32();
|
|
|
|
|
|
int32_t statValue = static_cast<int32_t>(packet.readUInt32());
|
2026-02-26 00:59:07 -08:00
|
|
|
|
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;
|
feat: capture and display all item stat types in tooltips
Previously only the 5 primary stats (Str/Agi/Sta/Int/Spi) were stored,
discarding hit rating, crit, haste, attack power, spell power, resilience,
expertise, armor penetration, MP5, and many others.
Changes:
- Add ItemDef::ExtraStat and ItemQueryResponseData::ExtraStat arrays
- All three expansion parsers (WotLK/TBC/Classic) now capture non-primary
stat type/value pairs into extraStats instead of silently dropping them
- All 5 rebuildOnlineInventory paths propagate extraStats to ItemDef
- Tooltip now renders each extra stat on its own line with a name lookup
covering all common WotLK stat types (hit, crit, haste, AP, SP, etc.)
- Also fix Classic/TBC bag-content and bank-bag paths that were missing
bindType, description propagation from previous commits
2026-03-10 17:00:24 -07:00
|
|
|
|
default:
|
|
|
|
|
|
if (statValue != 0)
|
|
|
|
|
|
data.extraStats.push_back({statType, statValue});
|
|
|
|
|
|
break;
|
2026-02-06 03:11:43 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:08:59 -07:00
|
|
|
|
// ScalingStatDistribution and ScalingStatValue
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_WARNING("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before scaling stats (entry=", data.entry, ")");
|
|
|
|
|
|
return true; // Have core fields; scaling is optional
|
|
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
packet.readUInt32(); // ScalingStatDistribution
|
|
|
|
|
|
packet.readUInt32(); // ScalingStatValue
|
|
|
|
|
|
|
2026-02-26 00:59:07 -08:00
|
|
|
|
// WotLK 3.3.5a: MAX_ITEM_PROTO_DAMAGES = 2
|
|
|
|
|
|
bool haveWeaponDamage = false;
|
|
|
|
|
|
for (int i = 0; i < 2; 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);
|
2026-02-18 03:46:03 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-26 00:59:07 -08:00
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
|
2026-02-26 00:59:07 -08:00
|
|
|
|
data.armor = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
packet.readUInt32(); // HolyRes
|
|
|
|
|
|
packet.readUInt32(); // FireRes
|
|
|
|
|
|
packet.readUInt32(); // NatureRes
|
|
|
|
|
|
packet.readUInt32(); // FrostRes
|
|
|
|
|
|
packet.readUInt32(); // ShadowRes
|
|
|
|
|
|
packet.readUInt32(); // ArcaneRes
|
|
|
|
|
|
data.delayMs = packet.readUInt32();
|
|
|
|
|
|
packet.readUInt32(); // AmmoType
|
|
|
|
|
|
packet.readFloat(); // RangedModRange
|
|
|
|
|
|
|
|
|
|
|
|
// 5 item spells: SpellId, SpellTrigger, SpellCharges, SpellCooldown, SpellCategory, SpellCategoryCooldown
|
|
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
|
|
|
|
if (packet.getReadPos() + 24 > packet.getSize()) break;
|
|
|
|
|
|
data.spells[i].spellId = packet.readUInt32();
|
|
|
|
|
|
data.spells[i].spellTrigger = packet.readUInt32();
|
|
|
|
|
|
packet.readUInt32(); // SpellCharges
|
|
|
|
|
|
packet.readUInt32(); // SpellCooldown
|
|
|
|
|
|
packet.readUInt32(); // SpellCategory
|
|
|
|
|
|
packet.readUInt32(); // SpellCategoryCooldown
|
|
|
|
|
|
}
|
2026-02-06 03:11:43 -08:00
|
|
|
|
|
2026-03-10 16:47:55 -07:00
|
|
|
|
// Bonding type (0=none, 1=BoP, 2=BoE, 3=BoU, 4=BoQ)
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize())
|
|
|
|
|
|
data.bindType = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Flavor/lore text (Description cstring)
|
|
|
|
|
|
if (packet.getReadPos() < packet.getSize())
|
|
|
|
|
|
data.description = packet.readString();
|
|
|
|
|
|
|
2026-03-10 17:05:04 -07:00
|
|
|
|
// Post-description fields: PageText, LanguageID, PageMaterial, StartQuest
|
|
|
|
|
|
if (packet.getReadPos() + 16 <= packet.getSize()) {
|
|
|
|
|
|
packet.readUInt32(); // PageText
|
|
|
|
|
|
packet.readUInt32(); // LanguageID
|
|
|
|
|
|
packet.readUInt32(); // PageMaterial
|
|
|
|
|
|
data.startQuestId = packet.readUInt32(); // StartQuest
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 12:15:08 -07:00
|
|
|
|
// WotLK 3.3.5a: additional fields after StartQuest (read up to socket data)
|
|
|
|
|
|
// LockID(4), Material(4), Sheath(4), RandomProperty(4), RandomSuffix(4),
|
|
|
|
|
|
// Block(4), ItemSet(4), MaxDurability(4), Area(4), Map(4), BagFamily(4),
|
|
|
|
|
|
// TotemCategory(4) = 48 bytes before sockets
|
|
|
|
|
|
constexpr size_t kPreSocketSkip = 48;
|
|
|
|
|
|
if (packet.getReadPos() + kPreSocketSkip + 28 <= packet.getSize()) {
|
2026-03-12 12:35:56 -07:00
|
|
|
|
// LockID(0), Material(1), Sheath(2), RandomProperty(3), RandomSuffix(4), Block(5)
|
|
|
|
|
|
for (size_t i = 0; i < 6; ++i) packet.readUInt32();
|
|
|
|
|
|
data.itemSetId = packet.readUInt32(); // ItemSet(6)
|
|
|
|
|
|
// MaxDurability(7), Area(8), Map(9), BagFamily(10), TotemCategory(11)
|
|
|
|
|
|
for (size_t i = 0; i < 5; ++i) packet.readUInt32();
|
2026-03-12 12:15:08 -07:00
|
|
|
|
// 3 socket slots: socketColor (4 bytes each)
|
|
|
|
|
|
data.socketColor[0] = packet.readUInt32();
|
|
|
|
|
|
data.socketColor[1] = packet.readUInt32();
|
|
|
|
|
|
data.socketColor[2] = packet.readUInt32();
|
|
|
|
|
|
// 3 socket content (gem enchantment IDs — skip, not currently displayed)
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
// socketBonus (enchantmentId)
|
|
|
|
|
|
data.socketBonus = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 03:11:43 -08:00
|
|
|
|
data.valid = !data.name.empty();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Creature Movement
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool MonsterMoveParser::parse(network::Packet& packet, MonsterMoveData& data) {
|
|
|
|
|
|
// PackedGuid
|
|
|
|
|
|
data.guid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
if (data.guid == 0) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// uint8 unk (toggle for MOVEMENTFLAG2_UNK7)
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) return false;
|
|
|
|
|
|
packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
// Current position (server coords: float x, y, z)
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return false;
|
|
|
|
|
|
data.x = packet.readFloat();
|
|
|
|
|
|
data.y = packet.readFloat();
|
|
|
|
|
|
data.z = packet.readFloat();
|
|
|
|
|
|
|
|
|
|
|
|
// uint32 splineId
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// uint8 moveType
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) return false;
|
|
|
|
|
|
data.moveType = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.moveType == 1) {
|
|
|
|
|
|
// Stop - no more required data
|
|
|
|
|
|
data.destX = data.x;
|
|
|
|
|
|
data.destY = data.y;
|
|
|
|
|
|
data.destZ = data.z;
|
|
|
|
|
|
data.hasDest = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read facing data based on move type
|
|
|
|
|
|
if (data.moveType == 2) {
|
|
|
|
|
|
// FacingSpot: float x, y, z
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return false;
|
|
|
|
|
|
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
|
|
|
|
|
} else if (data.moveType == 3) {
|
|
|
|
|
|
// FacingTarget: uint64 guid
|
|
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) return false;
|
|
|
|
|
|
data.facingTarget = packet.readUInt64();
|
|
|
|
|
|
} else if (data.moveType == 4) {
|
|
|
|
|
|
// FacingAngle: float angle
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
data.facingAngle = packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// uint32 splineFlags
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
data.splineFlags = packet.readUInt32();
|
|
|
|
|
|
|
2026-02-17 18:16:53 -08:00
|
|
|
|
// WotLK 3.3.5a SplineFlags (from TrinityCore/MaNGOS MoveSplineFlag.h):
|
|
|
|
|
|
// Animation = 0x00400000
|
|
|
|
|
|
// Parabolic = 0x00000800
|
|
|
|
|
|
// Catmullrom = 0x00080000 \ either means uncompressed (absolute) waypoints
|
|
|
|
|
|
// Flying = 0x00002000 /
|
|
|
|
|
|
|
|
|
|
|
|
// [if Animation] uint8 animationType + int32 effectStartTime (5 bytes)
|
|
|
|
|
|
if (data.splineFlags & 0x00400000) {
|
|
|
|
|
|
if (packet.getReadPos() + 5 > packet.getSize()) return false;
|
|
|
|
|
|
packet.readUInt8(); // animationType
|
|
|
|
|
|
packet.readUInt32(); // effectStartTime (int32, read as uint32 same size)
|
2026-02-06 13:47:03 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// uint32 duration
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
data.duration = packet.readUInt32();
|
|
|
|
|
|
|
2026-02-17 18:16:53 -08:00
|
|
|
|
// [if Parabolic] float verticalAcceleration + int32 effectStartTime (8 bytes)
|
|
|
|
|
|
if (data.splineFlags & 0x00000800) {
|
2026-02-06 13:47:03 -08:00
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) return false;
|
2026-02-17 18:16:53 -08:00
|
|
|
|
packet.readFloat(); // verticalAcceleration
|
2026-02-06 13:47:03 -08:00
|
|
|
|
packet.readUInt32(); // effectStartTime
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// uint32 pointCount
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
uint32_t pointCount = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
if (pointCount == 0) return true;
|
|
|
|
|
|
|
2026-03-11 14:13:09 -07:00
|
|
|
|
// Cap pointCount to prevent excessive iteration from malformed packets
|
|
|
|
|
|
constexpr uint32_t kMaxSplinePoints = 1000;
|
|
|
|
|
|
if (pointCount > kMaxSplinePoints) {
|
|
|
|
|
|
LOG_WARNING("SMSG_MONSTER_MOVE: pointCount=", pointCount, " exceeds max ", kMaxSplinePoints,
|
|
|
|
|
|
" (guid=0x", std::hex, data.guid, std::dec, "), capping");
|
|
|
|
|
|
pointCount = kMaxSplinePoints;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-17 18:16:53 -08:00
|
|
|
|
// Catmullrom or Flying → all waypoints stored as absolute float3 (uncompressed).
|
|
|
|
|
|
// Otherwise: first float3 is final destination, remaining are packed deltas.
|
|
|
|
|
|
bool uncompressed = (data.splineFlags & (0x00080000 | 0x00002000)) != 0;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
|
|
|
|
|
|
if (uncompressed) {
|
|
|
|
|
|
// Read last point as destination
|
|
|
|
|
|
// Skip to last point: each point is 12 bytes
|
|
|
|
|
|
for (uint32_t i = 0; i < pointCount - 1; i++) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
|
|
|
|
|
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
|
|
|
|
|
data.destX = packet.readFloat();
|
|
|
|
|
|
data.destY = packet.readFloat();
|
|
|
|
|
|
data.destZ = packet.readFloat();
|
|
|
|
|
|
data.hasDest = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Compressed: first 3 floats are the destination (final point)
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
|
|
|
|
|
data.destX = packet.readFloat();
|
|
|
|
|
|
data.destY = packet.readFloat();
|
|
|
|
|
|
data.destZ = packet.readFloat();
|
|
|
|
|
|
data.hasDest = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("MonsterMove: guid=0x", std::hex, data.guid, std::dec,
|
|
|
|
|
|
" type=", (int)data.moveType, " dur=", data.duration, "ms",
|
|
|
|
|
|
" dest=(", data.destX, ",", data.destY, ",", data.destZ, ")");
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-18 03:13:17 -08:00
|
|
|
|
bool MonsterMoveParser::parseVanilla(network::Packet& packet, MonsterMoveData& data) {
|
|
|
|
|
|
data.guid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
if (data.guid == 0) return false;
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return false;
|
|
|
|
|
|
data.x = packet.readFloat();
|
|
|
|
|
|
data.y = packet.readFloat();
|
|
|
|
|
|
data.z = packet.readFloat();
|
|
|
|
|
|
|
|
|
|
|
|
// Turtle WoW movement payload uses a spline-style layout after XYZ:
|
|
|
|
|
|
// uint32 splineIdOrTick
|
|
|
|
|
|
// uint8 moveType
|
|
|
|
|
|
// [if moveType 2/3/4] facing payload
|
|
|
|
|
|
// uint32 splineFlags
|
|
|
|
|
|
// [if Animation] uint8 + uint32
|
|
|
|
|
|
// uint32 duration
|
|
|
|
|
|
// [if Parabolic] float + uint32
|
|
|
|
|
|
// uint32 pointCount
|
|
|
|
|
|
// float[3] dest
|
|
|
|
|
|
// uint32 packedPoints[pointCount-1]
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
/*uint32_t splineIdOrTick =*/ packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) return false;
|
|
|
|
|
|
data.moveType = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.moveType == 1) {
|
|
|
|
|
|
data.destX = data.x;
|
|
|
|
|
|
data.destY = data.y;
|
|
|
|
|
|
data.destZ = data.z;
|
|
|
|
|
|
data.hasDest = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (data.moveType == 2) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return false;
|
|
|
|
|
|
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
|
|
|
|
|
} else if (data.moveType == 3) {
|
|
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) return false;
|
|
|
|
|
|
data.facingTarget = packet.readUInt64();
|
|
|
|
|
|
} else if (data.moveType == 4) {
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
data.facingAngle = packet.readFloat();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
data.splineFlags = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Animation flag (same bit as WotLK MoveSplineFlag::Animation)
|
|
|
|
|
|
if (data.splineFlags & 0x00400000) {
|
|
|
|
|
|
if (packet.getReadPos() + 5 > packet.getSize()) return false;
|
|
|
|
|
|
packet.readUInt8();
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
data.duration = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Parabolic flag (same bit as WotLK MoveSplineFlag::Parabolic)
|
|
|
|
|
|
if (data.splineFlags & 0x00000800) {
|
|
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) return false;
|
|
|
|
|
|
packet.readFloat();
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return false;
|
|
|
|
|
|
uint32_t pointCount = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
if (pointCount == 0) return true;
|
2026-03-11 14:13:09 -07:00
|
|
|
|
|
|
|
|
|
|
// Cap pointCount to prevent excessive iteration from malformed packets
|
|
|
|
|
|
constexpr uint32_t kMaxSplinePoints = 1000;
|
|
|
|
|
|
if (pointCount > kMaxSplinePoints) {
|
|
|
|
|
|
LOG_WARNING("SMSG_MONSTER_MOVE(Vanilla): pointCount=", pointCount, " exceeds max ", kMaxSplinePoints,
|
|
|
|
|
|
" (guid=0x", std::hex, data.guid, std::dec, "), capping");
|
|
|
|
|
|
pointCount = kMaxSplinePoints;
|
|
|
|
|
|
}
|
2026-02-18 03:13:17 -08:00
|
|
|
|
|
|
|
|
|
|
// First float[3] is destination.
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
|
|
|
|
|
data.destX = packet.readFloat();
|
|
|
|
|
|
data.destY = packet.readFloat();
|
|
|
|
|
|
data.destZ = packet.readFloat();
|
|
|
|
|
|
data.hasDest = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Remaining waypoints are packed as uint32 deltas.
|
|
|
|
|
|
if (pointCount > 1) {
|
|
|
|
|
|
size_t skipBytes = static_cast<size_t>(pointCount - 1) * 4;
|
|
|
|
|
|
size_t newPos = packet.getReadPos() + skipBytes;
|
|
|
|
|
|
if (newPos <= packet.getSize()) {
|
|
|
|
|
|
packet.setReadPos(newPos);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("MonsterMove(turtle): guid=0x", std::hex, data.guid, std::dec,
|
|
|
|
|
|
" type=", (int)data.moveType, " dur=", data.duration, "ms",
|
|
|
|
|
|
" dest=(", data.destX, ",", data.destY, ",", data.destZ, ")");
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 2: Combat Core
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool AttackStartParser::parse(network::Packet& packet, AttackStartData& data) {
|
|
|
|
|
|
if (packet.getSize() < 16) return false;
|
|
|
|
|
|
data.attackerGuid = packet.readUInt64();
|
|
|
|
|
|
data.victimGuid = packet.readUInt64();
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Attack started: 0x", std::hex, data.attackerGuid,
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
" -> 0x", data.victimGuid, std::dec);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AttackStopParser::parse(network::Packet& packet, AttackStopData& data) {
|
|
|
|
|
|
data.attackerGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
data.victimGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
if (packet.getReadPos() < packet.getSize()) {
|
|
|
|
|
|
data.unknown = packet.readUInt32();
|
|
|
|
|
|
}
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Attack stopped: 0x", std::hex, data.attackerGuid, std::dec);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpdateData& data) {
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Upfront validation: hitInfo(4) + packed GUIDs(1-8 each) + totalDamage(4) + subDamageCount(1) = 13 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 13) return false;
|
|
|
|
|
|
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.hitInfo = packet.readUInt32();
|
|
|
|
|
|
data.attackerGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
2026-03-11 14:32:03 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate totalDamage + subDamageCount can be read (5 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 5) {
|
|
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.totalDamage = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
data.subDamageCount = packet.readUInt8();
|
|
|
|
|
|
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Cap subDamageCount to prevent OOM (each entry is 20 bytes: 4+4+4+4+4)
|
|
|
|
|
|
if (data.subDamageCount > 64) {
|
|
|
|
|
|
LOG_WARNING("AttackerStateUpdate: subDamageCount capped (requested=", (int)data.subDamageCount, ")");
|
|
|
|
|
|
data.subDamageCount = 64;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.subDamages.reserve(data.subDamageCount);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
for (uint8_t i = 0; i < data.subDamageCount; ++i) {
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Each sub-damage entry needs 20 bytes: schoolMask(4) + damage(4) + intDamage(4) + absorbed(4) + resisted(4)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 20) {
|
|
|
|
|
|
LOG_WARNING("AttackerStateUpdate: truncated subDamage at index ", (int)i, "/", (int)data.subDamageCount);
|
|
|
|
|
|
data.subDamageCount = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
SubDamage sub;
|
|
|
|
|
|
sub.schoolMask = packet.readUInt32();
|
|
|
|
|
|
sub.damage = packet.readFloat();
|
|
|
|
|
|
sub.intDamage = packet.readUInt32();
|
|
|
|
|
|
sub.absorbed = packet.readUInt32();
|
|
|
|
|
|
sub.resisted = packet.readUInt32();
|
|
|
|
|
|
data.subDamages.push_back(sub);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Validate victimState + overkill fields (8 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_WARNING("AttackerStateUpdate: truncated victimState/overkill");
|
|
|
|
|
|
data.victimState = 0;
|
|
|
|
|
|
data.overkill = 0;
|
|
|
|
|
|
return !data.subDamages.empty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.victimState = packet.readUInt32();
|
|
|
|
|
|
data.overkill = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Read blocked amount (optional, 4 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() >= 4) {
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.blocked = packet.readUInt32();
|
2026-03-11 14:32:03 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
data.blocked = 0;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 04:25:45 -07:00
|
|
|
|
LOG_DEBUG("Melee hit: ", data.totalDamage, " damage",
|
|
|
|
|
|
data.isCrit() ? " (CRIT)" : "",
|
|
|
|
|
|
data.isMiss() ? " (MISS)" : "");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& data) {
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Upfront validation: packed GUIDs(1-8 each) + spellId(4) + damage(4) + overkill(4) + schoolMask(1) + absorbed(4) + resisted(4) = 30 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 30) return false;
|
|
|
|
|
|
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
data.attackerGuid = UpdateObjectParser::readPackedGuid(packet);
|
2026-03-11 14:32:03 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate core fields (spellId + damage + overkill + schoolMask + absorbed + resisted = 21 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 21) {
|
|
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.spellId = packet.readUInt32();
|
|
|
|
|
|
data.damage = packet.readUInt32();
|
|
|
|
|
|
data.overkill = packet.readUInt32();
|
|
|
|
|
|
data.schoolMask = packet.readUInt8();
|
|
|
|
|
|
data.absorbed = packet.readUInt32();
|
|
|
|
|
|
data.resisted = packet.readUInt32();
|
|
|
|
|
|
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Skip remaining fields (periodicLog + unused + blocked + flags = 10 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 10) {
|
|
|
|
|
|
LOG_WARNING("SpellDamageLog: truncated trailing fields");
|
|
|
|
|
|
data.isCrit = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint8_t periodicLog = packet.readUInt8();
|
|
|
|
|
|
(void)periodicLog;
|
|
|
|
|
|
packet.readUInt8(); // unused
|
|
|
|
|
|
packet.readUInt32(); // blocked
|
|
|
|
|
|
uint32_t flags = packet.readUInt32();
|
|
|
|
|
|
(void)flags;
|
|
|
|
|
|
// Check crit flag
|
|
|
|
|
|
data.isCrit = (flags & 0x02) != 0;
|
|
|
|
|
|
|
2026-03-10 04:25:45 -07:00
|
|
|
|
LOG_DEBUG("Spell damage: spellId=", data.spellId, " dmg=", data.damage,
|
|
|
|
|
|
data.isCrit ? " CRIT" : "");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SpellHealLogParser::parse(network::Packet& packet, SpellHealLogData& data) {
|
2026-03-11 14:32:03 -07:00
|
|
|
|
// Upfront validation: packed GUIDs(1-8 each) + spellId(4) + heal(4) + overheal(4) + absorbed(4) + critFlag(1) = 21 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 21) return false;
|
|
|
|
|
|
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
2026-03-11 14:32:03 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate remaining fields (spellId + heal + overheal + absorbed + critFlag = 17 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 17) {
|
|
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.spellId = packet.readUInt32();
|
|
|
|
|
|
data.heal = packet.readUInt32();
|
|
|
|
|
|
data.overheal = packet.readUInt32();
|
|
|
|
|
|
data.absorbed = packet.readUInt32();
|
|
|
|
|
|
uint8_t critFlag = packet.readUInt8();
|
|
|
|
|
|
data.isCrit = (critFlag != 0);
|
|
|
|
|
|
|
2026-03-10 04:25:45 -07:00
|
|
|
|
LOG_DEBUG("Spell heal: spellId=", data.spellId, " heal=", data.heal,
|
|
|
|
|
|
data.isCrit ? " CRIT" : "");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 12:07:58 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// XP Gain
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool XpGainParser::parse(network::Packet& packet, XpGainData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: victimGuid(8) + totalXp(4) + type(1)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 13) {
|
|
|
|
|
|
LOG_WARNING("SMSG_LOG_XPGAIN: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 12:07:58 -08:00
|
|
|
|
data.victimGuid = packet.readUInt64();
|
|
|
|
|
|
data.totalXp = packet.readUInt32();
|
|
|
|
|
|
data.type = packet.readUInt8();
|
|
|
|
|
|
if (data.type == 0) {
|
2026-02-06 15:41:29 -08:00
|
|
|
|
// Kill XP: float groupRate (1.0 = solo) + uint8 RAF flag
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate before reading conditional fields
|
|
|
|
|
|
if (packet.getReadPos() + 5 <= packet.getSize()) {
|
|
|
|
|
|
float groupRate = packet.readFloat();
|
|
|
|
|
|
packet.readUInt8(); // RAF bonus flag
|
|
|
|
|
|
// Group bonus = total - (total / rate); only if grouped (rate > 1)
|
|
|
|
|
|
if (groupRate > 1.0f) {
|
|
|
|
|
|
data.groupBonus = data.totalXp - static_cast<uint32_t>(data.totalXp / groupRate);
|
|
|
|
|
|
}
|
2026-02-06 15:41:29 -08:00
|
|
|
|
}
|
2026-02-05 12:07:58 -08:00
|
|
|
|
}
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("XP gain: ", data.totalXp, " xp (type=", static_cast<int>(data.type), ")");
|
2026-02-05 12:07:58 -08:00
|
|
|
|
return data.totalXp > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 3: Spells, Action Bar, Auras
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
2026-03-11 04:38:30 -07:00
|
|
|
|
bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data,
|
|
|
|
|
|
bool vanillaFormat) {
|
2026-03-11 14:10:20 -07:00
|
|
|
|
// Validate minimum packet size for header: talentSpec(1) + spellCount(2)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 3) {
|
|
|
|
|
|
LOG_ERROR("SMSG_INITIAL_SPELLS: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.talentSpec = packet.readUInt8();
|
|
|
|
|
|
uint16_t spellCount = packet.readUInt16();
|
|
|
|
|
|
|
2026-03-11 14:10:20 -07:00
|
|
|
|
// Cap spell count to prevent excessive iteration
|
|
|
|
|
|
constexpr uint16_t kMaxSpells = 256;
|
|
|
|
|
|
if (spellCount > kMaxSpells) {
|
|
|
|
|
|
LOG_WARNING("SMSG_INITIAL_SPELLS: spellCount=", spellCount, " exceeds max ", kMaxSpells,
|
|
|
|
|
|
", capping");
|
|
|
|
|
|
spellCount = kMaxSpells;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 04:38:30 -07:00
|
|
|
|
LOG_DEBUG("SMSG_INITIAL_SPELLS: spellCount=", spellCount,
|
|
|
|
|
|
vanillaFormat ? " (vanilla uint16 format)" : " (TBC/WotLK uint32 format)");
|
2026-02-10 01:24:37 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.spellIds.reserve(spellCount);
|
|
|
|
|
|
for (uint16_t i = 0; i < spellCount; ++i) {
|
2026-03-11 14:10:20 -07:00
|
|
|
|
// Vanilla spell: spellId(2) + slot(2) = 4 bytes
|
|
|
|
|
|
// TBC/WotLK spell: spellId(4) + unknown(2) = 6 bytes
|
|
|
|
|
|
size_t spellEntrySize = vanillaFormat ? 4 : 6;
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < spellEntrySize) {
|
|
|
|
|
|
LOG_WARNING("SMSG_INITIAL_SPELLS: spell ", i, " truncated (", spellCount, " expected)");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 18:59:09 -08:00
|
|
|
|
uint32_t spellId;
|
|
|
|
|
|
if (vanillaFormat) {
|
|
|
|
|
|
spellId = packet.readUInt16();
|
|
|
|
|
|
packet.readUInt16(); // slot
|
|
|
|
|
|
} else {
|
|
|
|
|
|
spellId = packet.readUInt32();
|
|
|
|
|
|
packet.readUInt16(); // unknown (always 0)
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
if (spellId != 0) {
|
|
|
|
|
|
data.spellIds.push_back(spellId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:10:20 -07:00
|
|
|
|
// Validate minimum packet size for cooldownCount (2 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 2) {
|
|
|
|
|
|
LOG_WARNING("SMSG_INITIAL_SPELLS: truncated before cooldownCount (parsed ", data.spellIds.size(),
|
|
|
|
|
|
" spells)");
|
|
|
|
|
|
return true; // Have spells; cooldowns are optional
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint16_t cooldownCount = packet.readUInt16();
|
2026-03-11 14:10:20 -07:00
|
|
|
|
|
|
|
|
|
|
// Cap cooldown count to prevent excessive iteration
|
|
|
|
|
|
constexpr uint16_t kMaxCooldowns = 256;
|
|
|
|
|
|
if (cooldownCount > kMaxCooldowns) {
|
|
|
|
|
|
LOG_WARNING("SMSG_INITIAL_SPELLS: cooldownCount=", cooldownCount, " exceeds max ", kMaxCooldowns,
|
|
|
|
|
|
", capping");
|
|
|
|
|
|
cooldownCount = kMaxCooldowns;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.cooldowns.reserve(cooldownCount);
|
|
|
|
|
|
for (uint16_t i = 0; i < cooldownCount; ++i) {
|
2026-03-11 14:10:20 -07:00
|
|
|
|
// Vanilla cooldown: spellId(2) + itemId(2) + categoryId(2) + cooldownMs(4) + categoryCooldownMs(4) = 14 bytes
|
|
|
|
|
|
// TBC/WotLK cooldown: spellId(4) + itemId(2) + categoryId(2) + cooldownMs(4) + categoryCooldownMs(4) = 16 bytes
|
|
|
|
|
|
size_t cooldownEntrySize = vanillaFormat ? 14 : 16;
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < cooldownEntrySize) {
|
|
|
|
|
|
LOG_WARNING("SMSG_INITIAL_SPELLS: cooldown ", i, " truncated (", cooldownCount, " expected)");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
SpellCooldownEntry entry;
|
2026-02-13 18:59:09 -08:00
|
|
|
|
if (vanillaFormat) {
|
|
|
|
|
|
entry.spellId = packet.readUInt16();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
entry.spellId = packet.readUInt32();
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
entry.itemId = packet.readUInt16();
|
|
|
|
|
|
entry.categoryId = packet.readUInt16();
|
|
|
|
|
|
entry.cooldownMs = packet.readUInt32();
|
|
|
|
|
|
entry.categoryCooldownMs = packet.readUInt32();
|
|
|
|
|
|
data.cooldowns.push_back(entry);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 01:24:37 -08:00
|
|
|
|
LOG_INFO("Initial spells parsed: ", data.spellIds.size(), " spells, ",
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.cooldowns.size(), " cooldowns");
|
2026-02-10 01:24:37 -08:00
|
|
|
|
|
|
|
|
|
|
if (!data.spellIds.empty()) {
|
|
|
|
|
|
std::string first10;
|
|
|
|
|
|
for (size_t i = 0; i < std::min(size_t(10), data.spellIds.size()); ++i) {
|
|
|
|
|
|
if (!first10.empty()) first10 += ", ";
|
|
|
|
|
|
first10 += std::to_string(data.spellIds[i]);
|
|
|
|
|
|
}
|
2026-03-10 04:52:22 -07:00
|
|
|
|
LOG_DEBUG("Initial spell IDs (first 10): ", first10);
|
2026-02-10 01:24:37 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet CastSpellPacket::build(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CAST_SPELL));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt8(castCount);
|
|
|
|
|
|
packet.writeUInt32(spellId);
|
|
|
|
|
|
packet.writeUInt8(0x00); // castFlags = 0 for normal cast
|
|
|
|
|
|
|
|
|
|
|
|
// SpellCastTargets
|
|
|
|
|
|
if (targetGuid != 0) {
|
|
|
|
|
|
packet.writeUInt32(0x02); // TARGET_FLAG_UNIT
|
|
|
|
|
|
|
|
|
|
|
|
// Write packed GUID
|
|
|
|
|
|
uint8_t mask = 0;
|
|
|
|
|
|
uint8_t bytes[8];
|
|
|
|
|
|
int byteCount = 0;
|
|
|
|
|
|
uint64_t g = targetGuid;
|
|
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
|
|
uint8_t b = g & 0xFF;
|
|
|
|
|
|
if (b != 0) {
|
|
|
|
|
|
mask |= (1 << i);
|
|
|
|
|
|
bytes[byteCount++] = b;
|
|
|
|
|
|
}
|
|
|
|
|
|
g >>= 8;
|
|
|
|
|
|
}
|
|
|
|
|
|
packet.writeUInt8(mask);
|
|
|
|
|
|
for (int i = 0; i < byteCount; ++i) {
|
|
|
|
|
|
packet.writeUInt8(bytes[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
packet.writeUInt32(0x00); // TARGET_FLAG_SELF
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_CAST_SPELL: spell=", spellId, " target=0x",
|
|
|
|
|
|
std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet CancelAuraPacket::build(uint32_t spellId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_AURA));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt32(spellId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 22:53:09 -07:00
|
|
|
|
network::Packet PetActionPacket::build(uint64_t petGuid, uint32_t action, uint64_t targetGuid) {
|
|
|
|
|
|
// CMSG_PET_ACTION: petGuid(8) + action(4) + targetGuid(8)
|
2026-02-26 10:41:29 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_PET_ACTION));
|
|
|
|
|
|
packet.writeUInt64(petGuid);
|
|
|
|
|
|
packet.writeUInt32(action);
|
2026-03-09 22:53:09 -07:00
|
|
|
|
packet.writeUInt64(targetGuid);
|
2026-02-26 10:41:29 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool CastFailedParser::parse(network::Packet& packet, CastFailedData& data) {
|
2026-03-11 14:35:29 -07:00
|
|
|
|
// WotLK format: castCount(1) + spellId(4) + result(1) = 6 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 6) return false;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.castCount = packet.readUInt8();
|
|
|
|
|
|
data.spellId = packet.readUInt32();
|
|
|
|
|
|
data.result = packet.readUInt8();
|
|
|
|
|
|
LOG_INFO("Cast failed: spell=", data.spellId, " result=", (int)data.result);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
2026-03-11 14:28:41 -07:00
|
|
|
|
// Upfront validation: packed GUID(1-8) + packed GUID(1-8) + castCount(1) + spellId(4) + castFlags(4) + castTime(4) = 22 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 22) return false;
|
|
|
|
|
|
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
|
2026-03-11 14:28:41 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate remaining fixed fields (castCount + spellId + castFlags + castTime = 9 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 9) {
|
|
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.castCount = packet.readUInt8();
|
|
|
|
|
|
data.spellId = packet.readUInt32();
|
|
|
|
|
|
data.castFlags = packet.readUInt32();
|
|
|
|
|
|
data.castTime = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
// Read target flags and target (simplified)
|
2026-03-11 14:28:41 -07:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() >= 4) {
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint32_t targetFlags = packet.readUInt32();
|
2026-03-11 14:28:41 -07:00
|
|
|
|
if ((targetFlags & 0x02) && packet.getSize() - packet.getReadPos() >= 1) { // TARGET_FLAG_UNIT, validate packed GUID read
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
LOG_DEBUG("Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
2026-03-11 14:28:41 -07:00
|
|
|
|
// Upfront validation: packed GUID(1-8) + packed GUID(1-8) + castCount(1) + spellId(4) + castFlags(4) + timestamp(4) + hitCount(1) + missCount(1) = 24 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 24) return false;
|
|
|
|
|
|
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
|
2026-03-11 14:28:41 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate remaining fixed fields up to hitCount/missCount
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 14) { // castCount(1) + spellId(4) + castFlags(4) + timestamp(4) + hitCount(1)
|
|
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.castCount = packet.readUInt8();
|
|
|
|
|
|
data.spellId = packet.readUInt32();
|
|
|
|
|
|
data.castFlags = packet.readUInt32();
|
|
|
|
|
|
// Timestamp in 3.3.5a
|
|
|
|
|
|
packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
data.hitCount = packet.readUInt8();
|
2026-03-11 14:28:41 -07:00
|
|
|
|
// Cap hit count to prevent DoS via massive arrays
|
|
|
|
|
|
if (data.hitCount > 128) {
|
|
|
|
|
|
LOG_WARNING("Spell go: hitCount capped (requested=", (int)data.hitCount, ")");
|
|
|
|
|
|
data.hitCount = 128;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.hitTargets.reserve(data.hitCount);
|
|
|
|
|
|
for (uint8_t i = 0; i < data.hitCount; ++i) {
|
2026-03-11 14:28:41 -07:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_WARNING("Spell go: truncated hit targets at index ", (int)i, "/", (int)data.hitCount);
|
|
|
|
|
|
data.hitCount = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.hitTargets.push_back(packet.readUInt64());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:28:41 -07:00
|
|
|
|
// Validate missCount field exists
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) {
|
|
|
|
|
|
return true; // Valid, just no misses
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.missCount = packet.readUInt8();
|
2026-03-11 14:28:41 -07:00
|
|
|
|
// Cap miss count to prevent DoS
|
|
|
|
|
|
if (data.missCount > 128) {
|
|
|
|
|
|
LOG_WARNING("Spell go: missCount capped (requested=", (int)data.missCount, ")");
|
|
|
|
|
|
data.missCount = 128;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 23:00:21 -07:00
|
|
|
|
data.missTargets.reserve(data.missCount);
|
2026-03-11 14:28:41 -07:00
|
|
|
|
for (uint8_t i = 0; i < data.missCount; ++i) {
|
|
|
|
|
|
// Each miss entry: packed GUID(1-8 bytes) + missType(1 byte), validate before reading
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 2) {
|
|
|
|
|
|
LOG_WARNING("Spell go: truncated miss targets at index ", (int)i, "/", (int)data.missCount);
|
|
|
|
|
|
data.missCount = i;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-03-09 23:00:21 -07:00
|
|
|
|
SpellGoMissEntry m;
|
|
|
|
|
|
m.targetGuid = UpdateObjectParser::readPackedGuid(packet); // packed GUID in WotLK
|
2026-03-11 14:28:41 -07:00
|
|
|
|
m.missType = (packet.getSize() - packet.getReadPos() >= 1) ? packet.readUInt8() : 0;
|
2026-03-09 23:00:21 -07:00
|
|
|
|
data.missTargets.push_back(m);
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
LOG_DEBUG("Spell go: spell=", data.spellId, " hits=", (int)data.hitCount,
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
" misses=", (int)data.missCount);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool isAll) {
|
2026-03-11 14:30:57 -07:00
|
|
|
|
// Validation: packed GUID (1-8 bytes minimum for reading)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) return false;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.guid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
|
2026-03-11 14:30:57 -07:00
|
|
|
|
// Cap number of aura entries to prevent unbounded loop DoS
|
|
|
|
|
|
uint32_t maxAuras = isAll ? 512 : 1;
|
|
|
|
|
|
uint32_t auraCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
while (packet.getReadPos() < packet.getSize() && auraCount < maxAuras) {
|
|
|
|
|
|
// Validate we can read slot (1) + spellId (4) = 5 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 5) {
|
|
|
|
|
|
LOG_DEBUG("Aura update: truncated entry at position ", auraCount);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint8_t slot = packet.readUInt8();
|
|
|
|
|
|
uint32_t spellId = packet.readUInt32();
|
2026-03-11 14:30:57 -07:00
|
|
|
|
auraCount++;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
AuraSlot aura;
|
|
|
|
|
|
if (spellId != 0) {
|
|
|
|
|
|
aura.spellId = spellId;
|
2026-03-11 14:30:57 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate flags + level + charges (3 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 3) {
|
|
|
|
|
|
LOG_WARNING("Aura update: truncated flags/level/charges at entry ", auraCount);
|
|
|
|
|
|
aura.flags = 0;
|
|
|
|
|
|
aura.level = 0;
|
|
|
|
|
|
aura.charges = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
aura.flags = packet.readUInt8();
|
|
|
|
|
|
aura.level = packet.readUInt8();
|
|
|
|
|
|
aura.charges = packet.readUInt8();
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
|
|
|
|
|
if (!(aura.flags & 0x08)) { // NOT_CASTER flag
|
2026-03-11 14:30:57 -07:00
|
|
|
|
// Validate space for packed GUID read (minimum 1 byte)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) {
|
|
|
|
|
|
aura.casterGuid = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
aura.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:30:57 -07:00
|
|
|
|
if (aura.flags & 0x20) { // DURATION - need 8 bytes (two uint32s)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) {
|
|
|
|
|
|
LOG_WARNING("Aura update: truncated duration fields at entry ", auraCount);
|
|
|
|
|
|
aura.maxDurationMs = 0;
|
|
|
|
|
|
aura.durationMs = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
aura.maxDurationMs = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
aura.durationMs = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 00:06:25 -08:00
|
|
|
|
if (aura.flags & 0x40) { // EFFECT_AMOUNTS
|
|
|
|
|
|
// Only read amounts for active effect indices (flags 0x01, 0x02, 0x04)
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
2026-02-08 00:06:25 -08:00
|
|
|
|
if (aura.flags & (1 << i)) {
|
2026-03-11 14:30:57 -07:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() >= 4) {
|
2026-02-08 00:06:25 -08:00
|
|
|
|
packet.readUInt32();
|
2026-03-11 14:30:57 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
LOG_WARNING("Aura update: truncated effect amount ", i, " at entry ", auraCount);
|
|
|
|
|
|
break;
|
2026-02-08 00:06:25 -08:00
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.updates.push_back({slot, aura});
|
|
|
|
|
|
|
|
|
|
|
|
// For single update, only one entry
|
|
|
|
|
|
if (!isAll) break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:30:57 -07:00
|
|
|
|
if (auraCount >= maxAuras && packet.getReadPos() < packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("Aura update: capped at ", maxAuras, " entries, remaining data ignored");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
LOG_DEBUG("Aura update for 0x", std::hex, data.guid, std::dec,
|
|
|
|
|
|
": ", data.updates.size(), " slots");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SpellCooldownParser::parse(network::Packet& packet, SpellCooldownData& data) {
|
2026-03-11 14:33:02 -07:00
|
|
|
|
// Upfront validation: guid(8) + flags(1) = 9 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 9) return false;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.guid = packet.readUInt64();
|
|
|
|
|
|
data.flags = packet.readUInt8();
|
|
|
|
|
|
|
2026-03-11 14:33:02 -07:00
|
|
|
|
// Cap cooldown entries to prevent unbounded memory allocation (each entry is 8 bytes)
|
|
|
|
|
|
uint32_t maxCooldowns = 512;
|
|
|
|
|
|
uint32_t cooldownCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
while (packet.getReadPos() + 8 <= packet.getSize() && cooldownCount < maxCooldowns) {
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint32_t spellId = packet.readUInt32();
|
|
|
|
|
|
uint32_t cooldownMs = packet.readUInt32();
|
|
|
|
|
|
data.cooldowns.push_back({spellId, cooldownMs});
|
2026-03-11 14:33:02 -07:00
|
|
|
|
cooldownCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (cooldownCount >= maxCooldowns && packet.getReadPos() + 8 <= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("Spell cooldowns: capped at ", maxCooldowns, " entries, remaining data ignored");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Spell cooldowns: ", data.cooldowns.size(), " entries");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 4: Group/Party System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GroupInvitePacket::build(const std::string& playerName) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_INVITE));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeString(playerName);
|
|
|
|
|
|
packet.writeUInt32(0); // unused
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_GROUP_INVITE: ", playerName);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GroupInviteResponseParser::parse(network::Packet& packet, GroupInviteResponseData& data) {
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Validate minimum packet size: canAccept(1)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) {
|
|
|
|
|
|
LOG_WARNING("SMSG_GROUP_INVITE: packet too small (", packet.getSize(), " bytes)");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.canAccept = packet.readUInt8();
|
2026-03-11 15:08:48 -07:00
|
|
|
|
// Note: inviterName is a string, which is always safe to read even if empty
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.inviterName = packet.readString();
|
|
|
|
|
|
LOG_INFO("Group invite from: ", data.inviterName, " (canAccept=", (int)data.canAccept, ")");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GroupAcceptPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_ACCEPT));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt32(0); // unused in 3.3.5a
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GroupDeclinePacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_DECLINE));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 00:58:56 -07:00
|
|
|
|
bool GroupListParser::parse(network::Packet& packet, GroupListData& data, bool hasRoles) {
|
|
|
|
|
|
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
|
|
|
|
|
|
|
|
|
|
|
|
if (rem() < 3) return false;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.groupType = packet.readUInt8();
|
2026-03-10 00:58:56 -07:00
|
|
|
|
data.subGroup = packet.readUInt8();
|
|
|
|
|
|
data.flags = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
// WotLK 3.3.5a added a roles byte (tank/healer/dps) for the dungeon finder.
|
|
|
|
|
|
// Classic 1.12 and TBC 2.4.3 do not have this byte.
|
|
|
|
|
|
if (hasRoles) {
|
|
|
|
|
|
if (rem() < 1) return false;
|
|
|
|
|
|
data.roles = packet.readUInt8();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.roles = 0;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-03-10 00:58:56 -07:00
|
|
|
|
// WotLK: LFG data gated by groupType bit 0x04 (LFD group type)
|
|
|
|
|
|
if (hasRoles && (data.groupType & 0x04)) {
|
|
|
|
|
|
if (rem() < 5) return false;
|
|
|
|
|
|
packet.readUInt8(); // lfg state
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.readUInt32(); // lfg entry
|
2026-03-10 00:58:56 -07:00
|
|
|
|
// WotLK 3.3.5a may or may not send the lfg flags byte — read it only if present
|
|
|
|
|
|
if (rem() >= 13) { // enough for lfgFlags(1)+groupGuid(8)+counter(4)
|
|
|
|
|
|
packet.readUInt8(); // lfg flags
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 00:58:56 -07:00
|
|
|
|
if (rem() < 12) return false;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.readUInt64(); // group GUID
|
2026-03-10 00:58:56 -07:00
|
|
|
|
packet.readUInt32(); // update counter
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
|
2026-03-10 00:58:56 -07:00
|
|
|
|
if (rem() < 4) return false;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.memberCount = packet.readUInt32();
|
2026-03-10 00:58:56 -07:00
|
|
|
|
if (data.memberCount > 40) {
|
|
|
|
|
|
LOG_WARNING("GroupListParser: implausible memberCount=", data.memberCount, ", clamping");
|
|
|
|
|
|
data.memberCount = 40;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.members.reserve(data.memberCount);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < data.memberCount; ++i) {
|
2026-03-10 00:58:56 -07:00
|
|
|
|
if (rem() == 0) break;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
GroupMember member;
|
2026-03-10 00:58:56 -07:00
|
|
|
|
member.name = packet.readString();
|
|
|
|
|
|
if (rem() < 8) break;
|
|
|
|
|
|
member.guid = packet.readUInt64();
|
|
|
|
|
|
if (rem() < 3) break;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
member.isOnline = packet.readUInt8();
|
|
|
|
|
|
member.subGroup = packet.readUInt8();
|
2026-03-10 00:58:56 -07:00
|
|
|
|
member.flags = packet.readUInt8();
|
|
|
|
|
|
// WotLK added per-member roles byte; Classic/TBC do not have it.
|
|
|
|
|
|
if (hasRoles) {
|
|
|
|
|
|
if (rem() < 1) break;
|
|
|
|
|
|
member.roles = packet.readUInt8();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
member.roles = 0;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.members.push_back(member);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 00:58:56 -07:00
|
|
|
|
if (rem() < 8) {
|
|
|
|
|
|
LOG_INFO("Group list: ", data.memberCount, " members (no leader GUID in packet)");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.leaderGuid = packet.readUInt64();
|
|
|
|
|
|
|
2026-03-10 00:58:56 -07:00
|
|
|
|
if (data.memberCount > 0 && rem() >= 10) {
|
|
|
|
|
|
data.lootMethod = packet.readUInt8();
|
|
|
|
|
|
data.looterGuid = packet.readUInt64();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.lootThreshold = packet.readUInt8();
|
2026-03-10 00:58:56 -07:00
|
|
|
|
// Dungeon difficulty (heroic/normal) — Classic doesn't send this; TBC/WotLK do
|
|
|
|
|
|
if (rem() >= 1) data.difficultyId = packet.readUInt8();
|
|
|
|
|
|
// Raid difficulty — WotLK only
|
|
|
|
|
|
if (rem() >= 1) data.raidDifficultyId = packet.readUInt8();
|
|
|
|
|
|
// Extra byte in some 3.3.5a builds
|
|
|
|
|
|
if (hasRoles && rem() >= 1) packet.readUInt8();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Group list: ", data.memberCount, " members, leader=0x",
|
|
|
|
|
|
std::hex, data.leaderGuid, std::dec);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PartyCommandResultParser::parse(network::Packet& packet, PartyCommandResultData& data) {
|
2026-03-11 14:38:11 -07:00
|
|
|
|
// Upfront validation: command(4) + name(var) + result(4) = 8 bytes minimum (plus name string)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 8) return false;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.command = static_cast<PartyCommand>(packet.readUInt32());
|
|
|
|
|
|
data.name = packet.readString();
|
2026-03-11 14:38:11 -07:00
|
|
|
|
|
|
|
|
|
|
// Validate result field exists (4 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 4) {
|
|
|
|
|
|
data.result = static_cast<PartyResult>(0);
|
|
|
|
|
|
return true; // Partial read is acceptable
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.result = static_cast<PartyResult>(packet.readUInt32());
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Party command result: ", (int)data.result);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GroupDeclineResponseParser::parse(network::Packet& packet, GroupDeclineData& data) {
|
2026-03-11 14:38:11 -07:00
|
|
|
|
// Upfront validation: playerName is a CString (minimum 1 null terminator)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) return false;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.playerName = packet.readString();
|
|
|
|
|
|
LOG_INFO("Group decline from: ", data.playerName);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 5: Loot System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet LootPacket::build(uint64_t targetGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(targetGuid);
|
|
|
|
|
|
LOG_DEBUG("Built CMSG_LOOT: target=0x", std::hex, targetGuid, std::dec);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AutostoreLootItemPacket::build(uint8_t slotIndex) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_AUTOSTORE_LOOT_ITEM));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt8(slotIndex);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 00:59:07 -08:00
|
|
|
|
network::Packet UseItemPacket::build(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid, uint32_t spellId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_USE_ITEM));
|
2026-02-06 18:34:45 -08:00
|
|
|
|
packet.writeUInt8(bagIndex);
|
|
|
|
|
|
packet.writeUInt8(slotIndex);
|
2026-02-08 00:59:40 -08:00
|
|
|
|
packet.writeUInt8(0); // cast count
|
2026-02-26 00:59:07 -08:00
|
|
|
|
packet.writeUInt32(spellId); // spell id from item data
|
|
|
|
|
|
packet.writeUInt64(itemGuid); // full 8-byte GUID
|
2026-02-06 18:34:45 -08:00
|
|
|
|
packet.writeUInt32(0); // glyph index
|
|
|
|
|
|
packet.writeUInt8(0); // cast flags
|
|
|
|
|
|
// SpellCastTargets: self
|
|
|
|
|
|
packet.writeUInt32(0x00);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 19:13:38 -08:00
|
|
|
|
network::Packet AutoEquipItemPacket::build(uint8_t srcBag, uint8_t srcSlot) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_AUTOEQUIP_ITEM));
|
2026-02-06 19:13:38 -08:00
|
|
|
|
packet.writeUInt8(srcBag);
|
|
|
|
|
|
packet.writeUInt8(srcSlot);
|
2026-02-06 18:34:45 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 14:55:27 -08:00
|
|
|
|
network::Packet SwapItemPacket::build(uint8_t dstBag, uint8_t dstSlot, uint8_t srcBag, uint8_t srcSlot) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SWAP_ITEM));
|
2026-02-12 14:55:27 -08:00
|
|
|
|
packet.writeUInt8(dstBag);
|
|
|
|
|
|
packet.writeUInt8(dstSlot);
|
|
|
|
|
|
packet.writeUInt8(srcBag);
|
|
|
|
|
|
packet.writeUInt8(srcSlot);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet SwapInvItemPacket::build(uint8_t srcSlot, uint8_t dstSlot) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SWAP_INV_ITEM));
|
2026-02-12 14:55:27 -08:00
|
|
|
|
packet.writeUInt8(srcSlot);
|
|
|
|
|
|
packet.writeUInt8(dstSlot);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 19:24:44 -08:00
|
|
|
|
network::Packet LootMoneyPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT_MONEY));
|
2026-02-06 19:24:44 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
network::Packet LootReleasePacket::build(uint64_t lootGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT_RELEASE));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(lootGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 04:01:07 -07:00
|
|
|
|
bool LootResponseParser::parse(network::Packet& packet, LootResponseData& data, bool isWotlkFormat) {
|
2026-02-18 04:06:14 -08:00
|
|
|
|
data = LootResponseData{};
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 14) {
|
|
|
|
|
|
LOG_WARNING("LootResponseParser: packet too short");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.lootGuid = packet.readUInt64();
|
|
|
|
|
|
data.lootType = packet.readUInt8();
|
|
|
|
|
|
data.gold = packet.readUInt32();
|
|
|
|
|
|
uint8_t itemCount = packet.readUInt8();
|
|
|
|
|
|
|
2026-03-11 04:01:07 -07:00
|
|
|
|
// Item wire size:
|
|
|
|
|
|
// WotLK 3.3.5a: slot(1)+itemId(4)+count(4)+displayInfo(4)+randSuffix(4)+randProp(4)+slotType(1) = 22
|
|
|
|
|
|
// Classic/TBC: slot(1)+itemId(4)+count(4)+displayInfo(4)+slotType(1) = 14
|
|
|
|
|
|
const size_t kItemSize = isWotlkFormat ? 22u : 14u;
|
|
|
|
|
|
|
2026-02-18 23:46:11 -08:00
|
|
|
|
auto parseLootItemList = [&](uint8_t listCount, bool markQuestItems) -> bool {
|
|
|
|
|
|
for (uint8_t i = 0; i < listCount; ++i) {
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
2026-03-11 04:01:07 -07:00
|
|
|
|
if (remaining < kItemSize) {
|
2026-02-18 04:06:14 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-18 23:46:11 -08:00
|
|
|
|
|
2026-02-18 04:06:14 -08:00
|
|
|
|
LootItem item;
|
2026-03-11 04:01:07 -07:00
|
|
|
|
item.slotIndex = packet.readUInt8();
|
|
|
|
|
|
item.itemId = packet.readUInt32();
|
|
|
|
|
|
item.count = packet.readUInt32();
|
|
|
|
|
|
item.displayInfoId = packet.readUInt32();
|
2026-02-18 23:46:11 -08:00
|
|
|
|
|
2026-03-11 04:01:07 -07:00
|
|
|
|
if (isWotlkFormat) {
|
|
|
|
|
|
item.randomSuffix = packet.readUInt32();
|
2026-02-18 23:46:11 -08:00
|
|
|
|
item.randomPropertyId = packet.readUInt32();
|
|
|
|
|
|
} else {
|
2026-03-11 04:01:07 -07:00
|
|
|
|
item.randomSuffix = 0;
|
2026-02-18 23:46:11 -08:00
|
|
|
|
item.randomPropertyId = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-18 04:06:14 -08:00
|
|
|
|
item.lootSlotType = packet.readUInt8();
|
2026-03-11 04:01:07 -07:00
|
|
|
|
item.isQuestItem = markQuestItems;
|
2026-02-18 04:06:14 -08:00
|
|
|
|
data.items.push_back(item);
|
|
|
|
|
|
}
|
2026-02-18 23:46:11 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
data.items.reserve(itemCount);
|
|
|
|
|
|
if (!parseLootItemList(itemCount, false)) {
|
|
|
|
|
|
LOG_WARNING("LootResponseParser: truncated regular item list");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t questItemCount = 0;
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() >= 1) {
|
|
|
|
|
|
questItemCount = packet.readUInt8();
|
|
|
|
|
|
data.items.reserve(data.items.size() + questItemCount);
|
|
|
|
|
|
if (!parseLootItemList(questItemCount, true)) {
|
|
|
|
|
|
LOG_WARNING("LootResponseParser: truncated quest item list");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-02-18 04:06:14 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Loot response: ", (int)itemCount, " regular + ", (int)questItemCount,
|
2026-02-18 04:06:14 -08:00
|
|
|
|
" quest items, ", data.gold, " copper");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 5: NPC Gossip
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GossipHelloPacket::build(uint64_t npcGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GOSSIP_HELLO));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 21:12:54 -08:00
|
|
|
|
network::Packet QuestgiverHelloPacket::build(uint64_t npcGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_HELLO));
|
2026-02-07 21:12:54 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 11:45:35 -08:00
|
|
|
|
network::Packet GossipSelectOptionPacket::build(uint64_t npcGuid, uint32_t menuId, uint32_t optionId, const std::string& code) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GOSSIP_SELECT_OPTION));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
2026-02-06 11:45:35 -08:00
|
|
|
|
packet.writeUInt32(menuId);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt32(optionId);
|
|
|
|
|
|
if (!code.empty()) {
|
|
|
|
|
|
packet.writeString(code);
|
|
|
|
|
|
}
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 11:45:35 -08:00
|
|
|
|
network::Packet QuestgiverQueryQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_QUERY_QUEST));
|
2026-02-06 11:45:35 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
packet.writeUInt32(questId);
|
|
|
|
|
|
packet.writeUInt8(1); // isDialogContinued = 1 (from gossip)
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet QuestgiverAcceptQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_ACCEPT_QUEST));
|
2026-02-06 11:45:35 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
packet.writeUInt32(questId);
|
2026-02-20 23:20:02 -08:00
|
|
|
|
packet.writeUInt32(0); // AzerothCore/WotLK expects trailing unk1
|
2026-02-06 11:45:35 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 11:59:51 -08:00
|
|
|
|
bool QuestDetailsParser::parse(network::Packet& packet, QuestDetailsData& data) {
|
2026-02-17 05:27:03 -08:00
|
|
|
|
if (packet.getSize() < 20) return false;
|
2026-02-06 11:59:51 -08:00
|
|
|
|
data.npcGuid = packet.readUInt64();
|
2026-02-17 05:27:03 -08:00
|
|
|
|
|
|
|
|
|
|
// WotLK has informUnit(u64) before questId; Vanilla/TBC do not.
|
|
|
|
|
|
// Detect: try WotLK first (read informUnit + questId), then check if title
|
|
|
|
|
|
// string looks valid. If not, rewind and try vanilla (questId directly).
|
|
|
|
|
|
size_t preInform = packet.getReadPos();
|
2026-02-06 11:59:51 -08:00
|
|
|
|
/*informUnit*/ packet.readUInt64();
|
|
|
|
|
|
data.questId = packet.readUInt32();
|
2026-02-20 23:20:02 -08:00
|
|
|
|
data.title = normalizeWowTextTokens(packet.readString());
|
2026-02-17 05:27:03 -08:00
|
|
|
|
if (data.title.empty() || data.questId > 100000) {
|
|
|
|
|
|
// Likely vanilla format — rewind past informUnit
|
|
|
|
|
|
packet.setReadPos(preInform);
|
|
|
|
|
|
data.questId = packet.readUInt32();
|
2026-02-20 23:20:02 -08:00
|
|
|
|
data.title = normalizeWowTextTokens(packet.readString());
|
2026-02-17 05:27:03 -08:00
|
|
|
|
}
|
2026-02-20 23:20:02 -08:00
|
|
|
|
data.details = normalizeWowTextTokens(packet.readString());
|
|
|
|
|
|
data.objectives = normalizeWowTextTokens(packet.readString());
|
2026-02-06 12:08:47 -08:00
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 10 > packet.getSize()) {
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Quest details (short): id=", data.questId, " title='", data.title, "'");
|
2026-02-06 12:08:47 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 11:59:51 -08:00
|
|
|
|
/*activateAccept*/ packet.readUInt8();
|
|
|
|
|
|
/*flags*/ packet.readUInt32();
|
|
|
|
|
|
data.suggestedPlayers = packet.readUInt32();
|
|
|
|
|
|
/*isFinished*/ packet.readUInt8();
|
|
|
|
|
|
|
2026-02-06 12:08:47 -08:00
|
|
|
|
// Reward choice items: server always writes 6 entries (QUEST_REWARD_CHOICES_COUNT)
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize()) {
|
|
|
|
|
|
/*choiceCount*/ packet.readUInt32();
|
|
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
2026-03-10 19:05:34 -07:00
|
|
|
|
uint32_t itemId = packet.readUInt32();
|
|
|
|
|
|
uint32_t count = packet.readUInt32();
|
|
|
|
|
|
uint32_t dispId = packet.readUInt32();
|
|
|
|
|
|
if (itemId != 0) {
|
|
|
|
|
|
QuestRewardItem ri;
|
|
|
|
|
|
ri.itemId = itemId; ri.count = count; ri.displayInfoId = dispId;
|
|
|
|
|
|
ri.choiceSlot = static_cast<uint32_t>(i);
|
|
|
|
|
|
data.rewardChoiceItems.push_back(ri);
|
|
|
|
|
|
}
|
2026-02-06 12:08:47 -08:00
|
|
|
|
}
|
2026-02-06 11:59:51 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 12:08:47 -08:00
|
|
|
|
// Reward items: server always writes 4 entries (QUEST_REWARDS_COUNT)
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize()) {
|
|
|
|
|
|
/*rewardCount*/ packet.readUInt32();
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
2026-03-10 19:05:34 -07:00
|
|
|
|
uint32_t itemId = packet.readUInt32();
|
|
|
|
|
|
uint32_t count = packet.readUInt32();
|
|
|
|
|
|
uint32_t dispId = packet.readUInt32();
|
|
|
|
|
|
if (itemId != 0) {
|
|
|
|
|
|
QuestRewardItem ri;
|
|
|
|
|
|
ri.itemId = itemId; ri.count = count; ri.displayInfoId = dispId;
|
|
|
|
|
|
data.rewardItems.push_back(ri);
|
|
|
|
|
|
}
|
2026-02-06 12:08:47 -08:00
|
|
|
|
}
|
2026-02-06 11:59:51 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 12:08:47 -08:00
|
|
|
|
// Money and XP rewards
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize())
|
|
|
|
|
|
data.rewardMoney = packet.readUInt32();
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize())
|
2026-02-06 11:59:51 -08:00
|
|
|
|
data.rewardXp = packet.readUInt32();
|
|
|
|
|
|
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Quest details: id=", data.questId, " title='", data.title, "'");
|
2026-02-06 11:59:51 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data) {
|
2026-03-11 14:34:20 -07:00
|
|
|
|
// Upfront validation: npcGuid(8) + menuId(4) + titleTextId(4) + optionCount(4) = 20 bytes minimum
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 20) return false;
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.npcGuid = packet.readUInt64();
|
|
|
|
|
|
data.menuId = packet.readUInt32();
|
|
|
|
|
|
data.titleTextId = packet.readUInt32();
|
|
|
|
|
|
uint32_t optionCount = packet.readUInt32();
|
|
|
|
|
|
|
2026-03-11 14:34:20 -07:00
|
|
|
|
// Cap option count to prevent unbounded memory allocation
|
|
|
|
|
|
const uint32_t MAX_GOSSIP_OPTIONS = 64;
|
|
|
|
|
|
if (optionCount > MAX_GOSSIP_OPTIONS) {
|
|
|
|
|
|
LOG_WARNING("GossipMessageParser: optionCount capped (requested=", optionCount, ")");
|
|
|
|
|
|
optionCount = MAX_GOSSIP_OPTIONS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 19:55:32 -08:00
|
|
|
|
data.options.clear();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.options.reserve(optionCount);
|
|
|
|
|
|
for (uint32_t i = 0; i < optionCount; ++i) {
|
2026-03-11 14:34:20 -07:00
|
|
|
|
// Each option: id(4) + icon(1) + isCoded(1) + boxMoney(4) + text(var) + boxText(var)
|
|
|
|
|
|
// Minimum: 10 bytes + 2 empty strings (2 null terminators) = 12 bytes
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 12) {
|
|
|
|
|
|
LOG_WARNING("GossipMessageParser: truncated options at index ", i, "/", optionCount);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:34:20 -07:00
|
|
|
|
// Validate questCount field exists (4 bytes)
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 4) {
|
|
|
|
|
|
LOG_DEBUG("Gossip: ", data.options.size(), " options (no quest data)");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
uint32_t questCount = packet.readUInt32();
|
2026-03-11 14:34:20 -07:00
|
|
|
|
// Cap quest count to prevent unbounded memory allocation
|
|
|
|
|
|
const uint32_t MAX_GOSSIP_QUESTS = 64;
|
|
|
|
|
|
if (questCount > MAX_GOSSIP_QUESTS) {
|
|
|
|
|
|
LOG_WARNING("GossipMessageParser: questCount capped (requested=", questCount, ")");
|
|
|
|
|
|
questCount = MAX_GOSSIP_QUESTS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 19:55:32 -08:00
|
|
|
|
data.quests.clear();
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.quests.reserve(questCount);
|
|
|
|
|
|
for (uint32_t i = 0; i < questCount; ++i) {
|
2026-03-11 14:34:20 -07:00
|
|
|
|
// Each quest: questId(4) + questIcon(4) + questLevel(4) + questFlags(4) + isRepeatable(1) + title(var)
|
|
|
|
|
|
// Minimum: 17 bytes + empty string (1 null terminator) = 18 bytes
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 18) {
|
|
|
|
|
|
LOG_WARNING("GossipMessageParser: truncated quests at index ", i, "/", questCount);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
GossipQuestItem quest;
|
|
|
|
|
|
quest.questId = packet.readUInt32();
|
|
|
|
|
|
quest.questIcon = packet.readUInt32();
|
|
|
|
|
|
quest.questLevel = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
quest.questFlags = packet.readUInt32();
|
|
|
|
|
|
quest.isRepeatable = packet.readUInt8();
|
2026-02-20 23:20:02 -08:00
|
|
|
|
quest.title = normalizeWowTextTokens(packet.readString());
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.quests.push_back(quest);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:34:20 -07:00
|
|
|
|
LOG_DEBUG("Gossip: ", data.options.size(), " options, ", data.quests.size(), " quests");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 03:32:00 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Bind Point (Hearthstone)
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet BinderActivatePacket::build(uint64_t npcGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet pkt(wireOpcode(Opcode::CMSG_BINDER_ACTIVATE));
|
2026-02-08 03:32:00 -08:00
|
|
|
|
pkt.writeUInt64(npcGuid);
|
|
|
|
|
|
return pkt;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool BindPointUpdateParser::parse(network::Packet& packet, BindPointUpdateData& data) {
|
|
|
|
|
|
if (packet.getSize() < 20) return false;
|
|
|
|
|
|
data.x = packet.readFloat();
|
|
|
|
|
|
data.y = packet.readFloat();
|
|
|
|
|
|
data.z = packet.readFloat();
|
|
|
|
|
|
data.mapId = packet.readUInt32();
|
|
|
|
|
|
data.zoneId = packet.readUInt32();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 21:50:15 -08:00
|
|
|
|
bool QuestRequestItemsParser::parse(network::Packet& packet, QuestRequestItemsData& data) {
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 20) return false;
|
|
|
|
|
|
data.npcGuid = packet.readUInt64();
|
|
|
|
|
|
data.questId = packet.readUInt32();
|
2026-02-20 23:20:02 -08:00
|
|
|
|
data.title = normalizeWowTextTokens(packet.readString());
|
|
|
|
|
|
data.completionText = normalizeWowTextTokens(packet.readString());
|
2026-02-06 21:50:15 -08:00
|
|
|
|
|
2026-02-17 17:15:48 -08:00
|
|
|
|
if (packet.getReadPos() + 9 > packet.getSize()) {
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Quest request items (short): id=", data.questId, " title='", data.title, "'");
|
2026-02-06 21:50:15 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-19 01:12:14 -08:00
|
|
|
|
struct ParsedTail {
|
|
|
|
|
|
uint32_t requiredMoney = 0;
|
|
|
|
|
|
uint32_t completableFlags = 0;
|
|
|
|
|
|
std::vector<QuestRewardItem> requiredItems;
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
|
int score = -1;
|
|
|
|
|
|
};
|
2026-02-06 21:50:15 -08:00
|
|
|
|
|
2026-02-19 16:17:06 -08:00
|
|
|
|
auto parseTail = [&](size_t startPos, size_t prefixSkip) -> ParsedTail {
|
2026-02-19 01:12:14 -08:00
|
|
|
|
ParsedTail out;
|
|
|
|
|
|
packet.setReadPos(startPos);
|
2026-02-06 21:50:15 -08:00
|
|
|
|
|
2026-02-19 16:17:06 -08:00
|
|
|
|
if (packet.getReadPos() + prefixSkip > packet.getSize()) return out;
|
|
|
|
|
|
packet.setReadPos(packet.getReadPos() + prefixSkip);
|
2026-02-19 01:12:14 -08:00
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) return out;
|
|
|
|
|
|
out.requiredMoney = packet.readUInt32();
|
|
|
|
|
|
uint32_t requiredItemCount = packet.readUInt32();
|
|
|
|
|
|
if (requiredItemCount > 64) return out; // sanity guard against misalignment
|
|
|
|
|
|
|
|
|
|
|
|
out.requiredItems.reserve(requiredItemCount);
|
|
|
|
|
|
for (uint32_t i = 0; i < requiredItemCount; ++i) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return out;
|
|
|
|
|
|
QuestRewardItem item;
|
|
|
|
|
|
item.itemId = packet.readUInt32();
|
|
|
|
|
|
item.count = packet.readUInt32();
|
|
|
|
|
|
item.displayInfoId = packet.readUInt32();
|
|
|
|
|
|
if (item.itemId != 0) out.requiredItems.push_back(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
|
|
|
|
|
out.completableFlags = packet.readUInt32();
|
|
|
|
|
|
out.ok = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Prefer layouts that produce plausible quest-requirement shapes.
|
|
|
|
|
|
out.score = 0;
|
|
|
|
|
|
if (requiredItemCount <= 6) out.score += 4;
|
|
|
|
|
|
if (out.requiredItems.size() == requiredItemCount) out.score += 3;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
if ((out.completableFlags & ~0x3u) == 0) out.score += 5;
|
|
|
|
|
|
if (out.requiredMoney == 0) out.score += 4;
|
|
|
|
|
|
else if (out.requiredMoney <= 100000) out.score += 2; // <=10g is common
|
|
|
|
|
|
else if (out.requiredMoney >= 1000000) out.score -= 3; // implausible for most quests
|
|
|
|
|
|
if (!out.requiredItems.empty()) out.score += 1;
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining <= 16) out.score += 3;
|
|
|
|
|
|
else if (remaining <= 32) out.score += 2;
|
|
|
|
|
|
else if (remaining <= 64) out.score += 1;
|
|
|
|
|
|
if (prefixSkip == 0) out.score += 1;
|
|
|
|
|
|
else if (prefixSkip <= 12) out.score += 1;
|
2026-02-19 01:12:14 -08:00
|
|
|
|
return out;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
size_t tailStart = packet.getReadPos();
|
2026-02-19 16:17:06 -08:00
|
|
|
|
std::vector<ParsedTail> candidates;
|
|
|
|
|
|
candidates.reserve(25);
|
|
|
|
|
|
for (size_t skip = 0; skip <= 24; ++skip) {
|
|
|
|
|
|
candidates.push_back(parseTail(tailStart, skip));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-19 01:12:14 -08:00
|
|
|
|
const ParsedTail* chosen = nullptr;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
for (const auto& cand : candidates) {
|
|
|
|
|
|
if (!cand.ok) continue;
|
|
|
|
|
|
if (!chosen || cand.score > chosen->score) chosen = &cand;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!chosen) {
|
2026-02-19 01:12:14 -08:00
|
|
|
|
return true;
|
2026-02-06 21:50:15 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-19 01:12:14 -08:00
|
|
|
|
data.requiredMoney = chosen->requiredMoney;
|
|
|
|
|
|
data.completableFlags = chosen->completableFlags;
|
|
|
|
|
|
data.requiredItems = chosen->requiredItems;
|
2026-02-06 21:50:15 -08:00
|
|
|
|
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Quest request items: id=", data.questId, " title='", data.title,
|
2026-02-06 21:50:15 -08:00
|
|
|
|
"' items=", data.requiredItems.size(), " completable=", data.isCompletable());
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData& data) {
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 20) return false;
|
|
|
|
|
|
data.npcGuid = packet.readUInt64();
|
|
|
|
|
|
data.questId = packet.readUInt32();
|
2026-02-20 23:20:02 -08:00
|
|
|
|
data.title = normalizeWowTextTokens(packet.readString());
|
|
|
|
|
|
data.rewardText = normalizeWowTextTokens(packet.readString());
|
2026-02-06 21:50:15 -08:00
|
|
|
|
|
2026-03-11 01:00:08 -07:00
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) {
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Quest offer reward (short): id=", data.questId, " title='", data.title, "'");
|
2026-02-06 21:50:15 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 01:00:08 -07:00
|
|
|
|
// After the two strings the packet contains a variable prefix (autoFinish + optional fields)
|
|
|
|
|
|
// before the emoteCount. Different expansions and server emulator versions differ:
|
|
|
|
|
|
// Classic 1.12 : uint8 autoFinish + uint32 suggestedPlayers = 5 bytes
|
|
|
|
|
|
// TBC 2.4.3 : uint32 autoFinish + uint32 suggestedPlayers = 8 bytes (variable arrays)
|
|
|
|
|
|
// WotLK 3.3.5a : uint32 autoFinish + uint32 suggestedPlayers = 8 bytes (fixed 6/4 arrays)
|
|
|
|
|
|
// Some vanilla-family servers omit autoFinish entirely (0 bytes of prefix).
|
|
|
|
|
|
// We scan prefix sizes 0..16 bytes with both fixed and variable array layouts, scoring each.
|
|
|
|
|
|
|
2026-02-19 16:17:06 -08:00
|
|
|
|
struct ParsedTail {
|
|
|
|
|
|
uint32_t rewardMoney = 0;
|
|
|
|
|
|
uint32_t rewardXp = 0;
|
|
|
|
|
|
std::vector<QuestRewardItem> choiceRewards;
|
|
|
|
|
|
std::vector<QuestRewardItem> fixedRewards;
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
|
int score = -1000;
|
2026-03-11 01:00:08 -07:00
|
|
|
|
size_t prefixSkip = 0;
|
|
|
|
|
|
bool fixedArrays = false;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-11 01:00:08 -07:00
|
|
|
|
auto parseTail = [&](size_t startPos, size_t prefixSkip, bool fixedArrays) -> ParsedTail {
|
2026-02-19 16:17:06 -08:00
|
|
|
|
ParsedTail out;
|
2026-03-11 01:00:08 -07:00
|
|
|
|
out.prefixSkip = prefixSkip;
|
|
|
|
|
|
out.fixedArrays = fixedArrays;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
packet.setReadPos(startPos);
|
|
|
|
|
|
|
2026-03-11 01:00:08 -07:00
|
|
|
|
// Skip the prefix bytes (autoFinish + optional suggestedPlayers before emoteCount)
|
|
|
|
|
|
if (packet.getReadPos() + prefixSkip > packet.getSize()) return out;
|
|
|
|
|
|
packet.setReadPos(packet.getReadPos() + prefixSkip);
|
2026-02-19 16:17:06 -08:00
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
|
|
|
|
|
uint32_t emoteCount = packet.readUInt32();
|
2026-03-11 01:00:08 -07:00
|
|
|
|
if (emoteCount > 32) return out; // guard against misalignment
|
2026-02-19 16:17:06 -08:00
|
|
|
|
for (uint32_t i = 0; i < emoteCount; ++i) {
|
|
|
|
|
|
if (packet.getReadPos() + 8 > packet.getSize()) return out;
|
|
|
|
|
|
packet.readUInt32(); // delay
|
2026-03-11 01:00:08 -07:00
|
|
|
|
packet.readUInt32(); // emote type
|
2026-02-19 16:17:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
|
|
|
|
|
uint32_t choiceCount = packet.readUInt32();
|
|
|
|
|
|
if (choiceCount > 6) return out;
|
|
|
|
|
|
uint32_t choiceSlots = fixedArrays ? 6u : choiceCount;
|
|
|
|
|
|
out.choiceRewards.reserve(choiceCount);
|
|
|
|
|
|
uint32_t nonZeroChoice = 0;
|
|
|
|
|
|
for (uint32_t i = 0; i < choiceSlots; ++i) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return out;
|
|
|
|
|
|
QuestRewardItem item;
|
|
|
|
|
|
item.itemId = packet.readUInt32();
|
|
|
|
|
|
item.count = packet.readUInt32();
|
|
|
|
|
|
item.displayInfoId = packet.readUInt32();
|
|
|
|
|
|
item.choiceSlot = i;
|
|
|
|
|
|
if (item.itemId > 0) {
|
|
|
|
|
|
out.choiceRewards.push_back(item);
|
2026-03-11 01:00:08 -07:00
|
|
|
|
++nonZeroChoice;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
|
|
|
|
|
uint32_t rewardCount = packet.readUInt32();
|
|
|
|
|
|
if (rewardCount > 4) return out;
|
|
|
|
|
|
uint32_t rewardSlots = fixedArrays ? 4u : rewardCount;
|
|
|
|
|
|
out.fixedRewards.reserve(rewardCount);
|
|
|
|
|
|
uint32_t nonZeroFixed = 0;
|
|
|
|
|
|
for (uint32_t i = 0; i < rewardSlots; ++i) {
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) return out;
|
|
|
|
|
|
QuestRewardItem item;
|
|
|
|
|
|
item.itemId = packet.readUInt32();
|
|
|
|
|
|
item.count = packet.readUInt32();
|
|
|
|
|
|
item.displayInfoId = packet.readUInt32();
|
|
|
|
|
|
if (item.itemId > 0) {
|
|
|
|
|
|
out.fixedRewards.push_back(item);
|
2026-03-11 01:00:08 -07:00
|
|
|
|
++nonZeroFixed;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize())
|
|
|
|
|
|
out.rewardMoney = packet.readUInt32();
|
|
|
|
|
|
if (packet.getReadPos() + 4 <= packet.getSize())
|
|
|
|
|
|
out.rewardXp = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
out.ok = true;
|
|
|
|
|
|
out.score = 0;
|
2026-03-11 01:00:08 -07:00
|
|
|
|
// Prefer the standard WotLK/TBC 8-byte prefix (uint32 autoFinish + uint32 suggestedPlayers)
|
|
|
|
|
|
if (prefixSkip == 8) out.score += 3;
|
|
|
|
|
|
else if (prefixSkip == 5) out.score += 1; // Classic uint8 autoFinish + uint32 suggestedPlayers
|
|
|
|
|
|
// Prefer fixed arrays (WotLK/TBC servers always send 6+4 slots)
|
|
|
|
|
|
if (fixedArrays) out.score += 2;
|
|
|
|
|
|
// Valid counts
|
2026-02-19 16:17:06 -08:00
|
|
|
|
if (choiceCount <= 6) out.score += 3;
|
|
|
|
|
|
if (rewardCount <= 4) out.score += 3;
|
2026-03-11 01:00:08 -07:00
|
|
|
|
// All non-zero items are within declared counts
|
|
|
|
|
|
if (nonZeroChoice <= choiceCount) out.score += 2;
|
|
|
|
|
|
if (nonZeroFixed <= rewardCount) out.score += 2;
|
|
|
|
|
|
// No bytes left over (or only a few)
|
2026-02-19 16:17:06 -08:00
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
2026-03-11 01:00:08 -07:00
|
|
|
|
if (remaining == 0) out.score += 5;
|
|
|
|
|
|
else if (remaining <= 4) out.score += 3;
|
|
|
|
|
|
else if (remaining <= 8) out.score += 2;
|
|
|
|
|
|
else if (remaining <= 16) out.score += 1;
|
|
|
|
|
|
else out.score -= static_cast<int>(remaining / 4);
|
|
|
|
|
|
// Plausible money/XP values
|
|
|
|
|
|
if (out.rewardMoney < 5000000u) out.score += 1; // < 500g
|
|
|
|
|
|
if (out.rewardXp < 200000u) out.score += 1; // < 200k XP
|
2026-02-19 16:17:06 -08:00
|
|
|
|
return out;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
size_t tailStart = packet.getReadPos();
|
2026-03-11 01:00:08 -07:00
|
|
|
|
// Try prefix sizes 0..16 bytes with both fixed and variable array layouts
|
|
|
|
|
|
std::vector<ParsedTail> candidates;
|
|
|
|
|
|
candidates.reserve(34);
|
|
|
|
|
|
for (size_t skip = 0; skip <= 16; ++skip) {
|
|
|
|
|
|
candidates.push_back(parseTail(tailStart, skip, true)); // fixed arrays
|
|
|
|
|
|
candidates.push_back(parseTail(tailStart, skip, false)); // variable arrays
|
|
|
|
|
|
}
|
2026-02-19 16:17:06 -08:00
|
|
|
|
|
|
|
|
|
|
const ParsedTail* best = nullptr;
|
2026-03-11 01:00:08 -07:00
|
|
|
|
for (const auto& cand : candidates) {
|
|
|
|
|
|
if (!cand.ok) continue;
|
|
|
|
|
|
if (!best || cand.score > best->score) best = &cand;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (best) {
|
|
|
|
|
|
data.choiceRewards = best->choiceRewards;
|
2026-03-11 01:00:08 -07:00
|
|
|
|
data.fixedRewards = best->fixedRewards;
|
|
|
|
|
|
data.rewardMoney = best->rewardMoney;
|
|
|
|
|
|
data.rewardXp = best->rewardXp;
|
2026-02-19 16:17:06 -08:00
|
|
|
|
}
|
2026-02-06 21:50:15 -08:00
|
|
|
|
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Quest offer reward: id=", data.questId, " title='", data.title,
|
2026-03-11 01:00:08 -07:00
|
|
|
|
"' choices=", data.choiceRewards.size(), " fixed=", data.fixedRewards.size(),
|
|
|
|
|
|
" prefix=", (best ? best->prefixSkip : size_t(0)),
|
|
|
|
|
|
(best && best->fixedArrays ? " fixed" : " var"));
|
2026-02-06 21:50:15 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet QuestgiverCompleteQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_COMPLETE_QUEST));
|
2026-02-06 21:50:15 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
packet.writeUInt32(questId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-19 01:12:14 -08:00
|
|
|
|
network::Packet QuestgiverRequestRewardPacket::build(uint64_t npcGuid, uint32_t questId) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_REQUEST_REWARD));
|
|
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
packet.writeUInt32(questId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 21:50:15 -08:00
|
|
|
|
network::Packet QuestgiverChooseRewardPacket::build(uint64_t npcGuid, uint32_t questId, uint32_t rewardIndex) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_CHOOSE_REWARD));
|
2026-02-06 21:50:15 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
packet.writeUInt32(questId);
|
|
|
|
|
|
packet.writeUInt32(rewardIndex);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Phase 5: Vendor
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet ListInventoryPacket::build(uint64_t npcGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LIST_INVENTORY));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-19 16:17:06 -08:00
|
|
|
|
network::Packet BuyItemPacket::build(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_BUY_ITEM));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(vendorGuid);
|
2026-02-17 17:44:48 -08:00
|
|
|
|
packet.writeUInt32(itemId); // item entry
|
2026-02-19 16:17:06 -08:00
|
|
|
|
packet.writeUInt32(slot); // vendor slot index from SMSG_LIST_INVENTORY
|
Fix vendor buying, improve character select, parallelize WMO culling, and optimize collision
- Fix CMSG_BUY_ITEM count field from uint8 to uint32 (server silently dropped undersized packets)
- Character select screen: remember last selected character, two-column layout with details panel, double-click to enter world, responsive window sizing
- Fix stale character data between logins by replacing static init flag with per-character GUID tracking
- Parallelize WMO visibility culling across worker threads (same pattern as M2 renderer)
- Optimize WMO collision queries with world-space group bounds early rejection in getFloorHeight, checkWallCollision, isInsideWMO, and raycastBoundingBoxes
- Reduce camera ground samples from 5 to 3 movement-aligned probes
- Add WMO interior lighting, unlit materials, vertex color multiply, and alpha blending support
2026-02-07 15:29:19 -08:00
|
|
|
|
packet.writeUInt32(count);
|
2026-03-11 04:49:18 -07:00
|
|
|
|
// Note: WotLK/AzerothCore expects a trailing byte; Classic/TBC do not.
|
|
|
|
|
|
// This static helper always adds it (appropriate for CMaNGOS/AzerothCore).
|
|
|
|
|
|
// For Classic/TBC, use the GameHandler::buyItem() path which checks expansion.
|
2026-02-19 16:17:06 -08:00
|
|
|
|
packet.writeUInt8(0);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 19:50:22 -08:00
|
|
|
|
network::Packet SellItemPacket::build(uint64_t vendorGuid, uint64_t itemGuid, uint32_t count) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SELL_ITEM));
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
packet.writeUInt64(vendorGuid);
|
|
|
|
|
|
packet.writeUInt64(itemGuid);
|
2026-02-06 19:50:22 -08:00
|
|
|
|
packet.writeUInt32(count);
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-19 16:17:06 -08:00
|
|
|
|
network::Packet BuybackItemPacket::build(uint64_t vendorGuid, uint32_t slot) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_BUYBACK_ITEM));
|
|
|
|
|
|
packet.writeUInt64(vendorGuid);
|
|
|
|
|
|
packet.writeUInt32(slot);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
bool ListInventoryParser::parse(network::Packet& packet, ListInventoryData& data) {
|
2026-02-18 03:40:59 -08:00
|
|
|
|
data = ListInventoryData{};
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 9) {
|
|
|
|
|
|
LOG_WARNING("ListInventoryParser: packet too short");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.vendorGuid = packet.readUInt64();
|
|
|
|
|
|
uint8_t itemCount = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (itemCount == 0) {
|
|
|
|
|
|
LOG_INFO("Vendor has nothing for sale");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-17 18:08:00 -08:00
|
|
|
|
// Auto-detect whether server sends 7 fields (28 bytes/item) or 8 fields (32 bytes/item).
|
|
|
|
|
|
// Some servers omit the extendedCost field entirely; reading 8 fields on a 7-field packet
|
|
|
|
|
|
// misaligns every item after the first and produces garbage prices.
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
2026-02-18 03:40:59 -08:00
|
|
|
|
const size_t bytesPerItemNoExt = 28;
|
|
|
|
|
|
const size_t bytesPerItemWithExt = 32;
|
|
|
|
|
|
bool hasExtendedCost = false;
|
|
|
|
|
|
if (remaining < static_cast<size_t>(itemCount) * bytesPerItemNoExt) {
|
|
|
|
|
|
LOG_WARNING("ListInventoryParser: truncated packet (items=", (int)itemCount,
|
|
|
|
|
|
", remaining=", remaining, ")");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (remaining >= static_cast<size_t>(itemCount) * bytesPerItemWithExt) {
|
|
|
|
|
|
hasExtendedCost = true;
|
|
|
|
|
|
}
|
2026-02-17 18:08:00 -08:00
|
|
|
|
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.items.reserve(itemCount);
|
|
|
|
|
|
for (uint8_t i = 0; i < itemCount; ++i) {
|
2026-02-18 03:40:59 -08:00
|
|
|
|
const size_t perItemBytes = hasExtendedCost ? bytesPerItemWithExt : bytesPerItemNoExt;
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < perItemBytes) {
|
|
|
|
|
|
LOG_WARNING("ListInventoryParser: item ", (int)i, " truncated");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
VendorItem item;
|
|
|
|
|
|
item.slot = packet.readUInt32();
|
|
|
|
|
|
item.itemId = packet.readUInt32();
|
|
|
|
|
|
item.displayInfoId = packet.readUInt32();
|
|
|
|
|
|
item.maxCount = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
item.buyPrice = packet.readUInt32();
|
|
|
|
|
|
item.durability = packet.readUInt32();
|
|
|
|
|
|
item.stackCount = packet.readUInt32();
|
2026-02-17 18:08:00 -08:00
|
|
|
|
item.extendedCost = hasExtendedCost ? packet.readUInt32() : 0;
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
data.items.push_back(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 05:09:43 -07:00
|
|
|
|
LOG_DEBUG("Vendor inventory: ", (int)itemCount, " items (extendedCost: ", hasExtendedCost ? "yes" : "no", ")");
|
Add gameplay systems: combat, spells, groups, loot, vendors, and UI
Implement ~70 new protocol opcodes across 5 phases while maintaining
full 3.3.5a private server compatibility:
- Phase 1: Server-aware targeting (CMSG_SET_SELECTION), player/creature
name queries, CMSG_SET_ACTIVE_MOVER after login
- Phase 2: Auto-attack, melee/spell damage parsing, health/mana/power
tracking from UPDATE_OBJECT fields, floating combat text
- Phase 3: Spell casting, action bar (12 slots, keys 1-=), cast bar,
cooldown tracking, aura/buff system with cancellation
- Phase 4: Group invite/accept/decline/leave, party frames UI,
/invite chat command
- Phase 5: Loot window, NPC gossip dialog, vendor buy/sell interface
Also: disable debug HUD/panels by default, gate 3D rendering to
IN_GAME state only, fix window resize not updating UI positions.
2026-02-04 10:30:52 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 14:33:39 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Trainer
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
2026-03-10 01:20:41 -07:00
|
|
|
|
bool TrainerListParser::parse(network::Packet& packet, TrainerListData& data, bool isClassic) {
|
|
|
|
|
|
// WotLK per-entry: spellId(4) + state(1) + cost(4) + profDialog(4) + profButton(4) +
|
|
|
|
|
|
// reqLevel(1) + reqSkill(4) + reqSkillValue(4) + chain×3(12) = 38 bytes
|
|
|
|
|
|
// Classic per-entry: spellId(4) + state(1) + cost(4) + reqLevel(1) +
|
|
|
|
|
|
// reqSkill(4) + reqSkillValue(4) + chain×3(12) + unk(4) = 34 bytes
|
2026-02-08 14:33:39 -08:00
|
|
|
|
data = TrainerListData{};
|
2026-03-11 14:44:52 -07:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 16) return false; // guid(8) + type(4) + count(4)
|
|
|
|
|
|
|
2026-02-08 14:33:39 -08:00
|
|
|
|
data.trainerGuid = packet.readUInt64();
|
|
|
|
|
|
data.trainerType = packet.readUInt32();
|
|
|
|
|
|
uint32_t spellCount = packet.readUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
if (spellCount > 1000) {
|
|
|
|
|
|
LOG_ERROR("TrainerListParser: unreasonable spell count ", spellCount);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data.spells.reserve(spellCount);
|
|
|
|
|
|
for (uint32_t i = 0; i < spellCount; ++i) {
|
2026-03-11 14:44:52 -07:00
|
|
|
|
// Validate minimum entry size before reading
|
|
|
|
|
|
const size_t minEntrySize = isClassic ? 34 : 38;
|
|
|
|
|
|
if (packet.getReadPos() + minEntrySize > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("TrainerListParser: truncated at spell ", i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 14:33:39 -08:00
|
|
|
|
TrainerSpell spell;
|
2026-03-10 01:20:41 -07:00
|
|
|
|
spell.spellId = packet.readUInt32();
|
|
|
|
|
|
spell.state = packet.readUInt8();
|
|
|
|
|
|
spell.spellCost = packet.readUInt32();
|
|
|
|
|
|
if (isClassic) {
|
|
|
|
|
|
// Classic 1.12: reqLevel immediately after cost; no profDialog/profButton
|
|
|
|
|
|
spell.profDialog = 0;
|
|
|
|
|
|
spell.profButton = 0;
|
|
|
|
|
|
spell.reqLevel = packet.readUInt8();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// TBC / WotLK: profDialog + profButton before reqLevel
|
|
|
|
|
|
spell.profDialog = packet.readUInt32();
|
|
|
|
|
|
spell.profButton = packet.readUInt32();
|
|
|
|
|
|
spell.reqLevel = packet.readUInt8();
|
|
|
|
|
|
}
|
|
|
|
|
|
spell.reqSkill = packet.readUInt32();
|
2026-02-08 14:33:39 -08:00
|
|
|
|
spell.reqSkillValue = packet.readUInt32();
|
2026-03-10 01:20:41 -07:00
|
|
|
|
spell.chainNode1 = packet.readUInt32();
|
|
|
|
|
|
spell.chainNode2 = packet.readUInt32();
|
|
|
|
|
|
spell.chainNode3 = packet.readUInt32();
|
|
|
|
|
|
if (isClassic) {
|
|
|
|
|
|
packet.readUInt32(); // trailing unk / sort index
|
|
|
|
|
|
}
|
2026-02-08 14:33:39 -08:00
|
|
|
|
data.spells.push_back(spell);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:44:52 -07:00
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("TrainerListParser: truncated before greeting");
|
|
|
|
|
|
data.greeting.clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.greeting = packet.readString();
|
|
|
|
|
|
}
|
2026-02-08 14:33:39 -08:00
|
|
|
|
|
2026-03-10 01:20:41 -07:00
|
|
|
|
LOG_INFO("Trainer list (", isClassic ? "Classic" : "TBC/WotLK", "): ",
|
|
|
|
|
|
spellCount, " spells, type=", data.trainerType,
|
2026-02-08 14:33:39 -08:00
|
|
|
|
", greeting=\"", data.greeting, "\"");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 01:24:37 -08:00
|
|
|
|
network::Packet TrainerBuySpellPacket::build(uint64_t trainerGuid, uint32_t spellId) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_TRAINER_BUY_SPELL));
|
2026-02-08 14:33:39 -08:00
|
|
|
|
packet.writeUInt64(trainerGuid);
|
|
|
|
|
|
packet.writeUInt32(spellId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Talents
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool TalentsInfoParser::parse(network::Packet& packet, TalentsInfoData& data) {
|
2026-02-10 13:16:38 -08:00
|
|
|
|
// SMSG_TALENTS_INFO format (AzerothCore variant):
|
|
|
|
|
|
// uint8 activeSpec
|
|
|
|
|
|
// uint8 unspentPoints
|
|
|
|
|
|
// be32 talentCount (metadata, may not match entry count)
|
|
|
|
|
|
// be16 entryCount (actual number of id+rank entries)
|
|
|
|
|
|
// Entry[entryCount]: { le32 id, uint8 rank }
|
|
|
|
|
|
// le32 glyphSlots
|
|
|
|
|
|
// le16 glyphIds[glyphSlots]
|
|
|
|
|
|
|
|
|
|
|
|
const size_t startPos = packet.getReadPos();
|
|
|
|
|
|
const size_t remaining = packet.getSize() - startPos;
|
|
|
|
|
|
|
|
|
|
|
|
if (remaining < 2 + 4 + 2) {
|
|
|
|
|
|
LOG_ERROR("SMSG_TALENTS_INFO: packet too short (remaining=", remaining, ")");
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 13:16:38 -08:00
|
|
|
|
data = TalentsInfoData{};
|
|
|
|
|
|
|
|
|
|
|
|
// Read header
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
data.talentSpec = packet.readUInt8();
|
|
|
|
|
|
data.unspentPoints = packet.readUInt8();
|
2026-02-10 13:16:38 -08:00
|
|
|
|
|
|
|
|
|
|
// These two counts are big-endian (network byte order)
|
|
|
|
|
|
uint32_t talentCountBE = packet.readUInt32();
|
2026-02-23 16:30:49 +01:00
|
|
|
|
uint32_t talentCount = bswap32(talentCountBE);
|
2026-02-10 13:16:38 -08:00
|
|
|
|
|
|
|
|
|
|
uint16_t entryCountBE = packet.readUInt16();
|
2026-02-23 16:30:49 +01:00
|
|
|
|
uint16_t entryCount = bswap16(entryCountBE);
|
2026-02-10 13:16:38 -08:00
|
|
|
|
|
|
|
|
|
|
// Sanity check: prevent corrupt packets from allocating excessive memory
|
|
|
|
|
|
if (entryCount > 64) {
|
|
|
|
|
|
LOG_ERROR("SMSG_TALENTS_INFO: entryCount too large (", entryCount, "), rejecting packet");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
|
|
|
|
|
|
LOG_INFO("SMSG_TALENTS_INFO: spec=", (int)data.talentSpec,
|
2026-02-10 13:16:38 -08:00
|
|
|
|
" unspent=", (int)data.unspentPoints,
|
|
|
|
|
|
" talentCount=", talentCount,
|
|
|
|
|
|
" entryCount=", entryCount);
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
|
2026-02-10 13:16:38 -08:00
|
|
|
|
// Parse learned entries (id + rank pairs)
|
|
|
|
|
|
// These may be talents, glyphs, or other learned abilities
|
|
|
|
|
|
data.talents.clear();
|
|
|
|
|
|
data.talents.reserve(entryCount);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint16_t i = 0; i < entryCount; ++i) {
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 5) {
|
2026-02-10 13:16:38 -08:00
|
|
|
|
LOG_ERROR("SMSG_TALENTS_INFO: truncated entry list at i=", i);
|
|
|
|
|
|
return false;
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
}
|
2026-02-10 13:16:38 -08:00
|
|
|
|
uint32_t id = packet.readUInt32(); // LE
|
|
|
|
|
|
uint8_t rank = packet.readUInt8();
|
|
|
|
|
|
data.talents.push_back({id, rank});
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
|
2026-02-10 13:16:38 -08:00
|
|
|
|
LOG_INFO(" Entry: id=", id, " rank=", (int)rank);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse glyph tail: glyphSlots + glyphIds[]
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 1) {
|
|
|
|
|
|
LOG_WARNING("SMSG_TALENTS_INFO: no glyph tail data");
|
|
|
|
|
|
return true; // Not fatal, older formats may not have glyphs
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t glyphSlots = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
// Sanity check: Wrath has 6 glyph slots, cap at 12 for safety
|
|
|
|
|
|
if (glyphSlots > 12) {
|
|
|
|
|
|
LOG_WARNING("SMSG_TALENTS_INFO: glyphSlots too large (", (int)glyphSlots, "), clamping to 12");
|
|
|
|
|
|
glyphSlots = 12;
|
|
|
|
|
|
}
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
|
2026-02-10 13:16:38 -08:00
|
|
|
|
LOG_INFO(" GlyphSlots: ", (int)glyphSlots);
|
|
|
|
|
|
|
|
|
|
|
|
data.glyphs.clear();
|
|
|
|
|
|
data.glyphs.reserve(glyphSlots);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < glyphSlots; ++i) {
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 2) {
|
|
|
|
|
|
LOG_ERROR("SMSG_TALENTS_INFO: truncated glyph list at i=", i);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
uint16_t glyphId = packet.readUInt16(); // LE
|
|
|
|
|
|
data.glyphs.push_back(glyphId);
|
|
|
|
|
|
if (glyphId != 0) {
|
|
|
|
|
|
LOG_INFO(" Glyph slot ", i, ": ", glyphId);
|
|
|
|
|
|
}
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 13:16:38 -08:00
|
|
|
|
LOG_INFO("SMSG_TALENTS_INFO: bytesConsumed=", (packet.getReadPos() - startPos),
|
|
|
|
|
|
" bytesRemaining=", (packet.getSize() - packet.getReadPos()));
|
|
|
|
|
|
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet LearnTalentPacket::build(uint32_t talentId, uint32_t requestedRank) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_LEARN_TALENT));
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
packet.writeUInt32(talentId);
|
|
|
|
|
|
packet.writeUInt32(requestedRank);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet TalentWipeConfirmPacket::build(bool accept) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::MSG_TALENT_WIPE_CONFIRM));
|
Implement complete talent system with dual spec support
Network Protocol:
- Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data
- Add CMSG_LEARN_TALENT (0x251) to request learning talents
- Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching
- Parse talent spec, unspent points, and learned talent ranks
DBC Parsing:
- Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs
- Load TalentTab.dbc: talent tree definitions with correct field indices
- Fix localized string field handling (17 fields per string)
- Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips
- Class mask filtering using bitwise operations (1 << (class - 1))
UI Implementation:
- Complete talent tree UI with tabbed interface for specs
- Display talent icons from spell data with proper tinting/borders
- Enhanced tooltips: spell name, rank, current/next descriptions, prereqs
- Visual states: green (maxed), yellow (partial), white (available), gray (locked)
- Tier unlock system (5 points per tier requirement)
- Rank overlay on icons with shadow text
- Click to learn talents with validation
Dual Spec Support:
- Store unspent points and learned talents per spec (0 and 1)
- Track active spec and display its talents
- Spec switching UI with buttons for Spec 1/Spec 2
- Handle both SMSG_TALENTS_INFO packets from server at login
- Display unspent points for both specs in header
- Independent talent trees for each specialization
2026-02-10 02:00:13 -08:00
|
|
|
|
packet.writeUInt32(accept ? 1 : 0);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 22:49:23 -07:00
|
|
|
|
network::Packet ActivateTalentGroupPacket::build(uint32_t group) {
|
|
|
|
|
|
// CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE (0x4C3 in WotLK 3.3.5a)
|
|
|
|
|
|
// Payload: uint32 group (0 = primary, 1 = secondary)
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE));
|
|
|
|
|
|
packet.writeUInt32(group);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 17:27:20 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Death/Respawn
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet RepopRequestPacket::build() {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_REPOP_REQUEST));
|
2026-02-07 21:47:14 -08:00
|
|
|
|
packet.writeUInt8(1); // request release (1 = manual)
|
2026-02-06 17:27:20 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:45 -08:00
|
|
|
|
network::Packet SpiritHealerActivatePacket::build(uint64_t npcGuid) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SPIRIT_HEALER_ACTIVATE));
|
2026-02-06 18:34:45 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 21:47:14 -08:00
|
|
|
|
network::Packet ResurrectResponsePacket::build(uint64_t casterGuid, bool accept) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_RESURRECT_RESPONSE));
|
2026-02-07 21:47:14 -08:00
|
|
|
|
packet.writeUInt64(casterGuid);
|
|
|
|
|
|
packet.writeUInt8(accept ? 1 : 0);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 16:59:20 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Taxi / Flight Paths
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
bool ShowTaxiNodesParser::parse(network::Packet& packet, ShowTaxiNodesData& data) {
|
2026-02-14 16:47:21 -08:00
|
|
|
|
// Minimum: windowInfo(4) + npcGuid(8) + nearestNode(4) + at least 1 mask uint32(4)
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining < 4 + 8 + 4 + 4) {
|
|
|
|
|
|
LOG_ERROR("ShowTaxiNodesParser: packet too short (", remaining, " bytes)");
|
2026-02-07 16:59:20 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.windowInfo = packet.readUInt32();
|
|
|
|
|
|
data.npcGuid = packet.readUInt64();
|
|
|
|
|
|
data.nearestNode = packet.readUInt32();
|
2026-02-14 16:47:21 -08:00
|
|
|
|
// Read as many mask uint32s as available (Classic/Vanilla=4, WotLK=12)
|
|
|
|
|
|
size_t maskBytes = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
uint32_t maskCount = static_cast<uint32_t>(maskBytes / 4);
|
|
|
|
|
|
if (maskCount > TLK_TAXI_MASK_SIZE) maskCount = TLK_TAXI_MASK_SIZE;
|
|
|
|
|
|
for (uint32_t i = 0; i < maskCount; ++i) {
|
2026-02-07 16:59:20 -08:00
|
|
|
|
data.nodeMask[i] = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("ShowTaxiNodes: window=", data.windowInfo, " npc=0x", std::hex, data.npcGuid, std::dec,
|
2026-02-14 16:47:21 -08:00
|
|
|
|
" nearest=", data.nearestNode, " maskSlots=", maskCount);
|
2026-02-07 16:59:20 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ActivateTaxiReplyParser::parse(network::Packet& packet, ActivateTaxiReplyData& data) {
|
2026-02-08 03:05:38 -08:00
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining >= 4) {
|
|
|
|
|
|
data.result = packet.readUInt32();
|
|
|
|
|
|
} else if (remaining >= 1) {
|
|
|
|
|
|
data.result = packet.readUInt8();
|
|
|
|
|
|
} else {
|
2026-02-07 16:59:20 -08:00
|
|
|
|
LOG_ERROR("ActivateTaxiReplyParser: packet too short");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("ActivateTaxiReply: result=", data.result);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 03:05:38 -08:00
|
|
|
|
network::Packet ActivateTaxiExpressPacket::build(uint64_t npcGuid, uint32_t totalCost, const std::vector<uint32_t>& pathNodes) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ACTIVATETAXIEXPRESS));
|
2026-02-07 16:59:20 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
2026-02-08 03:05:38 -08:00
|
|
|
|
packet.writeUInt32(totalCost);
|
2026-02-07 16:59:20 -08:00
|
|
|
|
packet.writeUInt32(static_cast<uint32_t>(pathNodes.size()));
|
|
|
|
|
|
for (uint32_t nodeId : pathNodes) {
|
|
|
|
|
|
packet.writeUInt32(nodeId);
|
|
|
|
|
|
}
|
|
|
|
|
|
LOG_INFO("ActivateTaxiExpress: npc=0x", std::hex, npcGuid, std::dec,
|
2026-02-08 03:05:38 -08:00
|
|
|
|
" cost=", totalCost, " nodes=", pathNodes.size());
|
2026-02-07 16:59:20 -08:00
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 19:44:03 -08:00
|
|
|
|
network::Packet ActivateTaxiPacket::build(uint64_t npcGuid, uint32_t srcNode, uint32_t destNode) {
|
2026-02-12 22:56:36 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_ACTIVATETAXI));
|
2026-02-07 19:44:03 -08:00
|
|
|
|
packet.writeUInt64(npcGuid);
|
|
|
|
|
|
packet.writeUInt32(srcNode);
|
|
|
|
|
|
packet.writeUInt32(destNode);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GameObjectUsePacket::build(uint64_t guid) {
|
2026-02-20 02:50:59 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GAMEOBJ_USE));
|
2026-02-07 19:44:03 -08:00
|
|
|
|
packet.writeUInt64(guid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 14:00:41 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Mail System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GetMailListPacket::build(uint64_t mailboxGuid) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_GET_MAIL_LIST));
|
|
|
|
|
|
packet.writeUInt64(mailboxGuid);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet SendMailPacket::build(uint64_t mailboxGuid, const std::string& recipient,
|
|
|
|
|
|
const std::string& subject, const std::string& body,
|
2026-02-25 14:11:09 -08:00
|
|
|
|
uint32_t money, uint32_t cod,
|
|
|
|
|
|
const std::vector<uint64_t>& itemGuids) {
|
2026-02-16 18:46:44 -08:00
|
|
|
|
// WotLK 3.3.5a format
|
2026-02-15 14:00:41 -08:00
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_SEND_MAIL));
|
|
|
|
|
|
packet.writeUInt64(mailboxGuid);
|
|
|
|
|
|
packet.writeString(recipient);
|
|
|
|
|
|
packet.writeString(subject);
|
|
|
|
|
|
packet.writeString(body);
|
2026-02-16 18:46:44 -08:00
|
|
|
|
packet.writeUInt32(0); // stationery
|
2026-02-15 14:00:41 -08:00
|
|
|
|
packet.writeUInt32(0); // unknown
|
2026-02-25 14:11:09 -08:00
|
|
|
|
uint8_t attachCount = static_cast<uint8_t>(itemGuids.size());
|
|
|
|
|
|
packet.writeUInt8(attachCount);
|
|
|
|
|
|
for (uint8_t i = 0; i < attachCount; ++i) {
|
|
|
|
|
|
packet.writeUInt8(i); // attachment slot index
|
|
|
|
|
|
packet.writeUInt64(itemGuids[i]);
|
|
|
|
|
|
}
|
2026-02-15 14:00:41 -08:00
|
|
|
|
packet.writeUInt32(money);
|
|
|
|
|
|
packet.writeUInt32(cod);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet MailTakeMoneyPacket::build(uint64_t mailboxGuid, uint32_t mailId) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_MAIL_TAKE_MONEY));
|
|
|
|
|
|
packet.writeUInt64(mailboxGuid);
|
|
|
|
|
|
packet.writeUInt32(mailId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet MailTakeItemPacket::build(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemIndex) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_MAIL_TAKE_ITEM));
|
|
|
|
|
|
packet.writeUInt64(mailboxGuid);
|
|
|
|
|
|
packet.writeUInt32(mailId);
|
|
|
|
|
|
packet.writeUInt32(itemIndex);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet MailDeletePacket::build(uint64_t mailboxGuid, uint32_t mailId, uint32_t mailTemplateId) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_MAIL_DELETE));
|
|
|
|
|
|
packet.writeUInt64(mailboxGuid);
|
|
|
|
|
|
packet.writeUInt32(mailId);
|
|
|
|
|
|
packet.writeUInt32(mailTemplateId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet MailMarkAsReadPacket::build(uint64_t mailboxGuid, uint32_t mailId) {
|
|
|
|
|
|
network::Packet packet(wireOpcode(Opcode::CMSG_MAIL_MARK_AS_READ));
|
|
|
|
|
|
packet.writeUInt64(mailboxGuid);
|
|
|
|
|
|
packet.writeUInt32(mailId);
|
|
|
|
|
|
return packet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-16 18:46:44 -08:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// PacketParsers::parseMailList — WotLK 3.3.5a format (base/default)
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
bool PacketParsers::parseMailList(network::Packet& packet, std::vector<MailMessage>& inbox) {
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining < 5) return false;
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t totalCount = packet.readUInt32();
|
|
|
|
|
|
uint8_t shownCount = packet.readUInt8();
|
|
|
|
|
|
(void)totalCount;
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("SMSG_MAIL_LIST_RESULT (WotLK): total=", totalCount, " shown=", (int)shownCount);
|
|
|
|
|
|
|
|
|
|
|
|
inbox.clear();
|
|
|
|
|
|
inbox.reserve(shownCount);
|
|
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < shownCount; ++i) {
|
|
|
|
|
|
remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining < 2) break;
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t msgSize = packet.readUInt16();
|
|
|
|
|
|
size_t startPos = packet.getReadPos();
|
|
|
|
|
|
|
|
|
|
|
|
MailMessage msg;
|
|
|
|
|
|
if (remaining < static_cast<size_t>(msgSize) + 2) {
|
|
|
|
|
|
LOG_WARNING("Mail entry ", i, " truncated");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msg.messageId = packet.readUInt32();
|
|
|
|
|
|
msg.messageType = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
switch (msg.messageType) {
|
|
|
|
|
|
case 0: msg.senderGuid = packet.readUInt64(); break;
|
|
|
|
|
|
case 2: case 3: case 4: case 5:
|
|
|
|
|
|
msg.senderEntry = packet.readUInt32(); break;
|
|
|
|
|
|
default: msg.senderEntry = packet.readUInt32(); break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msg.cod = packet.readUInt32();
|
|
|
|
|
|
packet.readUInt32(); // item text id
|
|
|
|
|
|
packet.readUInt32(); // unknown
|
|
|
|
|
|
msg.stationeryId = packet.readUInt32();
|
|
|
|
|
|
msg.money = packet.readUInt32();
|
|
|
|
|
|
msg.flags = packet.readUInt32();
|
|
|
|
|
|
msg.expirationTime = packet.readFloat();
|
|
|
|
|
|
msg.mailTemplateId = packet.readUInt32();
|
|
|
|
|
|
msg.subject = packet.readString();
|
|
|
|
|
|
|
|
|
|
|
|
if (msg.mailTemplateId == 0) {
|
|
|
|
|
|
msg.body = packet.readString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t attachCount = packet.readUInt8();
|
|
|
|
|
|
msg.attachments.reserve(attachCount);
|
|
|
|
|
|
for (uint8_t j = 0; j < attachCount; ++j) {
|
|
|
|
|
|
MailAttachment att;
|
|
|
|
|
|
att.slot = packet.readUInt8();
|
|
|
|
|
|
att.itemGuidLow = packet.readUInt32();
|
|
|
|
|
|
att.itemId = packet.readUInt32();
|
|
|
|
|
|
for (int e = 0; e < 7; ++e) {
|
|
|
|
|
|
uint32_t enchId = packet.readUInt32();
|
|
|
|
|
|
packet.readUInt32(); // duration
|
|
|
|
|
|
packet.readUInt32(); // charges
|
|
|
|
|
|
if (e == 0) att.enchantId = enchId;
|
|
|
|
|
|
}
|
|
|
|
|
|
att.randomPropertyId = packet.readUInt32();
|
|
|
|
|
|
att.randomSuffix = packet.readUInt32();
|
|
|
|
|
|
att.stackCount = packet.readUInt32();
|
|
|
|
|
|
att.chargesOrDurability = packet.readUInt32();
|
|
|
|
|
|
att.maxDurability = packet.readUInt32();
|
|
|
|
|
|
msg.attachments.push_back(att);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msg.read = (msg.flags & 0x01) != 0;
|
|
|
|
|
|
inbox.push_back(std::move(msg));
|
|
|
|
|
|
|
|
|
|
|
|
// Skip unread bytes
|
|
|
|
|
|
size_t consumed = packet.getReadPos() - startPos;
|
|
|
|
|
|
if (consumed < msgSize) {
|
|
|
|
|
|
size_t skip = msgSize - consumed;
|
|
|
|
|
|
for (size_t s = 0; s < skip && packet.getReadPos() < packet.getSize(); ++s)
|
|
|
|
|
|
packet.readUInt8();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Parsed ", inbox.size(), " mail messages");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Bank System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet BankerActivatePacket::build(uint64_t guid) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_BANKER_ACTIVATE));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet BuyBankSlotPacket::build(uint64_t guid) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_BUY_BANK_SLOT));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AutoBankItemPacket::build(uint8_t srcBag, uint8_t srcSlot) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUTOBANK_ITEM));
|
|
|
|
|
|
p.writeUInt8(srcBag);
|
|
|
|
|
|
p.writeUInt8(srcSlot);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AutoStoreBankItemPacket::build(uint8_t srcBag, uint8_t srcSlot) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUTOSTORE_BANK_ITEM));
|
|
|
|
|
|
p.writeUInt8(srcBag);
|
|
|
|
|
|
p.writeUInt8(srcSlot);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Guild Bank System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankerActivatePacket::build(uint64_t guid) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANKER_ACTIVATE));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt8(0); // full slots update
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankQueryTabPacket::build(uint64_t guid, uint8_t tabId, bool fullUpdate) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_QUERY_TAB));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt8(tabId);
|
|
|
|
|
|
p.writeUInt8(fullUpdate ? 1 : 0);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankBuyTabPacket::build(uint64_t guid, uint8_t tabId) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_BUY_TAB));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt8(tabId);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankDepositMoneyPacket::build(uint64_t guid, uint32_t amount) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_DEPOSIT_MONEY));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt32(amount);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankWithdrawMoneyPacket::build(uint64_t guid, uint32_t amount) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_WITHDRAW_MONEY));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt32(amount);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankSwapItemsPacket::buildBankToInventory(
|
|
|
|
|
|
uint64_t guid, uint8_t tabId, uint8_t bankSlot,
|
|
|
|
|
|
uint8_t destBag, uint8_t destSlot, uint32_t splitCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_SWAP_ITEMS));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt8(0); // bankToCharacter = false -> bank source
|
|
|
|
|
|
p.writeUInt8(tabId);
|
|
|
|
|
|
p.writeUInt8(bankSlot);
|
|
|
|
|
|
p.writeUInt32(0); // itemEntry (unused client side)
|
|
|
|
|
|
p.writeUInt8(0); // autoStore = false
|
|
|
|
|
|
if (splitCount > 0) {
|
|
|
|
|
|
p.writeUInt8(splitCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
p.writeUInt8(destBag);
|
|
|
|
|
|
p.writeUInt8(destSlot);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet GuildBankSwapItemsPacket::buildInventoryToBank(
|
|
|
|
|
|
uint64_t guid, uint8_t tabId, uint8_t bankSlot,
|
|
|
|
|
|
uint8_t srcBag, uint8_t srcSlot, uint32_t splitCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_SWAP_ITEMS));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt8(1); // bankToCharacter = true -> char to bank
|
|
|
|
|
|
p.writeUInt8(tabId);
|
|
|
|
|
|
p.writeUInt8(bankSlot);
|
|
|
|
|
|
p.writeUInt32(0); // itemEntry
|
|
|
|
|
|
p.writeUInt8(0); // autoStore
|
|
|
|
|
|
if (splitCount > 0) {
|
|
|
|
|
|
p.writeUInt8(splitCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
p.writeUInt8(srcBag);
|
|
|
|
|
|
p.writeUInt8(srcSlot);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuildBankListParser::parse(network::Packet& packet, GuildBankData& data) {
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 14) return false;
|
|
|
|
|
|
|
|
|
|
|
|
data.money = packet.readUInt64();
|
|
|
|
|
|
data.tabId = packet.readUInt8();
|
|
|
|
|
|
data.withdrawAmount = static_cast<int32_t>(packet.readUInt32());
|
|
|
|
|
|
uint8_t fullUpdate = packet.readUInt8();
|
|
|
|
|
|
|
|
|
|
|
|
if (fullUpdate) {
|
2026-03-11 14:43:03 -07:00
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated before tabCount");
|
|
|
|
|
|
data.tabs.clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uint8_t tabCount = packet.readUInt8();
|
|
|
|
|
|
// Cap at 8 (normal guild bank tab limit in WoW)
|
|
|
|
|
|
if (tabCount > 8) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: tabCount capped (requested=", (int)tabCount, ")");
|
|
|
|
|
|
tabCount = 8;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.tabs.resize(tabCount);
|
|
|
|
|
|
for (uint8_t i = 0; i < tabCount; ++i) {
|
|
|
|
|
|
// Validate before reading strings
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated tab at index ", (int)i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.tabs[i].tabName = packet.readString();
|
|
|
|
|
|
if (packet.getReadPos() >= packet.getSize()) {
|
|
|
|
|
|
data.tabs[i].tabIcon.clear();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.tabs[i].tabIcon = packet.readString();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:43:03 -07:00
|
|
|
|
if (packet.getReadPos() + 1 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated before numSlots");
|
|
|
|
|
|
data.tabItems.clear();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
uint8_t numSlots = packet.readUInt8();
|
|
|
|
|
|
data.tabItems.clear();
|
|
|
|
|
|
for (uint8_t i = 0; i < numSlots; ++i) {
|
2026-03-11 14:43:03 -07:00
|
|
|
|
// Validate minimum bytes before reading slot (slotId(1) + itemEntry(4) = 5)
|
|
|
|
|
|
if (packet.getReadPos() + 5 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated slot at index ", (int)i);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
GuildBankItemSlot slot;
|
|
|
|
|
|
slot.slotId = packet.readUInt8();
|
|
|
|
|
|
slot.itemEntry = packet.readUInt32();
|
|
|
|
|
|
if (slot.itemEntry != 0) {
|
2026-03-11 14:43:03 -07:00
|
|
|
|
// Validate before reading enchant mask
|
|
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) break;
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// Enchant info
|
|
|
|
|
|
uint32_t enchantMask = packet.readUInt32();
|
|
|
|
|
|
for (int bit = 0; bit < 10; ++bit) {
|
|
|
|
|
|
if (enchantMask & (1u << bit)) {
|
2026-03-11 14:43:03 -07:00
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated enchant data");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
uint32_t enchId = packet.readUInt32();
|
|
|
|
|
|
uint32_t enchDur = packet.readUInt32();
|
|
|
|
|
|
uint32_t enchCharges = packet.readUInt32();
|
|
|
|
|
|
if (bit == 0) slot.enchantId = enchId;
|
|
|
|
|
|
(void)enchDur; (void)enchCharges;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-11 14:43:03 -07:00
|
|
|
|
// Validate before reading remaining item fields
|
|
|
|
|
|
if (packet.getReadPos() + 12 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated item fields");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
slot.stackCount = packet.readUInt32();
|
|
|
|
|
|
/*spare=*/ packet.readUInt32();
|
|
|
|
|
|
slot.randomPropertyId = packet.readUInt32();
|
|
|
|
|
|
if (slot.randomPropertyId) {
|
2026-03-11 14:43:03 -07:00
|
|
|
|
if (packet.getReadPos() + 4 > packet.getSize()) {
|
|
|
|
|
|
LOG_WARNING("GuildBankListParser: truncated suffix factor");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
/*suffixFactor=*/ packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
data.tabItems.push_back(slot);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// Auction House System
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionHelloPacket::build(uint64_t guid) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::MSG_AUCTION_HELLO));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AuctionHelloParser::parse(network::Packet& packet, AuctionHelloData& data) {
|
|
|
|
|
|
size_t remaining = packet.getSize() - packet.getReadPos();
|
|
|
|
|
|
if (remaining < 12) {
|
|
|
|
|
|
LOG_WARNING("AuctionHelloParser: too small, remaining=", remaining);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
data.auctioneerGuid = packet.readUInt64();
|
|
|
|
|
|
data.auctionHouseId = packet.readUInt32();
|
|
|
|
|
|
// WotLK has an extra uint8 enabled field; Vanilla does not
|
|
|
|
|
|
if (packet.getReadPos() < packet.getSize()) {
|
|
|
|
|
|
data.enabled = packet.readUInt8();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.enabled = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionListItemsPacket::build(
|
|
|
|
|
|
uint64_t guid, uint32_t offset,
|
|
|
|
|
|
const std::string& searchName,
|
|
|
|
|
|
uint8_t levelMin, uint8_t levelMax,
|
|
|
|
|
|
uint32_t invTypeMask, uint32_t itemClass,
|
|
|
|
|
|
uint32_t itemSubClass, uint32_t quality,
|
|
|
|
|
|
uint8_t usableOnly, uint8_t exactMatch)
|
|
|
|
|
|
{
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_LIST_ITEMS));
|
|
|
|
|
|
p.writeUInt64(guid);
|
|
|
|
|
|
p.writeUInt32(offset);
|
|
|
|
|
|
p.writeString(searchName);
|
|
|
|
|
|
p.writeUInt8(levelMin);
|
|
|
|
|
|
p.writeUInt8(levelMax);
|
|
|
|
|
|
p.writeUInt32(invTypeMask);
|
|
|
|
|
|
p.writeUInt32(itemClass);
|
|
|
|
|
|
p.writeUInt32(itemSubClass);
|
|
|
|
|
|
p.writeUInt32(quality);
|
|
|
|
|
|
p.writeUInt8(usableOnly);
|
|
|
|
|
|
p.writeUInt8(0); // getAll (0 = normal search)
|
2026-02-23 19:16:47 -08:00
|
|
|
|
p.writeUInt8(exactMatch);
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
// Sort columns (0 = none)
|
|
|
|
|
|
p.writeUInt8(0);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionSellItemPacket::build(
|
|
|
|
|
|
uint64_t auctioneerGuid, uint64_t itemGuid,
|
|
|
|
|
|
uint32_t stackCount, uint32_t bid,
|
|
|
|
|
|
uint32_t buyout, uint32_t duration)
|
|
|
|
|
|
{
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_SELL_ITEM));
|
|
|
|
|
|
p.writeUInt64(auctioneerGuid);
|
|
|
|
|
|
p.writeUInt32(1); // item count (WotLK supports multiple, we send 1)
|
|
|
|
|
|
p.writeUInt64(itemGuid);
|
|
|
|
|
|
p.writeUInt32(stackCount);
|
|
|
|
|
|
p.writeUInt32(bid);
|
|
|
|
|
|
p.writeUInt32(buyout);
|
|
|
|
|
|
p.writeUInt32(duration);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionPlaceBidPacket::build(uint64_t auctioneerGuid, uint32_t auctionId, uint32_t amount) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_PLACE_BID));
|
|
|
|
|
|
p.writeUInt64(auctioneerGuid);
|
|
|
|
|
|
p.writeUInt32(auctionId);
|
|
|
|
|
|
p.writeUInt32(amount);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionRemoveItemPacket::build(uint64_t auctioneerGuid, uint32_t auctionId) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_REMOVE_ITEM));
|
|
|
|
|
|
p.writeUInt64(auctioneerGuid);
|
|
|
|
|
|
p.writeUInt32(auctionId);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionListOwnerItemsPacket::build(uint64_t auctioneerGuid, uint32_t offset) {
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_LIST_OWNER_ITEMS));
|
|
|
|
|
|
p.writeUInt64(auctioneerGuid);
|
|
|
|
|
|
p.writeUInt32(offset);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
network::Packet AuctionListBidderItemsPacket::build(
|
|
|
|
|
|
uint64_t auctioneerGuid, uint32_t offset,
|
|
|
|
|
|
const std::vector<uint32_t>& outbiddedIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_LIST_BIDDER_ITEMS));
|
|
|
|
|
|
p.writeUInt64(auctioneerGuid);
|
|
|
|
|
|
p.writeUInt32(offset);
|
|
|
|
|
|
p.writeUInt32(static_cast<uint32_t>(outbiddedIds.size()));
|
|
|
|
|
|
for (uint32_t id : outbiddedIds)
|
|
|
|
|
|
p.writeUInt32(id);
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 01:25:27 -07:00
|
|
|
|
bool AuctionListResultParser::parse(network::Packet& packet, AuctionListResult& data, int numEnchantSlots) {
|
|
|
|
|
|
// Per-entry fixed size: auctionId(4) + itemEntry(4) + enchantSlots×3×4 +
|
|
|
|
|
|
// randProp(4) + suffix(4) + stack(4) + charges(4) + flags(4) +
|
|
|
|
|
|
// ownerGuid(8) + startBid(4) + outbid(4) + buyout(4) + expire(4) +
|
|
|
|
|
|
// bidderGuid(8) + curBid(4)
|
|
|
|
|
|
// Classic: numEnchantSlots=1 → 80 bytes/entry
|
|
|
|
|
|
// TBC/WotLK: numEnchantSlots=3 → 104 bytes/entry
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 4) return false;
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t count = packet.readUInt32();
|
2026-03-11 14:37:27 -07:00
|
|
|
|
// Cap auction count to prevent unbounded memory allocation
|
|
|
|
|
|
const uint32_t MAX_AUCTION_RESULTS = 256;
|
|
|
|
|
|
if (count > MAX_AUCTION_RESULTS) {
|
|
|
|
|
|
LOG_WARNING("AuctionListResultParser: count capped (requested=", count, ")");
|
|
|
|
|
|
count = MAX_AUCTION_RESULTS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
data.auctions.clear();
|
|
|
|
|
|
data.auctions.reserve(count);
|
|
|
|
|
|
|
2026-03-10 01:25:27 -07:00
|
|
|
|
const size_t minPerEntry = static_cast<size_t>(8 + numEnchantSlots * 12 + 28 + 8 + 8);
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
2026-03-10 01:25:27 -07:00
|
|
|
|
if (packet.getReadPos() + minPerEntry > packet.getSize()) break;
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
AuctionEntry e;
|
|
|
|
|
|
e.auctionId = packet.readUInt32();
|
|
|
|
|
|
e.itemEntry = packet.readUInt32();
|
2026-03-10 01:25:27 -07:00
|
|
|
|
// First enchant slot always present
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
e.enchantId = packet.readUInt32();
|
2026-03-10 01:25:27 -07:00
|
|
|
|
packet.readUInt32(); // enchant1 duration
|
|
|
|
|
|
packet.readUInt32(); // enchant1 charges
|
|
|
|
|
|
// Extra enchant slots for TBC/WotLK
|
|
|
|
|
|
for (int s = 1; s < numEnchantSlots; ++s) {
|
|
|
|
|
|
packet.readUInt32(); // enchant N id
|
|
|
|
|
|
packet.readUInt32(); // enchant N duration
|
|
|
|
|
|
packet.readUInt32(); // enchant N charges
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
e.randomPropertyId = packet.readUInt32();
|
2026-03-10 01:25:27 -07:00
|
|
|
|
e.suffixFactor = packet.readUInt32();
|
|
|
|
|
|
e.stackCount = packet.readUInt32();
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
packet.readUInt32(); // item charges
|
|
|
|
|
|
packet.readUInt32(); // item flags (unused)
|
2026-03-10 01:25:27 -07:00
|
|
|
|
e.ownerGuid = packet.readUInt64();
|
|
|
|
|
|
e.startBid = packet.readUInt32();
|
|
|
|
|
|
e.minBidIncrement = packet.readUInt32();
|
|
|
|
|
|
e.buyoutPrice = packet.readUInt32();
|
|
|
|
|
|
e.timeLeftMs = packet.readUInt32();
|
|
|
|
|
|
e.bidderGuid = packet.readUInt64();
|
|
|
|
|
|
e.currentBid = packet.readUInt32();
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
data.auctions.push_back(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 01:25:27 -07:00
|
|
|
|
if (packet.getSize() - packet.getReadPos() >= 8) {
|
|
|
|
|
|
data.totalCount = packet.readUInt32();
|
|
|
|
|
|
data.searchDelay = packet.readUInt32();
|
|
|
|
|
|
}
|
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory
extension with 28 bank slots + 7 bank bags, and UI windows for personal
bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid).
Fix Classic gossip parser to omit boxMoney/boxText fields not present in
Vanilla protocol, fix gossip icon labels with text-based NPC type detection,
and add Turtle WoW opcode mappings for bank and auction interactions.
2026-02-16 21:11:18 -08:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool AuctionCommandResultParser::parse(network::Packet& packet, AuctionCommandResult& data) {
|
|
|
|
|
|
if (packet.getSize() - packet.getReadPos() < 12) return false;
|
|
|
|
|
|
data.auctionId = packet.readUInt32();
|
|
|
|
|
|
data.action = packet.readUInt32();
|
|
|
|
|
|
data.errorCode = packet.readUInt32();
|
|
|
|
|
|
if (data.errorCode != 0 && data.action == 2 && packet.getReadPos() + 4 <= packet.getSize()) {
|
|
|
|
|
|
data.bidError = packet.readUInt32();
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
} // namespace game
|
|
|
|
|
|
} // namespace wowee
|