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();
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 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;

View file

@ -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
}

View file

@ -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);
}