From e4c8229e522735b7f9dd0d2961ffd8b7be75e06b Mon Sep 17 00:00:00 2001 From: kuwacom Date: Wed, 4 Mar 2026 18:12:26 +0900 Subject: [PATCH] add: Dedicated Server implementation - Introduced `ServerMain.cpp` for the dedicated server logic, handling command-line arguments, server initialization, and network management. - Created `postbuild_server.ps1` script for post-build tasks, including copying necessary resources and DLLs for the dedicated server. - Added `CopyServerAssets.cmake` to manage the copying of server assets during the build process, ensuring required files are available for the dedicated server. - Defined project filters in `Minecraft.Server.vcxproj.filters` for better organization of server-related files. --- .gitignore | 9 + CMakeLists.txt | 63 +- COMPILE.md | 34 +- .../Common/Network/GameNetworkManager.cpp | 357 +++++----- .../Network/PlatformNetworkManagerStub.cpp | 8 +- Minecraft.Client/MinecraftServer.h | 2 + .../Windows64/Network/WinsockNetLayer.cpp | 19 +- .../Windows64/Network/WinsockNetLayer.h | 1 + Minecraft.Server/Minecraft.Server.vcxproj | 672 ++++++++++++++++++ .../Minecraft.Server.vcxproj.filters | 13 + Minecraft.Server/Windows64/ServerMain.cpp | 411 +++++++++++ .../Windows64/postbuild_server.ps1 | 70 ++ MinecraftConsoles.sln | 55 +- cmake/CopyServerAssets.cmake | 76 ++ 14 files changed, 1610 insertions(+), 180 deletions(-) create mode 100644 Minecraft.Server/Minecraft.Server.vcxproj create mode 100644 Minecraft.Server/Minecraft.Server.vcxproj.filters create mode 100644 Minecraft.Server/Windows64/ServerMain.cpp create mode 100644 Minecraft.Server/Windows64/postbuild_server.ps1 create mode 100644 cmake/CopyServerAssets.cmake diff --git a/.gitignore b/.gitignore index 9caff84a1..3bc14d738 100644 --- a/.gitignore +++ b/.gitignore @@ -423,6 +423,12 @@ Minecraft.World/x64_Debug/ Minecraft.World/Release/ Minecraft.World/x64_Release/ +Minecraft.Server/x64/ +Minecraft.Server/Debug/ +Minecraft.Server/x64_Debug/ +Minecraft.Server/Release/ +Minecraft.Server/x64_Release/ + build/* # Existing build output files @@ -433,3 +439,6 @@ build/* # Local saves Minecraft.Client/Saves/ + +tmp*/ +_server_asset_probe/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 977bce2d0..26e7945cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,58 @@ target_link_libraries(MinecraftClient PRIVATE > ) +set(MINECRAFT_SERVER_SOURCES ${MINECRAFT_CLIENT_SOURCES}) +list(APPEND MINECRAFT_SERVER_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Windows64/ServerMain.cpp" +) + +add_executable(MinecraftServer ${MINECRAFT_SERVER_SOURCES}) +target_include_directories(MinecraftServer PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/Iggy/include" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Xbox/Sentient/Include" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.World/x64headers" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Windows64" +) +target_compile_definitions(MinecraftServer PRIVATE + $<$:_LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_DEBUG;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64> + $<$>:_LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64> +) +if(MSVC) + configure_msvc_target(MinecraftServer) + target_link_options(MinecraftServer PRIVATE + $<$:/LTCG /INCREMENTAL:NO> + ) +endif() + +set_target_properties(MinecraftServer PROPERTIES + OUTPUT_NAME "Minecraft.Server" + VS_DEBUGGER_WORKING_DIRECTORY "$" + VS_DEBUGGER_COMMAND_ARGUMENTS "-port 25565 -bind 0.0.0.0 -name DedicatedServer" +) + +target_link_libraries(MinecraftServer PRIVATE + MinecraftWorld + d3d11 + XInput9_1_0 + wsock32 + legacy_stdio_definitions + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/Iggy/lib/iggy_w64.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/Iggy/lib/iggyperfmon_w64.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/Iggy/lib/iggyexpruntime_w64.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/Miles/lib/mss64.lib" + $<$: + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_d.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_d.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC_d.lib" + > + $<$>: + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/4JLibs/libs/4J_Input.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC.lib" + > +) + add_custom_command(TARGET MinecraftClient POST_BUILD COMMAND "${CMAKE_COMMAND}" -DPROJECT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" @@ -97,4 +149,13 @@ add_custom_command(TARGET MinecraftClient POST_BUILD VERBATIM ) -set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT MinecraftClient) +add_custom_command(TARGET MinecraftServer POST_BUILD + COMMAND "${CMAKE_COMMAND}" + -DPROJECT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" + -DOUTPUT_DIR="$" + -DCONFIGURATION=$ + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyServerAssets.cmake" + VERBATIM +) + +set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT MinecraftServer) diff --git a/COMPILE.md b/COMPILE.md index b62c95754..86fb5cee3 100644 --- a/COMPILE.md +++ b/COMPILE.md @@ -3,7 +3,9 @@ ## Visual Studio (`.sln`) 1. Open `MinecraftConsoles.sln` in Visual Studio 2022. -2. Set `Minecraft.Client` as the Startup Project. +2. Set Startup Project: + - Client: `Minecraft.Client` + - Dedicated server: `Minecraft.Server` 3. Select configuration: - `Debug` (recommended), or - `Release` @@ -12,6 +14,17 @@ - `Build > Build Solution` (or `Ctrl+Shift+B`) - Start debugging with `F5`. +### Dedicated server debug arguments + +- Default debugger arguments for `Minecraft.Server`: + - `-port 25565 -bind 0.0.0.0 -name DedicatedServer` +- You can override arguments in: + - `Project Properties > Debugging > Command Arguments` +- `Minecraft.Server` post-build copies only the dedicated-server asset set: + - `Common/Media/MediaWindows64.arc` + - `Common/res` + - `Windows64/GameHDD` + ## CMake (Windows x64) Configure (use your VS Community instance explicitly): @@ -32,6 +45,18 @@ Build Release: cmake --build build --config Release --target MinecraftClient ``` +Build Dedicated Server (Debug): + +```powershell +cmake --build build --config Debug --target MinecraftServer +``` + +Build Dedicated Server (Release): + +```powershell +cmake --build build --config Release --target MinecraftServer +``` + Run executable: ```powershell @@ -39,6 +64,13 @@ cd .\build\Debug .\MinecraftClient.exe ``` +Run dedicated server: + +```powershell +cd .\build\Debug +.\Minecraft.Server.exe -port 25565 -bind 0.0.0.0 -name DedicatedServer +``` + Notes: - The CMake build is Windows-only and x64-only. - Contributors on macOS or Linux need a Windows machine or VM to build the project. Running the game via Wine is separate from having a supported build environment. diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index b3fb5cd70..7b9583dd8 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -195,10 +195,12 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame #endif __int64 seed = 0; + bool dedicatedNoLocalHostPlayer = false; if(lpParameter != NULL) { NetworkGameInitData *param = (NetworkGameInitData *)lpParameter; seed = param->seed; + dedicatedNoLocalHostPlayer = param->dedicatedNoLocalHostPlayer; app.setLevelGenerationOptions(param->levelGen); if(param->levelGen != NULL) @@ -354,186 +356,199 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame // PRIMARY PLAYER vector createdConnections; - ClientConnection *connection; + ClientConnection *connection = NULL; - if( g_NetworkManager.IsHost() ) + if( g_NetworkManager.IsHost() && dedicatedNoLocalHostPlayer ) { - connection = new ClientConnection(minecraft, NULL); - } - else - { - INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(ProfileManager.GetLockedProfile()); - if(pNetworkPlayer == NULL) - { - MinecraftServer::HaltServer(); - app.DebugPrintf("%d\n",ProfileManager.GetLockedProfile()); - // If the player is NULL here then something went wrong in the session setup, and continuing will end up in a crash - return false; - } + app.DebugPrintf("Dedicated server mode: skipping local host client connection\n"); - Socket *socket = pNetworkPlayer->GetSocket(); - - // Fix for #13259 - CRASH: Gameplay: loading process is halted when player loads saved data - if(socket == NULL) - { - assert(false); - MinecraftServer::HaltServer(); - // If the socket is NULL here then something went wrong in the session setup, and continuing will end up in a crash - return false; - } - - connection = new ClientConnection(minecraft, socket); - } - - if( !connection->createdOk ) - { - assert(false); - delete connection; - connection = NULL; - MinecraftServer::HaltServer(); - return false; - } - - connection->send( shared_ptr( 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 + // Keep telemetry behavior consistent with host path. 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 (IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false); - else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER,false); - } - else - { - if(IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1POFFLINE,false); - else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1P,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( 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 || __PS3__ - } 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()) + else { -// assert(false); - MinecraftServer::HaltServer(); - return false; + if( g_NetworkManager.IsHost() ) + { + connection = new ClientConnection(minecraft, NULL); + } + else + { + INetworkPlayer *pNetworkPlayer = g_NetworkManager.GetLocalPlayerByUserIndex(ProfileManager.GetLockedProfile()); + if(pNetworkPlayer == NULL) + { + MinecraftServer::HaltServer(); + app.DebugPrintf("%d\n",ProfileManager.GetLockedProfile()); + // If the player is NULL here then something went wrong in the session setup, and continuing will end up in a crash + return false; + } + + Socket *socket = pNetworkPlayer->GetSocket(); + + // Fix for #13259 - CRASH: Gameplay: loading process is halted when player loads saved data + if(socket == NULL) + { + assert(false); + MinecraftServer::HaltServer(); + // If the socket is NULL here then something went wrong in the session setup, and continuing will end up in a crash + return false; + } + + connection = new ClientConnection(minecraft, socket); + } + + if( !connection->createdOk ) + { + assert(false); + delete connection; + connection = NULL; + MinecraftServer::HaltServer(); + return false; + } + + connection->send( shared_ptr( 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 (IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false); + else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER,false); + } + else + { + if(IsLocalGame()) ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1POFFLINE,false); + else ProfileManager.SetCurrentGameActivity(primaryPad,CONTEXT_PRESENCE_MULTIPLAYER_1P,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( 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 || __PS3__ + } 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; + } } diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 10d1a6a53..556862b94 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -361,9 +361,13 @@ void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame, _HostGame( localUsersMask, publicSlots, privateSlots ); #ifdef _WINDOWS64 - int port = WIN64_NET_DEFAULT_PORT; + int port = (g_Win64MultiplayerPort > 0) ? g_Win64MultiplayerPort : WIN64_NET_DEFAULT_PORT; + const char *bindIP = g_Win64MultiplayerIP; + if (bindIP != NULL && bindIP[0] == 0) + bindIP = NULL; + if (!WinsockNetLayer::IsActive()) - WinsockNetLayer::HostGame(port); + WinsockNetLayer::HostGame(bindIP, port); const wchar_t* hostName = IQNet::m_player[0].m_gamertag; unsigned int settings = app.GetGameHostOption(eGameHostOption_All); diff --git a/Minecraft.Client/MinecraftServer.h b/Minecraft.Client/MinecraftServer.h index 5f33fa853..a3f64ff4f 100644 --- a/Minecraft.Client/MinecraftServer.h +++ b/Minecraft.Client/MinecraftServer.h @@ -40,6 +40,7 @@ typedef struct _NetworkGameInitData LevelGenerationOptions *levelGen; DWORD texturePackId; bool findSeed; + bool dedicatedNoLocalHostPlayer; unsigned int xzSize; unsigned char hellScale; ESavePlatform savePlatform; @@ -52,6 +53,7 @@ typedef struct _NetworkGameInitData levelGen = NULL; texturePackId = 0; findSeed = false; + dedicatedNoLocalHostPlayer = false; xzSize = LEVEL_LEGACY_WIDTH; hellScale = HELL_LEVEL_LEGACY_SCALE; savePlatform = SAVE_FILE_PLATFORM_LOCAL; diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp index 19fc25987..5a87fdc29 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp @@ -47,7 +47,7 @@ std::vector WinsockNetLayer::s_freeSmallIds; bool g_Win64MultiplayerHost = false; bool g_Win64MultiplayerJoin = false; int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT; -char g_Win64MultiplayerIP[256] = "127.0.0.1"; +char g_Win64MultiplayerIP[256] = "0.0.0.0"; bool WinsockNetLayer::Initialize() { @@ -137,6 +137,11 @@ void WinsockNetLayer::Shutdown() } bool WinsockNetLayer::HostGame(int port) +{ + return HostGame(NULL, port); +} + +bool WinsockNetLayer::HostGame(const char *bindIp, int port) { if (!s_initialized && !Initialize()) return false; @@ -161,10 +166,16 @@ bool WinsockNetLayer::HostGame(int port) char portStr[16]; sprintf_s(portStr, "%d", port); - int iResult = getaddrinfo(NULL, portStr, &hints, &result); + const char *bindNode = NULL; + if (bindIp != NULL && bindIp[0] != 0 && strcmp(bindIp, "*") != 0) + { + bindNode = bindIp; + } + + int iResult = getaddrinfo(bindNode, portStr, &hints, &result); if (iResult != 0) { - app.DebugPrintf("getaddrinfo failed: %d\n", iResult); + app.DebugPrintf("getaddrinfo failed for bind '%s': %d\n", bindNode ? bindNode : "0.0.0.0", iResult); return false; } @@ -203,7 +214,7 @@ bool WinsockNetLayer::HostGame(int port) s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL); - app.DebugPrintf("Win64 LAN: Hosting on port %d\n", port); + app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n", bindNode ? bindNode : "0.0.0.0", port); return true; } diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h index 96b03c9ba..62ed50e7f 100644 --- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h +++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h @@ -62,6 +62,7 @@ public: static bool Initialize(); static void Shutdown(); + static bool HostGame(const char *bindIp, int port); static bool HostGame(int port); static bool JoinGame(const char *ip, int port); diff --git a/Minecraft.Server/Minecraft.Server.vcxproj b/Minecraft.Server/Minecraft.Server.vcxproj new file mode 100644 index 000000000..a32361380 --- /dev/null +++ b/Minecraft.Server/Minecraft.Server.vcxproj @@ -0,0 +1,672 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF} + Win32Proj + MinecraftServer + 10.0 + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\ + $(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\obj\MinecraftServer\ + Minecraft.Server + $(OutDir) + -port 25565 -bind 0.0.0.0 -name DedicatedServer + WindowsLocalDebugger + + + $(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\ + $(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\obj\MinecraftServer\ + Minecraft.Server + $(OutDir) + -port 25565 -bind 0.0.0.0 -name DedicatedServer + WindowsLocalDebugger + + + + Level3 + Disabled + Use + $(OutDir)MinecraftServer.pch + MultiThreadedDebug + ProgramDatabase + Sync + true + true + _LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_DEBUG;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64;%(PreprocessorDefinitions) + ..\Minecraft.Client;..\Minecraft.Client\Windows64\Iggy\include;..\Minecraft.Client\Xbox\Sentient\Include;..\Minecraft.World\x64headers;$(ProjectDir)Windows64;%(AdditionalIncludeDirectories) + + + false + + + _WINDOWS64;%(PreprocessorDefinitions) + ..\Minecraft.Client;..\Minecraft.Client\Xbox;%(AdditionalIncludeDirectories) + + + Console + mainCRTStartup + true + d3d11.lib;XInput9_1_0.lib;wsock32.lib;legacy_stdio_definitions.lib;..\Minecraft.World\x64_Debug\Minecraft.World.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggy_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyperfmon_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyexpruntime_w64.lib;..\Minecraft.Client\Windows64\Miles\lib\mss64.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Input_d.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Storage_d.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Render_PC_d.lib;%(AdditionalDependencies) + + + powershell -ExecutionPolicy Bypass -File "$(ProjectDir)Windows64\postbuild_server.ps1" -OutDir "$(OutDir)." -ProjectRoot "$(ProjectDir).." -Configuration "$(Configuration)" + + + + + Level3 + MaxSpeed + true + true + Use + $(OutDir)MinecraftServer.pch + MultiThreaded + Sync + true + true + _LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64;%(PreprocessorDefinitions) + ..\Minecraft.Client;..\Minecraft.Client\Windows64\Iggy\include;..\Minecraft.Client\Xbox\Sentient\Include;..\Minecraft.World\x64headers;$(ProjectDir)Windows64;%(AdditionalIncludeDirectories) + + + false + + + _WINDOWS64;%(PreprocessorDefinitions) + ..\Minecraft.Client;..\Minecraft.Client\Xbox;%(AdditionalIncludeDirectories) + + + Console + mainCRTStartup + true + true + true + d3d11.lib;XInput9_1_0.lib;wsock32.lib;legacy_stdio_definitions.lib;..\Minecraft.World\x64_Release\Minecraft.World.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggy_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyperfmon_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyexpruntime_w64.lib;..\Minecraft.Client\Windows64\Miles\lib\mss64.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Input.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Storage.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Render_PC.lib;%(AdditionalDependencies) + + + powershell -ExecutionPolicy Bypass -File "$(ProjectDir)Windows64\postbuild_server.ps1" -OutDir "$(OutDir)." -ProjectRoot "$(ProjectDir).." -Configuration "$(Configuration)" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + + + + Create + Create + + + + + + + + + + + + + {F046C3CE-9749-4823-B32B-D9CC10B1A2C8} + false + + + + + + + diff --git a/Minecraft.Server/Minecraft.Server.vcxproj.filters b/Minecraft.Server/Minecraft.Server.vcxproj.filters new file mode 100644 index 000000000..544f1c502 --- /dev/null +++ b/Minecraft.Server/Minecraft.Server.vcxproj.filters @@ -0,0 +1,13 @@ + + + + + {A8A47C24-66C0-4912-9D34-2CBF87F1D707} + + + + + Server + + + diff --git a/Minecraft.Server/Windows64/ServerMain.cpp b/Minecraft.Server/Windows64/ServerMain.cpp new file mode 100644 index 000000000..11d9fa671 --- /dev/null +++ b/Minecraft.Server/Windows64/ServerMain.cpp @@ -0,0 +1,411 @@ +#include "stdafx.h" + +#include "Common/App_Defines.h" +#include "Common/Network/GameNetworkManager.h" +#include "Input.h" +#include "Minecraft.h" +#include "MinecraftServer.h" +#include "Options.h" +#include "Tesselator.h" +#include "Windows64/4JLibs/inc/4J_Render.h" +#include "Windows64/GameConfig/Minecraft.spa.h" +#include "Windows64/KeyboardMouseInput.h" +#include "Windows64/Network/WinsockNetLayer.h" +#include "Windows64/Windows64_UIController.h" + +#include "../../Minecraft.World/AABB.h" +#include "../../Minecraft.World/Vec3.h" +#include "../../Minecraft.World/IntCache.h" +#include "../../Minecraft.World/TilePos.h" +#include "../../Minecraft.World/compression.h" +#include "../../Minecraft.World/OldChunkStorage.h" +#include "../../Minecraft.World/net.minecraft.world.level.tile.h" + +#include +#include +#include + +extern ATOM MyRegisterClass(HINSTANCE hInstance); +extern BOOL InitInstance(HINSTANCE hInstance, int nCmdShow); +extern HRESULT InitDevice(); +extern void CleanupDevice(); +extern void DefineActions(void); + +extern HWND g_hWnd; +extern int g_iScreenWidth; +extern int g_iScreenHeight; +extern char g_Win64Username[17]; +extern wchar_t g_Win64UsernameW[17]; +extern ID3D11Device* g_pd3dDevice; +extern ID3D11DeviceContext* g_pImmediateContext; +extern IDXGISwapChain* g_pSwapChain; +extern ID3D11RenderTargetView* g_pRenderTargetView; +extern ID3D11DepthStencilView* g_pDepthStencilView; +extern DWORD dwProfileSettingsA[]; + +static const int kProfileValueCount = 5; +static const int kProfileSettingCount = 4; + +struct DedicatedServerConfig +{ + int port; + char bindIP[256]; + char name[17]; + int maxPlayers; + __int64 seed; + bool hasSeed; + bool showHelp; +}; + +static volatile bool g_shutdownRequested = false; + +static BOOL WINAPI ConsoleCtrlHandlerProc(DWORD ctrlType) +{ + switch (ctrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_SHUTDOWN_EVENT: + g_shutdownRequested = true; + app.m_bShutdown = true; + return TRUE; + default: + return FALSE; + } +} + +static int WaitForServerStoppedThreadProc(void *) +{ + if (g_NetworkManager.ServerStoppedValid()) + { + g_NetworkManager.ServerStoppedWait(); + } + return 0; +} + +static void PrintUsage() +{ + printf("Minecraft.Server.exe [options]\n"); + printf(" -port <1-65535> Listen TCP port (default: 25565)\n"); + printf(" -ip Bind address (default: 0.0.0.0)\n"); + printf(" -bind Alias of -ip\n"); + printf(" -name Host display name (max 16 chars)\n"); + printf(" -maxplayers <1-8> Public slots (default: 8)\n"); + printf(" -seed World seed\n"); + printf(" -help Show this help\n"); +} + +static void LogStartupStep(const char *message) +{ + printf("[startup] %s\n", message); + fflush(stdout); +} + +static bool ParseIntArg(const char *value, int *outValue) +{ + if (value == NULL || *value == 0) + return false; + + char *end = NULL; + long parsed = strtol(value, &end, 10); + if (end == value || *end != 0) + return false; + + *outValue = (int)parsed; + return true; +} + +static bool ParseInt64Arg(const char *value, __int64 *outValue) +{ + if (value == NULL || *value == 0) + return false; + + char *end = NULL; + __int64 parsed = _strtoi64(value, &end, 10); + if (end == value || *end != 0) + return false; + + *outValue = parsed; + return true; +} + +static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *config) +{ + for (int i = 1; i < argc; ++i) + { + const char *arg = argv[i]; + if (_stricmp(arg, "-help") == 0 || _stricmp(arg, "--help") == 0 || _stricmp(arg, "-h") == 0) + { + config->showHelp = true; + return true; + } + else if ((_stricmp(arg, "-port") == 0) && (i + 1 < argc)) + { + int port = 0; + if (!ParseIntArg(argv[++i], &port) || port <= 0 || port > 65535) + { + printf("Invalid -port value.\n"); + return false; + } + config->port = port; + } + else if ((_stricmp(arg, "-ip") == 0 || _stricmp(arg, "-bind") == 0) && (i + 1 < argc)) + { + strncpy_s(config->bindIP, sizeof(config->bindIP), argv[++i], _TRUNCATE); + } + else if ((_stricmp(arg, "-name") == 0) && (i + 1 < argc)) + { + strncpy_s(config->name, sizeof(config->name), argv[++i], _TRUNCATE); + } + else if ((_stricmp(arg, "-maxplayers") == 0) && (i + 1 < argc)) + { + int maxPlayers = 0; + if (!ParseIntArg(argv[++i], &maxPlayers) || maxPlayers <= 0 || maxPlayers > MINECRAFT_NET_MAX_PLAYERS) + { + printf("Invalid -maxplayers value.\n"); + return false; + } + config->maxPlayers = maxPlayers; + } + else if ((_stricmp(arg, "-seed") == 0) && (i + 1 < argc)) + { + if (!ParseInt64Arg(argv[++i], &config->seed)) + { + printf("Invalid -seed value.\n"); + return false; + } + config->hasSeed = true; + } + else + { + printf("Unknown or incomplete argument: %s\n", arg); + return false; + } + } + + return true; +} + +static void SetExeWorkingDirectory() +{ + char exePath[MAX_PATH] = {}; + GetModuleFileNameA(NULL, exePath, MAX_PATH); + char *slash = strrchr(exePath, '\\'); + if (slash != NULL) + { + *(slash + 1) = 0; + SetCurrentDirectoryA(exePath); + } +} + +int main(int argc, char **argv) +{ + DedicatedServerConfig config; + config.port = WIN64_NET_DEFAULT_PORT; + strncpy_s(config.bindIP, sizeof(config.bindIP), "0.0.0.0", _TRUNCATE); + strncpy_s(config.name, sizeof(config.name), "DedicatedServer", _TRUNCATE); + config.maxPlayers = MINECRAFT_NET_MAX_PLAYERS; + config.seed = 0; + config.hasSeed = false; + config.showHelp = false; + + if (!ParseCommandLine(argc, argv, &config)) + { + PrintUsage(); + return 1; + } + if (config.showHelp) + { + PrintUsage(); + return 0; + } + + LogStartupStep("initializing process state"); + SetConsoleCtrlHandler(ConsoleCtrlHandlerProc, TRUE); + SetExeWorkingDirectory(); + + g_iScreenWidth = 1280; + g_iScreenHeight = 720; + + strncpy_s(g_Win64Username, sizeof(g_Win64Username), config.name, _TRUNCATE); + MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17); + + g_Win64MultiplayerHost = true; + g_Win64MultiplayerJoin = false; + g_Win64MultiplayerPort = config.port; + strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), config.bindIP, _TRUNCATE); + + LogStartupStep("registering hidden window class"); + HINSTANCE hInstance = GetModuleHandle(NULL); + MyRegisterClass(hInstance); + + LogStartupStep("creating hidden window"); + if (!InitInstance(hInstance, SW_HIDE)) + { + printf("Failed to create window instance.\n"); + return 2; + } + ShowWindow(g_hWnd, SW_HIDE); + + LogStartupStep("initializing graphics device wrappers"); + if (FAILED(InitDevice())) + { + printf("Failed to initialize D3D device.\n"); + CleanupDevice(); + return 2; + } + + LogStartupStep("loading media/string tables"); + app.loadMediaArchive(); + RenderManager.Initialise(g_pd3dDevice, g_pSwapChain); + app.loadStringTable(); + ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight); + + InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU); + KMInput.Init(g_hWnd); + DefineActions(); + InputManager.SetJoypadMapVal(0, 0); + InputManager.SetKeyRepeatRate(0.3f, 0.2f); + + ProfileManager.Initialise( + TITLEID_MINECRAFT, + app.m_dwOfferID, + PROFILE_VERSION_10, + kProfileValueCount, + kProfileSettingCount, + dwProfileSettingsA, + app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT, + &app.uiGameDefinedDataChangedBitmask); + ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app); + ProfileManager.SetDebugFullOverride(true); + + LogStartupStep("initializing network manager"); + g_NetworkManager.Initialise(); + + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i) + { + IQNet::m_player[i].m_smallId = (BYTE)i; + IQNet::m_player[i].m_isRemote = false; + IQNet::m_player[i].m_isHostPlayer = (i == 0); + swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i); + } + wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW); + WinsockNetLayer::Initialize(); + + Tesselator::CreateNewThreadStorage(1024 * 1024); + AABB::CreateNewThreadStorage(); + Vec3::CreateNewThreadStorage(); + IntCache::CreateNewThreadStorage(); + Compression::CreateNewThreadStorage(); + OldChunkStorage::CreateNewThreadStorage(); + Level::enableLightingCache(); + Tile::CreateNewThreadStorage(); + + LogStartupStep("creating Minecraft singleton"); + Minecraft::main(); + Minecraft *minecraft = Minecraft::GetInstance(); + if (minecraft == NULL) + { + printf("Minecraft initialization failed.\n"); + CleanupDevice(); + return 3; + } + + app.InitGameSettings(); + if (minecraft->options != NULL) + { + minecraft->options->set(Options::Option::MUSIC, 0.0f); + minecraft->options->set(Options::Option::SOUND, 0.0f); + } + + MinecraftServer::resetFlags(); + app.SetTutorialMode(false); + app.SetCorruptSaveDeleted(false); + app.SetGameHostOption(eGameHostOption_Difficulty, 1); + app.SetGameHostOption(eGameHostOption_FriendsOfFriends, 0); + app.SetGameHostOption(eGameHostOption_Gamertags, 1); + app.SetGameHostOption(eGameHostOption_BedrockFog, 1); + app.SetGameHostOption(eGameHostOption_GameType, 0); + app.SetGameHostOption(eGameHostOption_LevelType, 0); + app.SetGameHostOption(eGameHostOption_Structures, 1); + app.SetGameHostOption(eGameHostOption_BonusChest, 0); + app.SetGameHostOption(eGameHostOption_PvP, 1); + app.SetGameHostOption(eGameHostOption_TrustPlayers, 1); + app.SetGameHostOption(eGameHostOption_FireSpreads, 1); + app.SetGameHostOption(eGameHostOption_TNT, 1); + app.SetGameHostOption(eGameHostOption_HostCanFly, 1); + app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, 1); + app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 1); + + NetworkGameInitData *param = new NetworkGameInitData(); + if (config.hasSeed) + { + param->seed = config.seed; + } + param->saveData = NULL; + param->settings = app.GetGameHostOption(eGameHostOption_All); + param->dedicatedNoLocalHostPlayer = true; + + LogStartupStep("starting hosted network game thread"); + g_NetworkManager.HostGame(0, true, false, (unsigned char)config.maxPlayers, 0); + g_NetworkManager.FakeLocalPlayerJoined(); + + C4JThread *startThread = new C4JThread(&CGameNetworkManager::RunNetworkGameThreadProc, (LPVOID)param, "RunNetworkGame"); + startThread->Run(); + + while (startThread->isRunning() && !g_shutdownRequested) + { + g_NetworkManager.DoWork(); + ProfileManager.Tick(); + StorageManager.Tick(); + Sleep(10); + } + + startThread->WaitForCompletion(INFINITE); + int startupResult = startThread->GetExitCode(); + delete startThread; + + if (startupResult != 0) + { + printf("Failed to start dedicated server (code %d).\n", startupResult); + WinsockNetLayer::Shutdown(); + g_NetworkManager.Terminate(); + CleanupDevice(); + return 4; + } + + LogStartupStep("server startup complete"); + printf("Dedicated server listening on %s:%d\n", g_Win64MultiplayerIP, g_Win64MultiplayerPort); + + while (!g_shutdownRequested && !app.m_bShutdown) + { + g_NetworkManager.DoWork(); + ProfileManager.Tick(); + StorageManager.Tick(); + app.HandleXuiActions(); + + if (MinecraftServer::serverHalted()) + { + break; + } + + Sleep(10); + } + + printf("Stopping dedicated server...\n"); + MinecraftServer::HaltServer(); + + if (g_NetworkManager.ServerStoppedValid()) + { + C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped"); + waitThread.Run(); + waitThread.WaitForCompletion(INFINITE); + } + + WinsockNetLayer::Shutdown(); + g_NetworkManager.Terminate(); + CleanupDevice(); + + return 0; +} diff --git a/Minecraft.Server/Windows64/postbuild_server.ps1 b/Minecraft.Server/Windows64/postbuild_server.ps1 new file mode 100644 index 000000000..40a9b56a9 --- /dev/null +++ b/Minecraft.Server/Windows64/postbuild_server.ps1 @@ -0,0 +1,70 @@ +param( + [string]$OutDir, + [string]$ProjectRoot, + [string]$Configuration +) + +if ([string]::IsNullOrWhiteSpace($OutDir)) { + throw "OutDir is required." +} + +if ([string]::IsNullOrWhiteSpace($ProjectRoot)) { + $ProjectRoot = Resolve-Path (Join-Path $PSScriptRoot "..\\..") +} + +if ([string]::IsNullOrWhiteSpace($Configuration)) { + $Configuration = "Debug" +} + +$OutDir = [System.IO.Path]::GetFullPath($OutDir) +$ProjectRoot = [System.IO.Path]::GetFullPath($ProjectRoot) +$ClientRoot = Join-Path $ProjectRoot "Minecraft.Client" + +Write-Host "Server post-build started. OutDir: $OutDir" + +function Ensure-Dir([string]$path) { + if (-not (Test-Path $path)) { + New-Item -ItemType Directory -Path $path -Force | Out-Null + } +} + +function Copy-Tree-IfExists([string]$src, [string]$dst) { + if (Test-Path $src) { + Ensure-Dir $dst + xcopy /q /y /i /s /e /d "$src" "$dst" 2>$null | Out-Null + } +} + +function Copy-File-IfExists([string]$src, [string]$dst) { + if (Test-Path $src) { + $dstDir = Split-Path -Parent $dst + Ensure-Dir $dstDir + xcopy /q /y /d "$src" "$dstDir" 2>$null | Out-Null + } +} + +function Copy-FirstExisting([string[]]$candidates, [string]$dstFile) { + foreach ($candidate in $candidates) { + if (Test-Path $candidate) { + Copy-File-IfExists $candidate $dstFile + return + } + } +} + +# Dedicated server only needs core resources for current startup path. +Copy-File-IfExists (Join-Path $ClientRoot "Common\\Media\\MediaWindows64.arc") (Join-Path $OutDir "Common\\Media\\MediaWindows64.arc") +Copy-Tree-IfExists (Join-Path $ClientRoot "Common\\res") (Join-Path $OutDir "Common\\res") +Copy-Tree-IfExists (Join-Path $ClientRoot "Windows64\\GameHDD") (Join-Path $OutDir "Windows64\\GameHDD") + +# Runtime DLLs. +Copy-FirstExisting @( + (Join-Path $ClientRoot "Windows64\\Iggy\\lib\\redist64\\iggy_w64.dll"), + (Join-Path $ProjectRoot ("x64\\{0}\\iggy_w64.dll" -f $Configuration)) +) (Join-Path $OutDir "iggy_w64.dll") + +Copy-FirstExisting @( + (Join-Path $ClientRoot "Windows64\\Miles\\lib\\redist64\\mss64.dll"), + (Join-Path $ProjectRoot ("x64\\{0}\\mss64.dll" -f $Configuration)) +) (Join-Path $OutDir "mss64.dll") + diff --git a/MinecraftConsoles.sln b/MinecraftConsoles.sln index 1ce395672..9eb80a4bc 100644 --- a/MinecraftConsoles.sln +++ b/MinecraftConsoles.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.37012.4 d17.14 @@ -10,6 +10,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Minecraft.Client", "Minecra {F046C3CE-9749-4823-B32B-D9CC10B1A2C8} = {F046C3CE-9749-4823-B32B-D9CC10B1A2C8} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Minecraft.Server", "Minecraft.Server\Minecraft.Server.vcxproj", "{7CB40BFC-C8E4-4293-A22E-D2041348D5AF}" + ProjectSection(ProjectDependencies) = postProject + {F046C3CE-9749-4823-B32B-D9CC10B1A2C8} = {F046C3CE-9749-4823-B32B-D9CC10B1A2C8} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution ContentPackage_NO_TU|ARM64EC = ContentPackage_NO_TU|ARM64EC @@ -223,6 +228,54 @@ Global {1B9A8C38-DD48-448C-AA24-E1A35E0089A3}.ReleaseForArt|Windows64.ActiveCfg = ReleaseForArt|x64 {1B9A8C38-DD48-448C-AA24-E1A35E0089A3}.ReleaseForArt|Xbox 360.ActiveCfg = Release|Xbox 360 {1B9A8C38-DD48-448C-AA24-E1A35E0089A3}.ReleaseForArt|Xbox 360.Build.0 = Release|Xbox 360 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|ARM64EC.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|Durango.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|ORBIS.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|PS3.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|PSVita.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|Windows64.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|Windows64.Build.0 = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage_NO_TU|Xbox 360.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|ARM64EC.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|Durango.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|ORBIS.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|PS3.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|PSVita.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|Windows64.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|Windows64.Build.0 = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.CONTENTPACKAGE_SYMBOLS|Xbox 360.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|ARM64EC.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|Durango.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|ORBIS.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|PS3.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|PSVita.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|Windows64.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|Windows64.Build.0 = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ContentPackage|Xbox 360.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|ARM64EC.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|Durango.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|ORBIS.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|PS3.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|PSVita.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|Windows64.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|Windows64.Build.0 = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Debug|Xbox 360.ActiveCfg = Debug|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|ARM64EC.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|Durango.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|ORBIS.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|PS3.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|PSVita.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|Windows64.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|Windows64.Build.0 = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.Release|Xbox 360.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|ARM64EC.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|Durango.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|ORBIS.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|PS3.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|PSVita.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|Windows64.ActiveCfg = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|Windows64.Build.0 = Release|x64 + {7CB40BFC-C8E4-4293-A22E-D2041348D5AF}.ReleaseForArt|Xbox 360.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/cmake/CopyServerAssets.cmake b/cmake/CopyServerAssets.cmake new file mode 100644 index 000000000..7203ae473 --- /dev/null +++ b/cmake/CopyServerAssets.cmake @@ -0,0 +1,76 @@ +if(NOT DEFINED PROJECT_SOURCE_DIR OR NOT DEFINED OUTPUT_DIR OR NOT DEFINED CONFIGURATION) + message(FATAL_ERROR "CopyServerAssets.cmake requires PROJECT_SOURCE_DIR, OUTPUT_DIR, and CONFIGURATION.") +endif() + +string(REPLACE "\"" "" PROJECT_SOURCE_DIR "${PROJECT_SOURCE_DIR}") +string(REPLACE "\"" "" OUTPUT_DIR "${OUTPUT_DIR}") +string(REPLACE "\"" "" CONFIGURATION "${CONFIGURATION}") + +set(_project_dir "${PROJECT_SOURCE_DIR}/Minecraft.Client") + +function(copy_tree_if_exists src_rel dst_rel) + set(_src "${_project_dir}/${src_rel}") + set(_dst "${OUTPUT_DIR}/${dst_rel}") + + if(EXISTS "${_src}") + file(MAKE_DIRECTORY "${_dst}") + file(GLOB_RECURSE _files RELATIVE "${_src}" "${_src}/*") + + foreach(_file IN LISTS _files) + if(NOT _file MATCHES "\\.(cpp|c|h|hpp|xml|lang)$") + set(_full_src "${_src}/${_file}") + set(_full_dst "${_dst}/${_file}") + + if(IS_DIRECTORY "${_full_src}") + file(MAKE_DIRECTORY "${_full_dst}") + else() + get_filename_component(_dst_dir "${_full_dst}" DIRECTORY) + file(MAKE_DIRECTORY "${_dst_dir}") + execute_process( + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${_full_src}" "${_full_dst}" + ) + endif() + endif() + endforeach() + endif() +endfunction() + +function(copy_file_if_exists src_rel dst_rel) + set(_src "${PROJECT_SOURCE_DIR}/${src_rel}") + set(_dst "${OUTPUT_DIR}/${dst_rel}") + + get_filename_component(_dst_dir "${_dst}" DIRECTORY) + file(MAKE_DIRECTORY "${_dst_dir}") + + if(EXISTS "${_src}") + execute_process( + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${_src}" "${_dst}" + ) + endif() +endfunction() + +function(copy_first_existing dst_rel) + foreach(_candidate IN LISTS ARGN) + if(EXISTS "${PROJECT_SOURCE_DIR}/${_candidate}") + copy_file_if_exists("${_candidate}" "${dst_rel}") + return() + endif() + endforeach() +endfunction() + +# Dedicated server runtime assets (minimal set validated in startup tests). +copy_file_if_exists("Minecraft.Client/Common/Media/MediaWindows64.arc" "Common/Media/MediaWindows64.arc") +copy_tree_if_exists("Common/res" "Common/res") +copy_tree_if_exists("Windows64/GameHDD" "Windows64/GameHDD") + +copy_first_existing("iggy_w64.dll" + "Minecraft.Client/Windows64/Iggy/lib/redist64/iggy_w64.dll" + "x64/${CONFIGURATION}/iggy_w64.dll" +) +copy_first_existing("mss64.dll" + "Minecraft.Client/Windows64/Miles/lib/redist64/mss64.dll" + "x64/${CONFIGURATION}/mss64.dll" +) +