diff options
| author | kuwa <kuwa.com3@gmail.com> | 2026-03-15 16:32:50 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-15 02:32:50 -0500 |
| commit | f483074cd2ff2cc9e9c3ef1df4430d4a65d1fb2a (patch) | |
| tree | 9cd094e38b2259ea913fecb7d6fde16046277182 /Minecraft.Server/Windows64 | |
| parent | 4d200a589dd3d0a8424eaef6c0d6fd454d16e411 (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')
| -rw-r--r-- | Minecraft.Server/Windows64/ServerMain.cpp | 718 | ||||
| -rw-r--r-- | Minecraft.Server/Windows64/postbuild_server.ps1 | 65 |
2 files changed, 783 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; +} + diff --git a/Minecraft.Server/Windows64/postbuild_server.ps1 b/Minecraft.Server/Windows64/postbuild_server.ps1 new file mode 100644 index 00000000..27451ba2 --- /dev/null +++ b/Minecraft.Server/Windows64/postbuild_server.ps1 @@ -0,0 +1,65 @@ +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") + |
