diff options
| author | qwasdrizzel <145519042+qwasdrizzel@users.noreply.github.com> | 2026-03-16 21:44:26 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-16 21:44:26 -0500 |
| commit | ce739f6045ec72127491286ea3f3f21e537c1b55 (patch) | |
| tree | f33bd42a47c1b4a7b2153a7fb77127ee3b407db9 /Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp | |
| parent | 255a18fe8e9b57377975f82e2b227afe2a12eda0 (diff) | |
| parent | 5a59f5d146b43811dde6a5a0245ee9875d7b5cd1 (diff) | |
Merge branch 'smartcmd:main' into main
Diffstat (limited to 'Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp')
| -rw-r--r-- | Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp | 573 |
1 files changed, 495 insertions, 78 deletions
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index ca1d62af..981ab3ab 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -8,11 +8,25 @@ #include "WinsockNetLayer.h" #include "..\..\Common\Network\PlatformNetworkManagerStub.h" #include "..\..\..\Minecraft.World\Socket.h" +#if defined(MINECRAFT_SERVER_BUILD) +#include "..\..\..\Minecraft.Server\Access\Access.h" +#include "..\..\..\Minecraft.Server\ServerLogManager.h" +#endif +#include "..\..\..\Minecraft.World\DisconnectPacket.h" +#include "..\..\Minecraft.h" +#include "..\4JLibs\inc\4J_Profile.h" + +#include <string> + +static bool RecvExact(SOCKET sock, BYTE* buf, int len); +#if defined(MINECRAFT_SERVER_BUILD) +static bool TryGetNumericRemoteIp(const sockaddr_in &remoteAddress, std::string *outIp); +#endif SOCKET WinsockNetLayer::s_listenSocket = INVALID_SOCKET; SOCKET WinsockNetLayer::s_hostConnectionSocket = INVALID_SOCKET; -HANDLE WinsockNetLayer::s_acceptThread = NULL; -HANDLE WinsockNetLayer::s_clientRecvThread = NULL; +HANDLE WinsockNetLayer::s_acceptThread = nullptr; +HANDLE WinsockNetLayer::s_clientRecvThread = nullptr; bool WinsockNetLayer::s_isHost = false; bool WinsockNetLayer::s_connected = false; @@ -21,7 +35,7 @@ bool WinsockNetLayer::s_initialized = false; BYTE WinsockNetLayer::s_localSmallId = 0; BYTE WinsockNetLayer::s_hostSmallId = 0; -BYTE WinsockNetLayer::s_nextSmallId = 1; +unsigned int WinsockNetLayer::s_nextSmallId = XUSER_MAX_COUNT; CRITICAL_SECTION WinsockNetLayer::s_sendLock; CRITICAL_SECTION WinsockNetLayer::s_connectionsLock; @@ -29,14 +43,14 @@ CRITICAL_SECTION WinsockNetLayer::s_connectionsLock; std::vector<Win64RemoteConnection> WinsockNetLayer::s_connections; SOCKET WinsockNetLayer::s_advertiseSock = INVALID_SOCKET; -HANDLE WinsockNetLayer::s_advertiseThread = NULL; +HANDLE WinsockNetLayer::s_advertiseThread = nullptr; volatile bool WinsockNetLayer::s_advertising = false; Win64LANBroadcast WinsockNetLayer::s_advertiseData = {}; CRITICAL_SECTION WinsockNetLayer::s_advertiseLock; int WinsockNetLayer::s_hostGamePort = WIN64_NET_DEFAULT_PORT; SOCKET WinsockNetLayer::s_discoverySock = INVALID_SOCKET; -HANDLE WinsockNetLayer::s_discoveryThread = NULL; +HANDLE WinsockNetLayer::s_discoveryThread = nullptr; volatile bool WinsockNetLayer::s_discovering = false; CRITICAL_SECTION WinsockNetLayer::s_discoveryLock; std::vector<Win64LANSession> WinsockNetLayer::s_discoveredSessions; @@ -46,6 +60,12 @@ std::vector<BYTE> WinsockNetLayer::s_disconnectedSmallIds; CRITICAL_SECTION WinsockNetLayer::s_freeSmallIdLock; std::vector<BYTE> WinsockNetLayer::s_freeSmallIds; +SOCKET WinsockNetLayer::s_smallIdToSocket[256]; +CRITICAL_SECTION WinsockNetLayer::s_smallIdToSocketLock; + +SOCKET WinsockNetLayer::s_splitScreenSocket[XUSER_MAX_COUNT] = { INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET, INVALID_SOCKET }; +BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF }; +HANDLE WinsockNetLayer::s_splitScreenRecvThread[XUSER_MAX_COUNT] = {nullptr, nullptr, nullptr, nullptr}; bool g_Win64MultiplayerHost = false; bool g_Win64MultiplayerJoin = false; @@ -54,6 +74,7 @@ char g_Win64MultiplayerIP[256] = "127.0.0.1"; bool g_Win64DedicatedServer = false; int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT; char g_Win64DedicatedServerBindIP[256] = ""; +bool g_Win64DedicatedServerLanAdvertise = true; bool WinsockNetLayer::Initialize() { @@ -73,10 +94,17 @@ bool WinsockNetLayer::Initialize() InitializeCriticalSection(&s_discoveryLock); InitializeCriticalSection(&s_disconnectLock); InitializeCriticalSection(&s_freeSmallIdLock); + InitializeCriticalSection(&s_smallIdToSocketLock); + for (int i = 0; i < 256; i++) + s_smallIdToSocket[i] = INVALID_SOCKET; s_initialized = true; - StartDiscovery(); + // Dedicated Server does not use LAN session discovery and therefore does not initiate discovery. + if (!g_Win64DedicatedServer) + { + StartDiscovery(); + } return true; } @@ -101,6 +129,15 @@ void WinsockNetLayer::Shutdown() s_hostConnectionSocket = INVALID_SOCKET; } + // Stop accept loop first so no new RecvThread can be created while shutting down. + if (s_acceptThread != nullptr) + { + WaitForSingleObject(s_acceptThread, 2000); + CloseHandle(s_acceptThread); + s_acceptThread = nullptr; + } + + std::vector<HANDLE> recvThreads; EnterCriticalSection(&s_connectionsLock); for (size_t i = 0; i < s_connections.size(); i++) { @@ -108,35 +145,67 @@ void WinsockNetLayer::Shutdown() if (s_connections[i].tcpSocket != INVALID_SOCKET) { closesocket(s_connections[i].tcpSocket); + s_connections[i].tcpSocket = INVALID_SOCKET; + } + if (s_connections[i].recvThread != nullptr) + { + recvThreads.push_back(s_connections[i].recvThread); + s_connections[i].recvThread = nullptr; } } - s_connections.clear(); LeaveCriticalSection(&s_connectionsLock); - if (s_acceptThread != NULL) + // Wait for all host-side receive threads to exit before destroying state. + for (size_t i = 0; i < recvThreads.size(); i++) { - WaitForSingleObject(s_acceptThread, 2000); - CloseHandle(s_acceptThread); - s_acceptThread = NULL; + WaitForSingleObject(recvThreads[i], 2000); + CloseHandle(recvThreads[i]); } - if (s_clientRecvThread != NULL) + EnterCriticalSection(&s_connectionsLock); + s_connections.clear(); + LeaveCriticalSection(&s_connectionsLock); + + if (s_clientRecvThread != nullptr) { WaitForSingleObject(s_clientRecvThread, 2000); CloseHandle(s_clientRecvThread); - s_clientRecvThread = NULL; + s_clientRecvThread = nullptr; + } + + for (int i = 0; i < XUSER_MAX_COUNT; i++) + { + if (s_splitScreenSocket[i] != INVALID_SOCKET) + { + closesocket(s_splitScreenSocket[i]); + s_splitScreenSocket[i] = INVALID_SOCKET; + } + if (s_splitScreenRecvThread[i] != nullptr) + { + WaitForSingleObject(s_splitScreenRecvThread[i], 2000); + CloseHandle(s_splitScreenRecvThread[i]); + s_splitScreenRecvThread[i] = nullptr; + } + s_splitScreenSmallId[i] = 0xFF; } if (s_initialized) { + EnterCriticalSection(&s_disconnectLock); + s_disconnectedSmallIds.clear(); + LeaveCriticalSection(&s_disconnectLock); + + EnterCriticalSection(&s_freeSmallIdLock); + s_freeSmallIds.clear(); + LeaveCriticalSection(&s_freeSmallIdLock); + DeleteCriticalSection(&s_sendLock); DeleteCriticalSection(&s_connectionsLock); DeleteCriticalSection(&s_advertiseLock); DeleteCriticalSection(&s_discoveryLock); DeleteCriticalSection(&s_disconnectLock); - s_disconnectedSmallIds.clear(); DeleteCriticalSection(&s_freeSmallIdLock); - s_freeSmallIds.clear(); + DeleteCriticalSection(&s_smallIdToSocketLock); WSACleanup(); s_initialized = false; } @@ -149,30 +218,34 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) s_isHost = true; s_localSmallId = 0; s_hostSmallId = 0; - s_nextSmallId = 1; + s_nextSmallId = XUSER_MAX_COUNT; s_hostGamePort = port; EnterCriticalSection(&s_freeSmallIdLock); s_freeSmallIds.clear(); LeaveCriticalSection(&s_freeSmallIdLock); + EnterCriticalSection(&s_smallIdToSocketLock); + for (int i = 0; i < 256; i++) + s_smallIdToSocket[i] = INVALID_SOCKET; + LeaveCriticalSection(&s_smallIdToSocketLock); struct addrinfo hints = {}; - struct addrinfo* result = NULL; + struct addrinfo* result = nullptr; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = (bindIp == NULL || bindIp[0] == 0) ? AI_PASSIVE : 0; + hints.ai_flags = (bindIp == nullptr || bindIp[0] == 0) ? AI_PASSIVE : 0; char portStr[16]; sprintf_s(portStr, "%d", port); - const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL; + const char* resolvedBindIp = (bindIp != nullptr && bindIp[0] != 0) ? bindIp : nullptr; int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result); if (iResult != 0) { app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n", - resolvedBindIp != NULL ? resolvedBindIp : "*", + resolvedBindIp != nullptr ? resolvedBindIp : "*", port, iResult); return false; @@ -189,7 +262,7 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) int opt = 1; setsockopt(s_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)); - iResult = ::bind(s_listenSocket, result->ai_addr, (int)result->ai_addrlen); + iResult = ::bind(s_listenSocket, result->ai_addr, static_cast<int>(result->ai_addrlen)); freeaddrinfo(result); if (iResult == SOCKET_ERROR) { @@ -211,10 +284,10 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) s_active = true; s_connected = true; - s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL); + s_acceptThread = CreateThread(nullptr, 0, AcceptThreadProc, nullptr, 0, nullptr); app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n", - resolvedBindIp != NULL ? resolvedBindIp : "*", + resolvedBindIp != nullptr ? resolvedBindIp : "*", port); return true; } @@ -234,8 +307,19 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) s_hostConnectionSocket = INVALID_SOCKET; } + // Wait for old client recv thread to fully exit before starting a new connection. + // Without this, the old thread can read from the new socket (s_hostConnectionSocket + // is a global) and steal bytes from the new connection's TCP stream, causing + // packet stream misalignment on reconnect. + if (s_clientRecvThread != nullptr) + { + WaitForSingleObject(s_clientRecvThread, 5000); + CloseHandle(s_clientRecvThread); + s_clientRecvThread = nullptr; + } + struct addrinfo hints = {}; - struct addrinfo* result = NULL; + struct addrinfo* result = nullptr; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; @@ -267,7 +351,7 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) int noDelay = 1; setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay)); - iResult = connect(s_hostConnectionSocket, result->ai_addr, (int)result->ai_addrlen); + iResult = connect(s_hostConnectionSocket, result->ai_addr, static_cast<int>(result->ai_addrlen)); if (iResult == SOCKET_ERROR) { int err = WSAGetLastError(); @@ -289,6 +373,27 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) continue; } + if (assignBuf[0] == WIN64_SMALLID_REJECT) + { + BYTE rejectBuf[5]; + if (!RecvExact(s_hostConnectionSocket, rejectBuf, 5)) + { + app.DebugPrintf("Failed to receive reject reason from host\n"); + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + Sleep(200); + continue; + } + // rejectBuf[0] = packet id (255), rejectBuf[1..4] = 4-byte big-endian reason + int reason = ((rejectBuf[1] & 0xff) << 24) | ((rejectBuf[2] & 0xff) << 16) | + ((rejectBuf[3] & 0xff) << 8) | (rejectBuf[4] & 0xff); + Minecraft::GetInstance()->connectionDisconnected(ProfileManager.GetPrimaryPad(), (DisconnectPacket::eDisconnectReason)reason); + closesocket(s_hostConnectionSocket); + s_hostConnectionSocket = INVALID_SOCKET; + freeaddrinfo(result); + return false; + } + assignedSmallId = assignBuf[0]; connected = true; break; @@ -301,27 +406,39 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port) } s_localSmallId = assignedSmallId; + // Save the host IP and port so JoinSplitScreen can connect to the same host + // regardless of how the connection was initiated (UI vs command line). + strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), ip, _TRUNCATE); + g_Win64MultiplayerPort = port; + app.DebugPrintf("Win64 LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId); s_active = true; s_connected = true; - s_clientRecvThread = CreateThread(NULL, 0, ClientRecvThreadProc, NULL, 0, NULL); + s_clientRecvThread = CreateThread(nullptr, 0, ClientRecvThreadProc, nullptr, 0, nullptr); return true; } bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize) { - if (sock == INVALID_SOCKET || dataSize <= 0) return false; - + if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) return false; + + // TODO: s_sendLock is a single global lock for ALL sockets. If one client's + // send() blocks (TCP window full, slow WiFi), every other write thread stalls + // waiting for this lock — no data flows to any player until the slow send + // completes. This scales badly with player count (8+ players = noticeable). + // Fix: replace with per-socket locks indexed by smallId (s_perSocketSendLock[256]). + // The lock only needs to prevent interleaving of header+payload on the SAME socket; + // sends to different sockets are independent and should never block each other. EnterCriticalSection(&s_sendLock); BYTE header[4]; - header[0] = (BYTE)((dataSize >> 24) & 0xFF); - header[1] = (BYTE)((dataSize >> 16) & 0xFF); - header[2] = (BYTE)((dataSize >> 8) & 0xFF); - header[3] = (BYTE)(dataSize & 0xFF); + header[0] = static_cast<BYTE>((dataSize >> 24) & 0xFF); + header[1] = static_cast<BYTE>((dataSize >> 16) & 0xFF); + header[2] = static_cast<BYTE>((dataSize >> 8) & 0xFF); + header[3] = static_cast<BYTE>(dataSize & 0xFF); int totalSent = 0; int toSend = 4; @@ -339,7 +456,7 @@ bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize) totalSent = 0; while (totalSent < dataSize) { - int sent = send(sock, (const char*)data + totalSent, dataSize - totalSent, 0); + int sent = send(sock, static_cast<const char *>(data) + totalSent, dataSize - totalSent, 0); if (sent == SOCKET_ERROR || sent == 0) { LeaveCriticalSection(&s_sendLock); @@ -370,18 +487,31 @@ 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++) - { - if (s_connections[i].smallId == smallId && s_connections[i].active) - { - SOCKET sock = s_connections[i].tcpSocket; - LeaveCriticalSection(&s_connectionsLock); - return sock; - } - } - LeaveCriticalSection(&s_connectionsLock); - return INVALID_SOCKET; + EnterCriticalSection(&s_smallIdToSocketLock); + SOCKET sock = s_smallIdToSocket[smallId]; + LeaveCriticalSection(&s_smallIdToSocketLock); + return sock; +} + +void WinsockNetLayer::ClearSocketForSmallId(BYTE smallId) +{ + EnterCriticalSection(&s_smallIdToSocketLock); + s_smallIdToSocket[smallId] = INVALID_SOCKET; + LeaveCriticalSection(&s_smallIdToSocketLock); +} + +// Send reject handshake: sentinel 0xFF + DisconnectPacket wire format (1 byte id 255 + 4 byte big-endian reason). Then caller closes socket. +static void SendRejectWithReason(SOCKET clientSocket, DisconnectPacket::eDisconnectReason reason) +{ + BYTE buf[6]; + buf[0] = WIN64_SMALLID_REJECT; + buf[1] = (BYTE)255; // DisconnectPacket packet id + int r = (int)reason; + buf[2] = (BYTE)((r >> 24) & 0xff); + buf[3] = (BYTE)((r >> 16) & 0xff); + buf[4] = (BYTE)((r >> 8) & 0xff); + buf[5] = (BYTE)(r & 0xff); + send(clientSocket, (const char*)buf, sizeof(buf), 0); } static bool RecvExact(SOCKET sock, BYTE* buf, int len) @@ -396,24 +526,54 @@ static bool RecvExact(SOCKET sock, BYTE* buf, int len) return true; } +#if defined(MINECRAFT_SERVER_BUILD) +static bool TryGetNumericRemoteIp(const sockaddr_in &remoteAddress, std::string *outIp) +{ + if (outIp == nullptr) + { + return false; + } + + outIp->clear(); + char ipBuffer[64] = {}; + const char *ip = inet_ntop(AF_INET, (void *)&remoteAddress.sin_addr, ipBuffer, sizeof(ipBuffer)); + if (ip == nullptr || ip[0] == 0) + { + return false; + } + + *outIp = ip; + return true; +} +#endif + void WinsockNetLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char* data, unsigned int dataSize) { INetworkPlayer* pPlayerFrom = g_NetworkManager.GetPlayerBySmallId(fromSmallId); INetworkPlayer* pPlayerTo = g_NetworkManager.GetPlayerBySmallId(toSmallId); - if (pPlayerFrom == NULL || pPlayerTo == NULL) return; + if (pPlayerFrom == nullptr || pPlayerTo == nullptr) + { + app.DebugPrintf("NET RECV: DROPPED %u bytes from=%d to=%d (player NULL: from=%p to=%p)\n", + dataSize, fromSmallId, toSmallId, pPlayerFrom, pPlayerTo); + return; + } if (s_isHost) { ::Socket* pSocket = pPlayerFrom->GetSocket(); - if (pSocket != NULL) + if (pSocket != nullptr) pSocket->pushDataToQueue(data, dataSize, false); + else + app.DebugPrintf("NET RECV: DROPPED %u bytes, host pSocket NULL for from=%d\n", dataSize, fromSmallId); } else { ::Socket* pSocket = pPlayerTo->GetSocket(); - if (pSocket != NULL) + if (pSocket != nullptr) pSocket->pushDataToQueue(data, dataSize, true); + else + app.DebugPrintf("NET RECV: DROPPED %u bytes, client pSocket NULL for to=%d\n", dataSize, toSmallId); } } @@ -421,7 +581,10 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) { while (s_active) { - SOCKET clientSocket = accept(s_listenSocket, NULL, NULL); + sockaddr_in remoteAddress; + ZeroMemory(&remoteAddress, sizeof(remoteAddress)); + int remoteAddressLength = sizeof(remoteAddress); + SOCKET clientSocket = accept(s_listenSocket, (sockaddr*)&remoteAddress, &remoteAddressLength); if (clientSocket == INVALID_SOCKET) { if (s_active) @@ -432,10 +595,54 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) int noDelay = 1; setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay)); +#if defined(MINECRAFT_SERVER_BUILD) + std::string remoteIp; + const bool hasRemoteIp = TryGetNumericRemoteIp(remoteAddress, &remoteIp); + const char *remoteIpForLog = hasRemoteIp ? remoteIp.c_str() : "unknown"; + if (g_Win64DedicatedServer) + { + ServerRuntime::ServerLogManager::OnIncomingTcpConnection(remoteIpForLog); + if (hasRemoteIp && ServerRuntime::Access::IsIpBanned(remoteIp)) + { + ServerRuntime::ServerLogManager::OnRejectedTcpConnection(remoteIpForLog, ServerRuntime::ServerLogManager::eTcpRejectReason_BannedIp); + SendRejectWithReason(clientSocket, DisconnectPacket::eDisconnect_Banned); + closesocket(clientSocket); + continue; + } + } +#endif + extern QNET_STATE _iQNetStubState; if (_iQNetStubState != QNET_STATE_GAME_PLAY) { - app.DebugPrintf("Win64 LAN: Rejecting connection, game not ready\n"); +#if defined(MINECRAFT_SERVER_BUILD) + if (g_Win64DedicatedServer) + { + ServerRuntime::ServerLogManager::OnRejectedTcpConnection(remoteIpForLog, ServerRuntime::ServerLogManager::eTcpRejectReason_GameNotReady); + } + else +#endif + { + app.DebugPrintf("Win64 LAN: Rejecting connection, game not ready\n"); + } + closesocket(clientSocket); + continue; + } + + extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager; + if (g_pPlatformNetworkManager != nullptr && !g_pPlatformNetworkManager->CanAcceptMoreConnections()) + { +#if defined(MINECRAFT_SERVER_BUILD) + if (g_Win64DedicatedServer) + { + ServerRuntime::ServerLogManager::OnRejectedTcpConnection(remoteIpForLog, ServerRuntime::ServerLogManager::eTcpRejectReason_ServerFull); + } + else +#endif + { + app.DebugPrintf("Win64 LAN: Rejecting connection, server at max players\n"); + } + SendRejectWithReason(clientSocket, DisconnectPacket::eDisconnect_ServerFull); closesocket(clientSocket); continue; } @@ -447,14 +654,24 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) assignedSmallId = s_freeSmallIds.back(); s_freeSmallIds.pop_back(); } - else if (s_nextSmallId < MINECRAFT_NET_MAX_PLAYERS) + else if (s_nextSmallId < (unsigned int)MINECRAFT_NET_MAX_PLAYERS) { - assignedSmallId = s_nextSmallId++; + assignedSmallId = (BYTE)s_nextSmallId++; } else { LeaveCriticalSection(&s_freeSmallIdLock); - app.DebugPrintf("Win64 LAN: Server full, rejecting connection\n"); +#if defined(MINECRAFT_SERVER_BUILD) + if (g_Win64DedicatedServer) + { + ServerRuntime::ServerLogManager::OnRejectedTcpConnection(remoteIpForLog, ServerRuntime::ServerLogManager::eTcpRejectReason_ServerFull); + } + else +#endif + { + app.DebugPrintf("Win64 LAN: Server full, rejecting connection\n"); + } + SendRejectWithReason(clientSocket, DisconnectPacket::eDisconnect_ServerFull); closesocket(clientSocket); continue; } @@ -466,6 +683,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) { app.DebugPrintf("Failed to send small ID to client\n"); closesocket(clientSocket); + PushFreeSmallId(assignedSmallId); continue; } @@ -473,14 +691,27 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) conn.tcpSocket = clientSocket; conn.smallId = assignedSmallId; conn.active = true; - conn.recvThread = NULL; + conn.recvThread = nullptr; EnterCriticalSection(&s_connectionsLock); s_connections.push_back(conn); - int connIdx = (int)s_connections.size() - 1; + int connIdx = static_cast<int>(s_connections.size()) - 1; LeaveCriticalSection(&s_connectionsLock); - app.DebugPrintf("Win64 LAN: Client connected, assigned smallId=%d\n", assignedSmallId); +#if defined(MINECRAFT_SERVER_BUILD) + if (g_Win64DedicatedServer) + { + ServerRuntime::ServerLogManager::OnAcceptedTcpConnection(assignedSmallId, remoteIpForLog); + } + else +#endif + { + app.DebugPrintf("Win64 LAN: Client connected, assigned smallId=%d\n", assignedSmallId); + } + + EnterCriticalSection(&s_smallIdToSocketLock); + s_smallIdToSocket[assignedSmallId] = clientSocket; + LeaveCriticalSection(&s_smallIdToSocketLock); IQNetPlayer* qnetPlayer = &IQNet::m_player[assignedSmallId]; @@ -492,10 +723,10 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) DWORD* threadParam = new DWORD; *threadParam = connIdx; - HANDLE hThread = CreateThread(NULL, 0, RecvThreadProc, threadParam, 0, NULL); + HANDLE hThread = CreateThread(nullptr, 0, RecvThreadProc, threadParam, 0, nullptr); EnterCriticalSection(&s_connectionsLock); - if (connIdx < (int)s_connections.size()) + if (connIdx < static_cast<int>(s_connections.size())) s_connections[connIdx].recvThread = hThread; LeaveCriticalSection(&s_connectionsLock); } @@ -504,11 +735,11 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) { - DWORD connIdx = *(DWORD*)param; - delete (DWORD*)param; + DWORD connIdx = *static_cast<DWORD *>(param); + delete static_cast<DWORD *>(param); EnterCriticalSection(&s_connectionsLock); - if (connIdx >= (DWORD)s_connections.size()) + if (connIdx >= static_cast<DWORD>(s_connections.size())) { LeaveCriticalSection(&s_connectionsLock); return 0; @@ -530,10 +761,10 @@ DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) } int packetSize = - ((uint32_t)header[0] << 24) | - ((uint32_t)header[1] << 16) | - ((uint32_t)header[2] << 8) | - ((uint32_t)header[3]); + (static_cast<uint32_t>(header[0]) << 24) | + (static_cast<uint32_t>(header[1]) << 16) | + (static_cast<uint32_t>(header[2]) << 8) | + static_cast<uint32_t>(header[3]); if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE) { @@ -544,7 +775,7 @@ DWORD WINAPI WinsockNetLayer::RecvThreadProc(LPVOID param) break; } - if ((int)recvBuf.size() < packetSize) + if (static_cast<int>(recvBuf.size()) < packetSize) { recvBuf.resize(packetSize); app.DebugPrintf("Win64 LAN: Resized host recv buffer to %d bytes for client smallId=%d\n", packetSize, clientSmallId); @@ -598,8 +829,22 @@ bool WinsockNetLayer::PopDisconnectedSmallId(BYTE* outSmallId) void WinsockNetLayer::PushFreeSmallId(BYTE smallId) { + // SmallIds 0..(XUSER_MAX_COUNT-1) are permanently reserved for the host's + // local pads and must never be recycled to remote clients. + if (smallId < (BYTE)XUSER_MAX_COUNT) + return; + EnterCriticalSection(&s_freeSmallIdLock); - s_freeSmallIds.push_back(smallId); + // Guard against double-recycle: the reconnect path (queueSmallIdForRecycle) and + // the DoWork disconnect path can both push the same smallId. If we allow duplicates, + // AcceptThread will hand out the same smallId to two different connections. + bool alreadyFree = false; + for (size_t i = 0; i < s_freeSmallIds.size(); i++) + { + if (s_freeSmallIds[i] == smallId) { alreadyFree = true; break; } + } + if (!alreadyFree) + s_freeSmallIds.push_back(smallId); LeaveCriticalSection(&s_freeSmallIdLock); } @@ -619,6 +864,171 @@ void WinsockNetLayer::CloseConnectionBySmallId(BYTE smallId) LeaveCriticalSection(&s_connectionsLock); } +BYTE WinsockNetLayer::GetSplitScreenSmallId(int padIndex) +{ + if (padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) return 0xFF; + return s_splitScreenSmallId[padIndex]; +} + +SOCKET WinsockNetLayer::GetLocalSocket(BYTE senderSmallId) +{ + if (senderSmallId == s_localSmallId) + return s_hostConnectionSocket; + for (int i = 1; i < XUSER_MAX_COUNT; i++) + { + if (s_splitScreenSmallId[i] == senderSmallId && s_splitScreenSocket[i] != INVALID_SOCKET) + return s_splitScreenSocket[i]; + } + return INVALID_SOCKET; +} + +bool WinsockNetLayer::JoinSplitScreen(int padIndex, BYTE* outSmallId) +{ + if (!s_active || s_isHost || padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) + return false; + + if (s_splitScreenSocket[padIndex] != INVALID_SOCKET) + { + return false; + } + + struct addrinfo hints = {}; + struct addrinfo* result = nullptr; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + char portStr[16]; + sprintf_s(portStr, "%d", g_Win64MultiplayerPort); + if (getaddrinfo(g_Win64MultiplayerIP, portStr, &hints, &result) != 0 || result == nullptr) + { + app.DebugPrintf("Win64 LAN: Split-screen getaddrinfo failed for %s:%d\n", g_Win64MultiplayerIP, g_Win64MultiplayerPort); + return false; + } + + SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); + if (sock == INVALID_SOCKET) + { + freeaddrinfo(result); + return false; + } + + int noDelay = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay)); + + if (connect(sock, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR) + { + app.DebugPrintf("Win64 LAN: Split-screen connect() failed: %d\n", WSAGetLastError()); + closesocket(sock); + freeaddrinfo(result); + return false; + } + freeaddrinfo(result); + + BYTE assignBuf[1]; + if (!RecvExact(sock, assignBuf, 1)) + { + app.DebugPrintf("Win64 LAN: Split-screen failed to receive smallId\n"); + closesocket(sock); + return false; + } + + if (assignBuf[0] == WIN64_SMALLID_REJECT) + { + BYTE rejectBuf[5]; + RecvExact(sock, rejectBuf, 5); + app.DebugPrintf("Win64 LAN: Split-screen connection rejected\n"); + closesocket(sock); + return false; + } + + BYTE assignedSmallId = assignBuf[0]; + s_splitScreenSocket[padIndex] = sock; + s_splitScreenSmallId[padIndex] = assignedSmallId; + *outSmallId = assignedSmallId; + + app.DebugPrintf("Win64 LAN: Split-screen pad %d connected, assigned smallId=%d\n", padIndex, assignedSmallId); + + int* threadParam = new int; + *threadParam = padIndex; + s_splitScreenRecvThread[padIndex] = CreateThread(nullptr, 0, SplitScreenRecvThreadProc, threadParam, 0, nullptr); + if (s_splitScreenRecvThread[padIndex] == nullptr) + { + delete threadParam; + closesocket(sock); + s_splitScreenSocket[padIndex] = INVALID_SOCKET; + s_splitScreenSmallId[padIndex] = 0xFF; + app.DebugPrintf("Win64 LAN: CreateThread failed for split-screen pad %d\n", padIndex); + return false; + } + + return true; +} + +void WinsockNetLayer::CloseSplitScreenConnection(int padIndex) +{ + if (padIndex <= 0 || padIndex >= XUSER_MAX_COUNT) return; + + if (s_splitScreenSocket[padIndex] != INVALID_SOCKET) + { + closesocket(s_splitScreenSocket[padIndex]); + s_splitScreenSocket[padIndex] = INVALID_SOCKET; + } + s_splitScreenSmallId[padIndex] = 0xFF; + if (s_splitScreenRecvThread[padIndex] != nullptr) + { + WaitForSingleObject(s_splitScreenRecvThread[padIndex], 2000); + CloseHandle(s_splitScreenRecvThread[padIndex]); + s_splitScreenRecvThread[padIndex] = nullptr; + } +} + +DWORD WINAPI WinsockNetLayer::SplitScreenRecvThreadProc(LPVOID param) +{ + int padIndex = *(int*)param; + delete (int*)param; + + SOCKET sock = s_splitScreenSocket[padIndex]; + BYTE localSmallId = s_splitScreenSmallId[padIndex]; + std::vector<BYTE> recvBuf; + recvBuf.resize(WIN64_NET_RECV_BUFFER_SIZE); + + while (s_active && s_splitScreenSocket[padIndex] != INVALID_SOCKET) + { + BYTE header[4]; + if (!RecvExact(sock, header, 4)) + { + app.DebugPrintf("Win64 LAN: Split-screen pad %d disconnected from host\n", padIndex); + break; + } + + int packetSize = ((uint32_t)header[0] << 24) | ((uint32_t)header[1] << 16) | + ((uint32_t)header[2] << 8) | ((uint32_t)header[3]); + if (packetSize <= 0 || packetSize > WIN64_NET_MAX_PACKET_SIZE) + { + app.DebugPrintf("Win64 LAN: Split-screen pad %d invalid packet size %d\n", padIndex, packetSize); + break; + } + + if ((int)recvBuf.size() < packetSize) + recvBuf.resize(packetSize); + + if (!RecvExact(sock, &recvBuf[0], packetSize)) + { + app.DebugPrintf("Win64 LAN: Split-screen pad %d disconnected from host (body)\n", padIndex); + break; + } + + HandleDataReceived(s_hostSmallId, localSmallId, &recvBuf[0], packetSize); + } + + EnterCriticalSection(&s_disconnectLock); + s_disconnectedSmallIds.push_back(localSmallId); + LeaveCriticalSection(&s_disconnectLock); + + return 0; +} + DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param) { std::vector<BYTE> recvBuf; @@ -643,7 +1053,7 @@ DWORD WINAPI WinsockNetLayer::ClientRecvThreadProc(LPVOID param) break; } - if ((int)recvBuf.size() < packetSize) + if (static_cast<int>(recvBuf.size()) < packetSize) { recvBuf.resize(packetSize); app.DebugPrintf("Win64 LAN: Resized client recv buffer to %d bytes\n", packetSize); @@ -671,7 +1081,7 @@ bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t* hostName, un memset(&s_advertiseData, 0, sizeof(s_advertiseData)); s_advertiseData.magic = WIN64_LAN_BROADCAST_MAGIC; s_advertiseData.netVersion = netVer; - s_advertiseData.gamePort = (WORD)gamePort; + s_advertiseData.gamePort = static_cast<WORD>(gamePort); wcsncpy_s(s_advertiseData.hostName, 32, hostName, _TRUNCATE); s_advertiseData.playerCount = 1; s_advertiseData.maxPlayers = MINECRAFT_NET_MAX_PLAYERS; @@ -693,7 +1103,7 @@ bool WinsockNetLayer::StartAdvertising(int gamePort, const wchar_t* hostName, un setsockopt(s_advertiseSock, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)); s_advertising = true; - s_advertiseThread = CreateThread(NULL, 0, AdvertiseThreadProc, NULL, 0, NULL); + s_advertiseThread = CreateThread(nullptr, 0, AdvertiseThreadProc, nullptr, 0, nullptr); app.DebugPrintf("Win64 LAN: Started advertising on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT); return true; @@ -709,11 +1119,11 @@ void WinsockNetLayer::StopAdvertising() s_advertiseSock = INVALID_SOCKET; } - if (s_advertiseThread != NULL) + if (s_advertiseThread != nullptr) { WaitForSingleObject(s_advertiseThread, 2000); CloseHandle(s_advertiseThread); - s_advertiseThread = NULL; + s_advertiseThread = nullptr; } } @@ -724,6 +1134,13 @@ void WinsockNetLayer::UpdateAdvertisePlayerCount(BYTE count) LeaveCriticalSection(&s_advertiseLock); } +void WinsockNetLayer::UpdateAdvertiseMaxPlayers(BYTE maxPlayers) +{ + EnterCriticalSection(&s_advertiseLock); + s_advertiseData.maxPlayers = maxPlayers; + LeaveCriticalSection(&s_advertiseLock); +} + void WinsockNetLayer::UpdateAdvertiseJoinable(bool joinable) { EnterCriticalSection(&s_advertiseLock); @@ -792,7 +1209,7 @@ bool WinsockNetLayer::StartDiscovery() setsockopt(s_discoverySock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); s_discovering = true; - s_discoveryThread = CreateThread(NULL, 0, DiscoveryThreadProc, NULL, 0, NULL); + s_discoveryThread = CreateThread(nullptr, 0, DiscoveryThreadProc, nullptr, 0, nullptr); app.DebugPrintf("Win64 LAN: Listening for LAN games on UDP port %d\n", WIN64_LAN_DISCOVERY_PORT); return true; @@ -808,11 +1225,11 @@ void WinsockNetLayer::StopDiscovery() s_discoverySock = INVALID_SOCKET; } - if (s_discoveryThread != NULL) + if (s_discoveryThread != nullptr) { WaitForSingleObject(s_discoveryThread, 2000); CloseHandle(s_discoveryThread); - s_discoveryThread = NULL; + s_discoveryThread = nullptr; } EnterCriticalSection(&s_discoveryLock); @@ -846,7 +1263,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) continue; } - if (recvLen < (int)sizeof(Win64LANBroadcast)) + if (recvLen < static_cast<int>(sizeof(Win64LANBroadcast))) continue; Win64LANBroadcast* broadcast = (Win64LANBroadcast*)recvBuf; @@ -864,7 +1281,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) for (size_t i = 0; i < s_discoveredSessions.size(); i++) { if (strcmp(s_discoveredSessions[i].hostIP, senderIP) == 0 && - s_discoveredSessions[i].hostPort == (int)broadcast->gamePort) + s_discoveredSessions[i].hostPort == static_cast<int>(broadcast->gamePort)) { s_discoveredSessions[i].netVersion = broadcast->netVersion; wcsncpy_s(s_discoveredSessions[i].hostName, 32, broadcast->hostName, _TRUNCATE); @@ -885,7 +1302,7 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param) Win64LANSession session; memset(&session, 0, sizeof(session)); strncpy_s(session.hostIP, sizeof(session.hostIP), senderIP, _TRUNCATE); - session.hostPort = (int)broadcast->gamePort; + session.hostPort = static_cast<int>(broadcast->gamePort); session.netVersion = broadcast->netVersion; wcsncpy_s(session.hostName, 32, broadcast->hostName, _TRUNCATE); session.playerCount = broadcast->playerCount; |
