From 66ffc689d19ea7f09cd19c43e001b8f8f0633499 Mon Sep 17 00:00:00 2001 From: kuwacom Date: Tue, 3 Mar 2026 04:12:38 +0900 Subject: [PATCH] fix: Fix reconnect failure after leaving host session --- .../Common/Network/GameNetworkManager.cpp | 23 +++++++- .../Network/PlatformNetworkManagerStub.cpp | 33 +++++++++--- .../Windows64/Network/WinsockNetLayer.cpp | 54 +++++++++++++++---- 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index d6ae57351..425c936e7 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -1396,7 +1396,19 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc Minecraft *pMinecraft = Minecraft::GetInstance(); Socket *socket = NULL; - shared_ptr mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()]; + int localUserIndex = -1; + shared_ptr mpPlayer; + if (localPlayer) + { + localUserIndex = pNetworkPlayer->GetUserIndex(); +#ifdef _WINDOWS64 + // Win64 client local user is always the primary pad, not the network small-id slot. + if (!g_NetworkManager.IsHost()) + localUserIndex = ProfileManager.GetPrimaryPad(); +#endif + if (localUserIndex >= 0 && localUserIndex < XUSER_MAX_COUNT) + mpPlayer = pMinecraft->localplayers[localUserIndex]; + } if( localPlayer && mpPlayer != NULL && mpPlayer->connection != NULL) { // If we already have a MultiplayerLocalPlayer here then we are doing a session type change @@ -1425,7 +1437,14 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc // the player in to the game server if( localPlayer && g_NetworkManager.IsInGameplay() ) { - int idx = pNetworkPlayer->GetUserIndex(); + int idx = localUserIndex; + if (idx < 0 || idx >= XUSER_MAX_COUNT) + idx = ProfileManager.GetPrimaryPad(); + if (idx < 0 || idx >= XUSER_MAX_COUNT) + { + app.DebugPrintf("CreateSocket: invalid local user index %d\n", idx); + return; + } app.DebugPrintf("Creating new client connection for idx: %d\n", idx); ClientConnection *connection; diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 8e66cf3e2..636f9b908 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -270,25 +270,44 @@ void CPlatformNetworkManagerStub::DoWork() // Keep LAN search ticking whenever the join menu callback is active, even if QNet state // is not idle due to prior connection attempts. TickSearch(); - if (_iQNetStubState == QNET_STATE_GAME_PLAY && m_pIQNet->IsHost()) + if (m_pIQNet->IsHost()) { BYTE disconnectedSmallId; while (WinsockNetLayer::PopDisconnectedSmallId(&disconnectedSmallId)) { - IQNetPlayer *qnetPlayer = m_pIQNet->GetPlayerBySmallId(disconnectedSmallId); - if (qnetPlayer != NULL && qnetPlayer->m_smallId == disconnectedSmallId) + if (disconnectedSmallId == 0 || disconnectedSmallId >= MINECRAFT_NET_MAX_PLAYERS) + continue; + + app.DebugPrintf("Win64 LAN: Processing disconnected smallId=%d\n", disconnectedSmallId); + + IQNetPlayer *qnetPlayer = &IQNet::m_player[disconnectedSmallId]; + if (qnetPlayer->m_smallId == disconnectedSmallId) { - NotifyPlayerLeaving(qnetPlayer); + if (qnetPlayer->GetCustomDataValue() != 0) + { + NotifyPlayerLeaving(qnetPlayer); + } + else + { + app.DebugPrintf("Win64 LAN: smallId=%d had no active network player object\n", disconnectedSmallId); + } qnetPlayer->m_smallId = 0; qnetPlayer->m_isRemote = false; qnetPlayer->m_isHostPlayer = false; qnetPlayer->m_gamertag[0] = 0; qnetPlayer->SetCustomDataValue(0); - WinsockNetLayer::PushFreeSmallId(disconnectedSmallId); - if (IQNet::s_playerCount > 1) - IQNet::s_playerCount--; } + WinsockNetLayer::PushFreeSmallId(disconnectedSmallId); } + + // Keep player-count in sync with active remote slots. + DWORD highestUsedIndex = 0; + for (DWORD i = 1; i < MINECRAFT_NET_MAX_PLAYERS; i++) + { + if (IQNet::m_player[i].GetCustomDataValue() != 0) + highestUsedIndex = i; + } + IQNet::s_playerCount = highestUsedIndex + 1; } #endif } diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index 647c4d4de..b256c9b25 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -397,11 +397,12 @@ bool WinsockNetLayer::SendToSmallId(BYTE targetSmallId, const void *data, int da SOCKET WinsockNetLayer::GetSocketForSmallId(BYTE smallId) { EnterCriticalSection(&s_connectionsLock); - for (size_t i = 0; i < s_connections.size(); i++) + for (size_t i = s_connections.size(); i > 0; i--) { - if (s_connections[i].smallId == smallId && s_connections[i].active) + Win64RemoteConnection &conn = s_connections[i - 1]; + if (conn.smallId == smallId && conn.active && conn.tcpSocket != INVALID_SOCKET) { - SOCKET sock = s_connections[i].tcpSocket; + SOCKET sock = conn.tcpSocket; LeaveCriticalSection(&s_connectionsLock); return sock; } @@ -492,9 +493,26 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) { app.DebugPrintf("Failed to send small ID to client\n"); closesocket(clientSocket); + PushFreeSmallId(assignedSmallId); continue; } + // If an old slot for this smallId somehow remains, retire it before reusing the id. + EnterCriticalSection(&s_connectionsLock); + for (size_t i = 0; i < s_connections.size(); i++) + { + if (s_connections[i].smallId == assignedSmallId) + { + s_connections[i].active = false; + if (s_connections[i].tcpSocket != INVALID_SOCKET) + { + closesocket(s_connections[i].tcpSocket); + s_connections[i].tcpSocket = INVALID_SOCKET; + } + } + } + LeaveCriticalSection(&s_connectionsLock); + Win64RemoteConnection conn; conn.tcpSocket = clientSocket; conn.smallId = assignedSmallId; @@ -574,18 +592,21 @@ DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) delete[] recvBuf; EnterCriticalSection(&s_connectionsLock); - for (size_t i = 0; i < s_connections.size(); i++) + if (connIdx < (DWORD)s_connections.size()) { - if (s_connections[i].smallId == clientSmallId) + s_connections[connIdx].active = false; + if (s_connections[connIdx].tcpSocket != INVALID_SOCKET) { - s_connections[i].active = false; - closesocket(s_connections[i].tcpSocket); - s_connections[i].tcpSocket = INVALID_SOCKET; - break; + closesocket(s_connections[connIdx].tcpSocket); + s_connections[connIdx].tcpSocket = INVALID_SOCKET; } } LeaveCriticalSection(&s_connectionsLock); + // Return this id immediately so rapid reconnect attempts don't exhaust the id pool + // while the game thread catches up with disconnect processing. + PushFreeSmallId(clientSmallId); + EnterCriticalSection(&s_disconnectLock); s_disconnectedSmallIds.push_back(clientSmallId); LeaveCriticalSection(&s_disconnectLock); @@ -609,8 +630,21 @@ bool WinsockNetLayer::PopDisconnectedSmallId(BYTE *outSmallId) void WinsockNetLayer::PushFreeSmallId(BYTE smallId) { + if (smallId == 0 || smallId >= MINECRAFT_NET_MAX_PLAYERS) + return; + EnterCriticalSection(&s_freeSmallIdLock); - s_freeSmallIds.push_back(smallId); + bool found = false; + for (size_t i = 0; i < s_freeSmallIds.size(); i++) + { + if (s_freeSmallIds[i] == smallId) + { + found = true; + break; + } + } + if (!found) + s_freeSmallIds.push_back(smallId); LeaveCriticalSection(&s_freeSmallIdLock); }