From 38d24bff0ce98b318756bbb1c78f428e13a9ea06 Mon Sep 17 00:00:00 2001 From: lag <115616336+lag@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:22:25 -0600 Subject: [PATCH] Server chunk optimizations for large player counts, server full notification fix, added to server.properties. --- .../Network/PlatformNetworkManagerStub.cpp | 40 +++++- .../Network/PlatformNetworkManagerStub.h | 5 + .../Common/UI/UIScene_JoinMenu.cpp | 42 ++++++ Minecraft.Client/MinecraftServer.cpp | 16 ++- Minecraft.Client/MinecraftServer.h | 4 + Minecraft.Client/PlayerChunkMap.cpp | 124 +++++------------- Minecraft.Client/PlayerList.cpp | 11 +- .../Windows64/Network/WinsockNetLayer.cpp | 94 +++++++++++-- .../Windows64/Network/WinsockNetLayer.h | 8 ++ 9 files changed, 231 insertions(+), 113 deletions(-) diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 80fbd98cf..b7cb11372 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -8,6 +8,8 @@ #include "..\..\Windows64\Windows64_Xuid.h" #include "..\..\Minecraft.h" #include "..\..\User.h" +#include "..\..\MinecraftServer.h" +#include "..\..\PlayerList.h" #include #endif @@ -238,15 +240,33 @@ void CPlatformNetworkManagerStub::DoWork() qnetPlayer->m_resolvedXuid = INVALID_XUID; qnetPlayer->m_gamertag[0] = 0; qnetPlayer->SetCustomDataValue(0); - WinsockNetLayer::PushFreeSmallId(disconnectedSmallId); if (IQNet::s_playerCount > 1) IQNet::s_playerCount--; } + // Always return smallId to the free pool so it can be reused (game may have already cleared the slot). + WinsockNetLayer::PushFreeSmallId(disconnectedSmallId); + // Clear O(1) socket lookup so GetSocketForSmallId stays fast (s_connections never shrinks). + WinsockNetLayer::ClearSocketForSmallId(disconnectedSmallId); + // Clear chunk visibility flags for this system so rejoin gets fresh chunk state. + SystemFlagRemoveBySmallId((int)disconnectedSmallId); } } #endif } +bool CPlatformNetworkManagerStub::CanAcceptMoreConnections() +{ +#ifdef _WINDOWS64 + MinecraftServer* server = MinecraftServer::getInstance(); + if (server == NULL) return true; + PlayerList* list = server->getPlayerList(); + if (list == NULL) return true; + return (unsigned int)list->players.size() < (unsigned int)list->getMaxPlayers(); +#else + return true; +#endif +} + int CPlatformNetworkManagerStub::GetPlayerCount() { return m_pIQNet->GetPlayerCount(); @@ -581,6 +601,7 @@ CPlatformNetworkManagerStub::PlayerFlags::PlayerFlags(INetworkPlayer *pNetworkPl this->flags = new unsigned char [ count / 8 ]; memset( this->flags, 0, count / 8 ); this->count = count; + this->m_smallId = (pNetworkPlayer && pNetworkPlayer->IsLocal()) ? 256 : (pNetworkPlayer ? (int)pNetworkPlayer->GetSmallId() : -1); } CPlatformNetworkManagerStub::PlayerFlags::~PlayerFlags() { @@ -618,6 +639,23 @@ void CPlatformNetworkManagerStub::SystemFlagRemovePlayer(INetworkPlayer *pNetwor } } +// Clear chunk flags for a system when they disconnect (by smallId). Call even when we don't find the player, +// so we always clear and the smallId can be reused without stale "chunk seen" state. +void CPlatformNetworkManagerStub::SystemFlagRemoveBySmallId(int smallId) +{ + if (smallId < 0) return; + for (unsigned int i = 0; i < m_playerFlags.size(); i++) + { + if (m_playerFlags[i]->m_smallId == smallId) + { + delete m_playerFlags[i]; + m_playerFlags[i] = m_playerFlags.back(); + m_playerFlags.pop_back(); + return; + } + } +} + void CPlatformNetworkManagerStub::SystemFlagReset() { for( unsigned int i = 0; i < m_playerFlags.size(); i++ ) diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h index 28953cecb..261639f2a 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h @@ -98,12 +98,14 @@ private: INetworkPlayer *m_pNetworkPlayer; unsigned char *flags; unsigned int count; + int m_smallId; PlayerFlags(INetworkPlayer *pNetworkPlayer, unsigned int count); ~PlayerFlags(); }; vector m_playerFlags; void SystemFlagAddPlayer(INetworkPlayer *pNetworkPlayer); void SystemFlagRemovePlayer(INetworkPlayer *pNetworkPlayer); + void SystemFlagRemoveBySmallId(int smallId); void SystemFlagReset(); public: virtual void SystemFlagSet(INetworkPlayer *pNetworkPlayer, int index); @@ -161,6 +163,9 @@ public: virtual void GetFullFriendSessionInfo( FriendSessionInfo *foundSession, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam ); virtual void ForceFriendsSessionRefresh(); + // Win64: used by accept thread to reject connections when server is at max players (so we don't assign smallId > max). + bool CanAcceptMoreConnections(); + public: void NotifyPlayerJoined( IQNetPlayer *pQNetPlayer ); void NotifyPlayerLeaving(IQNetPlayer* pQNetPlayer); diff --git a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp index c036f7bf9..6e3549221 100644 --- a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp @@ -532,6 +532,48 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass) break; } + if( exitReasonStringId == -1 ) + { + Minecraft* pMinecraft = Minecraft::GetInstance(); + int primaryPad = ProfileManager.GetPrimaryPad(); + if( pMinecraft->m_connectionFailed[primaryPad] ) + { + switch( pMinecraft->m_connectionFailedReason[primaryPad] ) + { + case DisconnectPacket::eDisconnect_LoginTooLong: + exitReasonStringId = IDS_DISCONNECTED_LOGIN_TOO_LONG; + break; + case DisconnectPacket::eDisconnect_ServerFull: + exitReasonStringId = IDS_DISCONNECTED_SERVER_FULL; + break; + case DisconnectPacket::eDisconnect_Kicked: + exitReasonStringId = IDS_DISCONNECTED_KICKED; + break; + case DisconnectPacket::eDisconnect_NoUGC_AllLocal: + exitReasonStringId = IDS_NO_USER_CREATED_CONTENT_PRIVILEGE_ALL_LOCAL; + break; + case DisconnectPacket::eDisconnect_NoUGC_Single_Local: + exitReasonStringId = IDS_NO_USER_CREATED_CONTENT_PRIVILEGE_SINGLE_LOCAL; + break; + case DisconnectPacket::eDisconnect_NoFlying: + exitReasonStringId = IDS_DISCONNECTED_FLYING; + break; + case DisconnectPacket::eDisconnect_Quitting: + exitReasonStringId = IDS_DISCONNECTED_SERVER_QUIT; + break; + case DisconnectPacket::eDisconnect_OutdatedServer: + exitReasonStringId = IDS_DISCONNECTED_SERVER_OLD; + break; + case DisconnectPacket::eDisconnect_OutdatedClient: + exitReasonStringId = IDS_DISCONNECTED_CLIENT_OLD; + break; + default: + exitReasonStringId = IDS_CONNECTION_LOST_SERVER; + break; + } + } + } + if( exitReasonStringId == -1 ) { ui.NavigateBack(pClass->m_iPad); diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp index 012ae19bd..b7da7c43b 100644 --- a/Minecraft.Client/MinecraftServer.cpp +++ b/Minecraft.Client/MinecraftServer.cpp @@ -682,6 +682,12 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW } #endif setPlayers(new PlayerList(this)); +#ifdef _WINDOWS64 + { + int maxP = getPlayerList()->getMaxPlayers(); + WinsockNetLayer::UpdateAdvertiseMaxPlayers((BYTE)(maxP > 255 ? 255 : maxP)); + } +#endif // 4J-JEV: Need to wait for levelGenerationOptions to load. while ( app.getLevelGenerationOptions() != NULL && !app.getLevelGenerationOptions()->hasLoadedData() ) @@ -2347,15 +2353,19 @@ void MinecraftServer::chunkPacketManagement_PostTick() } #else -// 4J Added +// 4J Added - round-robin chunk sends by player index. Compare vs the player at the current queue index, +// not GetSessionIndex() (smallId), so reused smallIds after many connect/disconnects still get chunk sends. bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player) { if( player == NULL ) return false; int time = GetTickCount(); - if( player->GetSessionIndex() == s_slowQueuePlayerIndex && (time - s_slowQueueLastTime) > MINECRAFT_SERVER_SLOW_QUEUE_DELAY ) + DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount(); + if( currentPlayerCount == 0 ) return false; + int index = s_slowQueuePlayerIndex % (int)currentPlayerCount; + INetworkPlayer *queuePlayer = g_NetworkManager.GetPlayerByIndex( index ); + if( queuePlayer != NULL && (player == queuePlayer || player->IsSameSystem(queuePlayer)) && (time - s_slowQueueLastTime) > MINECRAFT_SERVER_SLOW_QUEUE_DELAY ) { -// app.DebugPrintf("Slow queue OK for player #%d\n", player->GetSessionIndex()); return true; } diff --git a/Minecraft.Client/MinecraftServer.h b/Minecraft.Client/MinecraftServer.h index 7fa8c7522..a432f1ed8 100644 --- a/Minecraft.Client/MinecraftServer.h +++ b/Minecraft.Client/MinecraftServer.h @@ -18,7 +18,11 @@ class LevelType; class ProgressRenderer; class CommandDispatcher; +#if defined(_WINDOWS64) +#define MINECRAFT_SERVER_SLOW_QUEUE_DELAY 0 // Removed slow queue because at large player counts, chunks stopped appearing +#else #define MINECRAFT_SERVER_SLOW_QUEUE_DELAY 250 +#endif #if defined _XBOX_ONE || defined _XBOX || defined __ORBIS__ || defined __PS3__ || defined __PSVITA__ #define _ACK_CHUNK_SEND_THROTTLING diff --git a/Minecraft.Client/PlayerChunkMap.cpp b/Minecraft.Client/PlayerChunkMap.cpp index ae81bd699..0753417b0 100644 --- a/Minecraft.Client/PlayerChunkMap.cpp +++ b/Minecraft.Client/PlayerChunkMap.cpp @@ -12,6 +12,7 @@ #include "..\Minecraft.World\ArrayWithLength.h" #include "..\Minecraft.World\System.h" #include "PlayerList.h" +#include PlayerChunkMap::PlayerChunk::PlayerChunk(int x, int z, PlayerChunkMap *pcm) : pos(x,z) { @@ -204,106 +205,53 @@ void PlayerChunkMap::PlayerChunk::prioritiseTileChanges() prioritised = true; } +// One system id per machine so we send at most once per system. Local = 256, remote = GetSmallId(). +static int getSystemIdForSentTo(INetworkPlayer* np) +{ + if (np == NULL) return -1; + return np->IsLocal() ? 256 : (int)np->GetSmallId(); +} + void PlayerChunkMap::PlayerChunk::broadcast(shared_ptr packet) { - vector< shared_ptr > sentTo; - for (unsigned int i = 0; i < players.size(); i++) + std::unordered_set sentToSystemIds; // O(1) "already sent to this system" check instead of O(N) scan + for (unsigned int i = 0; i < players.size(); i++) { - shared_ptr player = players[i]; - - // 4J - don't send to a player we've already sent this data to that shares the same machine. TileUpdatePacket, - // ChunkTilesUpdatePacket and SignUpdatePacket all used to limit themselves to sending once to each machine - // by only sending to the primary player on each machine. This was causing trouble for split screen - // as updates were only coming in for the region round this one player. Now these packets can be sent to any - // player, but we try to restrict the network impact this has by not resending to the one machine - bool dontSend = false; - if( sentTo.size() ) - { - INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer(); - if( thisPlayer == NULL ) - { - dontSend = true; - } - else - { - for(unsigned int j = 0; j < sentTo.size(); j++ ) - { - shared_ptr player2 = sentTo[j]; - INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer(); - if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) ) - { - dontSend = true; - } - } - } - } - if( dontSend ) - { + shared_ptr player = players[i]; + INetworkPlayer* thisPlayer = player->connection->getNetworkPlayer(); + if (thisPlayer == NULL) continue; + int sysId = getSystemIdForSentTo(thisPlayer); + if (sysId >= 0 && sentToSystemIds.find(sysId) != sentToSystemIds.end()) continue; - } - // 4J Changed to get the flag index for the player before we send a packet. This flag is updated when we queue - // for send the first BlockRegionUpdatePacket for this chunk to that player/players system. Therefore there is no need to - // send tile updates or other updates until that has been sent int flagIndex = ServerPlayer::getFlagIndexForChunk(pos, parent->dimension); - if (player->seenChunks.find(pos) != player->seenChunks.end() && (player->connection->isLocal() || g_NetworkManager.SystemFlagGet(player->connection->getNetworkPlayer(),flagIndex) )) + if (player->seenChunks.find(pos) != player->seenChunks.end() && (player->connection->isLocal() || g_NetworkManager.SystemFlagGet(thisPlayer, flagIndex))) { - player->connection->send(packet); - sentTo.push_back(player); - } - } - // Now also check round all the players that are involved in this game. We also want to send the packet - // to them if their system hasn't received it already, but they have received the first BlockRegionUpdatePacket for this - // chunk - - // Make sure we are only doing this for BlockRegionUpdatePacket, ChunkTilesUpdatePacket and TileUpdatePacket. - // We'll be potentially sending to players who aren't on the same level as this packet is intended for, - // and only these 3 packets have so far been updated to be able to encode the level so they are robust - // enough to cope with this - if(!( ( packet->getId() == 51 ) || ( packet->getId() == 52 ) || ( packet->getId() == 53 ) ) ) - { - return; + player->connection->send(packet); + if (sysId >= 0) sentToSystemIds.insert(sysId); + } } + // Also send to other server players who have this chunk (may not be in this chunk's players list) + if (!((packet->getId() == 51) || (packet->getId() == 52) || (packet->getId() == 53))) + return; - for( int i = 0; i < parent->level->getServer()->getPlayers()->players.size(); i++ ) + const vector >& allPlayers = parent->level->getServer()->getPlayers()->players; + for (size_t i = 0; i < allPlayers.size(); i++) { - shared_ptr player = parent->level->getServer()->getPlayers()->players[i]; - // Don't worry about local players, they get all their updates through sharing level with the server anyway - if ( player->connection == NULL ) continue; - if( player->connection->isLocal() ) continue; + shared_ptr player = allPlayers[i]; + if (player->connection == NULL || player->connection->isLocal()) continue; - // Don't worry about this player if they haven't had this chunk yet (this flag will be the - // same for all players on the same system) - int flagIndex = ServerPlayer::getFlagIndexForChunk(pos,parent->dimension); - if(!g_NetworkManager.SystemFlagGet(player->connection->getNetworkPlayer(),flagIndex)) continue; + INetworkPlayer* thisPlayer = player->connection->getNetworkPlayer(); + if (thisPlayer == NULL) continue; + int sysId = getSystemIdForSentTo(thisPlayer); + if (sysId >= 0 && sentToSystemIds.find(sysId) != sentToSystemIds.end()) + continue; - // From here on the same rules as in the loop above - don't send it if we've already sent to the same system - bool dontSend = false; - if( sentTo.size() ) - { - INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer(); - if( thisPlayer == NULL ) - { - dontSend = true; - } - else - { - for(unsigned int j = 0; j < sentTo.size(); j++ ) - { - shared_ptr player2 = sentTo[j]; - INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer(); - if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) ) - { - dontSend = true; - } - } - } - } - if( !dontSend ) - { - player->connection->send(packet); - sentTo.push_back(player); - } + int flagIndex = ServerPlayer::getFlagIndexForChunk(pos, parent->dimension); + if (!g_NetworkManager.SystemFlagGet(thisPlayer, flagIndex)) continue; + + player->connection->send(packet); + if (sysId >= 0) sentToSystemIds.insert(sysId); } } diff --git a/Minecraft.Client/PlayerList.cpp b/Minecraft.Client/PlayerList.cpp index 8fa013d47..1742756ec 100644 --- a/Minecraft.Client/PlayerList.cpp +++ b/Minecraft.Client/PlayerList.cpp @@ -56,11 +56,8 @@ PlayerList::PlayerList(MinecraftServer *server) //int viewDistance = server->settings->getInt(L"view-distance", 10); -#ifdef _WINDOWS64 - maxPlayers = MINECRAFT_NET_MAX_PLAYERS; -#else - maxPlayers = server->settings->getInt(L"max-players", 20); -#endif + int rawMax = server->settings->getInt(L"max-players", 8); + maxPlayers = (unsigned int)Mth::clamp(rawMax, 1, MINECRAFT_NET_MAX_PLAYERS); doWhiteList = false; InitializeCriticalSection(&m_kickPlayersCS); InitializeCriticalSection(&m_closePlayersCS); @@ -521,11 +518,7 @@ if (player->riding != NULL) shared_ptr PlayerList::getPlayerForLogin(PendingConnection *pendingConnection, const wstring& userName, PlayerUID xuid, PlayerUID onlineXuid) { -#ifdef _WINDOWS64 - if (players.size() >= (unsigned int)MINECRAFT_NET_MAX_PLAYERS) -#else if (players.size() >= (unsigned int)maxPlayers) -#endif { pendingConnection->disconnect(DisconnectPacket::eDisconnect_ServerFull); return shared_ptr(); diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index bda5062dd..c76bc2fe8 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -8,6 +8,11 @@ #include "WinsockNetLayer.h" #include "..\..\Common\Network\PlatformNetworkManagerStub.h" #include "..\..\..\Minecraft.World\Socket.h" +#include "..\..\..\Minecraft.World\DisconnectPacket.h" +#include "..\..\Minecraft.h" +#include "..\4JLibs\inc\4J_Profile.h" + +static bool RecvExact(SOCKET sock, BYTE* buf, int len); SOCKET WinsockNetLayer::s_listenSocket = INVALID_SOCKET; SOCKET WinsockNetLayer::s_hostConnectionSocket = INVALID_SOCKET; @@ -46,6 +51,8 @@ std::vector WinsockNetLayer::s_disconnectedSmallIds; CRITICAL_SECTION WinsockNetLayer::s_freeSmallIdLock; std::vector WinsockNetLayer::s_freeSmallIds; +SOCKET WinsockNetLayer::s_smallIdToSocket[256]; +CRITICAL_SECTION WinsockNetLayer::s_smallIdToSocketLock; bool g_Win64MultiplayerHost = false; bool g_Win64MultiplayerJoin = false; @@ -73,6 +80,9 @@ 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; @@ -137,6 +147,7 @@ void WinsockNetLayer::Shutdown() s_disconnectedSmallIds.clear(); DeleteCriticalSection(&s_freeSmallIdLock); s_freeSmallIds.clear(); + DeleteCriticalSection(&s_smallIdToSocketLock); WSACleanup(); s_initialized = false; } @@ -155,6 +166,10 @@ bool WinsockNetLayer::HostGame(int port, const char* bindIp) 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; @@ -289,6 +304,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; @@ -370,18 +406,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) @@ -440,6 +489,15 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) continue; } + extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager; + if (g_pPlatformNetworkManager != NULL && !g_pPlatformNetworkManager->CanAcceptMoreConnections()) + { + app.DebugPrintf("Win64 LAN: Rejecting connection, server at max players\n"); + SendRejectWithReason(clientSocket, DisconnectPacket::eDisconnect_ServerFull); + closesocket(clientSocket); + continue; + } + BYTE assignedSmallId; EnterCriticalSection(&s_freeSmallIdLock); if (!s_freeSmallIds.empty()) @@ -455,6 +513,7 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) { LeaveCriticalSection(&s_freeSmallIdLock); app.DebugPrintf("Win64 LAN: Server full, rejecting connection\n"); + SendRejectWithReason(clientSocket, DisconnectPacket::eDisconnect_ServerFull); closesocket(clientSocket); continue; } @@ -482,6 +541,10 @@ DWORD WINAPI WinsockNetLayer::AcceptThreadProc(LPVOID param) 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]; extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost, bool isLocal); @@ -724,6 +787,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); diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h index 59be9c6b8..f30240d32 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -13,6 +13,7 @@ #define WIN64_NET_DEFAULT_PORT 25565 #define WIN64_NET_MAX_CLIENTS 255 +#define WIN64_SMALLID_REJECT 0xFF #define WIN64_NET_RECV_BUFFER_SIZE 65536 #define WIN64_NET_MAX_PACKET_SIZE (4 * 1024 * 1024) #define WIN64_LAN_DISCOVERY_PORT 25566 @@ -89,6 +90,7 @@ public: static bool StartAdvertising(int gamePort, const wchar_t* hostName, unsigned int gameSettings, unsigned int texPackId, unsigned char subTexId, unsigned short netVer); static void StopAdvertising(); static void UpdateAdvertisePlayerCount(BYTE count); + static void UpdateAdvertiseMaxPlayers(BYTE maxPlayers); static void UpdateAdvertiseJoinable(bool joinable); static bool StartDiscovery(); @@ -141,6 +143,12 @@ private: static CRITICAL_SECTION s_freeSmallIdLock; static std::vector s_freeSmallIds; + // O(1) smallId -> socket lookup so we don't scan s_connections (which never shrinks) on every send + static SOCKET s_smallIdToSocket[256]; + static CRITICAL_SECTION s_smallIdToSocketLock; + +public: + static void ClearSocketForSmallId(BYTE smallId); }; extern bool g_Win64MultiplayerHost;