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
@@ -358,12 +358,24 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame
// PRIMARY PLAYER
vector<ClientConnection *> createdConnections;
ClientConnection *connection;
ClientConnection *connection = NULL;
if( g_NetworkManager.IsHost() )
#ifdef MINECRAFT_SERVER_BUILD
{
app.DebugPrintf("Dedicated server mode: skipping local host client connection\n");
// Keep telemetry behavior consistent with the host path.
INT multiplayerInstanceId = TelemetryManager->GenerateMultiplayerInstanceId();
TelemetryManager->SetMultiplayerInstanceId(multiplayerInstanceId);
app.SetGameMode( eMode_Multiplayer );
}
#else
{
connection = new ClientConnection(minecraft, NULL);
}
#endif
else
{
INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(ProfileManager.GetLockedProfile());
@@ -389,155 +401,159 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame
connection = new ClientConnection(minecraft, socket);
}
if( !connection->createdOk )
if (connection != NULL)
{
assert(false);
delete connection;
connection = NULL;
MinecraftServer::HaltServer();
return false;
}
connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(minecraft->user->name) ) );
// Tick connection until we're ready to go. The stages involved in this are:
// (1) Creating the ClientConnection sends a prelogin packet to the server
// (2) the server sends a prelogin back, which is handled by the clientConnection, and returns a login packet
// (3) the server sends a login back, which is handled by the client connection to start the game
if( !g_NetworkManager.IsHost() )
{
Minecraft::GetInstance()->progressRenderer->progressStart(IDS_PROGRESS_CONNECTING);
}
else
{
// 4J Stu - Host needs to generate a unique multiplayer id for sentient telemetry reporting
INT multiplayerInstanceId = TelemetryManager->GenerateMultiplayerInstanceId();
TelemetryManager->SetMultiplayerInstanceId(multiplayerInstanceId);
}
TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected();
do
{
app.DebugPrintf("ticking connection A\n");
connection->tick();
// 4J Stu - We were ticking this way too fast which could cause the connection to time out
// The connections should tick at 20 per second
Sleep(50);
} while ( (IsInSession() && !connection->isStarted() && !connection->isClosed() && !g_NetworkManager.IsLeavingGame()) || tPack->isLoadingData() || (Minecraft::GetInstance()->skins->needsUIUpdate() || ui.IsReloadingSkin()) );
ui.CleanUpSkinReload();
// 4J Stu - Fix for #11279 - CRASH: TCR 001: BAS Game Stability: Signing out of game will cause title to crash
// We need to break out of the above loop if m_bLeavingGame is set, and close the connection
if( g_NetworkManager.IsLeavingGame() || !IsInSession() )
{
connection->close();
}
if( connection->isStarted() && !connection->isClosed() )
{
createdConnections.push_back( connection );
int primaryPad = ProfileManager.GetPrimaryPad();
app.SetRichPresenceContext(primaryPad,CONTEXT_GAME_STATE_BLANK);
if (GetPlayerCount() > 1) // Are we offline or online, and how many players are there
if( !connection->createdOk )
{
if (IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false);
else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER,false);
assert(false);
delete connection;
connection = NULL;
MinecraftServer::HaltServer();
return false;
}
connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(minecraft->user->name) ) );
// Tick connection until we're ready to go. The stages involved in this are:
// (1) Creating the ClientConnection sends a prelogin packet to the server
// (2) the server sends a prelogin back, which is handled by the clientConnection, and returns a login packet
// (3) the server sends a login back, which is handled by the client connection to start the game
if( !g_NetworkManager.IsHost() )
{
Minecraft::GetInstance()->progressRenderer->progressStart(IDS_PROGRESS_CONNECTING);
}
else
{
if(IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1POFFLINE,false);
else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1P,false);
// 4J Stu - Host needs to generate a unique multiplayer id for sentient telemetry reporting
INT multiplayerInstanceId = TelemetryManager->GenerateMultiplayerInstanceId();
TelemetryManager->SetMultiplayerInstanceId(multiplayerInstanceId);
}
TexturePack *tPack = Minecraft::GetInstance()->skins->getSelected();
do
{
app.DebugPrintf("ticking connection A\n");
connection->tick();
// 4J Stu - We were ticking this way too fast which could cause the connection to time out
// The connections should tick at 20 per second
Sleep(50);
} while ( (IsInSession() && !connection->isStarted() && !connection->isClosed() && !g_NetworkManager.IsLeavingGame()) || tPack->isLoadingData() || (Minecraft::GetInstance()->skins->needsUIUpdate() || ui.IsReloadingSkin()) );
ui.CleanUpSkinReload();
// 4J Stu - Fix for #11279 - CRASH: TCR 001: BAS Game Stability: Signing out of game will cause title to crash
// We need to break out of the above loop if m_bLeavingGame is set, and close the connection
if( g_NetworkManager.IsLeavingGame() || !IsInSession() )
{
connection->close();
}
// ALL OTHER LOCAL PLAYERS
for(int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
if( connection->isStarted() && !connection->isClosed() )
{
// Already have setup the primary pad
if(idx == ProfileManager.GetPrimaryPad() ) continue;
if( GetLocalPlayerByUserIndex(idx) != NULL && !ProfileManager.IsSignedIn(idx) )
{
INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(idx);
Socket *socket = pNetworkPlayer->GetSocket();
app.DebugPrintf("Closing socket due to player %d not being signed in any more\n");
if( !socket->close(false) ) socket->close(true);
continue;
}
// By default when we host we only have the local player, but currently allow multiple local players to join
// when joining any other way, so just because they are signed in doesn't mean they are in the session
// 4J Stu - If they are in the session, then we should add them to the game. Otherwise we won't be able to add them later
INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(idx);
if( pNetworkPlayer == NULL )
continue;
ClientConnection *connection;
Socket *socket = pNetworkPlayer->GetSocket();
connection = new ClientConnection(minecraft, socket, idx);
minecraft->addPendingLocalConnection(idx, connection);
//minecraft->createExtraLocalPlayer(idx, (convStringToWstring( ProfileManager.GetGamertag(idx) )).c_str(), idx, connection);
// Open the socket on the server end to accept incoming data
Socket::addIncomingSocket(socket);
connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(convStringToWstring( ProfileManager.GetGamertag(idx) )) ) );
createdConnections.push_back( connection );
// Tick connection until we're ready to go. The stages involved in this are:
// (1) Creating the ClientConnection sends a prelogin packet to the server
// (2) the server sends a prelogin back, which is handled by the clientConnection, and returns a login packet
// (3) the server sends a login back, which is handled by the client connection to start the game
do
int primaryPad = ProfileManager.GetPrimaryPad();
app.SetRichPresenceContext(primaryPad,CONTEXT_GAME_STATE_BLANK);
if (GetPlayerCount() > 1) // Are we offline or online, and how many players are there
{
// We need to keep ticking the connections for players that already logged in
for(AUTO_VAR(it, createdConnections.begin()); it < createdConnections.end(); ++it)
{
(*it)->tick();
}
// 4J Stu - We were ticking this way too fast which could cause the connection to time out
// The connections should tick at 20 per second
Sleep(50);
app.DebugPrintf("<***> %d %d %d %d %d\n",IsInSession(), !connection->isStarted(),!connection->isClosed(),ProfileManager.IsSignedIn(idx),!g_NetworkManager.IsLeavingGame());
#if defined _XBOX || defined __PS3__ && !defined DISABLE_PSN
} while (IsInSession() && !connection->isStarted() && !connection->isClosed() && ProfileManager.IsSignedIn(idx) && !g_NetworkManager.IsLeavingGame() );
#else
// TODO - This SHOULD be something just like the code above but temporarily changing here so that we don't have to depend on the profilemanager behaviour
} while (IsInSession() && !connection->isStarted() && !connection->isClosed() && !g_NetworkManager.IsLeavingGame() );
#endif
// 4J Stu - Fix for #11279 - CRASH: TCR 001: BAS Game Stability: Signing out of game will cause title to crash
// We need to break out of the above loop if m_bLeavingGame is set, and stop creating new connections
// The connections in the createdConnections vector get closed at the end of the thread
if( g_NetworkManager.IsLeavingGame() || !IsInSession() ) break;
if( ProfileManager.IsSignedIn(idx) && !connection->isClosed() )
{
app.SetRichPresenceContext(idx,CONTEXT_GAME_STATE_BLANK);
if (IsLocalGame()) ProfileManager.SetCurrentGameActivity(idx,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false);
else ProfileManager.SetCurrentGameActivity(idx,CONTEXT_PRESENCE_MULTIPLAYER,false);
if (IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false);
else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER,false);
}
else
{
connection->close();
AUTO_VAR(it, find( createdConnections.begin(), createdConnections.end(), connection ));
if(it != createdConnections.end() ) createdConnections.erase( it );
if(IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1POFFLINE,false);
else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1P,false);
}
}
app.SetGameMode( eMode_Multiplayer );
}
else if ( connection->isClosed() || !IsInSession())
{
// assert(false);
MinecraftServer::HaltServer();
return false;
// ALL OTHER LOCAL PLAYERS
for(int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
{
// Already have setup the primary pad
if(idx == ProfileManager.GetPrimaryPad() ) continue;
if( GetLocalPlayerByUserIndex(idx) != NULL && !ProfileManager.IsSignedIn(idx) )
{
INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(idx);
Socket *socket = pNetworkPlayer->GetSocket();
app.DebugPrintf("Closing socket due to player %d not being signed in any more\n");
if( !socket->close(false) ) socket->close(true);
continue;
}
// By default when we host we only have the local player, but currently allow multiple local players to join
// when joining any other way, so just because they are signed in doesn't mean they are in the session
// 4J Stu - If they are in the session, then we should add them to the game. Otherwise we won't be able to add them later
INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(idx);
if( pNetworkPlayer == NULL )
continue;
ClientConnection *connection;
Socket *socket = pNetworkPlayer->GetSocket();
connection = new ClientConnection(minecraft, socket, idx);
minecraft->addPendingLocalConnection(idx, connection);
//minecraft->createExtraLocalPlayer(idx, (convStringToWstring( ProfileManager.GetGamertag(idx) )).c_str(), idx, connection);
// Open the socket on the server end to accept incoming data
Socket::addIncomingSocket(socket);
connection->send( shared_ptr<PreLoginPacket>( new PreLoginPacket(convStringToWstring( ProfileManager.GetGamertag(idx) )) ) );
createdConnections.push_back( connection );
// Tick connection until we're ready to go. The stages involved in this are:
// (1) Creating the ClientConnection sends a prelogin packet to the server
// (2) the server sends a prelogin back, which is handled by the clientConnection, and returns a login packet
// (3) the server sends a login back, which is handled by the client connection to start the game
do
{
// We need to keep ticking the connections for players that already logged in
for(AUTO_VAR(it, createdConnections.begin()); it < createdConnections.end(); ++it)
{
(*it)->tick();
}
// 4J Stu - We were ticking this way too fast which could cause the connection to time out
// The connections should tick at 20 per second
Sleep(50);
app.DebugPrintf("<***> %d %d %d %d %d\n",IsInSession(), !connection->isStarted(),!connection->isClosed(),ProfileManager.IsSignedIn(idx),!g_NetworkManager.IsLeavingGame());
#if defined _XBOX || defined __PS3__ && !defined DISABLE_PSN
} while (IsInSession() && !connection->isStarted() && !connection->isClosed() && ProfileManager.IsSignedIn(idx) && !g_NetworkManager.IsLeavingGame() );
#else
// TODO - This SHOULD be something just like the code above but temporarily changing here so that we don't have to depend on the profilemanager behaviour
} while (IsInSession() && !connection->isStarted() && !connection->isClosed() && !g_NetworkManager.IsLeavingGame() );
#endif
// 4J Stu - Fix for #11279 - CRASH: TCR 001: BAS Game Stability: Signing out of game will cause title to crash
// We need to break out of the above loop if m_bLeavingGame is set, and stop creating new connections
// The connections in the createdConnections vector get closed at the end of the thread
if( g_NetworkManager.IsLeavingGame() || !IsInSession() ) break;
if( ProfileManager.IsSignedIn(idx) && !connection->isClosed() )
{
app.SetRichPresenceContext(idx,CONTEXT_GAME_STATE_BLANK);
if (IsLocalGame()) ProfileManager.SetCurrentGameActivity(idx,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false);
else ProfileManager.SetCurrentGameActivity(idx,CONTEXT_PRESENCE_MULTIPLAYER,false);
}
else
{
connection->close();
AUTO_VAR(it, find( createdConnections.begin(), createdConnections.end(), connection ));
if(it != createdConnections.end() ) createdConnections.erase( it );
}
}
app.SetGameMode( eMode_Multiplayer );
}
else if ( connection->isClosed() || !IsInSession())
{
// assert(false);
MinecraftServer::HaltServer();
return false;
}
}
@@ -547,7 +563,7 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame
{
(*it)->close();
}
// assert(false);
//assert(false);
MinecraftServer::HaltServer();
return false;
}