diff --git a/Data/expansions/classic/opcodes.json b/Data/expansions/classic/opcodes.json index 025ea4f5..f5ccd758 100644 --- a/Data/expansions/classic/opcodes.json +++ b/Data/expansions/classic/opcodes.json @@ -101,6 +101,7 @@ "SMSG_ATTACKSTART": "0x143", "SMSG_ATTACKSTOP": "0x144", "SMSG_ATTACKERSTATEUPDATE": "0x14A", + "SMSG_PARTYKILLLOG": "0x1F5", "SMSG_SPELLNONMELEEDAMAGELOG": "0x250", "SMSG_SPELLHEALLOG": "0x150", "SMSG_SPELLENERGIZELOG": "0x151", diff --git a/Data/expansions/tbc/opcodes.json b/Data/expansions/tbc/opcodes.json index d0c17cd6..950ba606 100644 --- a/Data/expansions/tbc/opcodes.json +++ b/Data/expansions/tbc/opcodes.json @@ -102,6 +102,7 @@ "SMSG_ATTACKSTART": "0x143", "SMSG_ATTACKSTOP": "0x144", "SMSG_ATTACKERSTATEUPDATE": "0x14A", + "SMSG_PARTYKILLLOG": "0x1F5", "SMSG_SPELLNONMELEEDAMAGELOG": "0x250", "SMSG_SPELLHEALLOG": "0x150", "SMSG_SPELLENERGIZELOG": "0x151", diff --git a/Data/expansions/turtle/opcodes.json b/Data/expansions/turtle/opcodes.json index dab26819..6ea9ca22 100644 --- a/Data/expansions/turtle/opcodes.json +++ b/Data/expansions/turtle/opcodes.json @@ -38,7 +38,7 @@ "SMSG_ACCOUNT_DATA_TIMES": "0x209", "SMSG_UPDATE_OBJECT": "0x0A9", "SMSG_COMPRESSED_UPDATE_OBJECT": "0x1F6", - "SMSG_UNKNOWN_1F5": "0x1F5", + "SMSG_PARTYKILLLOG": "0x1F5", "SMSG_MONSTER_MOVE_TRANSPORT": "0x2AE", "SMSG_SPLINE_MOVE_SET_WALK_MODE": "0x30E", "SMSG_SPLINE_MOVE_SET_RUN_MODE": "0x30D", diff --git a/Data/expansions/wotlk/opcodes.json b/Data/expansions/wotlk/opcodes.json index 267f46e7..2f597ae6 100644 --- a/Data/expansions/wotlk/opcodes.json +++ b/Data/expansions/wotlk/opcodes.json @@ -104,6 +104,7 @@ "SMSG_ATTACKSTART": "0x143", "SMSG_ATTACKSTOP": "0x144", "SMSG_ATTACKERSTATEUPDATE": "0x14A", + "SMSG_PARTYKILLLOG": "0x1F5", "SMSG_SPELLNONMELEEDAMAGELOG": "0x250", "SMSG_SPELLHEALLOG": "0x150", "SMSG_SPELLENERGIZELOG": "0x151", diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index ddfe9de3..1a047378 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace wowee::game { class TransportManager; @@ -1237,6 +1238,8 @@ private: // Movement MovementInfo movementInfo; // Current player movement state uint32_t movementTime = 0; // Movement timestamp counter + std::chrono::steady_clock::time_point movementClockStart_ = std::chrono::steady_clock::now(); + uint32_t lastMovementTimestampMs_ = 0; bool serverMovementAllowed_ = true; // Inventory @@ -1518,6 +1521,7 @@ private: std::unordered_map taxiCostMap_; // destNodeId -> total cost in copper void buildTaxiCostMap(); void applyTaxiMountForCurrentNode(); + uint32_t nextMovementTimestampMs(); void sanitizeMovementForTaxi(); void startClientTaxiPath(const std::vector& pathNodes); void updateClientTaxi(float deltaTime); diff --git a/include/game/opcode_table.hpp b/include/game/opcode_table.hpp index 1e17ed5b..d3861522 100644 --- a/include/game/opcode_table.hpp +++ b/include/game/opcode_table.hpp @@ -65,7 +65,6 @@ enum class LogicalOpcode : uint16_t { // ---- Entity/Object updates ---- SMSG_UPDATE_OBJECT, SMSG_COMPRESSED_UPDATE_OBJECT, - SMSG_UNKNOWN_1F5, SMSG_MONSTER_MOVE_TRANSPORT, SMSG_SPLINE_MOVE_SET_WALK_MODE, SMSG_SPLINE_MOVE_SET_RUN_MODE, @@ -226,6 +225,7 @@ enum class LogicalOpcode : uint16_t { CMSG_GROUP_DISBAND, SMSG_GROUP_LIST, SMSG_PARTY_COMMAND_RESULT, + SMSG_PARTYKILLLOG, MSG_RAID_TARGET_UPDATE, CMSG_REQUEST_RAID_INFO, SMSG_RAID_INSTANCE_INFO, diff --git a/src/auth/auth_handler.cpp b/src/auth/auth_handler.cpp index 2e6d89b1..a6ad394a 100644 --- a/src/auth/auth_handler.cpp +++ b/src/auth/auth_handler.cpp @@ -406,7 +406,7 @@ void AuthHandler::handleRealmListResponse(network::Packet& packet) { void AuthHandler::handlePacket(network::Packet& packet) { if (packet.getSize() < 1) { - LOG_WARNING("Received empty packet"); + LOG_DEBUG("Received empty auth packet (ignored)"); return; } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 7d1610c4..ef1519a0 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1004,7 +1004,7 @@ void GameHandler::update(float deltaTime) { void GameHandler::handlePacket(network::Packet& packet) { if (packet.getSize() < 1) { - LOG_WARNING("Received empty packet"); + LOG_DEBUG("Received empty world packet (ignored)"); return; } @@ -1249,11 +1249,6 @@ void GameHandler::handlePacket(network::Packet& packet) { handleCompressedUpdateObject(packet); } break; - case Opcode::SMSG_UNKNOWN_1F5: - // Observed custom server packet (16 bytes). Consume safely for stream alignment. - packet.setReadPos(packet.getSize()); - break; - case Opcode::SMSG_DESTROY_OBJECT: // Can be received after entering world if (state == WorldState::IN_WORLD) { @@ -1565,6 +1560,11 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_PARTY_COMMAND_RESULT: handlePartyCommandResult(packet); break; + case Opcode::SMSG_PARTYKILLLOG: + // Classic-era packet: killer GUID + victim GUID. + // XP and combat state are handled by other packets; consume to avoid warning spam. + packet.setReadPos(packet.getSize()); + break; // ---- Guild ---- case Opcode::SMSG_GUILD_INFO: @@ -2986,7 +2986,9 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) { movementInfo.orientation = core::coords::serverToCanonicalYaw(data.orientation); movementInfo.flags = 0; movementInfo.flags2 = 0; - movementInfo.time = 0; + movementClockStart_ = std::chrono::steady_clock::now(); + lastMovementTimestampMs_ = 0; + movementInfo.time = nextMovementTimestampMs(); resurrectPending_ = false; resurrectRequestPending_ = false; onTaxiFlight_ = false; @@ -3991,6 +3993,28 @@ void GameHandler::handlePong(network::Packet& packet) { LOG_DEBUG("Heartbeat acknowledged (sequence: ", data.sequence, ")"); } +uint32_t GameHandler::nextMovementTimestampMs() { + auto now = std::chrono::steady_clock::now(); + uint64_t elapsed = static_cast( + std::chrono::duration_cast(now - movementClockStart_).count()) + 1ULL; + if (elapsed > std::numeric_limits::max()) { + movementClockStart_ = now; + elapsed = 1ULL; + } + + uint32_t candidate = static_cast(elapsed); + if (candidate <= lastMovementTimestampMs_) { + candidate = lastMovementTimestampMs_ + 1U; + if (candidate == 0) { + movementClockStart_ = now; + candidate = 1U; + } + } + + lastMovementTimestampMs_ = candidate; + return candidate; +} + void GameHandler::sendMovement(Opcode opcode) { if (state != WorldState::IN_WORLD) { LOG_WARNING("Cannot send movement in state: ", (int)state); @@ -4009,11 +4033,8 @@ void GameHandler::sendMovement(Opcode opcode) { if ((onTaxiFlight_ || taxiMountActive_) && !taxiAllowed) return; if (resurrectPending_ && !taxiAllowed) return; - // Use real millisecond timestamp (server validates for anti-cheat) - static auto startTime = std::chrono::steady_clock::now(); - auto now = std::chrono::steady_clock::now(); - movementInfo.time = static_cast( - std::chrono::duration_cast(now - startTime).count()); + // Always send a strictly increasing non-zero client movement clock value. + movementInfo.time = nextMovementTimestampMs(); // Update movement flags based on opcode switch (opcode) { @@ -7831,6 +7852,11 @@ void GameHandler::handleForceRunSpeedChange(network::Packet& packet) { ack.writeUInt32(counter); MovementInfo wire = movementInfo; + wire.time = nextMovementTimestampMs(); + if (wire.hasFlag(MovementFlags::ONTRANSPORT)) { + wire.transportTime = wire.time; + wire.transportTime2 = wire.time; + } glm::vec3 serverPos = core::coords::canonicalToServer(glm::vec3(wire.x, wire.y, wire.z)); wire.x = serverPos.x; wire.y = serverPos.y; @@ -7842,7 +7868,11 @@ void GameHandler::handleForceRunSpeedChange(network::Packet& packet) { wire.transportY = serverTransport.y; wire.transportZ = serverTransport.z; } - MovementPacket::writeMovementPayload(ack, wire); + if (packetParsers_) { + packetParsers_->writeMovementPayload(ack, wire); + } else { + MovementPacket::writeMovementPayload(ack, wire); + } ack.writeFloat(newSpeed); socket->send(ack); diff --git a/src/game/opcode_table.cpp b/src/game/opcode_table.cpp index 3cf822e7..35223267 100644 --- a/src/game/opcode_table.cpp +++ b/src/game/opcode_table.cpp @@ -64,7 +64,6 @@ static const OpcodeNameEntry kOpcodeNames[] = { {"SMSG_NOTIFICATION", LogicalOpcode::SMSG_NOTIFICATION}, {"SMSG_UPDATE_OBJECT", LogicalOpcode::SMSG_UPDATE_OBJECT}, {"SMSG_COMPRESSED_UPDATE_OBJECT", LogicalOpcode::SMSG_COMPRESSED_UPDATE_OBJECT}, - {"SMSG_UNKNOWN_1F5", LogicalOpcode::SMSG_UNKNOWN_1F5}, {"SMSG_MONSTER_MOVE_TRANSPORT", LogicalOpcode::SMSG_MONSTER_MOVE_TRANSPORT}, {"SMSG_SPLINE_MOVE_SET_WALK_MODE", LogicalOpcode::SMSG_SPLINE_MOVE_SET_WALK_MODE}, {"SMSG_SPLINE_MOVE_SET_RUN_MODE", LogicalOpcode::SMSG_SPLINE_MOVE_SET_RUN_MODE}, @@ -185,6 +184,7 @@ static const OpcodeNameEntry kOpcodeNames[] = { {"CMSG_GROUP_DISBAND", LogicalOpcode::CMSG_GROUP_DISBAND}, {"SMSG_GROUP_LIST", LogicalOpcode::SMSG_GROUP_LIST}, {"SMSG_PARTY_COMMAND_RESULT", LogicalOpcode::SMSG_PARTY_COMMAND_RESULT}, + {"SMSG_PARTYKILLLOG", LogicalOpcode::SMSG_PARTYKILLLOG}, {"MSG_RAID_TARGET_UPDATE", LogicalOpcode::MSG_RAID_TARGET_UPDATE}, {"CMSG_REQUEST_RAID_INFO", LogicalOpcode::CMSG_REQUEST_RAID_INFO}, {"SMSG_RAID_INSTANCE_INFO", LogicalOpcode::SMSG_RAID_INSTANCE_INFO},