From d7e701a1cb1d1cc5c45f09ccaa3c1ae23939d491 Mon Sep 17 00:00:00 2001 From: Kitzunu <24550914+Kitzunu@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:11:11 +0100 Subject: [PATCH] feat(Core/PacketIO): Implement STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT (#15059) Co-authored-by: Vladimir Merzliakov <29081+VladimirMangos@users.noreply.github.com> Co-authored-by: megamage <35114+megamage@users.noreply.github.com> Co-authored-by: Shauren Co-authored-by: Giacomo Pozzoni --- src/server/game/Handlers/MiscHandler.cpp | 4 ++ src/server/game/Handlers/TradeHandler.cpp | 6 +- src/server/game/Server/Protocol/Opcodes.cpp | 4 +- src/server/game/Server/Protocol/Opcodes.h | 3 +- src/server/game/Server/WorldSession.cpp | 65 +++++++++++++++------ src/server/game/Server/WorldSession.h | 4 +- 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index d78818a47..4d14585a2 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -481,6 +481,10 @@ void WorldSession::HandlePlayerLogoutOpcode(WorldPackets::Character::PlayerLogou void WorldSession::HandleLogoutCancelOpcode(WorldPackets::Character::LogoutCancel& /*logoutCancel*/) { + // Player have already logged out serverside, too late to cancel + if (!GetPlayer()) + return; + SetLogoutStartTime(0); SendPacket(WorldPackets::Character::LogoutCancelAck().Write()); diff --git a/src/server/game/Handlers/TradeHandler.cpp b/src/server/game/Handlers/TradeHandler.cpp index b212e97ad..a4e64b9e7 100644 --- a/src/server/game/Handlers/TradeHandler.cpp +++ b/src/server/game/Handlers/TradeHandler.cpp @@ -527,7 +527,7 @@ void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/) void WorldSession::SendCancelTrade() { - if (PlayerLogout()) + if (PlayerRecentlyLoggedOut() || PlayerLogout()) return; SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); @@ -535,7 +535,9 @@ void WorldSession::SendCancelTrade() void WorldSession::HandleCancelTradeOpcode(WorldPacket& /*recvPacket*/) { - _player->TradeCancel(true); + // sended also after LOGOUT COMPLETE + if (_player) // needed because STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT + _player->TradeCancel(true); } void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket) diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 77052de11..85bcb4186 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -206,7 +206,7 @@ void OpcodeTable::Initialize() /*0x04B*/ DEFINE_HANDLER(CMSG_LOGOUT_REQUEST, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLogoutRequestOpcode ); /*0x04C*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOGOUT_RESPONSE, STATUS_NEVER); /*0x04D*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOGOUT_COMPLETE, STATUS_NEVER); - /*0x04E*/ DEFINE_HANDLER(CMSG_LOGOUT_CANCEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLogoutCancelOpcode ); + /*0x04E*/ DEFINE_HANDLER(CMSG_LOGOUT_CANCEL, STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, PROCESS_THREADUNSAFE, &WorldSession::HandleLogoutCancelOpcode); /*0x04F*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOGOUT_CANCEL_ACK, STATUS_NEVER); /*0x050*/ DEFINE_HANDLER(CMSG_NAME_QUERY, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleNameQueryOpcode ); /*0x051*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_NAME_QUERY_RESPONSE, STATUS_NEVER); @@ -412,7 +412,7 @@ void OpcodeTable::Initialize() /*0x119*/ DEFINE_HANDLER(CMSG_IGNORE_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleIgnoreTradeOpcode ); /*0x11A*/ DEFINE_HANDLER(CMSG_ACCEPT_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAcceptTradeOpcode ); /*0x11B*/ DEFINE_HANDLER(CMSG_UNACCEPT_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleUnacceptTradeOpcode ); - /*0x11C*/ DEFINE_HANDLER(CMSG_CANCEL_TRADE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelTradeOpcode ); + /*0x11C*/ DEFINE_HANDLER(CMSG_CANCEL_TRADE, STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelTradeOpcode); /*0x11D*/ DEFINE_HANDLER(CMSG_SET_TRADE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTradeItemOpcode ); /*0x11E*/ DEFINE_HANDLER(CMSG_CLEAR_TRADE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleClearTradeItemOpcode ); /*0x11F*/ DEFINE_HANDLER(CMSG_SET_TRADE_GOLD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTradeGoldOpcode ); diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h index 037949916..3eb61151e 100644 --- a/src/server/game/Server/Protocol/Opcodes.h +++ b/src/server/game/Server/Protocol/Opcodes.h @@ -1353,9 +1353,10 @@ typedef Opcodes OpcodeServer; /// Player state enum SessionStatus { - STATUS_AUTHED = 0, // Player authenticated (_player == nullptr, m_GUID has garbage) + STATUS_AUTHED = 0, // Player authenticated (_player == nullptr, m_playerRecentlyLogout = false or will be reset before handler call, m_GUID have garbage) STATUS_LOGGEDIN, // Player in game (_player != nullptr, m_GUID == _player->GetGUID(), inWorld()) STATUS_TRANSFER, // Player transferring to another map (_player != nullptr, m_GUID == _player->GetGUID(), !inWorld()) + STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, // _player != nullptr or _player == nullptr && m_playerRecentlyLogout && m_playerLogout, m_GUID store last _player guid) STATUS_NEVER, // Opcode not accepted from client (deprecated or server side only) STATUS_UNHANDLED, // Opcode not handled yet }; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 90efea5ff..4381e2e03 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -121,6 +121,7 @@ WorldSession::WorldSession(uint32 id, std::string&& name, std::shared_ptrGetDefaultDbcLocale()), m_sessionDbLocaleIndex(locale), @@ -326,28 +327,48 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) case STATUS_LOGGEDIN: if (!_player) { - // pussywizard: such packets were sent to do something for a character that has already logged out, skip them - } - else if (!_player->IsInWorld()) - { - // pussywizard: such packets may do something important and the player is just being teleported, move to the end of the queue - // pussywizard: previously such were skipped, so leave it as it is xD proper code below if we wish to change that - - // pussywizard: requeue only important packets not related to maps (PROCESS_THREADUNSAFE) - /*if (opHandle.packetProcessing == PROCESS_THREADUNSAFE) + // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets + //! If player didn't log out a while ago, it means packets are being sent while the server does not recognize + //! the client to be in world yet. We will re-add the packets to the bottom of the queue and process them later. + if (!m_playerRecentlyLogout) { - if (!firstDelayedPacket) - firstDelayedPacket = packet; + requeuePackets.push_back(packet); deletePacket = false; QueuePacket(packet); - }*/ - } - else if (_player->IsInWorld() && AntiDOS.EvaluateOpcode(*packet, currentTime)) - { - if (!sScriptMgr->CanPacketReceive(this, *packet)) - { - break; + + LOG_DEBUG("network", "Re-enqueueing packet with opcode %s with with status STATUS_LOGGEDIN. " + "Player {} is currently not in world yet.", GetOpcodeNameForLogging(static_cast(packet->GetOpcode())), GetPlayerInfo()); } + } + else if (_player->IsInWorld()) + { + if (AntiDOS.EvaluateOpcode(*packet, currentTime)) + { + if (!sScriptMgr->CanPacketReceive(this, *packet)) + { + break; + } + + opHandle->Call(this, *packet); + LogUnprocessedTail(packet); + } + else + processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop + } + + // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer + break; + case STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT: + if (!_player && !m_playerRecentlyLogout) // There's a short delay between _player = null and m_playerRecentlyLogout = true during logout + { + LogUnexpectedOpcode(packet, "STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT", + "the player has not logged in yet and not recently logout"); + } + else if (AntiDOS.EvaluateOpcode(*packet, currentTime)) + { + // not expected _player or must checked in packet hanlder + if (!sScriptMgr->CanPacketReceive(this, *packet)) + break; opHandle->Call(this, *packet); LogUnprocessedTail(packet); @@ -373,6 +394,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) if (m_inQueue) // prevent cheating break; + // some auth opcodes can be recieved before STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT opcodes + // however when we recieve CMSG_CHAR_ENUM we are surely no longer during the logout process. + if (packet->GetOpcode() == CMSG_CHAR_ENUM) + m_playerRecentlyLogout = false; + if (AntiDOS.EvaluateOpcode(*packet, currentTime)) { if (!sScriptMgr->CanPacketReceive(this, *packet)) @@ -714,6 +740,7 @@ void WorldSession::LogoutPlayer(bool save) m_playerLogout = false; m_playerSave = false; + m_playerRecentlyLogout = true; SetLogoutStartTime(0); } @@ -1261,6 +1288,8 @@ void WorldSession::SendAddonsInfo() void WorldSession::SetPlayer(Player* player) { _player = player; + + // set m_GUID that can be used while player loggined and later until m_playerRecentlyLogout not reset if (_player) m_GUIDLow = _player->GetGUID().GetCounter(); } diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 64ce0703f..e6b5de118 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -334,6 +334,7 @@ public: bool PlayerLoading() const { return m_playerLoading; } bool PlayerLogout() const { return m_playerLogout; } + bool PlayerRecentlyLoggedOut() const { return m_playerRecentlyLogout; } bool PlayerLogoutWithSave() const { return m_playerLogout && m_playerSave; } void ReadAddonsInfo(ByteBuffer& data); @@ -1139,7 +1140,7 @@ private: // characters who failed on Player::BuildEnumData shouldn't login GuidSet _legitCharacters; - ObjectGuid::LowType m_GUIDLow; + ObjectGuid::LowType m_GUIDLow; // set logined or recently logout player (while m_playerRecentlyLogout set) Player* _player; std::shared_ptr m_Socket; std::string m_Address; @@ -1160,6 +1161,7 @@ private: bool m_inQueue; // session wait in auth.queue bool m_playerLoading; // code processed in LoginPlayer bool m_playerLogout; // code processed in LogoutPlayer + bool m_playerRecentlyLogout; bool m_playerSave; LocaleConstant m_sessionDbcLocale; LocaleConstant m_sessionDbLocaleIndex;