From abb18e3e12a85f700f5b97812469e6cb9b54587c Mon Sep 17 00:00:00 2001 From: itsRevela Date: Sat, 11 Apr 2026 08:13:46 -0500 Subject: [PATCH] fix: new players kicked when joining right after being whitelisted When a new player was whitelisted while they were sitting on the join screen, their next attempt would insta-kick before the world ever loaded, and the retry after that would let them in for roughly 20 seconds before booting them with "connection closed". Two separate bugs were colliding. The first kick was a stale cancel flag on the client. When the server rejects a join, the "Connecting to host..." screen tears down, and its teardown path fires the cancel callback defensively. That flag would latch on without ever being consumed, so the very first tick of the next join attempt saw it and immediately closed the fresh connection. Clearing the flag when a new join starts prevents this. The second kick was an orphan on the server. When the first failed join's TCP dropped, its slot got recycled for the next successful join, but the half-built login object from the broken attempt was still in the pending queue. 30 seconds later its "login took too long" timer fired, and the disconnect packet it tried to send was routed to whoever currently held that slot, which was now the new in-world player. It landed on their live socket and kicked them. Telling the game's Socket layer about the TCP drop lets the orphan clean itself up, and refusing to write on an already-closing socket stops any late packet from leaking into the recycled slot. --- Minecraft.Client/Common/Network/GameNetworkManager.cpp | 6 ++++++ .../Common/Network/PlatformNetworkManagerStub.cpp | 7 +++++++ Minecraft.World/Socket.cpp | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index ca13e019..0e83c8b2 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -204,6 +204,12 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame ProfileManager.SetDeferredSignoutEnabled(true); #endif + // Clear any stale cancel flag latched by the previous join's progress + // UI teardown, otherwise the next join's first tick insta-closes. + EnterCriticalSection(&bCancelRequestedCS); + g_NetworkManager.m_bCancelRequested = false; + LeaveCriticalSection(&bCancelRequestedCS); + int64_t seed = 0; bool dedicatedNoLocalHostPlayer = false; if (lpParameter != nullptr) diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 89b7cb6e..d06e4372 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -134,7 +134,14 @@ void CPlatformNetworkManagerStub::NotifyPlayerLeaving(IQNetPlayer* pQNetPlayer) if (socket != nullptr) { if (m_pIQNet->IsHost()) + { g_NetworkManager.CloseConnection(networkPlayer); + + // Propagate the TCP drop to the game Socket so any orphaned + // PendingConnection at this smallId cleans up before its login + // timer fires and leaks a DisconnectPacket to the reused slot. + socket->close(true); + } } if (m_pIQNet->IsHost()) diff --git a/Minecraft.World/Socket.cpp b/Minecraft.World/Socket.cpp index f07e3b2b..74e14889 100644 --- a/Minecraft.World/Socket.cpp +++ b/Minecraft.World/Socket.cpp @@ -493,6 +493,10 @@ void Socket::SocketOutputStreamNetwork::writeWithFlags(byteArray b, unsigned int } else { + // Don't write on a closing socket: an orphan whose smallId has been + // recycled would otherwise leak a packet onto the new client. + if( m_socket->isClosing() ) return; + XRNM_SEND_BUFFER buffer; buffer.pbyData = &b[offset]; buffer.dwDataSize = length;