diff options
| author | kuwa <kuwa.com3@gmail.com> | 2026-03-16 05:50:12 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-15 15:50:12 -0500 |
| commit | e9f5b4b6f0bff50691acae160434c69c4a89231e (patch) | |
| tree | f4fd30618a619b54a23e90e40110b5efa2ee9719 | |
| parent | 7b643cdd750df389a0a3b1a75937cd6e693eb4ac (diff) | |
Fix server DedicatedServer issues (#1266)
* 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
* make chunks delay less for dedi
* fix: prevent overwriting allow-flight value on server startup
* fix: mitigate entity id overflow and crash for max chunk updates
* remove autosave prompt for dedicated server
* fix: fix `Failed to create window instance.`
Wait for Xvfb to be fully ready before starting.
* Revert wrong readme order
---------
Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
Co-authored-by: Loki Rautio <lokirautio@gmail.com>
| -rw-r--r-- | Minecraft.Client/MinecraftServer.cpp | 525 | ||||
| -rw-r--r-- | Minecraft.Client/ServerPlayer.cpp | 37 | ||||
| -rw-r--r-- | Minecraft.World/Entity.cpp | 11 | ||||
| -rw-r--r-- | Minecraft.World/Level.h | 5 | ||||
| -rw-r--r-- | docker/dedicated-server/entrypoint.sh | 78 |
5 files changed, 386 insertions, 270 deletions
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp index a545eff8..1e3ed74e 100644 --- a/Minecraft.Client/MinecraftServer.cpp +++ b/Minecraft.Client/MinecraftServer.cpp @@ -657,7 +657,7 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW setFlightAllowed(GetDedicatedServerBool(settings, L"allow-flight", true)); // 4J Stu - Enabling flight to stop it kicking us when we use it -#ifdef _DEBUG_MENUS_ENABLED +#if (defined _DEBUG_MENUS_ENABLED && defined _DEBUG) setFlightAllowed(true); #endif @@ -1715,330 +1715,345 @@ void MinecraftServer::setPlayerIdleTimeout(int playerIdleTimeout) extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b; void MinecraftServer::run(int64_t seed, void *lpParameter) { - NetworkGameInitData *initData = nullptr; - DWORD initSettings = 0; - bool findSeed = false; + NetworkGameInitData *initData = nullptr; + DWORD initSettings = 0; + bool findSeed = false; if(lpParameter != nullptr) - { - initData = static_cast<NetworkGameInitData *>(lpParameter); - initSettings = app.GetGameHostOption(eGameHostOption_All); - findSeed = initData->findSeed; - m_texturePackId = initData->texturePackId; - } - // try { // 4J - removed try/catch/finally - bool didInit = false; + { + initData = static_cast<NetworkGameInitData *>(lpParameter); + initSettings = app.GetGameHostOption(eGameHostOption_All); + findSeed = initData->findSeed; + m_texturePackId = initData->texturePackId; + } + // try { // 4J - removed try/catch/finally + bool didInit = false; if (initServer(seed, initData, initSettings,findSeed)) - { - didInit = true; - ServerLevel *levelNormalDimension = levels[0]; - // 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there - Minecraft *pMinecraft = Minecraft::GetInstance(); + { + didInit = true; + ServerLevel *levelNormalDimension = levels[0]; + // 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there + Minecraft *pMinecraft = Minecraft::GetInstance(); LevelData *pLevelData=levelNormalDimension->getLevelData(); if(pLevelData && pLevelData->getHasStronghold()==false) - { + { int x,z; if(app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold,&x,&z)) - { - pLevelData->setXStronghold(x); - pLevelData->setZStronghold(z); - pLevelData->setHasStronghold(); - } - } - - int64_t lastTime = getCurrentTimeMillis(); - int64_t unprocessedTime = 0; - while (running && !s_bServerHalted) - { - int64_t now = getCurrentTimeMillis(); - - // 4J Stu - When we pause the server, we don't want to count that as time passed - // 4J Stu - TU-1 hotifx - Remove this line. We want to make sure that we tick connections at the proper rate when paused + { + pLevelData->setXStronghold(x); + pLevelData->setZStronghold(z); + pLevelData->setHasStronghold(); + } + } + + int64_t lastTime = getCurrentTimeMillis(); + int64_t unprocessedTime = 0; + while (running && !s_bServerHalted) + { + int64_t now = getCurrentTimeMillis(); + + // 4J Stu - When we pause the server, we don't want to count that as time passed + // 4J Stu - TU-1 hotifx - Remove this line. We want to make sure that we tick connections at the proper rate when paused //Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost //if(m_isServerPaused) lastTime = now; - int64_t passedTime = now - lastTime; - if (passedTime > MS_PER_TICK * 40) - { - // logger.warning("Can't keep up! Did the system time change, or is the server overloaded?"); - passedTime = MS_PER_TICK * 40; - } - if (passedTime < 0) - { - // logger.warning("Time ran backwards! Did the system time change?"); - passedTime = 0; - } - unprocessedTime += passedTime; - lastTime = now; - - // 4J Added ability to pause the server + int64_t passedTime = now - lastTime; + if (passedTime > MS_PER_TICK * 40) + { + // logger.warning("Can't keep up! Did the system time change, or is the server overloaded?"); + passedTime = MS_PER_TICK * 40; + } + if (passedTime < 0) + { + // logger.warning("Time ran backwards! Did the system time change?"); + passedTime = 0; + } + unprocessedTime += passedTime; + lastTime = now; + + // 4J Added ability to pause the server if( !m_isServerPaused ) - { - bool didTick = false; - if (levels[0]->allPlayersAreSleeping()) - { - tick(); - unprocessedTime = 0; - } - else - { - // int tickcount = 0; - // int64_t beforeall = System::currentTimeMillis(); - while (unprocessedTime > MS_PER_TICK) - { - unprocessedTime -= MS_PER_TICK; - chunkPacketManagement_PreTick(); -// int64_t before = System::currentTimeMillis(); - tick(); -// int64_t after = System::currentTimeMillis(); -// PIXReportCounter(L"Server time",(float)(after-before)); - - chunkPacketManagement_PostTick(); - } -// int64_t afterall = System::currentTimeMillis(); -// PIXReportCounter(L"Server time all",(float)(afterall-beforeall)); -// PIXReportCounter(L"Server ticks",(float)tickcount); - } - } - else - { - // 4J Stu - TU1-hotfix + { + bool didTick = false; + if (levels[0]->allPlayersAreSleeping()) + { + tick(); + unprocessedTime = 0; + } + else + { + // int tickcount = 0; + // int64_t beforeall = System::currentTimeMillis(); + while (unprocessedTime > MS_PER_TICK) + { + unprocessedTime -= MS_PER_TICK; + chunkPacketManagement_PreTick(); + // int64_t before = System::currentTimeMillis(); + tick(); + // int64_t after = System::currentTimeMillis(); + // PIXReportCounter(L"Server time",(float)(after-before)); + + chunkPacketManagement_PostTick(); + } + // int64_t afterall = System::currentTimeMillis(); + // PIXReportCounter(L"Server time all",(float)(afterall-beforeall)); + // PIXReportCounter(L"Server ticks",(float)tickcount); + } + } + else + { + // 4J Stu - TU1-hotfix //Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost - // The connections should tick at the same frequency even when paused - while (unprocessedTime > MS_PER_TICK) - { - unprocessedTime -= MS_PER_TICK; - // Keep ticking the connections to stop them timing out - connection->tick(); - } - } + // The connections should tick at the same frequency even when paused + while (unprocessedTime > MS_PER_TICK) + { + unprocessedTime -= MS_PER_TICK; + // Keep ticking the connections to stop them timing out + connection->tick(); + } + } if(MinecraftServer::setTimeAtEndOfTick) - { - MinecraftServer::setTimeAtEndOfTick = false; - for (unsigned int i = 0; i < levels.length; i++) - { - // if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether - { - ServerLevel *level = levels[i]; + { + MinecraftServer::setTimeAtEndOfTick = false; + for (unsigned int i = 0; i < levels.length; i++) + { + // if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether + { + ServerLevel *level = levels[i]; level->setGameTime( MinecraftServer::setTime ); - } - } - } + } + } + } if(MinecraftServer::setTimeOfDayAtEndOfTick) - { - MinecraftServer::setTimeOfDayAtEndOfTick = false; - for (unsigned int i = 0; i < levels.length; i++) - { - if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true)) - { - ServerLevel *level = levels[i]; + { + MinecraftServer::setTimeOfDayAtEndOfTick = false; + for (unsigned int i = 0; i < levels.length; i++) + { + if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true)) + { + ServerLevel *level = levels[i]; level->setDayTime( MinecraftServer::setTimeOfDay ); - } - } - } + } + } + } - // Process delayed actions - eXuiServerAction eAction; - LPVOID param; + // Process delayed actions + eXuiServerAction eAction; + LPVOID param; for(int i=0;i<XUSER_MAX_COUNT;i++) - { - eAction = app.GetXuiServerAction(i); - param = app.GetXuiServerActionParam(i); + { + eAction = app.GetXuiServerAction(i); + param = app.GetXuiServerActionParam(i); switch(eAction) - { - case eXuiServerAction_AutoSaveGame: + { + case eXuiServerAction_AutoSaveGame: +#if defined(_XBOX_ONE) || defined(__ORBIS__) || defined(MINECRAFT_SERVER_BUILD) + { #if defined(_XBOX_ONE) || defined(__ORBIS__) - { - PIXBeginNamedEvent(0,"Autosave"); + PIXBeginNamedEvent(0, "Autosave"); - // Get the frequency of the timer - LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime; - float fElapsedTime = 0.0f; - QueryPerformanceFrequency( &qwTicksPerSec ); - float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart; + // Get the frequency of the timer + LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime; + float fElapsedTime = 0.0f; + QueryPerformanceFrequency(&qwTicksPerSec); + float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart; - // Save the start time - QueryPerformanceCounter( &qwTime ); + // Save the start time + QueryPerformanceCounter(&qwTime); +#endif - if (players != nullptr) - { - players->saveAll(nullptr); - } + if (players != nullptr) + { + players->saveAll(nullptr); + } - for (unsigned int j = 0; j < levels.length; j++) - { + for (unsigned int j = 0; j < levels.length; j++) + { if( s_bServerHalted ) break; - // 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat - // with the data from the nethers leveldata. - // Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting. - ServerLevel *level = levels[levels.length - 1 - j]; - PIXBeginNamedEvent(0, "Saving level %d",levels.length - 1 - j); - level->save(false, nullptr, true); - PIXEndNamedEvent(); - } - if( !s_bServerHalted ) - { - PIXBeginNamedEvent(0,"Saving game rules"); - saveGameRules(); - PIXEndNamedEvent(); - - PIXBeginNamedEvent(0,"Save to disc"); - levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true); - PIXEndNamedEvent(); - } - PIXEndNamedEvent(); - - QueryPerformanceCounter( &qwNewTime ); - qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart; - fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart)); - app.DebugPrintf("Autosave: Elapsed time %f\n", fElapsedTime); - } - break; + // 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat + // with the data from the nethers leveldata. + // Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting. + ServerLevel *level = levels[levels.length - 1 - j]; +#if defined(_XBOX_ONE) || defined(__ORBIS__) + PIXBeginNamedEvent(0, "Saving level %d", levels.length - 1 - j); #endif - case eXuiServerAction_SaveGame: - app.EnterSaveNotificationSection(); - if (players != nullptr) - { - players->saveAll(Minecraft::GetInstance()->progressRenderer); - } + level->save(false, nullptr, true); +#if defined(_XBOX_ONE) || defined(__ORBIS__) + PIXEndNamedEvent(); +#endif + } + if (!s_bServerHalted) + { +#if defined(_XBOX_ONE) || defined(__ORBIS__) + PIXBeginNamedEvent(0, "Saving game rules"); +#endif + saveGameRules(); +#if defined(_XBOX_ONE) || defined(__ORBIS__) + PIXEndNamedEvent(); - players->broadcastAll(std::make_shared<UpdateProgressPacket>(20)); + PIXBeginNamedEvent(0, "Save to disc"); +#endif + levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true); +#if defined(_XBOX_ONE) || defined(__ORBIS__) + PIXEndNamedEvent(); +#endif + } - for (unsigned int j = 0; j < levels.length; j++) - { +#if defined(_XBOX_ONE) || defined(__ORBIS__) + PIXEndNamedEvent(); + + QueryPerformanceCounter(&qwNewTime); + qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart; + fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart)); + app.DebugPrintf("Autosave: Elapsed time %f\n", fElapsedTime); +#endif + } + break; +#endif + case eXuiServerAction_SaveGame: + app.EnterSaveNotificationSection(); + if (players != nullptr) + { + players->saveAll(Minecraft::GetInstance()->progressRenderer); + } + + players->broadcastAll(std::make_shared<UpdateProgressPacket>(20)); + + for (unsigned int j = 0; j < levels.length; j++) + { if( s_bServerHalted ) break; - // 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat - // with the data from the nethers leveldata. - // Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting. - ServerLevel *level = levels[levels.length - 1 - j]; + // 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat + // with the data from the nethers leveldata. + // Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting. + ServerLevel *level = levels[levels.length - 1 - j]; level->save(true, Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame)); - players->broadcastAll(std::make_shared<UpdateProgressPacket>(33 + (j * 33))); - } + players->broadcastAll(std::make_shared<UpdateProgressPacket>(33 + (j * 33))); + } if( !s_bServerHalted ) - { - saveGameRules(); + { + saveGameRules(); levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame)); - } - app.LeaveSaveNotificationSection(); - break; - case eXuiServerAction_DropItem: - // Find the player, and drop the id at their feet - { - shared_ptr<ServerPlayer> player = players->players.at(0); + } + app.LeaveSaveNotificationSection(); + break; + case eXuiServerAction_DropItem: + // Find the player, and drop the id at their feet + { + shared_ptr<ServerPlayer> player = players->players.at(0); size_t id = (size_t) param; - player->drop(std::make_shared<ItemInstance>(id, 1, 0)); - } - break; - case eXuiServerAction_SpawnMob: - { - shared_ptr<ServerPlayer> player = players->players.at(0); - eINSTANCEOF factory = static_cast<eINSTANCEOF>((size_t)param); + player->drop(std::make_shared<ItemInstance>(id, 1, 0)); + } + break; + case eXuiServerAction_SpawnMob: + { + shared_ptr<ServerPlayer> player = players->players.at(0); + eINSTANCEOF factory = static_cast<eINSTANCEOF>((size_t)param); shared_ptr<Mob> mob = dynamic_pointer_cast<Mob>(EntityIO::newByEnumType(factory,player->level )); mob->moveTo(player->x+1, player->y, player->z+1, player->level->random->nextFloat() * 360, 0); mob->setDespawnProtected(); // 4J added, default to being protected against despawning (has to be done after initial position is set) - player->level->addEntity(mob); - } - break; - case eXuiServerAction_PauseServer: + player->level->addEntity(mob); + } + break; + case eXuiServerAction_PauseServer: m_isServerPaused = ( (size_t) param == TRUE ); if( m_isServerPaused ) - { - m_serverPausedEvent->Set(); - } - break; - case eXuiServerAction_ToggleRain: - { - bool isRaining = levels[0]->getLevelData()->isRaining(); - levels[0]->getLevelData()->setRaining(!isRaining); - levels[0]->getLevelData()->setRainTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); - } - break; - case eXuiServerAction_ToggleThunder: - { - bool isThundering = levels[0]->getLevelData()->isThundering(); - levels[0]->getLevelData()->setThundering(!isThundering); - levels[0]->getLevelData()->setThunderTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); - } - break; - case eXuiServerAction_ServerSettingChanged_Gamertags: - players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags))); - break; - case eXuiServerAction_ServerSettingChanged_BedrockFog: - players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All))); - break; - - case eXuiServerAction_ServerSettingChanged_Difficulty: - players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty)); - break; - case eXuiServerAction_ExportSchematic: + { + m_serverPausedEvent->Set(); + } + break; + case eXuiServerAction_ToggleRain: + { + bool isRaining = levels[0]->getLevelData()->isRaining(); + levels[0]->getLevelData()->setRaining(!isRaining); + levels[0]->getLevelData()->setRainTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); + } + break; + case eXuiServerAction_ToggleThunder: + { + bool isThundering = levels[0]->getLevelData()->isThundering(); + levels[0]->getLevelData()->setThundering(!isThundering); + levels[0]->getLevelData()->setThunderTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); + } + break; + case eXuiServerAction_ServerSettingChanged_Gamertags: + players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags))); + break; + case eXuiServerAction_ServerSettingChanged_BedrockFog: + players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All))); + break; + + case eXuiServerAction_ServerSettingChanged_Difficulty: + players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty)); + break; + case eXuiServerAction_ExportSchematic: #ifndef _CONTENT_PACKAGE - app.EnterSaveNotificationSection(); + app.EnterSaveNotificationSection(); //players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) ); if( !s_bServerHalted ) - { - ConsoleSchematicFile::XboxSchematicInitParam *initData = static_cast<ConsoleSchematicFile::XboxSchematicInitParam *>(param); + { + ConsoleSchematicFile::XboxSchematicInitParam *initData = static_cast<ConsoleSchematicFile::XboxSchematicInitParam *>(param); #ifdef _XBOX - File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics"); + File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics"); #else - File targetFileDir(L"Schematics"); + File targetFileDir(L"Schematics"); #endif if(!targetFileDir.exists()) targetFileDir.mkdir(); - wchar_t filename[128]; + wchar_t filename[128]; swprintf(filename,128,L"%ls%dx%dx%d.sch",initData->name,(initData->endX - initData->startX + 1), (initData->endY - initData->startY + 1), (initData->endZ - initData->startZ + 1)); File dataFile = File( targetFileDir, wstring(filename) ); if(dataFile.exists()) dataFile._delete(); - FileOutputStream fos = FileOutputStream(dataFile); - DataOutputStream dos = DataOutputStream(&fos); - ConsoleSchematicFile::generateSchematicFile(&dos, levels[0], initData->startX, initData->startY, initData->startZ, initData->endX, initData->endY, initData->endZ, initData->bSaveMobs, initData->compressionType); - dos.close(); - - delete initData; - } - app.LeaveSaveNotificationSection(); + FileOutputStream fos = FileOutputStream(dataFile); + DataOutputStream dos = DataOutputStream(&fos); + ConsoleSchematicFile::generateSchematicFile(&dos, levels[0], initData->startX, initData->startY, initData->startZ, initData->endX, initData->endY, initData->endZ, initData->bSaveMobs, initData->compressionType); + dos.close(); + + delete initData; + } + app.LeaveSaveNotificationSection(); #endif - break; - case eXuiServerAction_SetCameraLocation: + break; + case eXuiServerAction_SetCameraLocation: #ifndef _CONTENT_PACKAGE - { - DebugSetCameraPosition *pos = static_cast<DebugSetCameraPosition *>(param); + { + DebugSetCameraPosition *pos = static_cast<DebugSetCameraPosition *>(param); app.DebugPrintf( "DEBUG: Player=%i\n", pos->player ); app.DebugPrintf( "DEBUG: Teleporting to pos=(%f.2, %f.2, %f.2), looking at=(%f.2,%f.2)\n", - pos->m_camX, pos->m_camY, pos->m_camZ, + pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev ); - shared_ptr<ServerPlayer> player = players->players.at(pos->player); + shared_ptr<ServerPlayer> player = players->players.at(pos->player); player->debug_setPosition( pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev ); - // Doesn't work + // Doesn't work //player->setYHeadRot(pos->m_yRot); //player->absMoveTo(pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev); - } + } #endif - break; - } + break; + } app.SetXuiServerAction(i,eXuiServerAction_Idle); - } + } - Sleep(1); - } - } + Sleep(1); + } + } //else - //{ + //{ // while (running) - // { + // { // handleConsoleInputs(); - // Sleep(10); + // Sleep(10); // } //} #if 0 @@ -2065,9 +2080,9 @@ void MinecraftServer::run(int64_t seed, void *lpParameter) } #endif - // 4J Stu - Stop the server when the loops complete, as the finally would do - stopServer(didInit); - stopped = true; + // 4J Stu - Stop the server when the loops complete, as the finally would do + stopServer(didInit); + stopped = true; } void MinecraftServer::broadcastStartSavingPacket() @@ -2375,6 +2390,9 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player) { if( player == nullptr ) return false; +#ifdef MINECRAFT_SERVER_BUILD + return true; +#else int time = GetTickCount(); DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount(); if( currentPlayerCount == 0 ) return false; @@ -2386,6 +2404,7 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player) } return false; +#endif } void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player) diff --git a/Minecraft.Client/ServerPlayer.cpp b/Minecraft.Client/ServerPlayer.cpp index c7ff4fda..f57e8a3c 100644 --- a/Minecraft.Client/ServerPlayer.cpp +++ b/Minecraft.Client/ServerPlayer.cpp @@ -146,7 +146,8 @@ void ServerPlayer::flagEntitiesToBeRemoved(unsigned int *flags, bool *removedFou if( ( *removedFound ) == false ) { *removedFound = true; - memset(flags, 0, 2048/32); + // before this left 192 bytes uninitialized!!!!! + memset(flags, 0, (2048 / 32) * sizeof(unsigned int)); } for(int index : entitiesToRemove) @@ -376,6 +377,9 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks) // connection->done); // } +#ifdef MINECRAFT_SERVER_BUILD + if (dontDelayChunks || (canSendToPlayer && !connection->done)) +#else if( dontDelayChunks || (canSendToPlayer && #ifdef _XBOX_ONE @@ -390,21 +394,22 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks) #endif //(tickCount - lastBrupSendTickCount) > (connection->getNetworkPlayer()->GetCurrentRtt()>>4) && !connection->done) ) - { - lastBrupSendTickCount = tickCount; - okToSend = true; - MinecraftServer::chunkPacketManagement_DidSendTo(connection->getNetworkPlayer()); - -// static unordered_map<wstring,int64_t> mapLastTime; -// int64_t thisTime = System::currentTimeMillis(); -// int64_t lastTime = mapLastTime[connection->getNetworkPlayer()->GetUID().toString()]; -// app.DebugPrintf(" - OK to send (%d ms since last)\n", thisTime - lastTime); -// mapLastTime[connection->getNetworkPlayer()->GetUID().toString()] = thisTime; - } - else - { - // app.DebugPrintf(" - <NOT OK>\n"); - } +#endif + { + lastBrupSendTickCount = tickCount; + okToSend = true; + MinecraftServer::chunkPacketManagement_DidSendTo(connection->getNetworkPlayer()); + + // static unordered_map<wstring,int64_t> mapLastTime; + // int64_t thisTime = System::currentTimeMillis(); + // int64_t lastTime = mapLastTime[connection->getNetworkPlayer()->GetUID().toString()]; + // app.DebugPrintf(" - OK to send (%d ms since last)\n", thisTime - lastTime); + // mapLastTime[connection->getNetworkPlayer()->GetUID().toString()] = thisTime; + } + else + { + // app.DebugPrintf(" - <NOT OK>\n"); + } } if (okToSend) diff --git a/Minecraft.World/Entity.cpp b/Minecraft.World/Entity.cpp index d754330b..924312e5 100644 --- a/Minecraft.World/Entity.cpp +++ b/Minecraft.World/Entity.cpp @@ -96,9 +96,20 @@ int Entity::getSmallId() puiUsedFlags++; } +#ifdef MINECRAFT_SERVER_BUILD + // in mc server dedi, a server with 8+ playerrs can cause this to go wack + int fallbackId = Entity::entityCounter++; + + if (entityCounter == 0x7ffffff) + { + entityCounter = 2048; + } + return fallbackId; +#else app.DebugPrintf("Out of small entity Ids... possible leak?\n"); __debugbreak(); return -1; +#endif } void Entity::countFlagsForPIX() diff --git a/Minecraft.World/Level.h b/Minecraft.World/Level.h index eb750aff..2be87472 100644 --- a/Minecraft.World/Level.h +++ b/Minecraft.World/Level.h @@ -16,7 +16,12 @@ using namespace std; // 4J Stu - This value should be big enough that we don't get any crashes causes by memory overwrites, // however it does seem way too large for what is actually needed. Needs further investigation +#ifdef MINECRAFT_SERVER_BUILD +// fixes a crash when 8+ players are present +#define LEVEL_CHUNKS_TO_UPDATE_MAX (32*32*8) +#else #define LEVEL_CHUNKS_TO_UPDATE_MAX (19*19*8) +#endif class Vec3; class ChunkSource; diff --git a/docker/dedicated-server/entrypoint.sh b/docker/dedicated-server/entrypoint.sh index a8f8907a..26b40da9 100644 --- a/docker/dedicated-server/entrypoint.sh +++ b/docker/dedicated-server/entrypoint.sh @@ -30,6 +30,65 @@ ensure_persist_file() { ln -sfn "${persist_path}" "${runtime_path}" } +wait_for_xvfb_ready() { + local display="$1" + local xvfb_pid="$2" + local wait_seconds="${XVFB_WAIT_SECONDS:-10}" + local wait_ticks=$((wait_seconds * 10)) + local display_number="${display#:}" + display_number="${display_number%%.*}" + + if [ -z "${display_number}" ] || ! [[ "${display_number}" =~ ^[0-9]+$ ]]; then + echo "[error] Invalid DISPLAY format for Xvfb wait: ${display}" >&2 + return 1 + fi + + local socket_path="/tmp/.X11-unix/X${display_number}" + local elapsed=0 + + while [ "${elapsed}" -lt "${wait_ticks}" ]; do + if ! kill -0 "${xvfb_pid}" 2>/dev/null; then + echo "[error] Xvfb exited before the display became ready." >&2 + if [ -f /tmp/xvfb.log ]; then + echo "[error] ---- /tmp/xvfb.log ----" >&2 + tail -n 200 /tmp/xvfb.log >&2 || true + echo "[error] ----------------------" >&2 + fi + return 1 + fi + + if [ -S "${socket_path}" ]; then + # Keep a short extra delay so Wine does not race display handshake. + sleep 0.2 + if kill -0 "${xvfb_pid}" 2>/dev/null && [ -S "${socket_path}" ]; then + return 0 + fi + fi + + # One more liveness check after the ready probe branch. + if ! kill -0 "${xvfb_pid}" 2>/dev/null; then + echo "[error] Xvfb exited during display readiness probe." >&2 + if [ -f /tmp/xvfb.log ]; then + echo "[error] ---- /tmp/xvfb.log ----" >&2 + tail -n 200 /tmp/xvfb.log >&2 || true + echo "[error] ----------------------" >&2 + fi + return 1 + fi + + sleep 0.1 + elapsed=$((elapsed + 1)) + done + + echo "[error] Timed out waiting for Xvfb display ${display}." >&2 + if [ -f /tmp/xvfb.log ]; then + echo "[error] ---- /tmp/xvfb.log ----" >&2 + tail -n 200 /tmp/xvfb.log >&2 || true + echo "[error] ----------------------" >&2 + fi + return 1 +} + if [ ! -d "$SERVER_DIR" ]; then echo "[error] Server directory not found: $SERVER_DIR" >&2 exit 1 @@ -78,8 +137,25 @@ fi if [ -z "${DISPLAY:-}" ]; then export DISPLAY="${XVFB_DISPLAY:-:99}" XVFB_SCREEN="${XVFB_SCREEN:-64x64x16}" + DISPLAY_NUMBER="${DISPLAY#:}" + DISPLAY_NUMBER="${DISPLAY_NUMBER%%.*}" + if [ -z "${DISPLAY_NUMBER}" ] || ! [[ "${DISPLAY_NUMBER}" =~ ^[0-9]+$ ]]; then + echo "[error] Invalid XVFB_DISPLAY format: ${DISPLAY}" >&2 + exit 1 + fi + XVFB_SOCKET="/tmp/.X11-unix/X${DISPLAY_NUMBER}" + XVFB_LOCK="/tmp/.X${DISPLAY_NUMBER}-lock" + # The check is performed assuming the same container will be restarted. + if [ -S "${XVFB_SOCKET}" ] || [ -e "${XVFB_LOCK}" ]; then + echo "[warn] Removing stale Xvfb state for ${DISPLAY} before startup." >&2 + rm -f "${XVFB_SOCKET}" "${XVFB_LOCK}" + fi Xvfb "${DISPLAY}" -nolisten tcp -screen 0 "${XVFB_SCREEN}" >/tmp/xvfb.log 2>&1 & - sleep 0.2 + XVFB_PID=$! + wait_for_xvfb_ready "${DISPLAY}" "${XVFB_PID}" + echo "[info] Xvfb ready on ${DISPLAY} (pid=${XVFB_PID}, screen=${XVFB_SCREEN})" +else + echo "[info] Using existing DISPLAY=${DISPLAY}; skipping Xvfb startup" fi args=( |
