fix: Fix reconnect failure after leaving host session

This commit is contained in:
kuwacom 2026-03-03 04:12:38 +09:00
parent 7bea3a33fd
commit 66ffc689d1
3 changed files with 91 additions and 19 deletions

View file

@ -1396,7 +1396,19 @@ void CGameNetworkManager::CreateSocket( INetworkPlayer *pNetworkPlayer, bool loc
Minecraft *pMinecraft = Minecraft::GetInstance(); Minecraft *pMinecraft = Minecraft::GetInstance();
Socket *socket = NULL; Socket *socket = NULL;
shared_ptr<MultiplayerLocalPlayer> mpPlayer = pMinecraft->localplayers[pNetworkPlayer->GetUserIndex()]; int localUserIndex = -1;
shared_ptr<MultiplayerLocalPlayer> 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( localPlayer && mpPlayer != NULL && mpPlayer->connection != NULL)
{ {
// If we already have a MultiplayerLocalPlayer here then we are doing a session type change // 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 // the player in to the game server
if( localPlayer && g_NetworkManager.IsInGameplay() ) 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); app.DebugPrintf("Creating new client connection for idx: %d\n", idx);
ClientConnection *connection; ClientConnection *connection;

View file

@ -270,25 +270,44 @@ void CPlatformNetworkManagerStub::DoWork()
// Keep LAN search ticking whenever the join menu callback is active, even if QNet state // Keep LAN search ticking whenever the join menu callback is active, even if QNet state
// is not idle due to prior connection attempts. // is not idle due to prior connection attempts.
TickSearch(); TickSearch();
if (_iQNetStubState == QNET_STATE_GAME_PLAY && m_pIQNet->IsHost()) if (m_pIQNet->IsHost())
{ {
BYTE disconnectedSmallId; BYTE disconnectedSmallId;
while (WinsockNetLayer::PopDisconnectedSmallId(&disconnectedSmallId)) while (WinsockNetLayer::PopDisconnectedSmallId(&disconnectedSmallId))
{ {
IQNetPlayer *qnetPlayer = m_pIQNet->GetPlayerBySmallId(disconnectedSmallId); if (disconnectedSmallId == 0 || disconnectedSmallId >= MINECRAFT_NET_MAX_PLAYERS)
if (qnetPlayer != NULL && qnetPlayer->m_smallId == disconnectedSmallId) 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_smallId = 0;
qnetPlayer->m_isRemote = false; qnetPlayer->m_isRemote = false;
qnetPlayer->m_isHostPlayer = false; qnetPlayer->m_isHostPlayer = false;
qnetPlayer->m_gamertag[0] = 0; qnetPlayer->m_gamertag[0] = 0;
qnetPlayer->SetCustomDataValue(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 #endif
} }

View file

@ -397,11 +397,12 @@ bool WinsockNetLayer::SendToSmallId(BYTE targetSmallId, const void *data, int da
SOCKET WinsockNetLayer::GetSocketForSmallId(BYTE smallId) SOCKET WinsockNetLayer::GetSocketForSmallId(BYTE smallId)
{ {
EnterCriticalSection(&s_connectionsLock); 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); LeaveCriticalSection(&s_connectionsLock);
return sock; return sock;
} }
@ -492,9 +493,26 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param)
{ {
app.DebugPrintf("Failed to send small ID to client\n"); app.DebugPrintf("Failed to send small ID to client\n");
closesocket(clientSocket); closesocket(clientSocket);
PushFreeSmallId(assignedSmallId);
continue; 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; Win64RemoteConnection conn;
conn.tcpSocket = clientSocket; conn.tcpSocket = clientSocket;
conn.smallId = assignedSmallId; conn.smallId = assignedSmallId;
@ -574,18 +592,21 @@ DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param)
delete[] recvBuf; delete[] recvBuf;
EnterCriticalSection(&s_connectionsLock); 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[connIdx].tcpSocket);
closesocket(s_connections[i].tcpSocket); s_connections[connIdx].tcpSocket = INVALID_SOCKET;
s_connections[i].tcpSocket = INVALID_SOCKET;
break;
} }
} }
LeaveCriticalSection(&s_connectionsLock); 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); EnterCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.push_back(clientSmallId); s_disconnectedSmallIds.push_back(clientSmallId);
LeaveCriticalSection(&s_disconnectLock); LeaveCriticalSection(&s_disconnectLock);
@ -609,8 +630,21 @@ bool WinsockNetLayer::PopDisconnectedSmallId(BYTE *outSmallId)
void WinsockNetLayer::PushFreeSmallId(BYTE smallId) void WinsockNetLayer::PushFreeSmallId(BYTE smallId)
{ {
if (smallId == 0 || smallId >= MINECRAFT_NET_MAX_PLAYERS)
return;
EnterCriticalSection(&s_freeSmallIdLock); 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); LeaveCriticalSection(&s_freeSmallIdLock);
} }