feat: add smartcmd dedicated server

major bug: when connecting to the server a second time, it gets softlocked and keeps repeating ticking connection A\n in debug logs until it times out.
This commit is contained in:
2026-08-29 14:01:24 +03:00
parent a1541c590d
commit 4b962fcb21
146 changed files with 32837 additions and 272 deletions
@@ -1,8 +1,17 @@
#include "stdafx.h"
#include <string>
#include "NetworkSocketLayer.h"
#include "..\..\Common\Network\PlatformNetworkManager.h"
#include "..\..\..\Minecraft.World\Socket.h"
#if defined(MINECRAFT_SERVER_BUILD)
#include "..\..\..\Minecraft.Server\Access\Access.h"
#include "..\..\..\Minecraft.Server\ServerLogManager.h"
#endif
#if defined(MINECRAFT_SERVER_BUILD)
static bool TryGetNumericRemoteIp(const sockaddr_in &remoteAddress, std::string *outIp);
#endif
SOCKET NetworkSocketLayer::s_listenSocket = INVALID_SOCKET;
SOCKET NetworkSocketLayer::s_hostConnectionSocket = INVALID_SOCKET;
@@ -55,23 +64,26 @@ std::vector<LANSession> NetworkSocketLayer::s_discoveredSessions;
CRITICAL_SECTION NetworkSocketLayer::s_disconnectLock;
std::vector<BYTE> NetworkSocketLayer::s_disconnectedSmallIds;
CRITICAL_SECTION NetworkSocketLayer::s_pendingJoinLock;
std::vector<BYTE> NetworkSocketLayer::s_pendingJoinSmallIds;
CRITICAL_SECTION NetworkSocketLayer::s_freeSmallIdLock;
std::vector<BYTE> NetworkSocketLayer::s_freeSmallIds;
// only goes true on a successful Initialize(), and a failed one gets retried
static bool s_locksCreated = false;
#if defined _WINDOWS64
#if !defined _WINDOWS64
bool g_MultiplayerHost = true;
bool g_MultiplayerJoin = false;
#else
bool g_MultiplayerHost = false;
bool g_MultiplayerJoin = true;
#endif
#ifdef MINECRAFT_SERVER_BUILD
int g_DedicatedServerPort = NETWORK_LAN_DEFAULT_PORT;
char g_DedicatedServerBindIP[256] = "";
bool g_DedicatedServerLanAdvertise = true;
#endif
int g_MultiplayerPort = NETWORK_LAN_DEFAULT_PORT;
char g_MultiplayerIP[256] = "192.168.0.69";
char g_MultiplayerIP[256] = "192.168.8.27";
bool NetworkSocketLayer::Initialize()
@@ -86,7 +98,6 @@ bool NetworkSocketLayer::Initialize()
InitializeCriticalSection(&s_advertiseLock);
InitializeCriticalSection(&s_discoveryLock);
InitializeCriticalSection(&s_disconnectLock);
InitializeCriticalSection(&s_pendingJoinLock);
InitializeCriticalSection(&s_freeSmallIdLock);
for (int i = 0; i < NETWORK_LAN_MAX_CLIENTS + 1; i++)
@@ -139,7 +150,10 @@ bool NetworkSocketLayer::Initialize()
s_initialized = true;
// Dedicated Server does not use LAN session discovery and therefore does not initiate discovery.
#ifndef MINECRAFT_SERVER_BUILD
StartDiscovery();
#endif
return true;
}
@@ -224,8 +238,6 @@ void NetworkSocketLayer::Shutdown()
DeleteCriticalSection(&s_discoveryLock);
DeleteCriticalSection(&s_disconnectLock);
s_disconnectedSmallIds.clear();
DeleteCriticalSection(&s_pendingJoinLock);
s_pendingJoinSmallIds.clear();
DeleteCriticalSection(&s_freeSmallIdLock);
s_freeSmallIds.clear();
s_locksCreated = false;
@@ -799,6 +811,12 @@ bool NetworkSocketLayer::JoinGame(const char *ip, int port)
{
return false;
}
// Clear the recv timeout now that the handshake is complete.
// The recv thread should block indefinitely waiting for data.
DWORD noTimeout = 0;
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&noTimeout, sizeof(noTimeout));
s_localSmallId = assignedSmallId;
app.DebugPrintf("LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId);
@@ -907,6 +925,20 @@ SOCKET NetworkSocketLayer::GetSocketForSmallId(BYTE smallId)
#endif
}
// 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] = NETWORK_LAN_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)
{
int totalRecv = 0;
@@ -919,6 +951,27 @@ 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 NetworkSocketLayer::HandleDataReceived(BYTE fromSmallId, BYTE toSmallId, unsigned char *data, unsigned int dataSize)
{
INetworkPlayer *pPlayerFrom = g_NetworkManager.GetPlayerBySmallId(fromSmallId);
@@ -951,9 +1004,15 @@ int NetworkSocketLayer::AcceptThreadProc(LPVOID param)
while (s_active)
{
#ifdef __PSVITA__
SOCKET clientSocket = accept(s_listenSocket, (SceNetSockaddr *)NULL, NULL);
sockaddr_in remoteAddress;
ZeroMemory(&remoteAddress, sizeof(remoteAddress));
int remoteAddressLength = sizeof(remoteAddress);
SOCKET clientSocket = accept(s_listenSocket, (SceNetSockaddr *)&remoteAddress, &remoteAddressLength);
#else
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);
#endif
#if defined _WINDOWS64 || defined _XBOX
if (clientSocket == INVALID_SOCKET)
@@ -974,10 +1033,28 @@ int NetworkSocketLayer::AcceptThreadProc(LPVOID param)
int noDelay = 1;
setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&noDelay, sizeof(noDelay));
extern QNET_STATE _iQNetStubState;
if (_iQNetStubState != QNET_STATE_GAME_PLAY && _iQNetStubState != QNET_STATE_SESSION_STARTING)
#if defined(MINECRAFT_SERVER_BUILD)
std::string remoteIp;
const bool hasRemoteIp = TryGetNumericRemoteIp(remoteAddress, &remoteIp);
const char *remoteIpForLog = hasRemoteIp ? remoteIp.c_str() : "unknown";
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)
{
#if defined(MINECRAFT_SERVER_BUILD)
ServerRuntime::ServerLogManager::OnRejectedTcpConnection(remoteIpForLog, ServerRuntime::ServerLogManager::eTcpRejectReason_GameNotReady);
#else
app.DebugPrintf("Win64 LAN: Rejecting connection, game not ready\n");
#endif
closesocket(clientSocket);
continue;
}
@@ -996,7 +1073,11 @@ int NetworkSocketLayer::AcceptThreadProc(LPVOID param)
else
{
LeaveCriticalSection(&s_freeSmallIdLock);
#if defined(MINECRAFT_SERVER_BUILD)
ServerRuntime::ServerLogManager::OnRejectedTcpConnection(remoteIpForLog, ServerRuntime::ServerLogManager::eTcpRejectReason_ServerFull);
#else
app.DebugPrintf("Win64 LAN: Server full, rejecting connection\n");
#endif
closesocket(clientSocket);
continue;
}
@@ -1026,16 +1107,19 @@ int NetworkSocketLayer::AcceptThreadProc(LPVOID param)
conn.active = true;
LeaveCriticalSection(&s_connectionsLock);
#if defined(MINECRAFT_SERVER_BUILD)
ServerRuntime::ServerLogManager::OnAcceptedTcpConnection(assignedSmallId, remoteIpForLog);
#else
app.DebugPrintf("Win64 LAN: Client connected, assigned smallId=%d\n", assignedSmallId);
#endif
IQNetPlayer *qnetPlayer = &IQNet::m_player[assignedSmallId];
IQNetPlayer* qnetPlayer = &IQNet::m_player[assignedSmallId];
extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer *player, BYTE smallId, bool isHost, bool isLocal);
extern void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost, bool isLocal);
Win64_SetupRemoteQNetPlayer(qnetPlayer, assignedSmallId, false, false);
EnterCriticalSection(&s_pendingJoinLock);
s_pendingJoinSmallIds.push_back(assignedSmallId);
LeaveCriticalSection(&s_pendingJoinLock);
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
g_pPlatformNetworkManager->NotifyPlayerJoined(qnetPlayer);
DWORD *threadParam = new DWORD;
*threadParam = assignedSmallId;
@@ -1142,20 +1226,6 @@ void NetworkSocketLayer::PushFreeSmallId(BYTE smallId)
LeaveCriticalSection(&s_freeSmallIdLock);
}
bool NetworkSocketLayer::PopPendingJoinSmallId(BYTE *outSmallId)
{
bool found = false;
EnterCriticalSection(&s_pendingJoinLock);
if (!s_pendingJoinSmallIds.empty())
{
*outSmallId = s_pendingJoinSmallIds.back();
s_pendingJoinSmallIds.pop_back();
found = true;
}
LeaveCriticalSection(&s_pendingJoinLock);
return found;
}
bool NetworkSocketLayer::IsSmallIdConnected(BYTE smallId)
{
if (smallId >= NETWORK_LAN_MAX_CLIENTS + 1) return false;