From 22513505faa79e35833c3805aa1e99261e2e7cab Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 15:23:02 -0700 Subject: [PATCH] Handle 50+ missing SMSG opcodes for logout, guild, talents, items, LFG, and GM tickets - SMSG_LOGOUT_CANCEL_ACK: consume server acknowledgment - SMSG_GUILD_DECLINE: show decliner name in chat - SMSG_TALENTS_INVOLUNTARILY_RESET: show reset notification - SMSG_UPDATE_ACCOUNT_DATA / COMPLETE: consume account data sync - SMSG_SET_REST_START: show resting state change message - SMSG_UPDATE_AURA_DURATION: update aura slot duration + timestamp - SMSG_ITEM_NAME_QUERY_RESPONSE: cache item name in itemInfoCache_ - SMSG_MOUNTSPECIAL_ANIM: consume packed GUID - SMSG_CHAR_CUSTOMIZE / SMSG_CHAR_FACTION_CHANGE: show result messages - SMSG_INVALIDATE_PLAYER: evict player name cache entry - SMSG_TRIGGER_MOVIE: consume - SMSG_EQUIPMENT_SET_LIST: parse and store equipment sets - SMSG_EQUIPMENT_SET_USE_RESULT: show failure message if non-zero - SMSG_LFG_UPDATE / LFG / LFM / QUEUED / PENDING_*: consume - SMSG_GMTICKET_CREATE / UPDATETEXT / DELETETICKET: show result messages - SMSG_GMTICKET_GETTICKET / SYSTEMSTATUS: consume - SMSG_ADD_RUNE_POWER / SMSG_RESYNC_RUNES: consume (DK rune tracking) - SMSG_AURACASTLOG, SMSG_SPELL*LOG*, SMSG_SPELL_CHANCE_*: consume - SMSG_CLEAR_EXTRA_AURA_INFO / COMPLAIN_RESULT / ITEM_REFUND_INFO_RESPONSE: consume - SMSG_ITEM_ENCHANT_TIME_UPDATE / LOOT_LIST / RESUME_CAST_BAR: consume - SMSG_THREAT_UPDATE / UPDATE_INSTANCE_* / SEND_ALL_COMBAT_LOG: consume - SMSG_SET_PROJECTILE_POSITION / AUCTION_LIST_PENDING_SALES: consume - SMSG_SERVER_FIRST_ACHIEVEMENT: parse name + achievement ID, show message - SMSG_SET_FORCED_REACTIONS: parse and store forced faction reaction overrides - SMSG_SPLINE_SET_FLIGHT/SWIM_BACK/WALK_SPEED / TURN_RATE / PITCH_RATE: consume - SMSG_SPLINE_MOVE_UNROOT / UNSET_FLYING / UNSET_HOVER / WATER_WALK: consume - SMSG_MOVE_GRAVITY_*/LAND_WALK/NORMAL_FALL/CAN_TRANSITION/COLLISION_HGT/FLIGHT: consume Adds EquipmentSet struct + equipmentSets_ storage, forcedReactions_ map. --- include/game/game_handler.hpp | 19 ++ src/game/game_handler.cpp | 315 ++++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 86e2687d..5aa6c560 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1258,6 +1258,11 @@ private: void handleSpellDamageLog(network::Packet& packet); void handleSpellHealLog(network::Packet& packet); + // ---- Equipment set handler ---- + void handleEquipmentSetList(network::Packet& packet); + void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs); + void handleSetForcedReactions(network::Packet& packet); + // ---- Phase 3 handlers ---- void handleInitialSpells(network::Packet& packet); void handleCastFailed(network::Packet& packet); @@ -2062,6 +2067,20 @@ private: uint64_t resurrectCasterGuid_ = 0; bool repopPending_ = false; uint64_t lastRepopRequestMs_ = 0; + + // ---- Equipment sets (SMSG_EQUIPMENT_SET_LIST) ---- + struct EquipmentSet { + uint64_t setGuid = 0; + uint32_t setId = 0; + std::string name; + std::string iconName; + uint32_t ignoreSlotMask = 0; + std::array itemGuids{}; + }; + std::vector equipmentSets_; + + // ---- Forced faction reactions (SMSG_SET_FORCED_REACTIONS) ---- + std::unordered_map forcedReactions_; // factionId -> reaction tier }; } // namespace game diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 12150b0d..6d806403 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4173,6 +4173,255 @@ void GameHandler::handlePacket(network::Packet& packet) { packet.setReadPos(packet.getSize()); break; + // ---- Logout cancel ACK ---- + case Opcode::SMSG_LOGOUT_CANCEL_ACK: + // loggingOut_ already cleared by cancelLogout(); this is server's confirmation + packet.setReadPos(packet.getSize()); + break; + + // ---- Guild decline ---- + case Opcode::SMSG_GUILD_DECLINE: { + if (packet.getReadPos() < packet.getSize()) { + std::string name = packet.readString(); + addSystemChatMessage(name + " declined your guild invitation."); + } + break; + } + + // ---- Talents involuntarily reset ---- + case Opcode::SMSG_TALENTS_INVOLUNTARILY_RESET: + addSystemChatMessage("Your talents have been reset by the server."); + packet.setReadPos(packet.getSize()); + break; + + // ---- Account data sync ---- + case Opcode::SMSG_UPDATE_ACCOUNT_DATA: + case Opcode::SMSG_UPDATE_ACCOUNT_DATA_COMPLETE: + packet.setReadPos(packet.getSize()); + break; + + // ---- Rest state ---- + case Opcode::SMSG_SET_REST_START: { + if (packet.getSize() - packet.getReadPos() >= 4) { + uint32_t restTrigger = packet.readUInt32(); + addSystemChatMessage(restTrigger > 0 ? "You are now resting." + : "You are no longer resting."); + } + break; + } + + // ---- Aura duration update ---- + case Opcode::SMSG_UPDATE_AURA_DURATION: { + if (packet.getSize() - packet.getReadPos() >= 5) { + uint8_t slot = packet.readUInt8(); + uint32_t durationMs = packet.readUInt32(); + handleUpdateAuraDuration(slot, durationMs); + } + break; + } + + // ---- Item name query response ---- + case Opcode::SMSG_ITEM_NAME_QUERY_RESPONSE: { + if (packet.getSize() - packet.getReadPos() >= 4) { + uint32_t itemId = packet.readUInt32(); + std::string name = packet.readString(); + if (!itemInfoCache_.count(itemId) && !name.empty()) { + ItemQueryResponseData stub; + stub.entry = itemId; + stub.name = std::move(name); + stub.valid = true; + itemInfoCache_[itemId] = std::move(stub); + } + } + packet.setReadPos(packet.getSize()); + break; + } + + // ---- Mount special animation ---- + case Opcode::SMSG_MOUNTSPECIAL_ANIM: + (void)UpdateObjectParser::readPackedGuid(packet); + break; + + // ---- Character customisation / faction change results ---- + case Opcode::SMSG_CHAR_CUSTOMIZE: { + if (packet.getSize() - packet.getReadPos() >= 1) { + uint8_t result = packet.readUInt8(); + addSystemChatMessage(result == 0 ? "Character customization complete." + : "Character customization failed."); + } + packet.setReadPos(packet.getSize()); + break; + } + case Opcode::SMSG_CHAR_FACTION_CHANGE: { + if (packet.getSize() - packet.getReadPos() >= 1) { + uint8_t result = packet.readUInt8(); + addSystemChatMessage(result == 0 ? "Faction change complete." + : "Faction change failed."); + } + packet.setReadPos(packet.getSize()); + break; + } + + // ---- Invalidate cached player data ---- + case Opcode::SMSG_INVALIDATE_PLAYER: { + if (packet.getSize() - packet.getReadPos() >= 8) { + uint64_t guid = packet.readUInt64(); + playerNameCache.erase(guid); + } + break; + } + + // ---- Movie trigger ---- + case Opcode::SMSG_TRIGGER_MOVIE: + packet.setReadPos(packet.getSize()); + break; + + // ---- Equipment sets ---- + case Opcode::SMSG_EQUIPMENT_SET_LIST: + handleEquipmentSetList(packet); + break; + case Opcode::SMSG_EQUIPMENT_SET_USE_RESULT: { + if (packet.getSize() - packet.getReadPos() >= 1) { + uint8_t result = packet.readUInt8(); + if (result != 0) addSystemChatMessage("Failed to equip item set."); + } + break; + } + + // ---- LFG informational (not yet surfaced in UI) ---- + case Opcode::SMSG_LFG_UPDATE: + case Opcode::SMSG_LFG_UPDATE_LFG: + case Opcode::SMSG_LFG_UPDATE_LFM: + case Opcode::SMSG_LFG_UPDATE_QUEUED: + case Opcode::SMSG_LFG_PENDING_INVITE: + case Opcode::SMSG_LFG_PENDING_MATCH: + case Opcode::SMSG_LFG_PENDING_MATCH_DONE: + packet.setReadPos(packet.getSize()); + break; + + // ---- GM Ticket responses ---- + case Opcode::SMSG_GMTICKET_CREATE: { + if (packet.getSize() - packet.getReadPos() >= 1) { + uint8_t res = packet.readUInt8(); + addSystemChatMessage(res == 1 ? "GM ticket submitted." + : "Failed to submit GM ticket."); + } + break; + } + case Opcode::SMSG_GMTICKET_UPDATETEXT: { + if (packet.getSize() - packet.getReadPos() >= 1) { + uint8_t res = packet.readUInt8(); + addSystemChatMessage(res == 1 ? "GM ticket updated." + : "Failed to update GM ticket."); + } + break; + } + case Opcode::SMSG_GMTICKET_DELETETICKET: { + if (packet.getSize() - packet.getReadPos() >= 1) { + uint8_t res = packet.readUInt8(); + addSystemChatMessage(res == 9 ? "GM ticket deleted." + : "No ticket to delete."); + } + break; + } + case Opcode::SMSG_GMTICKET_GETTICKET: + case Opcode::SMSG_GMTICKET_SYSTEMSTATUS: + packet.setReadPos(packet.getSize()); + break; + + // ---- DK rune tracking (not yet implemented) ---- + case Opcode::SMSG_ADD_RUNE_POWER: + case Opcode::SMSG_RESYNC_RUNES: + packet.setReadPos(packet.getSize()); + break; + + // ---- Spell combat logs (consume) ---- + case Opcode::SMSG_AURACASTLOG: + case Opcode::SMSG_SPELLBREAKLOG: + case Opcode::SMSG_SPELLDAMAGESHIELD: + case Opcode::SMSG_SPELLDISPELLOG: + case Opcode::SMSG_SPELLINSTAKILLLOG: + case Opcode::SMSG_SPELLLOGEXECUTE: + case Opcode::SMSG_SPELLORDAMAGE_IMMUNE: + case Opcode::SMSG_SPELLSTEALLOG: + case Opcode::SMSG_SPELL_CHANCE_PROC_LOG: + case Opcode::SMSG_SPELL_CHANCE_RESIST_PUSHBACK: + case Opcode::SMSG_SPELL_UPDATE_CHAIN_TARGETS: + packet.setReadPos(packet.getSize()); + break; + + // ---- Misc consume ---- + case Opcode::SMSG_CLEAR_EXTRA_AURA_INFO: + case Opcode::SMSG_COMPLAIN_RESULT: + case Opcode::SMSG_ITEM_REFUND_INFO_RESPONSE: + case Opcode::SMSG_ITEM_ENCHANT_TIME_UPDATE: + case Opcode::SMSG_LOOT_LIST: + case Opcode::SMSG_RESUME_CAST_BAR: + case Opcode::SMSG_THREAT_UPDATE: + case Opcode::SMSG_UPDATE_INSTANCE_OWNERSHIP: + case Opcode::SMSG_UPDATE_LAST_INSTANCE: + case Opcode::SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT: + case Opcode::SMSG_SEND_ALL_COMBAT_LOG: + case Opcode::SMSG_SET_PROJECTILE_POSITION: + case Opcode::SMSG_AUCTION_LIST_PENDING_SALES: + packet.setReadPos(packet.getSize()); + break; + + // ---- Server-first achievement broadcast ---- + case Opcode::SMSG_SERVER_FIRST_ACHIEVEMENT: { + // charName (cstring) + guid (uint64) + achievementId (uint32) + ... + if (packet.getReadPos() < packet.getSize()) { + std::string charName = packet.readString(); + if (packet.getSize() - packet.getReadPos() >= 12) { + /*uint64_t guid =*/ packet.readUInt64(); + uint32_t achievementId = packet.readUInt32(); + char buf[192]; + std::snprintf(buf, sizeof(buf), + "%s is the first on the realm to earn achievement #%u!", + charName.c_str(), achievementId); + addSystemChatMessage(buf); + } + } + packet.setReadPos(packet.getSize()); + break; + } + + // ---- Forced faction reactions ---- + case Opcode::SMSG_SET_FORCED_REACTIONS: + handleSetForcedReactions(packet); + break; + + // ---- Spline speed changes for other units ---- + case Opcode::SMSG_SPLINE_SET_FLIGHT_SPEED: + case Opcode::SMSG_SPLINE_SET_FLIGHT_BACK_SPEED: + case Opcode::SMSG_SPLINE_SET_SWIM_BACK_SPEED: + case Opcode::SMSG_SPLINE_SET_WALK_SPEED: + case Opcode::SMSG_SPLINE_SET_TURN_RATE: + case Opcode::SMSG_SPLINE_SET_PITCH_RATE: + packet.setReadPos(packet.getSize()); + break; + + // ---- Spline move flag changes for other units ---- + case Opcode::SMSG_SPLINE_MOVE_UNROOT: + case Opcode::SMSG_SPLINE_MOVE_UNSET_FLYING: + case Opcode::SMSG_SPLINE_MOVE_UNSET_HOVER: + case Opcode::SMSG_SPLINE_MOVE_WATER_WALK: + packet.setReadPos(packet.getSize()); + break; + + // ---- Player movement flag changes (server-pushed) ---- + case Opcode::SMSG_MOVE_GRAVITY_DISABLE: + case Opcode::SMSG_MOVE_GRAVITY_ENABLE: + case Opcode::SMSG_MOVE_LAND_WALK: + case Opcode::SMSG_MOVE_NORMAL_FALL: + case Opcode::SMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + case Opcode::SMSG_MOVE_UNSET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY: + case Opcode::SMSG_MOVE_SET_COLLISION_HGT: + case Opcode::SMSG_MOVE_SET_FLIGHT: + case Opcode::SMSG_MOVE_UNSET_FLIGHT: + packet.setReadPos(packet.getSize()); + break; + default: // In pre-world states we need full visibility (char create/login handshakes). // In-world we keep de-duplication to avoid heavy log I/O in busy areas. @@ -16444,5 +16693,71 @@ const std::string& GameHandler::getFactionNamePublic(uint32_t factionId) const { return empty; } +// --------------------------------------------------------------------------- +// Aura duration update +// --------------------------------------------------------------------------- + +void GameHandler::handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs) { + if (slot >= playerAuras.size()) return; + if (playerAuras[slot].isEmpty()) return; + playerAuras[slot].durationMs = static_cast(durationMs); + playerAuras[slot].receivedAtMs = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); +} + +// --------------------------------------------------------------------------- +// Equipment set list +// --------------------------------------------------------------------------- + +void GameHandler::handleEquipmentSetList(network::Packet& packet) { + if (packet.getSize() - packet.getReadPos() < 4) return; + uint32_t count = packet.readUInt32(); + if (count > 10) { + LOG_WARNING("SMSG_EQUIPMENT_SET_LIST: unexpected count ", count, ", ignoring"); + packet.setReadPos(packet.getSize()); + return; + } + equipmentSets_.clear(); + equipmentSets_.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + if (packet.getSize() - packet.getReadPos() < 16) break; + EquipmentSet es; + es.setGuid = packet.readUInt64(); + es.setId = packet.readUInt32(); + es.name = packet.readString(); + es.iconName = packet.readString(); + es.ignoreSlotMask = packet.readUInt32(); + for (int slot = 0; slot < 19; ++slot) { + if (packet.getSize() - packet.getReadPos() < 8) break; + es.itemGuids[slot] = packet.readUInt64(); + } + equipmentSets_.push_back(std::move(es)); + } + LOG_INFO("SMSG_EQUIPMENT_SET_LIST: ", equipmentSets_.size(), " equipment sets received"); +} + +// --------------------------------------------------------------------------- +// Forced faction reactions +// --------------------------------------------------------------------------- + +void GameHandler::handleSetForcedReactions(network::Packet& packet) { + if (packet.getSize() - packet.getReadPos() < 4) return; + uint32_t count = packet.readUInt32(); + if (count > 64) { + LOG_WARNING("SMSG_SET_FORCED_REACTIONS: suspicious count ", count, ", ignoring"); + packet.setReadPos(packet.getSize()); + return; + } + forcedReactions_.clear(); + for (uint32_t i = 0; i < count; ++i) { + if (packet.getSize() - packet.getReadPos() < 8) break; + uint32_t factionId = packet.readUInt32(); + uint32_t reaction = packet.readUInt32(); + forcedReactions_[factionId] = static_cast(reaction); + } + LOG_INFO("SMSG_SET_FORCED_REACTIONS: ", forcedReactions_.size(), " faction overrides"); +} + } // namespace game } // namespace wowee