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 1dc8a005ed

* 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>
This commit is contained in:
kuwa
2026-03-16 05:50:12 +09:00
committed by GitHub
parent 7b643cdd75
commit e9f5b4b6f0
5 changed files with 382 additions and 266 deletions

View File

@@ -657,7 +657,7 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
setFlightAllowed(GetDedicatedServerBool(settings, L"allow-flight", true)); setFlightAllowed(GetDedicatedServerBool(settings, L"allow-flight", true));
// 4J Stu - Enabling flight to stop it kicking us when we use it // 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); setFlightAllowed(true);
#endif #endif
@@ -1715,330 +1715,345 @@ void MinecraftServer::setPlayerIdleTimeout(int playerIdleTimeout)
extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b; extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b;
void MinecraftServer::run(int64_t seed, void *lpParameter) void MinecraftServer::run(int64_t seed, void *lpParameter)
{ {
NetworkGameInitData *initData = nullptr; NetworkGameInitData *initData = nullptr;
DWORD initSettings = 0; DWORD initSettings = 0;
bool findSeed = false; bool findSeed = false;
if(lpParameter != nullptr) if(lpParameter != nullptr)
{ {
initData = static_cast<NetworkGameInitData *>(lpParameter); initData = static_cast<NetworkGameInitData *>(lpParameter);
initSettings = app.GetGameHostOption(eGameHostOption_All); initSettings = app.GetGameHostOption(eGameHostOption_All);
findSeed = initData->findSeed; findSeed = initData->findSeed;
m_texturePackId = initData->texturePackId; m_texturePackId = initData->texturePackId;
} }
// try { // 4J - removed try/catch/finally // try { // 4J - removed try/catch/finally
bool didInit = false; bool didInit = false;
if (initServer(seed, initData, initSettings,findSeed)) if (initServer(seed, initData, initSettings,findSeed))
{ {
didInit = true; didInit = true;
ServerLevel *levelNormalDimension = levels[0]; ServerLevel *levelNormalDimension = levels[0];
// 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there // 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there
Minecraft *pMinecraft = Minecraft::GetInstance(); Minecraft *pMinecraft = Minecraft::GetInstance();
LevelData *pLevelData=levelNormalDimension->getLevelData(); LevelData *pLevelData=levelNormalDimension->getLevelData();
if(pLevelData && pLevelData->getHasStronghold()==false) if(pLevelData && pLevelData->getHasStronghold()==false)
{ {
int x,z; int x,z;
if(app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold,&x,&z)) if(app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold,&x,&z))
{ {
pLevelData->setXStronghold(x); pLevelData->setXStronghold(x);
pLevelData->setZStronghold(z); pLevelData->setZStronghold(z);
pLevelData->setHasStronghold(); pLevelData->setHasStronghold();
} }
} }
int64_t lastTime = getCurrentTimeMillis(); int64_t lastTime = getCurrentTimeMillis();
int64_t unprocessedTime = 0; int64_t unprocessedTime = 0;
while (running && !s_bServerHalted) while (running && !s_bServerHalted)
{ {
int64_t now = getCurrentTimeMillis(); int64_t now = getCurrentTimeMillis();
// 4J Stu - When we pause the server, we don't want to count that as time passed // 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 // 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 //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; //if(m_isServerPaused) lastTime = now;
int64_t passedTime = now - lastTime; int64_t passedTime = now - lastTime;
if (passedTime > MS_PER_TICK * 40) if (passedTime > MS_PER_TICK * 40)
{ {
// logger.warning("Can't keep up! Did the system time change, or is the server overloaded?"); // logger.warning("Can't keep up! Did the system time change, or is the server overloaded?");
passedTime = MS_PER_TICK * 40; passedTime = MS_PER_TICK * 40;
} }
if (passedTime < 0) if (passedTime < 0)
{ {
// logger.warning("Time ran backwards! Did the system time change?"); // logger.warning("Time ran backwards! Did the system time change?");
passedTime = 0; passedTime = 0;
} }
unprocessedTime += passedTime; unprocessedTime += passedTime;
lastTime = now; lastTime = now;
// 4J Added ability to pause the server // 4J Added ability to pause the server
if( !m_isServerPaused ) if( !m_isServerPaused )
{ {
bool didTick = false; bool didTick = false;
if (levels[0]->allPlayersAreSleeping()) if (levels[0]->allPlayersAreSleeping())
{ {
tick(); tick();
unprocessedTime = 0; unprocessedTime = 0;
} }
else else
{ {
// int tickcount = 0; // int tickcount = 0;
// int64_t beforeall = System::currentTimeMillis(); // int64_t beforeall = System::currentTimeMillis();
while (unprocessedTime > MS_PER_TICK) while (unprocessedTime > MS_PER_TICK)
{ {
unprocessedTime -= MS_PER_TICK; unprocessedTime -= MS_PER_TICK;
chunkPacketManagement_PreTick(); chunkPacketManagement_PreTick();
// int64_t before = System::currentTimeMillis(); // int64_t before = System::currentTimeMillis();
tick(); tick();
// int64_t after = System::currentTimeMillis(); // int64_t after = System::currentTimeMillis();
// PIXReportCounter(L"Server time",(float)(after-before)); // PIXReportCounter(L"Server time",(float)(after-before));
chunkPacketManagement_PostTick(); chunkPacketManagement_PostTick();
} }
// int64_t afterall = System::currentTimeMillis(); // int64_t afterall = System::currentTimeMillis();
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall)); // PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
// PIXReportCounter(L"Server ticks",(float)tickcount); // PIXReportCounter(L"Server ticks",(float)tickcount);
} }
} }
else else
{ {
// 4J Stu - TU1-hotfix // 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 //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 // The connections should tick at the same frequency even when paused
while (unprocessedTime > MS_PER_TICK) while (unprocessedTime > MS_PER_TICK)
{ {
unprocessedTime -= MS_PER_TICK; unprocessedTime -= MS_PER_TICK;
// Keep ticking the connections to stop them timing out // Keep ticking the connections to stop them timing out
connection->tick(); connection->tick();
} }
} }
if(MinecraftServer::setTimeAtEndOfTick) if(MinecraftServer::setTimeAtEndOfTick)
{ {
MinecraftServer::setTimeAtEndOfTick = false; MinecraftServer::setTimeAtEndOfTick = false;
for (unsigned int i = 0; i < levels.length; i++) for (unsigned int i = 0; i < levels.length; i++)
{ {
// if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether // if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether
{ {
ServerLevel *level = levels[i]; ServerLevel *level = levels[i];
level->setGameTime( MinecraftServer::setTime ); level->setGameTime( MinecraftServer::setTime );
} }
} }
} }
if(MinecraftServer::setTimeOfDayAtEndOfTick) if(MinecraftServer::setTimeOfDayAtEndOfTick)
{ {
MinecraftServer::setTimeOfDayAtEndOfTick = false; MinecraftServer::setTimeOfDayAtEndOfTick = false;
for (unsigned int i = 0; i < levels.length; i++) for (unsigned int i = 0; i < levels.length; i++)
{ {
if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true)) if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true))
{ {
ServerLevel *level = levels[i]; ServerLevel *level = levels[i];
level->setDayTime( MinecraftServer::setTimeOfDay ); level->setDayTime( MinecraftServer::setTimeOfDay );
} }
} }
} }
// Process delayed actions // Process delayed actions
eXuiServerAction eAction; eXuiServerAction eAction;
LPVOID param; LPVOID param;
for(int i=0;i<XUSER_MAX_COUNT;i++) for(int i=0;i<XUSER_MAX_COUNT;i++)
{ {
eAction = app.GetXuiServerAction(i); eAction = app.GetXuiServerAction(i);
param = app.GetXuiServerActionParam(i); param = app.GetXuiServerActionParam(i);
switch(eAction) switch(eAction)
{ {
case eXuiServerAction_AutoSaveGame: case eXuiServerAction_AutoSaveGame:
#if defined(_XBOX_ONE) || defined(__ORBIS__) || defined(MINECRAFT_SERVER_BUILD)
{
#if defined(_XBOX_ONE) || defined(__ORBIS__) #if defined(_XBOX_ONE) || defined(__ORBIS__)
{ PIXBeginNamedEvent(0, "Autosave");
PIXBeginNamedEvent(0,"Autosave");
// Get the frequency of the timer // Get the frequency of the timer
LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime; LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime;
float fElapsedTime = 0.0f; float fElapsedTime = 0.0f;
QueryPerformanceFrequency( &qwTicksPerSec ); QueryPerformanceFrequency(&qwTicksPerSec);
float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart; float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart;
// Save the start time // Save the start time
QueryPerformanceCounter( &qwTime ); QueryPerformanceCounter(&qwTime);
if (players != nullptr)
{
players->saveAll(nullptr);
}
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;
#endif #endif
case eXuiServerAction_SaveGame:
app.EnterSaveNotificationSection();
if (players != nullptr)
{
players->saveAll(Minecraft::GetInstance()->progressRenderer);
}
players->broadcastAll(std::make_shared<UpdateProgressPacket>(20)); 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];
#if defined(_XBOX_ONE) || defined(__ORBIS__)
PIXBeginNamedEvent(0, "Saving level %d", levels.length - 1 - j);
#endif
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();
PIXBeginNamedEvent(0, "Save to disc");
#endif
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true);
#if defined(_XBOX_ONE) || defined(__ORBIS__)
PIXEndNamedEvent();
#endif
}
#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; if( s_bServerHalted ) break;
// 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat // 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat
// with the data from the nethers leveldata. // 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. // 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]; ServerLevel *level = levels[levels.length - 1 - j];
level->save(true, Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame)); 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 ) if( !s_bServerHalted )
{ {
saveGameRules(); saveGameRules();
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame)); levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame));
} }
app.LeaveSaveNotificationSection(); app.LeaveSaveNotificationSection();
break; break;
case eXuiServerAction_DropItem: case eXuiServerAction_DropItem:
// Find the player, and drop the id at their feet // Find the player, and drop the id at their feet
{ {
shared_ptr<ServerPlayer> player = players->players.at(0); shared_ptr<ServerPlayer> player = players->players.at(0);
size_t id = (size_t) param; size_t id = (size_t) param;
player->drop(std::make_shared<ItemInstance>(id, 1, 0)); player->drop(std::make_shared<ItemInstance>(id, 1, 0));
} }
break; break;
case eXuiServerAction_SpawnMob: case eXuiServerAction_SpawnMob:
{ {
shared_ptr<ServerPlayer> player = players->players.at(0); shared_ptr<ServerPlayer> player = players->players.at(0);
eINSTANCEOF factory = static_cast<eINSTANCEOF>((size_t)param); eINSTANCEOF factory = static_cast<eINSTANCEOF>((size_t)param);
shared_ptr<Mob> mob = dynamic_pointer_cast<Mob>(EntityIO::newByEnumType(factory,player->level )); 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->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) mob->setDespawnProtected(); // 4J added, default to being protected against despawning (has to be done after initial position is set)
player->level->addEntity(mob); player->level->addEntity(mob);
} }
break; break;
case eXuiServerAction_PauseServer: case eXuiServerAction_PauseServer:
m_isServerPaused = ( (size_t) param == TRUE ); m_isServerPaused = ( (size_t) param == TRUE );
if( m_isServerPaused ) if( m_isServerPaused )
{ {
m_serverPausedEvent->Set(); m_serverPausedEvent->Set();
} }
break; break;
case eXuiServerAction_ToggleRain: case eXuiServerAction_ToggleRain:
{ {
bool isRaining = levels[0]->getLevelData()->isRaining(); bool isRaining = levels[0]->getLevelData()->isRaining();
levels[0]->getLevelData()->setRaining(!isRaining); levels[0]->getLevelData()->setRaining(!isRaining);
levels[0]->getLevelData()->setRainTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); levels[0]->getLevelData()->setRainTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2);
} }
break; break;
case eXuiServerAction_ToggleThunder: case eXuiServerAction_ToggleThunder:
{ {
bool isThundering = levels[0]->getLevelData()->isThundering(); bool isThundering = levels[0]->getLevelData()->isThundering();
levels[0]->getLevelData()->setThundering(!isThundering); levels[0]->getLevelData()->setThundering(!isThundering);
levels[0]->getLevelData()->setThunderTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2); levels[0]->getLevelData()->setThunderTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2);
} }
break; break;
case eXuiServerAction_ServerSettingChanged_Gamertags: case eXuiServerAction_ServerSettingChanged_Gamertags:
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags))); players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags)));
break; break;
case eXuiServerAction_ServerSettingChanged_BedrockFog: case eXuiServerAction_ServerSettingChanged_BedrockFog:
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All))); players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All)));
break; break;
case eXuiServerAction_ServerSettingChanged_Difficulty: case eXuiServerAction_ServerSettingChanged_Difficulty:
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty)); players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty));
break; break;
case eXuiServerAction_ExportSchematic: case eXuiServerAction_ExportSchematic:
#ifndef _CONTENT_PACKAGE #ifndef _CONTENT_PACKAGE
app.EnterSaveNotificationSection(); app.EnterSaveNotificationSection();
//players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) ); //players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) );
if( !s_bServerHalted ) if( !s_bServerHalted )
{ {
ConsoleSchematicFile::XboxSchematicInitParam *initData = static_cast<ConsoleSchematicFile::XboxSchematicInitParam *>(param); ConsoleSchematicFile::XboxSchematicInitParam *initData = static_cast<ConsoleSchematicFile::XboxSchematicInitParam *>(param);
#ifdef _XBOX #ifdef _XBOX
File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics"); File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics");
#else #else
File targetFileDir(L"Schematics"); File targetFileDir(L"Schematics");
#endif #endif
if(!targetFileDir.exists()) targetFileDir.mkdir(); 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)); 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) ); File dataFile = File( targetFileDir, wstring(filename) );
if(dataFile.exists()) dataFile._delete(); if(dataFile.exists()) dataFile._delete();
FileOutputStream fos = FileOutputStream(dataFile); FileOutputStream fos = FileOutputStream(dataFile);
DataOutputStream dos = DataOutputStream(&fos); 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); ConsoleSchematicFile::generateSchematicFile(&dos, levels[0], initData->startX, initData->startY, initData->startZ, initData->endX, initData->endY, initData->endZ, initData->bSaveMobs, initData->compressionType);
dos.close(); dos.close();
delete initData; delete initData;
} }
app.LeaveSaveNotificationSection(); app.LeaveSaveNotificationSection();
#endif #endif
break; break;
case eXuiServerAction_SetCameraLocation: case eXuiServerAction_SetCameraLocation:
#ifndef _CONTENT_PACKAGE #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: Player=%i\n", pos->player );
app.DebugPrintf( "DEBUG: Teleporting to pos=(%f.2, %f.2, %f.2), looking at=(%f.2,%f.2)\n", 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 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, player->debug_setPosition( pos->m_camX, pos->m_camY, pos->m_camZ,
pos->m_yRot, pos->m_elev ); pos->m_yRot, pos->m_elev );
// Doesn't work // Doesn't work
//player->setYHeadRot(pos->m_yRot); //player->setYHeadRot(pos->m_yRot);
//player->absMoveTo(pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev); //player->absMoveTo(pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev);
} }
#endif #endif
break; break;
} }
app.SetXuiServerAction(i,eXuiServerAction_Idle); app.SetXuiServerAction(i,eXuiServerAction_Idle);
} }
Sleep(1); Sleep(1);
} }
} }
//else //else
//{ //{
// while (running) // while (running)
// { // {
// handleConsoleInputs(); // handleConsoleInputs();
// Sleep(10); // Sleep(10);
// } // }
//} //}
#if 0 #if 0
@@ -2065,9 +2080,9 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
} }
#endif #endif
// 4J Stu - Stop the server when the loops complete, as the finally would do // 4J Stu - Stop the server when the loops complete, as the finally would do
stopServer(didInit); stopServer(didInit);
stopped = true; stopped = true;
} }
void MinecraftServer::broadcastStartSavingPacket() void MinecraftServer::broadcastStartSavingPacket()
@@ -2375,6 +2390,9 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
{ {
if( player == nullptr ) return false; if( player == nullptr ) return false;
#ifdef MINECRAFT_SERVER_BUILD
return true;
#else
int time = GetTickCount(); int time = GetTickCount();
DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount(); DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount();
if( currentPlayerCount == 0 ) return false; if( currentPlayerCount == 0 ) return false;
@@ -2386,6 +2404,7 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
} }
return false; return false;
#endif
} }
void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player) void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player)

