feat: add smartcmd dedicated server
major bug: when connecting to the server a second time, it gets softlocked and keeps repeating ticking connection A\n in debug logs until it times out.
This commit is contained in:
146 files changed
+32837
-272
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,730 @@
|
||||
#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 "Common/Network/NetworkSocketLayer.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/ConsoleSaveFileOriginal.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();
|
||||
//ConsoleSaveFileOriginal::flushPendingBackgroundSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* **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 = NETWORK_LAN_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_MultiplayerHost = true;
|
||||
g_MultiplayerJoin = false;
|
||||
g_MultiplayerPort = config.port;
|
||||
strncpy_s(g_MultiplayerIP, sizeof(g_MultiplayerIP), config.bindIP, _TRUNCATE);
|
||||
g_DedicatedServerPort = config.port;
|
||||
strncpy_s(g_DedicatedServerBindIP, sizeof(g_DedicatedServerBindIP), config.bindIP, _TRUNCATE);
|
||||
g_DedicatedServerLanAdvertise = 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);
|
||||
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);
|
||||
NetworkSocketLayer::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());
|
||||
NetworkSocketLayer::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);
|
||||
NetworkSocketLayer::Shutdown();
|
||||
g_NetworkManager.Terminate();
|
||||
CleanupDevice();
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
LogStartupStep("server startup complete");
|
||||
LogInfof("startup", "Dedicated server listening on %s:%d", g_MultiplayerIP, g_MultiplayerPort);
|
||||
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/* && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()*/)
|
||||
{
|
||||
LogWorldIO("autosave completed");
|
||||
autosaveRequested = false;
|
||||
}
|
||||
|
||||
if (MinecraftServer::serverHalted())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD now = GetTickCount();
|
||||
if ((LONG)(now - nextAutosaveTick) >= 0)
|
||||
{
|
||||
if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle/* && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()*/)
|
||||
{
|
||||
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/* && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()*/)
|
||||
{
|
||||
server->setSaveOnExit(true);
|
||||
LogWorldIO("requesting save before shutdown");
|
||||
LogWorldIO("using saveOnExit for shutdown");
|
||||
}
|
||||
|
||||
/*if (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
{
|
||||
LogWorldIO("Waiting for autosave to complete...");
|
||||
}*/
|
||||
|
||||
MinecraftServer::HaltServer();
|
||||
|
||||
if (g_NetworkManager.ServerStoppedValid())
|
||||
{
|
||||
C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped");
|
||||
waitThread.Run();
|
||||
while (waitThread.isRunning())
|
||||
{
|
||||
TickCoreSystems();
|
||||
Sleep(10);
|
||||
}
|
||||
waitThread.WaitForCompletion(INFINITE);
|
||||
}
|
||||
|
||||
/*while (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
{
|
||||
TickCoreSystems();
|
||||
Sleep(10);
|
||||
}*/
|
||||
|
||||
LogInfof("shutdown", "Cleaning up and exiting.");
|
||||
NetworkSocketLayer::Shutdown();
|
||||
LogDebugf("shutdown", "Network layer shutdown complete.");
|
||||
g_NetworkManager.Terminate();
|
||||
LogDebugf("shutdown", "Network manager terminated.");
|
||||
ServerRuntime::ServerLogManager::Shutdown();
|
||||
CleanupDevice();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user