aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client
diff options
context:
space:
mode:
authorKevin <115616336+lag@users.noreply.github.com>2026-03-06 19:23:32 -0600
committerGitHub <noreply@github.com>2026-03-06 19:23:32 -0600
commit13960a93b2a7c114446c109de059db305db4555d (patch)
tree1b681d91fd38f0d2da73024041e968160c22552b /Minecraft.Client
parent16446265d555d21f564b5989611a05918728d643 (diff)
Max players from 8 -> 255 + small connection optimizations (#722)
* Multiplayer 8 to max byte increase. Made-with: Cursor * Server chunk optimizations for large player counts, server full notification fix, added to server.properties.
Diffstat (limited to 'Minecraft.Client')
-rw-r--r--Minecraft.Client/ClientConnection.cpp3
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp40
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.h5
-rw-r--r--Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp42
-rw-r--r--Minecraft.Client/MinecraftServer.cpp16
-rw-r--r--Minecraft.Client/MinecraftServer.h4
-rw-r--r--Minecraft.Client/PendingConnection.cpp4
-rw-r--r--Minecraft.Client/PlayerChunkMap.cpp124
-rw-r--r--Minecraft.Client/PlayerList.cpp29
-rw-r--r--Minecraft.Client/PlayerList.h2
-rw-r--r--Minecraft.Client/PlayerRenderer.cpp51
-rw-r--r--Minecraft.Client/PlayerRenderer.h3
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp100
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.h12
14 files changed, 292 insertions, 143 deletions
diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp
index 315a8032..9b955cae 100644
--- a/Minecraft.Client/ClientConnection.cpp
+++ b/Minecraft.Client/ClientConnection.cpp
@@ -830,8 +830,9 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
// Current Win64 path: identify QNet player by name and attach packet XUID.
if (matchedQNetPlayer == NULL)
{
- for (BYTE smallId = 0; smallId < MINECRAFT_NET_MAX_PLAYERS; ++smallId)
+ for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i)
{
+ BYTE smallId = static_cast<BYTE>(i);
INetworkPlayer* np = g_NetworkManager.GetPlayerBySmallId(smallId);
if (np == NULL)
continue;
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
index a898c136..970dfd24 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 <iostream>
#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 28953cec..261639f2 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<PlayerFlags *> 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 c036f7bf..6e354922 100644
--- a/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp
@@ -534,6 +534,48 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass)
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);
}
else
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index 373a5e33..55b02cb9 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -682,6 +682,12 @@ bool MinecraftServer::initServer(int64_t 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 1b09e31d..28909791 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/PendingConnection.cpp b/Minecraft.Client/PendingConnection.cpp
index 72b8f382..29ab7c28 100644
--- a/Minecraft.Client/PendingConnection.cpp
+++ b/Minecraft.Client/PendingConnection.cpp
@@ -136,7 +136,9 @@ void PendingConnection::sendPreLoginResponse()
else
#endif
{
- connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(L"-", ugcXuids, ugcXuidCount, ugcFriendsOnlyBits, server->m_ugcPlayersVersion,szUniqueMapName,app.GetGameHostOption(eGameHostOption_All),hostIndex, server->m_texturePackId) ) );
+ DWORD cappedCount = (ugcXuidCount > 255u) ? 255u : static_cast<DWORD>(ugcXuidCount);
+ BYTE cappedHostIndex = (hostIndex >= 255u) ? 254 : static_cast<BYTE>(hostIndex);
+ connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(L"-", ugcXuids, cappedCount, ugcFriendsOnlyBits, server->m_ugcPlayersVersion,szUniqueMapName,app.GetGameHostOption(eGameHostOption_All),cappedHostIndex, server->m_texturePackId) ) );
}
}
diff --git a/Minecraft.Client/PlayerChunkMap.cpp b/Minecraft.Client/PlayerChunkMap.cpp
index 20898ce0..42df6284 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 <unordered_set>
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> packet)
{
- vector< shared_ptr<ServerPlayer> > sentTo;
- for (unsigned int i = 0; i < players.size(); i++)
+ std::unordered_set<int> 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<ServerPlayer> 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<ServerPlayer> player2 = sentTo[j];
- INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer();
- if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) )
- {
- dontSend = true;
- }
- }
- }
- }
- if( dontSend )
- {
+ shared_ptr<ServerPlayer> 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<shared_ptr<ServerPlayer> >& allPlayers = parent->level->getServer()->getPlayers()->players;
+ for (size_t i = 0; i < allPlayers.size(); i++)
{
- shared_ptr<ServerPlayer> 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<ServerPlayer> 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<ServerPlayer> 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 42724a4f..1742756e 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);
@@ -79,7 +76,7 @@ PlayerList::~PlayerList()
DeleteCriticalSection(&m_closePlayersCS);
}
-void PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer> player, shared_ptr<LoginPacket> packet)
+bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer> player, shared_ptr<LoginPacket> packet)
{
CompoundTag *playerTag = load(player);
@@ -129,13 +126,13 @@ void PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
ServerLevel *level = server->getLevel(player->dimension);
- DWORD playerIndex = 0;
+ DWORD playerIndex = (DWORD)MINECRAFT_NET_MAX_PLAYERS;
{
bool usedIndexes[MINECRAFT_NET_MAX_PLAYERS];
ZeroMemory( &usedIndexes, MINECRAFT_NET_MAX_PLAYERS * sizeof(bool) );
- for (auto& player : players )
+ for (auto& p : players )
{
- usedIndexes[player->getPlayerIndex()] = true;
+ usedIndexes[p->getPlayerIndex()] = true;
}
for(unsigned int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i)
{
@@ -146,6 +143,12 @@ void PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
}
}
}
+ if (playerIndex >= (unsigned int)MINECRAFT_NET_MAX_PLAYERS)
+ {
+ connection->send(shared_ptr<DisconnectPacket>(new DisconnectPacket(DisconnectPacket::eDisconnect_ServerFull)));
+ connection->sendAndQuit();
+ return false;
+ }
player->setPlayerIndex( playerIndex );
player->setCustomSkin( packet->m_playerSkinId );
player->setCustomCape( packet->m_playerCapeId );
@@ -233,8 +236,9 @@ void PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
addPlayerToReceiving( player );
+ int maxPlayersForPacket = getMaxPlayers() > 255 ? 255 : getMaxPlayers();
playerConnection->send( shared_ptr<LoginPacket>( new LoginPacket(L"", player->entityId, level->getLevelData()->getGenerator(), level->getSeed(), player->gameMode->getGameModeForPlayer()->getId(),
- (byte) level->dimension->id, (byte) level->getMaxBuildHeight(), (byte) getMaxPlayers(),
+ (byte) level->dimension->id, (byte) level->getMaxBuildHeight(), (byte) maxPlayersForPacket,
level->difficulty, TelemetryManager->GetMultiplayerInstanceID(), (BYTE)playerIndex, level->useNewSeaLevel(), player->getAllPlayerGamePrivileges(),
level->getLevelData()->getXZSize(), level->getLevelData()->getHellScale() ) ) );
playerConnection->send( shared_ptr<SetSpawnPositionPacket>( new SetSpawnPositionPacket(spawnPos->x, spawnPos->y, spawnPos->z) ) );
@@ -296,6 +300,7 @@ void PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
}
}
}
+ return true;
}
void PlayerList::updateEntireScoreboard(ServerScoreboard *scoreboard, shared_ptr<ServerPlayer> player)
@@ -513,11 +518,7 @@ if (player->riding != NULL)
shared_ptr<ServerPlayer> 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<ServerPlayer>();
diff --git a/Minecraft.Client/PlayerList.h b/Minecraft.Client/PlayerList.h
index 6a6ee94c..03ed2398 100644
--- a/Minecraft.Client/PlayerList.h
+++ b/Minecraft.Client/PlayerList.h
@@ -64,7 +64,7 @@ public:
public:
PlayerList(MinecraftServer *server);
~PlayerList();
- void placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer> player, shared_ptr<LoginPacket> packet);
+ bool placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer> player, shared_ptr<LoginPacket> packet);
protected:
void updateEntireScoreboard(ServerScoreboard *scoreboard, shared_ptr<ServerPlayer> player);
diff --git a/Minecraft.Client/PlayerRenderer.cpp b/Minecraft.Client/PlayerRenderer.cpp
index 71c05067..ff930a13 100644
--- a/Minecraft.Client/PlayerRenderer.cpp
+++ b/Minecraft.Client/PlayerRenderer.cpp
@@ -14,19 +14,44 @@
#include "..\Minecraft.World\net.minecraft.h"
#include "..\Minecraft.World\StringHelpers.h"
-const unsigned int PlayerRenderer::s_nametagColors[MINECRAFT_NET_MAX_PLAYERS] =
+static unsigned int nametagColorForIndex(int index)
{
- 0xff000000, // WHITE (represents the "white" player, but using black as the colour)
- 0xff33cc33, // GREEN
- 0xffcc3333, // RED
- 0xff3333cc, // BLUE
-#ifndef __PSVITA__ // only 4 player on Vita
- 0xffcc33cc, // PINK
- 0xffcc6633, // ORANGE
- 0xffcccc33, // YELLOW
- 0xff33dccc, // TURQUOISE
+ static const unsigned int s_firstColors[] = {
+ 0xff000000, // WHITE (represents the "white" player, but using black as the colour)
+ 0xff33cc33, // GREEN
+ 0xffcc3333, // RED
+ 0xff3333cc, // BLUE
+ 0xffcc33cc, // PINK
+ 0xffcc6633, // ORANGE
+ 0xffcccc33, // YELLOW
+ 0xff33dccc // TURQUOISE
+ };
+#ifndef __PSVITA__
+ if (index >= 0 && index < 8) // Use original colors for the first 8 players
+ return s_firstColors[index];
+ if (index >= 8 && index < MINECRAFT_NET_MAX_PLAYERS)
+ {
+ float h = (float)((index * 137) % 360) / 60.f;
+ int i = (int)h;
+ float f = h - i;
+ float q = 1.f - f;
+ float t = 1.f - (1.f - f);
+ float r = 0.f, g = 0.f, b = 0.f;
+ switch (i % 6)
+ {
+ case 0: r = 1.f; g = t; b = 0.f; break;
+ case 1: r = q; g = 1.f; b = 0.f; break;
+ case 2: r = 0.f; g = 1.f; b = t; break;
+ case 3: r = 0.f; g = q; b = 1.f; break;
+ case 4: r = t; g = 0.f; b = 1.f; break;
+ default: r = 1.f; g = 0.f; b = q; break;
+ }
+ int ri = (int)(r * 255.f) & 0xff, gi = (int)(g * 255.f) & 0xff, bi = (int)(b * 255.f) & 0xff;
+ return 0xff000000u | (ri << 16) | (gi << 8) | bi;
+ }
#endif
-};
+ return 0xFF000000; //Fallback if exceeds 256 somehow
+}
ResourceLocation PlayerRenderer::DEFAULT_LOCATION = ResourceLocation(TN_MOB_CHAR);
@@ -41,9 +66,7 @@ PlayerRenderer::PlayerRenderer() : LivingEntityRenderer( new HumanoidModel(0), 0
unsigned int PlayerRenderer::getNametagColour(int index)
{
if( index >= 0 && index < MINECRAFT_NET_MAX_PLAYERS)
- {
- return s_nametagColors[index];
- }
+ return nametagColorForIndex(index);
return 0xFF000000;
}
diff --git a/Minecraft.Client/PlayerRenderer.h b/Minecraft.Client/PlayerRenderer.h
index 25c32a30..494ff795 100644
--- a/Minecraft.Client/PlayerRenderer.h
+++ b/Minecraft.Client/PlayerRenderer.h
@@ -13,9 +13,6 @@ public:
static ResourceLocation DEFAULT_LOCATION;
private:
- // 4J Added
- static const unsigned int s_nametagColors[MINECRAFT_NET_MAX_PLAYERS];
-
HumanoidModel *humanoidModel;
HumanoidModel *armorParts1;
HumanoidModel *armorParts2;
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
index ca1d62af..c76bc2fe 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;
@@ -21,7 +26,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 = 1;
CRITICAL_SECTION WinsockNetLayer::s_sendLock;
CRITICAL_SECTION WinsockNetLayer::s_connectionsLock;
@@ -46,6 +51,8 @@ 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;
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())
@@ -447,14 +505,15 @@ 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");
+ 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 fd1280f7..f30240d3 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
@@ -12,7 +12,8 @@
#pragma comment(lib, "Ws2_32.lib")
#define WIN64_NET_DEFAULT_PORT 25565
-#define WIN64_NET_MAX_CLIENTS 7
+#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();
@@ -116,7 +118,7 @@ private:
static BYTE s_localSmallId;
static BYTE s_hostSmallId;
- static BYTE s_nextSmallId;
+ static unsigned int s_nextSmallId;
static CRITICAL_SECTION s_sendLock;
static CRITICAL_SECTION s_connectionsLock;
@@ -141,6 +143,12 @@ private:
static CRITICAL_SECTION s_freeSmallIdLock;
static std::vector<BYTE> 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;