View File

@@ -146,7 +146,8 @@ void ServerPlayer::flagEntitiesToBeRemoved(unsigned int *flags, bool *removedFou
if( ( *removedFound ) == false ) if( ( *removedFound ) == false )
{ {
*removedFound = true; *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) for(int index : entitiesToRemove)
@@ -376,6 +377,9 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
// connection->done); // connection->done);
// } // }
#ifdef MINECRAFT_SERVER_BUILD
if (dontDelayChunks || (canSendToPlayer && !connection->done))
#else
if( dontDelayChunks || if( dontDelayChunks ||
(canSendToPlayer && (canSendToPlayer &&
#ifdef _XBOX_ONE #ifdef _XBOX_ONE
@@ -390,21 +394,22 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
#endif #endif
//(tickCount - lastBrupSendTickCount) > (connection->getNetworkPlayer()->GetCurrentRtt()>>4) && //(tickCount - lastBrupSendTickCount) > (connection->getNetworkPlayer()->GetCurrentRtt()>>4) &&
!connection->done) ) !connection->done) )
{ #endif
lastBrupSendTickCount = tickCount; {
okToSend = true; lastBrupSendTickCount = tickCount;
MinecraftServer::chunkPacketManagement_DidSendTo(connection->getNetworkPlayer()); okToSend = true;
MinecraftServer::chunkPacketManagement_DidSendTo(connection->getNetworkPlayer());
// static unordered_map<wstring,int64_t> mapLastTime; // static unordered_map<wstring,int64_t> mapLastTime;
// int64_t thisTime = System::currentTimeMillis(); // int64_t thisTime = System::currentTimeMillis();
// int64_t lastTime = mapLastTime[connection->getNetworkPlayer()->GetUID().toString()]; // int64_t lastTime = mapLastTime[connection->getNetworkPlayer()->GetUID().toString()];
// app.DebugPrintf(" - OK to send (%d ms since last)\n", thisTime - lastTime); // app.DebugPrintf(" - OK to send (%d ms since last)\n", thisTime - lastTime);
// mapLastTime[connection->getNetworkPlayer()->GetUID().toString()] = thisTime; // mapLastTime[connection->getNetworkPlayer()->GetUID().toString()] = thisTime;
} }
else else
{ {
// app.DebugPrintf(" - <NOT OK>\n"); // app.DebugPrintf(" - <NOT OK>\n");
} }
} }
if (okToSend) if (okToSend)

