mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-03-22 17:38:12 +05:00
* 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 initialization1dc8a005ed* 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 commitaadb511, where newly created worlds are intentionally not saved to disk immediately. * update: add basically all configuration options that are implemented in the classes to `server.properties` * update: add LAN advertising configuration for server.properties LAN-Discovery, which isn’t needed in server mode and could potentially be a security risk, has also been disabled(only server mode). * add: add implementing interactive command line using linenoise - Integrated linenoise library for line editing and completion in the server console. - Updated ServerLogger to handle external writes safely during logging. - Modified ServerMain to initialize and manage the ServerCli for command input. - The implementation is separate from everything else, so it doesn't affect anything else. - The command input section and execution section are separated into threads. * update: enhance command line completion with predictive hints Like most command line tools, it highlights predictions in gray. * add: implement `StringUtils` for string manipulation and refactor usages Unified the scattered utility functions. * fix: send DisconnectPacket on shutdown and fix Win64 recv-thread teardown race Before this change, server/host shutdown closed sockets directly in ServerConnection::stop(), which bypassed the normal disconnect flow. As a result, clients could be dropped without receiving a proper DisconnectPacket during stop/kill/world-close paths. Also, WinsockNetLayer::Shutdown() could destroy synchronization objects while host-side recv threads were still exiting, causing a crash in RecvThreadProc (access violation on world close in host mode). * fix: return client to menus when Win64 host connection drops - Add client-side host disconnect handling in CPlatformNetworkManagerStub::DoWork() for _WINDOWS64. - When in QNET_STATE_GAME_PLAY as a non-host and WinsockNetLayer::IsConnected() becomes false, trigger g_NetworkManager.HandleDisconnect(false) to enter the normal disconnect/UI flow. - Use m_bLeaveGameOnTick as a one-shot guard to prevent repeated disconnect handling while the link remains down. - Reset m_bLeaveGameOnTick on LeaveGame(), HostGame(), and JoinGame() to avoid stale state across sessions. * update: converted Japanese comments to English * add: create `Minecraft.Server` developer guide in English and Japanese * update: add note about issue * add: add `nlohmann/json` json lib * add: add FileUtils Moved file operations to `utils`. * add: Dedicated Server BAN access manager with persistent player and IP bans - add Access frontend that publishes thread-safe ban manager snapshots for dedicated server use - add BanManager storage for banned-players.json and banned-ips.json with load/save/update flows - add persistent player and IP ban checks during dedicated server connection handling - add UTF-8 BOM-safe JSON parsing and shared file helpers backed by nlohmann/json - add Unicode-safe ban file read/write and safer atomic replacement behavior on Windows - add active-ban snapshot APIs and expiry-aware filtering for expires metadata - add RAII-based dedicated access shutdown handling during server startup and teardown * update: changed file read/write operations to use `FileUtils`. - As a side effect, saving has become faster! * fix: Re-added the source that had somehow disappeared. * add: significantly improved the dedicated server logging system - add ServerLogManager to Minecraft.Server as the single entry point for dedicated-server log output - forward CMinecraftApp logger output to the server logger when running with g_Win64DedicatedServer - add named network logs for incoming, accepted, rejected, and disconnected connections - cache connection metadata by smallId so player name and remote IP remain available for disconnect logs - keep Minecraft.Client changes minimal by using lightweight hook points and handling log orchestration on the server side * fix: added the updated library source * add: add `ban` and `pardon` commands for Player and IP * fix: fix stop command shutdown process add dedicated server shutdown request handling * fix: fixed the save logic during server shutdown Removed redundant repeated saves and eliminated the risks of async writes. * update: added new sever files to Docker entrypoint * fix: replace shutdown flag with atomic variable for thread safety * update: update Dedicated Server developer guide English is machine translated. Please forgive me. * update: check for the existence of `GameHDD` and create * add: add Whitelist to Dedicated Server * refactor: clean up and refactor the code - unify duplicated implementations that were copied repeatedly - update outdated patterns to more modern ones * fix: include UI header (new update fix) * fix: fix the detection range for excessive logging `getHighestNonEmptyY()` returning `-1` occurs normally when the chunk is entirely air. The caller (`Minecraft.World/LevelChunk.cpp:2400`) normalizes `-1` to `0`. * update: add world size config to dedicated server properties * update: update README add explanation of `server.properties` & launch arguments * update: add nightly release workflow for dedicated server and client builds to Actions * fix: update name for workflow * add random seed generation * add: add Docker nightly workflow for Dedicated Server publish to GitHub Container Registry * fix: ghost player when clients disconnect out of order #4 * fix: fix 7zip option * fix: fix Docker workflow for Dedicated Server artifact handling * add: add no build Dedicated Server startup scripts and Docker Compose * update: add README for Docker Dedicated Server setup with no local build * refactor: refactor command path structure As the number of commands has increased and become harder to navigate, each command has been organized into separate folders. * update: support stream(file stdin) input mode for server CLI Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux. * add: add new CLI Console Commands for Dedicated Server Most of these commands are executed using the command dispatcher implemented on the `Minecraft.World` side. When registering them with the dispatcher, the sender uses a permission-enabled configuration that treats the CLI as a player. - default game. - enchant - experience. - give - kill(currently, getting a permission error for some reason) - time - weather. - update tp & gamemode command * fix: change player map icon to random select * update: increase the player limit * add: restore the basic anti-cheat implementation and add spawn protection Added the following anti-cheat measures and add spawn protection to `server.properties`. - instant break - speed - reach * fix: fix Docker image tag --------- Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
403 lines
14 KiB
C++
403 lines
14 KiB
C++
#include "stdafx.h"
|
|
|
|
#include "ServerLogManager.h"
|
|
|
|
#include "Common\StringUtils.h"
|
|
#include "ServerLogger.h"
|
|
|
|
#include <array>
|
|
#include <mutex>
|
|
|
|
extern bool g_Win64DedicatedServer;
|
|
|
|
namespace ServerRuntime
|
|
{
|
|
namespace ServerLogManager
|
|
{
|
|
namespace
|
|
{
|
|
/**
|
|
* **!! This information is managed solely for logging purposes, but it is questionable from a liability perspective, so it will eventually need to be separated !!**
|
|
*
|
|
* Tracks the remote IP and accepted player name associated with one `smallId`
|
|
* 1つのsmallIdに紐づく接続IPとプレイヤー名を保持する
|
|
*/
|
|
struct ConnectionLogEntry
|
|
{
|
|
std::string remoteIp;
|
|
std::string playerName;
|
|
};
|
|
|
|
/**
|
|
* Owns the shared connection cache used by hook points running on different threads
|
|
* 複数スレッドのhookから共有される接続キャッシュを保持する
|
|
*/
|
|
struct ServerLogState
|
|
{
|
|
std::mutex stateLock;
|
|
std::array<ConnectionLogEntry, 256> entries;
|
|
};
|
|
|
|
ServerLogState g_serverLogState;
|
|
|
|
static bool IsDedicatedServerLoggingEnabled()
|
|
{
|
|
return g_Win64DedicatedServer;
|
|
}
|
|
|
|
static void ResetConnectionLogEntry(ConnectionLogEntry *entry)
|
|
{
|
|
if (entry == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
entry->remoteIp.clear();
|
|
entry->playerName.clear();
|
|
}
|
|
|
|
static std::string NormalizeRemoteIp(const char *ip)
|
|
{
|
|
if (ip == NULL || ip[0] == 0)
|
|
{
|
|
return std::string("unknown");
|
|
}
|
|
|
|
return std::string(ip);
|
|
}
|
|
|
|
static std::string NormalizePlayerName(const std::wstring &playerName)
|
|
{
|
|
std::string playerNameUtf8 = StringUtils::WideToUtf8(playerName);
|
|
if (playerNameUtf8.empty())
|
|
{
|
|
playerNameUtf8 = "<unknown>";
|
|
}
|
|
|
|
return playerNameUtf8;
|
|
}
|
|
|
|
// Default to the main app channel when the caller does not provide a source tag.
|
|
static const char *NormalizeClientLogSource(const char *source)
|
|
{
|
|
if (source == NULL || source[0] == 0)
|
|
{
|
|
return "app";
|
|
}
|
|
|
|
return source;
|
|
}
|
|
|
|
static void EmitClientDebugLogLine(const char *source, const std::string &line)
|
|
{
|
|
if (line.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LogDebugf("client", "[%s] %s", NormalizeClientLogSource(source), line.c_str());
|
|
}
|
|
|
|
// Split one debug payload into individual lines so each line becomes a prompt-safe server log entry.
|
|
static void ForwardClientDebugMessage(const char *source, const char *message)
|
|
{
|
|
if (message == NULL || message[0] == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const char *cursor = message;
|
|
while (*cursor != 0)
|
|
{
|
|
const char *lineStart = cursor;
|
|
while (*cursor != 0 && *cursor != '\r' && *cursor != '\n')
|
|
{
|
|
++cursor;
|
|
}
|
|
|
|
// Split multi-line client debug output into prompt-safe server log entries.
|
|
if (cursor > lineStart)
|
|
{
|
|
EmitClientDebugLogLine(source, std::string(lineStart, (size_t)(cursor - lineStart)));
|
|
}
|
|
|
|
while (*cursor == '\r' || *cursor == '\n')
|
|
{
|
|
++cursor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Share the same formatting path for app, user, and legacy debug-spew forwards.
|
|
static void ForwardFormattedClientDebugLogV(const char *source, const char *format, va_list args)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled() || format == NULL || format[0] == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char messageBuffer[2048] = {};
|
|
vsnprintf_s(messageBuffer, sizeof(messageBuffer), _TRUNCATE, format, args);
|
|
ForwardClientDebugMessage(source, messageBuffer);
|
|
}
|
|
|
|
static const char *TcpRejectReasonToString(ETcpRejectReason reason)
|
|
{
|
|
switch (reason)
|
|
{
|
|
case eTcpRejectReason_BannedIp: return "banned-ip";
|
|
case eTcpRejectReason_GameNotReady: return "game-not-ready";
|
|
case eTcpRejectReason_ServerFull: return "server-full";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
static const char *LoginRejectReasonToString(ELoginRejectReason reason)
|
|
{
|
|
switch (reason)
|
|
{
|
|
case eLoginRejectReason_BannedXuid: return "banned-xuid";
|
|
case eLoginRejectReason_NotWhitelisted: return "not-whitelisted";
|
|
case eLoginRejectReason_DuplicateXuid: return "duplicate-xuid";
|
|
case eLoginRejectReason_DuplicateName: return "duplicate-name";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
static const char *DisconnectReasonToString(DisconnectPacket::eDisconnectReason reason)
|
|
{
|
|
switch (reason)
|
|
{
|
|
case DisconnectPacket::eDisconnect_None: return "none";
|
|
case DisconnectPacket::eDisconnect_Quitting: return "quitting";
|
|
case DisconnectPacket::eDisconnect_Closed: return "closed";
|
|
case DisconnectPacket::eDisconnect_LoginTooLong: return "login-too-long";
|
|
case DisconnectPacket::eDisconnect_IllegalStance: return "illegal-stance";
|
|
case DisconnectPacket::eDisconnect_IllegalPosition: return "illegal-position";
|
|
case DisconnectPacket::eDisconnect_MovedTooQuickly: return "moved-too-quickly";
|
|
case DisconnectPacket::eDisconnect_NoFlying: return "no-flying";
|
|
case DisconnectPacket::eDisconnect_Kicked: return "kicked";
|
|
case DisconnectPacket::eDisconnect_TimeOut: return "timeout";
|
|
case DisconnectPacket::eDisconnect_Overflow: return "overflow";
|
|
case DisconnectPacket::eDisconnect_EndOfStream: return "end-of-stream";
|
|
case DisconnectPacket::eDisconnect_ServerFull: return "server-full";
|
|
case DisconnectPacket::eDisconnect_OutdatedServer: return "outdated-server";
|
|
case DisconnectPacket::eDisconnect_OutdatedClient: return "outdated-client";
|
|
case DisconnectPacket::eDisconnect_UnexpectedPacket: return "unexpected-packet";
|
|
case DisconnectPacket::eDisconnect_ConnectionCreationFailed: return "connection-creation-failed";
|
|
case DisconnectPacket::eDisconnect_NoMultiplayerPrivilegesHost: return "no-multiplayer-privileges-host";
|
|
case DisconnectPacket::eDisconnect_NoMultiplayerPrivilegesJoin: return "no-multiplayer-privileges-join";
|
|
case DisconnectPacket::eDisconnect_NoUGC_AllLocal: return "no-ugc-all-local";
|
|
case DisconnectPacket::eDisconnect_NoUGC_Single_Local: return "no-ugc-single-local";
|
|
case DisconnectPacket::eDisconnect_ContentRestricted_AllLocal: return "content-restricted-all-local";
|
|
case DisconnectPacket::eDisconnect_ContentRestricted_Single_Local: return "content-restricted-single-local";
|
|
case DisconnectPacket::eDisconnect_NoUGC_Remote: return "no-ugc-remote";
|
|
case DisconnectPacket::eDisconnect_NoFriendsInGame: return "no-friends-in-game";
|
|
case DisconnectPacket::eDisconnect_Banned: return "banned";
|
|
case DisconnectPacket::eDisconnect_NotFriendsWithHost: return "not-friends-with-host";
|
|
case DisconnectPacket::eDisconnect_NATMismatch: return "nat-mismatch";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only forward client-side debug output while the process is running as the dedicated server.
|
|
bool ShouldForwardClientDebugLogs()
|
|
{
|
|
return IsDedicatedServerLoggingEnabled();
|
|
}
|
|
|
|
void ForwardClientAppDebugLogV(const char *format, va_list args)
|
|
{
|
|
ForwardFormattedClientDebugLogV("app", format, args);
|
|
}
|
|
|
|
void ForwardClientUserDebugLogV(int user, const char *format, va_list args)
|
|
{
|
|
char source[32] = {};
|
|
_snprintf_s(source, sizeof(source), _TRUNCATE, "app:user=%d", user);
|
|
ForwardFormattedClientDebugLogV(source, format, args);
|
|
}
|
|
|
|
void ForwardClientDebugSpewLogV(const char *format, va_list args)
|
|
{
|
|
ForwardFormattedClientDebugLogV("debug-spew", format, args);
|
|
}
|
|
|
|
// Clear every cached connection slot during startup so stale metadata never leaks into future logs.
|
|
void Initialize()
|
|
{
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
for (size_t index = 0; index < g_serverLogState.entries.size(); ++index)
|
|
{
|
|
ResetConnectionLogEntry(&g_serverLogState.entries[index]);
|
|
}
|
|
}
|
|
|
|
// Reuse Initialize as the shutdown cleanup path because both operations wipe the cache.
|
|
void Shutdown()
|
|
{
|
|
Initialize();
|
|
}
|
|
|
|
// Log the raw socket arrival before a smallId is assigned so early rejects still have an IP in the logs.
|
|
void OnIncomingTcpConnection(const char *ip)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::string remoteIp = NormalizeRemoteIp(ip);
|
|
LogInfof("network", "incoming tcp connection from %s", remoteIp.c_str());
|
|
}
|
|
|
|
// TCP rejects happen before connection state is cached, so log directly from the supplied remote IP.
|
|
void OnRejectedTcpConnection(const char *ip, ETcpRejectReason reason)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::string remoteIp = NormalizeRemoteIp(ip);
|
|
LogWarnf("network", "rejected tcp connection from %s: reason=%s", remoteIp.c_str(), TcpRejectReasonToString(reason));
|
|
}
|
|
|
|
// Cache the accepted remote IP immediately so later login and disconnect logs can reuse it.
|
|
void OnAcceptedTcpConnection(unsigned char smallId, const char *ip)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::string remoteIp = NormalizeRemoteIp(ip);
|
|
{
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
ConnectionLogEntry &entry = g_serverLogState.entries[smallId];
|
|
ResetConnectionLogEntry(&entry);
|
|
entry.remoteIp = remoteIp;
|
|
}
|
|
|
|
LogInfof("network", "accepted tcp connection from %s as smallId=%u", remoteIp.c_str(), (unsigned)smallId);
|
|
}
|
|
|
|
// Once login succeeds, bind the resolved player name onto the cached transport entry.
|
|
void OnAcceptedPlayerLogin(unsigned char smallId, const std::wstring &playerName)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::string playerNameUtf8 = NormalizePlayerName(playerName);
|
|
std::string remoteIp("unknown");
|
|
{
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
ConnectionLogEntry &entry = g_serverLogState.entries[smallId];
|
|
entry.playerName = playerNameUtf8;
|
|
if (!entry.remoteIp.empty())
|
|
{
|
|
remoteIp = entry.remoteIp;
|
|
}
|
|
}
|
|
|
|
LogInfof("network", "accepted player login: name=\"%s\" ip=%s smallId=%u", playerNameUtf8.c_str(), remoteIp.c_str(), (unsigned)smallId);
|
|
}
|
|
|
|
// Read the cached IP for the rejection log, then clear the slot because the player never fully joined.
|
|
void OnRejectedPlayerLogin(unsigned char smallId, const std::wstring &playerName, ELoginRejectReason reason)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const std::string playerNameUtf8 = NormalizePlayerName(playerName);
|
|
std::string remoteIp("unknown");
|
|
{
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
ConnectionLogEntry &entry = g_serverLogState.entries[smallId];
|
|
if (!entry.remoteIp.empty())
|
|
{
|
|
remoteIp = entry.remoteIp;
|
|
}
|
|
ResetConnectionLogEntry(&entry);
|
|
}
|
|
|
|
LogWarnf("network", "rejected login from %s: name=\"%s\" reason=%s", remoteIp.c_str(), playerNameUtf8.c_str(), LoginRejectReasonToString(reason));
|
|
}
|
|
|
|
// Disconnect logging is the final consumer of cached metadata, so it also clears the slot afterward.
|
|
void OnPlayerDisconnected(
|
|
unsigned char smallId,
|
|
const std::wstring &playerName,
|
|
DisconnectPacket::eDisconnectReason reason,
|
|
bool initiatedByServer)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::string playerNameUtf8 = NormalizePlayerName(playerName);
|
|
std::string remoteIp("unknown");
|
|
{
|
|
// Copy state under lock and emit the log after unlocking so CLI output never blocks connection bookkeeping.
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
ConnectionLogEntry &entry = g_serverLogState.entries[smallId];
|
|
if (!entry.remoteIp.empty())
|
|
{
|
|
remoteIp = entry.remoteIp;
|
|
}
|
|
if (playerNameUtf8 == "<unknown>" && !entry.playerName.empty())
|
|
{
|
|
playerNameUtf8 = entry.playerName;
|
|
}
|
|
ResetConnectionLogEntry(&entry);
|
|
}
|
|
|
|
LogInfof(
|
|
"network",
|
|
"%s: name=\"%s\" ip=%s smallId=%u reason=%s",
|
|
initiatedByServer ? "disconnecting player" : "player disconnected",
|
|
playerNameUtf8.c_str(),
|
|
remoteIp.c_str(),
|
|
(unsigned)smallId,
|
|
DisconnectReasonToString(reason));
|
|
}
|
|
|
|
/**
|
|
* For logging purposes, the responsibility is technically misplaced, but the IP is cached in `LogManager`.
|
|
* Those cached values are then used to retrieve the player's IP.
|
|
*
|
|
* Eventually, this should be implemented in a separate class or on the `Minecraft.Client` side instead.
|
|
*/
|
|
bool TryGetConnectionRemoteIp(unsigned char smallId, std::string *outIp)
|
|
{
|
|
if (!IsDedicatedServerLoggingEnabled() || outIp == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
const ConnectionLogEntry &entry = g_serverLogState.entries[smallId];
|
|
if (entry.remoteIp.empty() || entry.remoteIp == "unknown")
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*outIp = entry.remoteIp;
|
|
return true;
|
|
}
|
|
|
|
// Provide explicit cache cleanup for paths that terminate without going through disconnect logging.
|
|
void ClearConnection(unsigned char smallId)
|
|
{
|
|
std::lock_guard<std::mutex> stateLock(g_serverLogState.stateLock);
|
|
ResetConnectionLogEntry(&g_serverLogState.entries[smallId]);
|
|
}
|
|
}
|
|
}
|