aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Server/Windows64/ServerMain.cpp
diff options
context:
space:
mode:
authorkuwa <kuwa.com3@gmail.com>2026-03-15 16:32:50 +0900
committerGitHub <noreply@github.com>2026-03-15 02:32:50 -0500
commitf483074cd2ff2cc9e9c3ef1df4430d4a65d1fb2a (patch)
tree9cd094e38b2259ea913fecb7d6fde16046277182 /Minecraft.Server/Windows64/ServerMain.cpp
parent4d200a589dd3d0a8424eaef6c0d6fd454d16e411 (diff)
Dedicated Server Software - Minecraft.Server.exe (#498)
* 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. * add: refactor world loader & add server properties - Introduced ServerLogger for logging startup steps and world I/O operations. - Implemented ServerProperties for loading and saving server configuration from `server.properties`. - Added WorldManager to handle world loading and creation based on server properties. - Updated ServerMain to integrate server properties loading and world management. - Enhanced project files to include new source and header files for the server components. * update: implement enhanced logging functionality with configurable log levels * update: update keyboard and mouse input initialization 1dc8a005ed111463c22c17b487e5ec8a3e2d30f3 * fix: change virtual screen resolution to 1920x1080(HD) Since 31881af56936aeef38ff322b975fd0 , `skinHud.swf` for 720 is not included in `MediaWindows64.arc`, the app crashes unless the virtual screen is set to HD. * fix: dedicated server build settings for miniaudio migration and missing sources - remove stale Windows64 Miles (mss64) link/copy references from server build - add Common/Filesystem/Filesystem.cpp to Minecraft.Server.vcxproj - add Windows64/PostProcesser.cpp to Minecraft.Server.vcxproj - fix unresolved externals (PostProcesser::*, FileExists) in dedicated server build * update: changed the virtual screen to 720p Since the crash caused by the 720p `skinHud.swf` not being included in `MediaWindows64.arc` has been resolved, switching back to 720p to reduce resource usage. * add: add Docker support for Dedicated Server add with entrypoint and build scripts * fix: add initial save for newly created worlds in dedicated server on the server side, I fixed the behavior introduced after commit aadb511, where newly created worlds are intentionally not saved to disk immediately. * update: add basically all configuration options that are implemented in the classes to `server.properties` * update: add LAN advertising configuration for server.properties LAN-Discovery, which isn’t needed in server mode and could potentially be a security risk, has also been disabled(only server mode). * add: add implementing interactive command line using linenoise - Integrated linenoise library for line editing and completion in the server console. - Updated ServerLogger to handle external writes safely during logging. - Modified ServerMain to initialize and manage the ServerCli for command input. - The implementation is separate from everything else, so it doesn't affect anything else. - The command input section and execution section are separated into threads. * update: enhance command line completion with predictive hints Like most command line tools, it highlights predictions in gray. * add: implement `StringUtils` for string manipulation and refactor usages Unified the scattered utility functions. * fix: send DisconnectPacket on shutdown and fix Win64 recv-thread teardown race Before this change, server/host shutdown closed sockets directly in ServerConnection::stop(), which bypassed the normal disconnect flow. As a result, clients could be dropped without receiving a proper DisconnectPacket during stop/kill/world-close paths. Also, WinsockNetLayer::Shutdown() could destroy synchronization objects while host-side recv threads were still exiting, causing a crash in RecvThreadProc (access violation on world close in host mode). * fix: return client to menus when Win64 host connection drops - Add client-side host disconnect handling in CPlatformNetworkManagerStub::DoWork() for _WINDOWS64. - When in QNET_STATE_GAME_PLAY as a non-host and WinsockNetLayer::IsConnected() becomes false, trigger g_NetworkManager.HandleDisconnect(false) to enter the normal disconnect/UI flow. - Use m_bLeaveGameOnTick as a one-shot guard to prevent repeated disconnect handling while the link remains down. - Reset m_bLeaveGameOnTick on LeaveGame(), HostGame(), and JoinGame() to avoid stale state across sessions. * update: converted Japanese comments to English * add: create `Minecraft.Server` developer guide in English and Japanese * update: add note about issue * add: add `nlohmann/json` json lib * add: add FileUtils Moved file operations to `utils`. * add: Dedicated Server BAN access manager with persistent player and IP bans - add Access frontend that publishes thread-safe ban manager snapshots for dedicated server use - add BanManager storage for banned-players.json and banned-ips.json with load/save/update flows - add persistent player and IP ban checks during dedicated server connection handling - add UTF-8 BOM-safe JSON parsing and shared file helpers backed by nlohmann/json - add Unicode-safe ban file read/write and safer atomic replacement behavior on Windows - add active-ban snapshot APIs and expiry-aware filtering for expires metadata - add RAII-based dedicated access shutdown handling during server startup and teardown * update: changed file read/write operations to use `FileUtils`. - As a side effect, saving has become faster! * fix: Re-added the source that had somehow disappeared. * add: significantly improved the dedicated server logging system - add ServerLogManager to Minecraft.Server as the single entry point for dedicated-server log output - forward CMinecraftApp logger output to the server logger when running with g_Win64DedicatedServer - add named network logs for incoming, accepted, rejected, and disconnected connections - cache connection metadata by smallId so player name and remote IP remain available for disconnect logs - keep Minecraft.Client changes minimal by using lightweight hook points and handling log orchestration on the server side * fix: added the updated library source * add: add `ban` and `pardon` commands for Player and IP * fix: fix stop command shutdown process add dedicated server shutdown request handling * fix: fixed the save logic during server shutdown Removed redundant repeated saves and eliminated the risks of async writes. * update: added new sever files to Docker entrypoint * fix: replace shutdown flag with atomic variable for thread safety * update: update Dedicated Server developer guide English is machine translated. Please forgive me. * update: check for the existence of `GameHDD` and create * add: add Whitelist to Dedicated Server * refactor: clean up and refactor the code - unify duplicated implementations that were copied repeatedly - update outdated patterns to more modern ones * fix: include UI header (new update fix) * fix: fix the detection range for excessive logging `getHighestNonEmptyY()` returning `-1` occurs normally when the chunk is entirely air. The caller (`Minecraft.World/LevelChunk.cpp:2400`) normalizes `-1` to `0`. * update: add world size config to dedicated server properties * update: update README add explanation of `server.properties` & launch arguments * update: add nightly release workflow for dedicated server and client builds to Actions * fix: update name for workflow * add random seed generation * add: add Docker nightly workflow for Dedicated Server publish to GitHub Container Registry * fix: ghost player when clients disconnect out of order #4 * fix: fix 7zip option * fix: fix Docker workflow for Dedicated Server artifact handling * add: add no build Dedicated Server startup scripts and Docker Compose * update: add README for Docker Dedicated Server setup with no local build * refactor: refactor command path structure As the number of commands has increased and become harder to navigate, each command has been organized into separate folders. * update: support stream(file stdin) input mode for server CLI Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux. * add: add new CLI Console Commands for Dedicated Server Most of these commands are executed using the command dispatcher implemented on the `Minecraft.World` side. When registering them with the dispatcher, the sender uses a permission-enabled configuration that treats the CLI as a player. - default game. - enchant - experience. - give - kill(currently, getting a permission error for some reason) - time - weather. - update tp & gamemode command * fix: change player map icon to random select * update: increase the player limit * add: restore the basic anti-cheat implementation and add spawn protection Added the following anti-cheat measures and add spawn protection to `server.properties`. - instant break - speed - reach * fix: fix Docker image tag --------- Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
Diffstat (limited to 'Minecraft.Server/Windows64/ServerMain.cpp')
-rw-r--r--Minecraft.Server/Windows64/ServerMain.cpp718
1 files changed, 718 insertions, 0 deletions
diff --git a/Minecraft.Server/Windows64/ServerMain.cpp b/Minecraft.Server/Windows64/ServerMain.cpp
new file mode 100644
index 00000000..a8d5fc66
--- /dev/null
+++ b/Minecraft.Server/Windows64/ServerMain.cpp
@@ -0,0 +1,718 @@
+#include "stdafx.h"
+
+#include "Common/App_Defines.h"
+#include "Common/Network/GameNetworkManager.h"
+#include "Input.h"
+#include "Minecraft.h"
+#include "MinecraftServer.h"
+#include "..\Access\Access.h"
+#include "..\Common\StringUtils.h"
+#include "..\ServerLogger.h"
+#include "..\ServerLogManager.h"
+#include "..\ServerProperties.h"
+#include "..\ServerShutdown.h"
+#include "..\WorldManager.h"
+#include "..\Console\ServerCli.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 "Common/UI/UI.h"
+
+#include "../../Minecraft.World/AABB.h"
+#include "../../Minecraft.World/Vec3.h"
+#include "../../Minecraft.World/IntCache.h"
+#include "../../Minecraft.World/ChunkSource.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 "../../Minecraft.World/Random.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <atomic>
+
+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;
+ int worldSize;
+ int worldSizeChunks;
+ int worldHellScale;
+ __int64 seed;
+ ServerRuntime::EServerLogLevel logLevel;
+ bool hasSeed;
+ bool showHelp;
+};
+
+static std::atomic<bool> g_shutdownRequested(false);
+static const DWORD kDefaultAutosaveIntervalMs = 60 * 1000;
+static const int kServerActionPad = 0;
+
+static bool IsShutdownRequested()
+{
+ return g_shutdownRequested.load();
+}
+
+namespace ServerRuntime
+{
+ void RequestDedicatedServerShutdown()
+ {
+ g_shutdownRequested.store(true);
+ }
+}
+
+/**
+ * Calls Access::Shutdown automatically once dedicated access control was initialized successfully
+ * アクセス制御初期化後のShutdownを自動化する
+ */
+class AccessShutdownGuard
+{
+public:
+ AccessShutdownGuard()
+ : m_active(false)
+ {
+ }
+
+ void Activate()
+ {
+ m_active = true;
+ }
+
+ ~AccessShutdownGuard()
+ {
+ if (m_active)
+ {
+ ServerRuntime::Access::Shutdown();
+ }
+ }
+
+private:
+ bool m_active;
+};
+static BOOL WINAPI ConsoleCtrlHandlerProc(DWORD ctrlType)
+{
+ switch (ctrlType)
+ {
+ case CTRL_C_EVENT:
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ ServerRuntime::RequestDedicatedServerShutdown();
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * **Wait For Server Stopped Signal**
+ *
+ * Thread entry used during shutdown to wait until the network layer reports server stop completion
+ * 停止通知待機用の終了スレッド処理
+ */
+static int WaitForServerStoppedThreadProc(void *)
+{
+ if (g_NetworkManager.ServerStoppedValid())
+ {
+ g_NetworkManager.ServerStoppedWait();
+ }
+ return 0;
+}
+
+static void PrintUsage()
+{
+ ServerRuntime::LogInfo("usage", "Minecraft.Server.exe [options]");
+ ServerRuntime::LogInfo("usage", " -port <1-65535> Listen TCP port (default: server.properties:server-port)");
+ ServerRuntime::LogInfo("usage", " -ip <addr> Bind address (default: server.properties:server-ip)");
+ ServerRuntime::LogInfo("usage", " -bind <addr> Alias of -ip");
+ ServerRuntime::LogInfo("usage", " -name <name> Host display name (max 16 chars, default: server.properties:server-name)");
+ ServerRuntime::LogInfo("usage", " -maxplayers <1-8> Public slots (default: server.properties:max-players)");
+ ServerRuntime::LogInfo("usage", " -seed <int64> World seed (overrides server.properties:level-seed)");
+ ServerRuntime::LogInfo("usage", " -loglevel <level> debug|info|warn|error (default: server.properties:log-level)");
+ ServerRuntime::LogInfo("usage", " -help Show this help");
+}
+
+using ServerRuntime::LoadServerPropertiesConfig;
+using ServerRuntime::LogError;
+using ServerRuntime::LogErrorf;
+using ServerRuntime::LogInfof;
+using ServerRuntime::LogDebugf;
+using ServerRuntime::LogStartupStep;
+using ServerRuntime::LogWarn;
+using ServerRuntime::LogWorldIO;
+using ServerRuntime::SaveServerPropertiesConfig;
+using ServerRuntime::SetServerLogLevel;
+using ServerRuntime::ServerPropertiesConfig;
+using ServerRuntime::TryParseServerLogLevel;
+using ServerRuntime::StringUtils::WideToUtf8;
+using ServerRuntime::BootstrapWorldForServer;
+using ServerRuntime::eWorldBootstrap_CreatedNew;
+using ServerRuntime::eWorldBootstrap_Failed;
+using ServerRuntime::eWorldBootstrap_Loaded;
+using ServerRuntime::WaitForWorldActionIdle;
+using ServerRuntime::WorldBootstrapResult;
+
+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)
+ {
+ LogError("startup", "Invalid -port value.");
+ 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)
+ {
+ LogError("startup", "Invalid -maxplayers value.");
+ return false;
+ }
+ config->maxPlayers = maxPlayers;
+ }
+ else if ((_stricmp(arg, "-seed") == 0) && (i + 1 < argc))
+ {
+ if (!ParseInt64Arg(argv[++i], &config->seed))
+ {
+ LogError("startup", "Invalid -seed value.");
+ return false;
+ }
+ config->hasSeed = true;
+ }
+ else if ((_stricmp(arg, "-loglevel") == 0) && (i + 1 < argc))
+ {
+ if (!TryParseServerLogLevel(argv[++i], &config->logLevel))
+ {
+ LogError("startup", "Invalid -loglevel value. Use debug/info/warn/error.");
+ return false;
+ }
+ }
+ else
+ {
+ LogErrorf("startup", "Unknown or incomplete argument: %s", 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);
+ }
+}
+
+static void ApplyServerPropertiesToDedicatedConfig(const ServerPropertiesConfig &serverProperties, DedicatedServerConfig *config)
+{
+ if (config == NULL)
+ {
+ return;
+ }
+
+ config->port = serverProperties.serverPort;
+ strncpy_s(
+ config->bindIP,
+ sizeof(config->bindIP),
+ serverProperties.serverIp.empty() ? "0.0.0.0" : serverProperties.serverIp.c_str(),
+ _TRUNCATE);
+ strncpy_s(
+ config->name,
+ sizeof(config->name),
+ serverProperties.serverName.empty() ? "DedicatedServer" : serverProperties.serverName.c_str(),
+ _TRUNCATE);
+ config->maxPlayers = serverProperties.maxPlayers;
+ config->worldSize = serverProperties.worldSize;
+ config->worldSizeChunks = serverProperties.worldSizeChunks;
+ config->worldHellScale = serverProperties.worldHellScale;
+ config->logLevel = serverProperties.logLevel;
+ config->hasSeed = serverProperties.hasSeed;
+ config->seed = serverProperties.seed;
+}
+
+/**
+ * **Tick Core Async Subsystems**
+ *
+ * Advances core subsystems for one frame to keep async processing alive
+ * Call continuously even inside wait loops to avoid stalling storage/profile/network work
+ * 非同期進行維持のためのティック処理
+ */
+static void TickCoreSystems()
+{
+ g_NetworkManager.DoWork();
+ ProfileManager.Tick();
+ StorageManager.Tick();
+}
+
+/**
+ * **Handle Queued XUI Server Action Once**
+ *
+ * Processes queued XUI/server action once
+ * XUIアクションの単発処理
+ */
+static void HandleXuiActions()
+{
+ app.HandleXuiActions();
+}
+
+/**
+ * Dedicated Server Entory Point
+ *
+ * 主な責務:
+ * - プロセス/描画/ネットワークの初期化
+ * - `WorldManager` によるワールドロードまたは新規作成
+ * - メインループと定期オートセーブ実行
+ * - 終了時の最終保存と各サブシステムの安全停止
+ */
+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.worldSize = e_worldSize_Classic;
+ config.worldSizeChunks = LEVEL_WIDTH_CLASSIC;
+ config.worldHellScale = HELL_LEVEL_SCALE_CLASSIC;
+ config.seed = 0;
+ config.logLevel = ServerRuntime::eServerLogLevel_Info;
+ config.hasSeed = false;
+ config.showHelp = false;
+
+ SetConsoleCtrlHandler(ConsoleCtrlHandlerProc, TRUE);
+ SetExeWorkingDirectory();
+
+ // Load base settings from server.properties, then override with CLI values when provided
+ ServerPropertiesConfig serverProperties = LoadServerPropertiesConfig();
+ ApplyServerPropertiesToDedicatedConfig(serverProperties, &config);
+
+ if (!ParseCommandLine(argc, argv, &config))
+ {
+ PrintUsage();
+ return 1;
+ }
+ if (config.showHelp)
+ {
+ PrintUsage();
+ return 0;
+ }
+
+ SetServerLogLevel(config.logLevel);
+ LogStartupStep("initializing process state");
+ AccessShutdownGuard accessShutdownGuard;
+
+ 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);
+ g_Win64DedicatedServer = true;
+ g_Win64DedicatedServerPort = config.port;
+ strncpy_s(g_Win64DedicatedServerBindIP, sizeof(g_Win64DedicatedServerBindIP), config.bindIP, _TRUNCATE);
+ g_Win64DedicatedServerLanAdvertise = serverProperties.lanAdvertise;
+ LogStartupStep("initializing server log manager");
+ ServerRuntime::ServerLogManager::Initialize();
+ LogStartupStep("initializing dedicated access control");
+ if (!ServerRuntime::Access::Initialize(".", serverProperties.whiteListEnabled))
+ {
+ LogError("startup", "Failed to initialize dedicated server access control.");
+ return 2;
+ }
+ accessShutdownGuard.Activate();
+ LogInfof("startup", "LAN advertise: %s", serverProperties.lanAdvertise ? "enabled" : "disabled");
+ LogInfof("startup", "Whitelist: %s", serverProperties.whiteListEnabled ? "enabled" : "disabled");
+ LogInfof("startup", "Spawn protection radius: %d", serverProperties.spawnProtectionRadius);
+#ifdef _LARGE_WORLDS
+ LogInfof(
+ "startup",
+ "World size preset: %d (xz=%d, hell-scale=%d)",
+ config.worldSize,
+ config.worldSizeChunks,
+ config.worldHellScale);
+#endif
+
+ LogStartupStep("registering hidden window class");
+ HINSTANCE hInstance = GetModuleHandle(NULL);
+ MyRegisterClass(hInstance);
+
+ LogStartupStep("creating hidden window");
+ if (!InitInstance(hInstance, SW_HIDE))
+ {
+ LogError("startup", "Failed to create window instance.");
+
+ return 2;
+ }
+ ShowWindow(g_hWnd, SW_HIDE);
+
+ LogStartupStep("initializing graphics device wrappers");
+ if (FAILED(InitDevice()))
+ {
+ LogError("startup", "Failed to initialize D3D device.");
+ 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);
+ g_KBMInput.Init();
+ 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)
+ {
+ LogError("startup", "Minecraft initialization failed.");
+ CleanupDevice();
+
+ return 3;
+ }
+
+ app.InitGameSettings();
+
+ MinecraftServer::resetFlags();
+ app.SetTutorialMode(false);
+ app.SetCorruptSaveDeleted(false);
+ app.SetGameHostOption(eGameHostOption_Difficulty, serverProperties.difficulty);
+ app.SetGameHostOption(eGameHostOption_FriendsOfFriends, serverProperties.friendsOfFriends ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_Gamertags, serverProperties.gamertags ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_BedrockFog, serverProperties.bedrockFog ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_GameType, serverProperties.gameMode);
+ app.SetGameHostOption(eGameHostOption_LevelType, serverProperties.levelTypeFlat ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_Structures, serverProperties.generateStructures ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_BonusChest, serverProperties.bonusChest ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_PvP, serverProperties.pvp ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TrustPlayers, serverProperties.trustPlayers ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_FireSpreads, serverProperties.fireSpreads ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TNT, serverProperties.tnt ? 1 : 0);
+ app.SetGameHostOption(
+ eGameHostOption_CheatsEnabled,
+ (serverProperties.hostCanFly || serverProperties.hostCanChangeHunger || serverProperties.hostCanBeInvisible) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_HostCanFly, serverProperties.hostCanFly ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, serverProperties.hostCanChangeHunger ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, serverProperties.hostCanBeInvisible ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_DisableSaving, serverProperties.disableSaving ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_MobGriefing, serverProperties.mobGriefing ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_KeepInventory, serverProperties.keepInventory ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_DoMobSpawning, serverProperties.doMobSpawning ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_DoMobLoot, serverProperties.doMobLoot ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_DoTileDrops, serverProperties.doTileDrops ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_NaturalRegeneration, serverProperties.naturalRegeneration ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_DoDaylightCycle, serverProperties.doDaylightCycle ? 1 : 0);
+#ifdef _LARGE_WORLDS
+ app.SetGameHostOption(eGameHostOption_WorldSize, serverProperties.worldSize);
+ // Apply desired target size for loading existing worlds.
+ // Expansion happens only when target size is larger than current level size.
+ app.SetGameNewWorldSize(serverProperties.worldSizeChunks, true);
+ app.SetGameNewHellScale(serverProperties.worldHellScale);
+#endif
+
+ StorageManager.SetSaveDisabled(serverProperties.disableSaving);
+ // Read world name and fixed save-id from server.properties
+ // Delegate load-vs-create decision to WorldManager
+ std::wstring targetWorldName = serverProperties.worldName;
+ if (targetWorldName.empty())
+ {
+ targetWorldName = L"world"; // Default world name
+ }
+ WorldBootstrapResult worldBootstrap = BootstrapWorldForServer(serverProperties, kServerActionPad, &TickCoreSystems);
+ if (worldBootstrap.status == eWorldBootstrap_Loaded)
+ {
+ const std::string &loadedSaveFilename = worldBootstrap.resolvedSaveId;
+ if (!loadedSaveFilename.empty() && _stricmp(loadedSaveFilename.c_str(), serverProperties.worldSaveId.c_str()) != 0)
+ {
+ // Persist the actually loaded save-id back to config
+ // Keep lookup keys aligned for next startup
+ LogWorldIO("updating level-id to loaded save filename");
+ serverProperties.worldSaveId = loadedSaveFilename;
+ if (!SaveServerPropertiesConfig(serverProperties))
+ {
+ LogWorldIO("failed to persist updated level-id");
+ }
+ }
+ }
+ else if (worldBootstrap.status == eWorldBootstrap_Failed)
+ {
+ LogErrorf("world-io", "Failed to load configured world \"%s\".", WideToUtf8(targetWorldName).c_str());
+ WinsockNetLayer::Shutdown();
+ g_NetworkManager.Terminate();
+ CleanupDevice();
+
+ return 4;
+ }
+
+ NetworkGameInitData *param = new NetworkGameInitData();
+ if (config.hasSeed)
+ {
+ param->seed = config.seed;
+ }
+ else
+ {
+ param->seed = (new Random())->nextLong();
+ }
+#ifdef _LARGE_WORLDS
+ param->xzSize = (unsigned int)config.worldSizeChunks;
+ param->hellScale = (unsigned char)config.worldHellScale;
+#endif
+ param->saveData = worldBootstrap.saveData;
+ 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() && !IsShutdownRequested())
+ {
+ TickCoreSystems();
+ Sleep(10);
+ }
+
+ startThread->WaitForCompletion(INFINITE);
+ int startupResult = startThread->GetExitCode();
+ delete startThread;
+
+ if (startupResult != 0)
+ {
+ LogErrorf("startup", "Failed to start dedicated server (code %d).", startupResult);
+ WinsockNetLayer::Shutdown();
+ g_NetworkManager.Terminate();
+ CleanupDevice();
+
+ return 4;
+ }
+
+ LogStartupStep("server startup complete");
+ LogInfof("startup", "Dedicated server listening on %s:%d", g_Win64MultiplayerIP, g_Win64MultiplayerPort);
+ if (worldBootstrap.status == eWorldBootstrap_CreatedNew && !IsShutdownRequested() && !app.m_bShutdown)
+ {
+ // Windows64 suppresses saveToDisc right after new world creation
+ // Dedicated Server explicitly runs the initial save here
+ LogWorldIO("requesting initial save for newly created world");
+ WaitForWorldActionIdle(kServerActionPad, 5000, &TickCoreSystems, &HandleXuiActions);
+ app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
+ if (!WaitForWorldActionIdle(kServerActionPad, 30000, &TickCoreSystems, &HandleXuiActions))
+ {
+ LogWorldIO("initial save timed out");
+ LogWarn("world-io", "Timed out waiting for initial save action to finish.");
+ }
+ else
+ {
+ LogWorldIO("initial save completed");
+ }
+ }
+ DWORD autosaveIntervalMs = kDefaultAutosaveIntervalMs;
+ if (serverProperties.autosaveIntervalSeconds > 0)
+ {
+ autosaveIntervalMs = (DWORD)(serverProperties.autosaveIntervalSeconds * 1000);
+ }
+ DWORD nextAutosaveTick = GetTickCount() + autosaveIntervalMs;
+ bool autosaveRequested = false;
+ ServerRuntime::ServerCli serverCli;
+ serverCli.Start();
+
+ while (!IsShutdownRequested() && !app.m_bShutdown)
+ {
+ TickCoreSystems();
+ HandleXuiActions();
+ serverCli.Poll();
+
+ if (IsShutdownRequested() || app.m_bShutdown)
+ {
+ break;
+ }
+
+ if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle)
+ {
+ LogWorldIO("autosave completed");
+ autosaveRequested = false;
+ }
+
+ if (MinecraftServer::serverHalted())
+ {
+ break;
+ }
+
+ DWORD now = GetTickCount();
+ if ((LONG)(now - nextAutosaveTick) >= 0)
+ {
+ if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle)
+ {
+ LogWorldIO("requesting autosave");
+ app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
+ autosaveRequested = true;
+ }
+ nextAutosaveTick = now + autosaveIntervalMs;
+ }
+
+ Sleep(10);
+ }
+ serverCli.Stop();
+ app.m_bShutdown = true;
+
+ LogInfof("shutdown", "Dedicated server stopped");
+ MinecraftServer *server = MinecraftServer::getInstance();
+ if (server != NULL)
+ {
+ server->setSaveOnExit(true);
+ }
+ if (server != NULL)
+ {
+ LogWorldIO("requesting save before shutdown");
+ LogWorldIO("using saveOnExit for shutdown");
+ }
+
+ MinecraftServer::HaltServer();
+
+ if (g_NetworkManager.ServerStoppedValid())
+ {
+ C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped");
+ waitThread.Run();
+ waitThread.WaitForCompletion(INFINITE);
+ }
+
+ LogInfof("shutdown", "Cleaning up and exiting.");
+ WinsockNetLayer::Shutdown();
+ LogDebugf("shutdown", "Network layer shutdown complete.");
+ g_NetworkManager.Terminate();
+ LogDebugf("shutdown", "Network manager terminated.");
+ ServerRuntime::ServerLogManager::Shutdown();
+ CleanupDevice();
+
+
+ return 0;
+}
+