View File

@@ -96,9 +96,20 @@ int Entity::getSmallId()
puiUsedFlags++; 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"); app.DebugPrintf("Out of small entity Ids... possible leak?\n");
__debugbreak(); __debugbreak();
return -1; return -1;
#endif
} }
void Entity::countFlagsForPIX() void Entity::countFlagsForPIX()

View File

@@ -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, // 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 // 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) #define LEVEL_CHUNKS_TO_UPDATE_MAX (19*19*8)
#endif
class Vec3; class Vec3;
class ChunkSource; class ChunkSource;

View File

@@ -30,6 +30,65 @@ ensure_persist_file() {
ln -sfn "${persist_path}" "${runtime_path}" 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 if [ ! -d "$SERVER_DIR" ]; then
echo "[error] Server directory not found: $SERVER_DIR" >&2 echo "[error] Server directory not found: $SERVER_DIR" >&2
exit 1 exit 1
@@ -78,8 +137,25 @@ fi
if [ -z "${DISPLAY:-}" ]; then if [ -z "${DISPLAY:-}" ]; then
export DISPLAY="${XVFB_DISPLAY:-:99}" export DISPLAY="${XVFB_DISPLAY:-:99}"
XVFB_SCREEN="${XVFB_SCREEN:-64x64x16}" 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 & 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 fi
args=( args=(