Dedicated Server Software - Minecraft.Server.exe (#498)

* add: Dedicated Server implementation

- Introduced `ServerMain.cpp` for the dedicated server logic, handling command-line arguments, server initialization, and network management.
- Created `postbuild_server.ps1` script for post-build tasks, including copying necessary resources and DLLs for the dedicated server.
- Added `CopyServerAssets.cmake` to manage the copying of server assets during the build process, ensuring required files are available for the dedicated server.
- Defined project filters in `Minecraft.Server.vcxproj.filters` for better organization of server-related files.

* add: refactor world loader & add server properties

- Introduced ServerLogger for logging startup steps and world I/O operations.
- Implemented ServerProperties for loading and saving server configuration from `server.properties`.
- Added WorldManager to handle world loading and creation based on server properties.
- Updated ServerMain to integrate server properties loading and world management.
- Enhanced project files to include new source and header files for the server components.

* update: implement enhanced logging functionality with configurable log levels

* update: update keyboard and mouse input initialization 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

---------

Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
This commit is contained in:
kuwa
2026-03-15 16:32:50 +09:00
committed by GitHub
parent 4d200a589d
commit f483074cd2
110 changed files with 38957 additions and 147 deletions

View File

@@ -0,0 +1,460 @@
#include "stdafx.h"
#include "Access.h"
#include "..\Common\StringUtils.h"
#include "..\ServerLogger.h"
#include <memory>
#include <mutex>
namespace ServerRuntime
{
namespace Access
{
namespace
{
/**
* **Access State**
*
* These features are used extensively from various parts of the code, so safe read/write handling is implemented
* Stores the published BAN manager snapshot plus a writer gate for clone-and-publish updates
* 公開中のBanManagerスナップショットと更新直列化用ロックを保持する
*/
struct AccessState
{
std::mutex stateLock;
std::mutex writeLock;
std::shared_ptr<BanManager> banManager;
std::shared_ptr<WhitelistManager> whitelistManager;
bool whitelistEnabled = false;
};
AccessState g_accessState;
/**
* Copies the currently published manager pointer so readers can work without holding the publish mutex
* 公開中のBanManager共有ポインタを複製取得する
*/
static std::shared_ptr<BanManager> GetBanManagerSnapshot()
{
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
return g_accessState.banManager;
}
/**
* Replaces the shared manager pointer with a fully prepared snapshot in one short critical section
* 準備完了したBanManagerスナップショットを短いロックで公開する
*/
static void PublishBanManagerSnapshot(const std::shared_ptr<BanManager> &banManager)
{
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
g_accessState.banManager = banManager;
}
static std::shared_ptr<WhitelistManager> GetWhitelistManagerSnapshot()
{
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
return g_accessState.whitelistManager;
}
static void PublishWhitelistManagerSnapshot(const std::shared_ptr<WhitelistManager> &whitelistManager)
{
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
g_accessState.whitelistManager = whitelistManager;
}
}
std::string FormatXuid(PlayerUID xuid)
{
if (xuid == INVALID_XUID)
{
return "";
}
char buffer[32] = {};
sprintf_s(buffer, sizeof(buffer), "0x%016llx", (unsigned long long)xuid);
return buffer;
}
bool TryParseXuid(const std::string &text, PlayerUID *outXuid)
{
if (outXuid == nullptr)
{
return false;
}
unsigned long long parsed = 0;
if (!StringUtils::TryParseUnsignedLongLong(text, &parsed) || parsed == 0ULL)
{
return false;
}
*outXuid = (PlayerUID)parsed;
return true;
}
bool Initialize(const std::string &baseDirectory, bool whitelistEnabled)
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
// Build the replacement manager privately so readers keep using the last published snapshot during disk I/O.
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(baseDirectory);
std::shared_ptr<WhitelistManager> whitelistManager = std::make_shared<WhitelistManager>(baseDirectory);
if (!banManager->EnsureBanFilesExist())
{
LogError("access", "failed to ensure dedicated server ban files exist");
return false;
}
if (!whitelistManager->EnsureWhitelistFileExists())
{
LogError("access", "failed to ensure dedicated server whitelist file exists");
return false;
}
if (!banManager->Reload())
{
LogError("access", "failed to load dedicated server ban files");
return false;
}
if (!whitelistManager->Reload())
{
LogError("access", "failed to load dedicated server whitelist file");
return false;
}
std::vector<BannedPlayerEntry> playerEntries;
std::vector<BannedIpEntry> ipEntries;
std::vector<WhitelistedPlayerEntry> whitelistEntries;
banManager->SnapshotBannedPlayers(&playerEntries);
banManager->SnapshotBannedIps(&ipEntries);
whitelistManager->SnapshotWhitelistedPlayers(&whitelistEntries);
PublishBanManagerSnapshot(banManager);
PublishWhitelistManagerSnapshot(whitelistManager);
{
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
g_accessState.whitelistEnabled = whitelistEnabled;
}
LogInfof(
"access",
"loaded %u player bans, %u ip bans, and %u whitelist entries (whitelist=%s)",
(unsigned)playerEntries.size(),
(unsigned)ipEntries.size(),
(unsigned)whitelistEntries.size(),
whitelistEnabled ? "enabled" : "disabled");
return true;
}
void Shutdown()
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
PublishBanManagerSnapshot(std::shared_ptr<BanManager>{});
PublishWhitelistManagerSnapshot(std::shared_ptr<WhitelistManager>{});
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
g_accessState.whitelistEnabled = false;
}
bool Reload()
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
std::shared_ptr<BanManager> current = GetBanManagerSnapshot();
std::shared_ptr<WhitelistManager> currentWhitelist = GetWhitelistManagerSnapshot();
if (current == nullptr || currentWhitelist == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(*current);
std::shared_ptr<WhitelistManager> whitelistManager = std::make_shared<WhitelistManager>(*currentWhitelist);
if (!banManager->EnsureBanFilesExist())
{
return false;
}
if (!whitelistManager->EnsureWhitelistFileExists())
{
return false;
}
if (!banManager->Reload())
{
return false;
}
if (!whitelistManager->Reload())
{
return false;
}
PublishBanManagerSnapshot(banManager);
PublishWhitelistManagerSnapshot(whitelistManager);
return true;
}
bool ReloadWhitelist()
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
const auto current = GetWhitelistManagerSnapshot();
if (current == nullptr)
{
return false;
}
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
if (!whitelistManager->EnsureWhitelistFileExists())
{
return false;
}
if (!whitelistManager->Reload())
{
return false;
}
PublishWhitelistManagerSnapshot(whitelistManager);
return true;
}
bool IsInitialized()
{
return GetBanManagerSnapshot() != nullptr && GetWhitelistManagerSnapshot() != nullptr;
}
bool IsWhitelistEnabled()
{
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
return g_accessState.whitelistEnabled;
}
void SetWhitelistEnabled(bool enabled)
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
std::lock_guard<std::mutex> stateLock(g_accessState.stateLock);
g_accessState.whitelistEnabled = enabled;
}
bool IsPlayerBanned(PlayerUID xuid)
{
const std::string formatted = FormatXuid(xuid);
if (formatted.empty())
{
return false;
}
std::shared_ptr<BanManager> banManager = GetBanManagerSnapshot();
return (banManager != nullptr) ? banManager->IsPlayerBannedByXuid(formatted) : false;
}
bool IsIpBanned(const std::string &ip)
{
std::shared_ptr<BanManager> banManager = GetBanManagerSnapshot();
return (banManager != nullptr) ? banManager->IsIpBanned(ip) : false;
}
bool IsPlayerWhitelisted(PlayerUID xuid)
{
const std::string formatted = FormatXuid(xuid);
if (formatted.empty())
{
return false;
}
std::shared_ptr<WhitelistManager> whitelistManager = GetWhitelistManagerSnapshot();
return (whitelistManager != nullptr) ? whitelistManager->IsPlayerWhitelistedByXuid(formatted) : false;
}
bool AddPlayerBan(PlayerUID xuid, const std::string &name, const BanMetadata &metadata)
{
const std::string formatted = FormatXuid(xuid);
if (formatted.empty())
{
return false;
}
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
std::shared_ptr<BanManager> current = GetBanManagerSnapshot();
if (current == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(*current);
BannedPlayerEntry entry;
entry.xuid = formatted;
entry.name = name;
entry.metadata = metadata;
if (!banManager->AddPlayerBan(entry))
{
return false;
}
PublishBanManagerSnapshot(banManager);
return true;
}
bool AddIpBan(const std::string &ip, const BanMetadata &metadata)
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
std::shared_ptr<BanManager> current = GetBanManagerSnapshot();
if (current == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(*current);
BannedIpEntry entry;
entry.ip = ip;
entry.metadata = metadata;
if (!banManager->AddIpBan(entry))
{
return false;
}
PublishBanManagerSnapshot(banManager);
return true;
}
bool RemovePlayerBan(PlayerUID xuid)
{
const std::string formatted = FormatXuid(xuid);
if (formatted.empty())
{
return false;
}
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
std::shared_ptr<BanManager> current = GetBanManagerSnapshot();
if (current == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(*current);
if (!banManager->RemovePlayerBanByXuid(formatted))
{
return false;
}
PublishBanManagerSnapshot(banManager);
return true;
}
bool RemoveIpBan(const std::string &ip)
{
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
std::shared_ptr<BanManager> current = GetBanManagerSnapshot();
if (current == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = std::make_shared<BanManager>(*current);
if (!banManager->RemoveIpBan(ip))
{
return false;
}
PublishBanManagerSnapshot(banManager);
return true;
}
bool AddWhitelistedPlayer(PlayerUID xuid, const std::string &name, const WhitelistMetadata &metadata)
{
const auto formatted = FormatXuid(xuid);
if (formatted.empty())
{
return false;
}
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
const auto current = GetWhitelistManagerSnapshot();
if (current == nullptr)
{
return false;
}
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
const WhitelistedPlayerEntry entry = { formatted, name, metadata };
if (!whitelistManager->AddPlayer(entry))
{
return false;
}
PublishWhitelistManagerSnapshot(whitelistManager);
return true;
}
bool RemoveWhitelistedPlayer(PlayerUID xuid)
{
const auto formatted = FormatXuid(xuid);
if (formatted.empty())
{
return false;
}
std::lock_guard<std::mutex> writeLock(g_accessState.writeLock);
const auto current = GetWhitelistManagerSnapshot();
if (current == nullptr)
{
return false;
}
auto whitelistManager = std::make_shared<WhitelistManager>(*current);
if (!whitelistManager->RemovePlayerByXuid(formatted))
{
return false;
}
PublishWhitelistManagerSnapshot(whitelistManager);
return true;
}
bool SnapshotBannedPlayers(std::vector<BannedPlayerEntry> *outEntries)
{
if (outEntries == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = GetBanManagerSnapshot();
if (banManager == nullptr)
{
outEntries->clear();
return false;
}
return banManager->SnapshotBannedPlayers(outEntries);
}
bool SnapshotBannedIps(std::vector<BannedIpEntry> *outEntries)
{
if (outEntries == nullptr)
{
return false;
}
std::shared_ptr<BanManager> banManager = GetBanManagerSnapshot();
if (banManager == nullptr)
{
outEntries->clear();
return false;
}
return banManager->SnapshotBannedIps(outEntries);
}
bool SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries)
{
if (outEntries == nullptr)
{
return false;
}
const auto whitelistManager = GetWhitelistManagerSnapshot();
if (whitelistManager == nullptr)
{
outEntries->clear();
return false;
}
return whitelistManager->SnapshotWhitelistedPlayers(outEntries);
}
}
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "BanManager.h"
#include "WhitelistManager.h"
namespace ServerRuntime
{
/**
* A frontend that will be general-purpose, assuming the implementation of whitelists and ops in the future.
*/
namespace Access
{
bool Initialize(const std::string &baseDirectory = ".", bool whitelistEnabled = false);
void Shutdown();
bool Reload();
bool ReloadWhitelist();
bool IsInitialized();
bool IsWhitelistEnabled();
void SetWhitelistEnabled(bool enabled);
bool IsPlayerBanned(PlayerUID xuid);
bool IsIpBanned(const std::string &ip);
bool IsPlayerWhitelisted(PlayerUID xuid);
bool AddPlayerBan(PlayerUID xuid, const std::string &name, const BanMetadata &metadata);
bool AddIpBan(const std::string &ip, const BanMetadata &metadata);
bool RemovePlayerBan(PlayerUID xuid);
bool RemoveIpBan(const std::string &ip);
bool AddWhitelistedPlayer(PlayerUID xuid, const std::string &name, const WhitelistMetadata &metadata);
bool RemoveWhitelistedPlayer(PlayerUID xuid);
/**
* Copies the current cached player bans for inspection or command output
* 現在のプレイヤーBAN一覧を複製取得
*/
bool SnapshotBannedPlayers(std::vector<BannedPlayerEntry> *outEntries);
/**
* Copies the current cached IP bans for inspection or command output
* 現在のIP BAN一覧を複製取得
*/
bool SnapshotBannedIps(std::vector<BannedIpEntry> *outEntries);
bool SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries);
std::string FormatXuid(PlayerUID xuid);
bool TryParseXuid(const std::string &text, PlayerUID *outXuid);
}
}

View File

@@ -0,0 +1,631 @@
#include "stdafx.h"
#include "BanManager.h"
#include "..\Common\AccessStorageUtils.h"
#include "..\Common\FileUtils.h"
#include "..\Common\NetworkUtils.h"
#include "..\Common\StringUtils.h"
#include "..\ServerLogger.h"
#include "..\vendor\nlohmann\json.hpp"
#include <algorithm>
#include <stdio.h>
namespace ServerRuntime
{
namespace Access
{
using OrderedJson = nlohmann::ordered_json;
namespace
{
static const char *kBannedPlayersFileName = "banned-players.json";
static const char *kBannedIpsFileName = "banned-ips.json";
static bool TryParseUtcTimestamp(const std::string &text, unsigned long long *outFileTime)
{
if (outFileTime == nullptr)
{
return false;
}
std::string trimmed = StringUtils::TrimAscii(text);
if (trimmed.empty())
{
return false;
}
unsigned year = 0;
unsigned month = 0;
unsigned day = 0;
unsigned hour = 0;
unsigned minute = 0;
unsigned second = 0;
if (sscanf_s(trimmed.c_str(), "%4u-%2u-%2uT%2u:%2u:%2uZ", &year, &month, &day, &hour, &minute, &second) != 6)
{
return false;
}
SYSTEMTIME utc = {};
utc.wYear = (WORD)year;
utc.wMonth = (WORD)month;
utc.wDay = (WORD)day;
utc.wHour = (WORD)hour;
utc.wMinute = (WORD)minute;
utc.wSecond = (WORD)second;
FILETIME fileTime = {};
if (!SystemTimeToFileTime(&utc, &fileTime))
{
return false;
}
ULARGE_INTEGER value = {};
value.LowPart = fileTime.dwLowDateTime;
value.HighPart = fileTime.dwHighDateTime;
*outFileTime = value.QuadPart;
return true;
}
static bool IsMetadataExpired(const BanMetadata &metadata, unsigned long long nowFileTime)
{
if (metadata.expires.empty())
{
return false;
}
unsigned long long expiresFileTime = 0;
if (!TryParseUtcTimestamp(metadata.expires, &expiresFileTime))
{
// Keep malformed metadata active instead of silently unbanning a player or address.
return false;
}
return expiresFileTime <= nowFileTime;
}
}
BanManager::BanManager(const std::string &baseDirectory)
: m_baseDirectory(baseDirectory.empty() ? "." : baseDirectory)
{
}
bool BanManager::EnsureBanFilesExist() const
{
const std::string playersPath = GetBannedPlayersFilePath();
const std::string ipsPath = GetBannedIpsFilePath();
const bool playersOk = AccessStorageUtils::EnsureJsonListFileExists(playersPath);
const bool ipsOk = AccessStorageUtils::EnsureJsonListFileExists(ipsPath);
if (!playersOk)
{
LogErrorf("access", "failed to create %s", playersPath.c_str());
}
if (!ipsOk)
{
LogErrorf("access", "failed to create %s", ipsPath.c_str());
}
return playersOk && ipsOk;
}
bool BanManager::Reload()
{
std::vector<BannedPlayerEntry> players;
std::vector<BannedIpEntry> ips;
if (!LoadPlayers(&players))
{
return false;
}
if (!LoadIps(&ips))
{
return false;
}
m_bannedPlayers.swap(players);
m_bannedIps.swap(ips);
return true;
}
bool BanManager::Save() const
{
std::vector<BannedPlayerEntry> players;
std::vector<BannedIpEntry> ips;
return SnapshotBannedPlayers(&players) &&
SnapshotBannedIps(&ips) &&
SavePlayers(players) &&
SaveIps(ips);
}
bool BanManager::LoadPlayers(std::vector<BannedPlayerEntry> *outEntries) const
{
if (outEntries == nullptr)
{
return false;
}
outEntries->clear();
std::string text;
const std::string path = GetBannedPlayersFilePath();
if (!FileUtils::ReadTextFile(path, &text))
{
LogErrorf("access", "failed to read %s", path.c_str());
return false;
}
if (text.empty())
{
text = "[]";
}
OrderedJson root;
try
{
// Strip an optional UTF-8 BOM because some editors prepend it when rewriting JSON files.
root = OrderedJson::parse(StringUtils::StripUtf8Bom(text));
}
catch (const nlohmann::json::exception &e)
{
LogErrorf("access", "failed to parse %s: %s", path.c_str(), e.what());
return false;
}
if (!root.is_array())
{
LogErrorf("access", "failed to parse %s: root json value is not an array", path.c_str());
return false;
}
const unsigned long long nowFileTime = FileUtils::GetCurrentUtcFileTime();
for (const auto &object : root)
{
if (!object.is_object())
{
LogWarnf("access", "skipping banned player entry that is not an object in %s", path.c_str());
continue;
}
std::string rawXuid;
if (!AccessStorageUtils::TryGetStringField(object, "xuid", &rawXuid))
{
LogWarnf("access", "skipping banned player entry without xuid in %s", path.c_str());
continue;
}
BannedPlayerEntry entry;
entry.xuid = AccessStorageUtils::NormalizeXuid(rawXuid);
if (entry.xuid.empty())
{
LogWarnf("access", "skipping banned player entry with empty xuid in %s", path.c_str());
continue;
}
AccessStorageUtils::TryGetStringField(object, "name", &entry.name);
AccessStorageUtils::TryGetStringField(object, "created", &entry.metadata.created);
AccessStorageUtils::TryGetStringField(object, "source", &entry.metadata.source);
AccessStorageUtils::TryGetStringField(object, "expires", &entry.metadata.expires);
AccessStorageUtils::TryGetStringField(object, "reason", &entry.metadata.reason);
NormalizeMetadata(&entry.metadata);
// Ignore entries that already expired before reload so the in-memory cache starts from the active set.
if (IsMetadataExpired(entry.metadata, nowFileTime))
{
continue;
}
outEntries->push_back(entry);
}
return true;
}
bool BanManager::LoadIps(std::vector<BannedIpEntry> *outEntries) const
{
if (outEntries == nullptr)
{
return false;
}
outEntries->clear();
std::string text;
const std::string path = GetBannedIpsFilePath();
if (!FileUtils::ReadTextFile(path, &text))
{
LogErrorf("access", "failed to read %s", path.c_str());
return false;
}
if (text.empty())
{
text = "[]";
}
OrderedJson root;
try
{
// Strip an optional UTF-8 BOM because some editors prepend it when rewriting JSON files.
root = OrderedJson::parse(StringUtils::StripUtf8Bom(text));
}
catch (const nlohmann::json::exception &e)
{
LogErrorf("access", "failed to parse %s: %s", path.c_str(), e.what());
return false;
}
if (!root.is_array())
{
LogErrorf("access", "failed to parse %s: root json value is not an array", path.c_str());
return false;
}
const unsigned long long nowFileTime = FileUtils::GetCurrentUtcFileTime();
for (const auto &object : root)
{
if (!object.is_object())
{
LogWarnf("access", "skipping banned ip entry that is not an object in %s", path.c_str());
continue;
}
std::string rawIp;
if (!AccessStorageUtils::TryGetStringField(object, "ip", &rawIp))
{
LogWarnf("access", "skipping banned ip entry without ip in %s", path.c_str());
continue;
}
BannedIpEntry entry;
entry.ip = NetworkUtils::NormalizeIpToken(rawIp);
if (entry.ip.empty())
{
LogWarnf("access", "skipping banned ip entry with empty ip in %s", path.c_str());
continue;
}
AccessStorageUtils::TryGetStringField(object, "created", &entry.metadata.created);
AccessStorageUtils::TryGetStringField(object, "source", &entry.metadata.source);
AccessStorageUtils::TryGetStringField(object, "expires", &entry.metadata.expires);
AccessStorageUtils::TryGetStringField(object, "reason", &entry.metadata.reason);
NormalizeMetadata(&entry.metadata);
// Ignore entries that already expired before reload so the in-memory cache starts from the active set.
if (IsMetadataExpired(entry.metadata, nowFileTime))
{
continue;
}
outEntries->push_back(entry);
}
return true;
}
bool BanManager::SavePlayers(const std::vector<BannedPlayerEntry> &entries) const
{
OrderedJson root = OrderedJson::array();
for (const auto &entry : entries)
{
OrderedJson object = OrderedJson::object();
object["xuid"] = AccessStorageUtils::NormalizeXuid(entry.xuid);
object["name"] = entry.name;
object["created"] = entry.metadata.created;
object["source"] = entry.metadata.source;
object["expires"] = entry.metadata.expires;
object["reason"] = entry.metadata.reason;
root.push_back(object);
}
const std::string path = GetBannedPlayersFilePath();
const std::string json = root.empty() ? std::string("[]\n") : (root.dump(2) + "\n");
if (!FileUtils::WriteTextFileAtomic(path, json))
{
LogErrorf("access", "failed to write %s", path.c_str());
return false;
}
return true;
}
bool BanManager::SaveIps(const std::vector<BannedIpEntry> &entries) const
{
OrderedJson root = OrderedJson::array();
for (const auto &entry : entries)
{
OrderedJson object = OrderedJson::object();
object["ip"] = NetworkUtils::NormalizeIpToken(entry.ip);
object["created"] = entry.metadata.created;
object["source"] = entry.metadata.source;
object["expires"] = entry.metadata.expires;
object["reason"] = entry.metadata.reason;
root.push_back(object);
}
const std::string path = GetBannedIpsFilePath();
const std::string json = root.empty() ? std::string("[]\n") : (root.dump(2) + "\n");
if (!FileUtils::WriteTextFileAtomic(path, json))
{
LogErrorf("access", "failed to write %s", path.c_str());
return false;
}
return true;
}
const std::vector<BannedPlayerEntry> &BanManager::GetBannedPlayers() const
{
return m_bannedPlayers;
}
const std::vector<BannedIpEntry> &BanManager::GetBannedIps() const
{
return m_bannedIps;
}
bool BanManager::SnapshotBannedPlayers(std::vector<BannedPlayerEntry> *outEntries) const
{
if (outEntries == nullptr)
{
return false;
}
outEntries->clear();
outEntries->reserve(m_bannedPlayers.size());
const unsigned long long nowFileTime = FileUtils::GetCurrentUtcFileTime();
for (const auto &entry : m_bannedPlayers)
{
if (!IsMetadataExpired(entry.metadata, nowFileTime))
{
outEntries->push_back(entry);
}
}
return true;
}
bool BanManager::SnapshotBannedIps(std::vector<BannedIpEntry> *outEntries) const
{
if (outEntries == nullptr)
{
return false;
}
outEntries->clear();
outEntries->reserve(m_bannedIps.size());
const unsigned long long nowFileTime = FileUtils::GetCurrentUtcFileTime();
for (const auto &entry : m_bannedIps)
{
if (!IsMetadataExpired(entry.metadata, nowFileTime))
{
outEntries->push_back(entry);
}
}
return true;
}
bool BanManager::IsPlayerBannedByXuid(const std::string &xuid) const
{
const std::string normalized = AccessStorageUtils::NormalizeXuid(xuid);
if (normalized.empty())
{
return false;
}
const unsigned long long nowFileTime = FileUtils::GetCurrentUtcFileTime();
return std::any_of(
m_bannedPlayers.begin(),
m_bannedPlayers.end(),
[&normalized, nowFileTime](const BannedPlayerEntry &entry)
{
return entry.xuid == normalized && !IsMetadataExpired(entry.metadata, nowFileTime);
});
}
bool BanManager::IsIpBanned(const std::string &ip) const
{
const std::string normalized = NetworkUtils::NormalizeIpToken(ip);
if (normalized.empty())
{
return false;
}
const unsigned long long nowFileTime = FileUtils::GetCurrentUtcFileTime();
return std::any_of(
m_bannedIps.begin(),
m_bannedIps.end(),
[&normalized, nowFileTime](const BannedIpEntry &entry)
{
return entry.ip == normalized && !IsMetadataExpired(entry.metadata, nowFileTime);
});
}
bool BanManager::AddPlayerBan(const BannedPlayerEntry &entry)
{
std::vector<BannedPlayerEntry> updatedEntries;
if (!SnapshotBannedPlayers(&updatedEntries))
{
return false;
}
BannedPlayerEntry normalized = entry;
normalized.xuid = AccessStorageUtils::NormalizeXuid(normalized.xuid);
NormalizeMetadata(&normalized.metadata);
if (normalized.xuid.empty())
{
return false;
}
const auto existing = std::find_if(
updatedEntries.begin(),
updatedEntries.end(),
[&normalized](const BannedPlayerEntry &candidate)
{
return candidate.xuid == normalized.xuid;
});
if (existing != updatedEntries.end())
{
// Update the existing entry in place so the stored list remains unique by canonical XUID.
*existing = normalized;
if (!SavePlayers(updatedEntries))
{
return false;
}
m_bannedPlayers.swap(updatedEntries);
return true;
}
updatedEntries.push_back(normalized);
if (!SavePlayers(updatedEntries))
{
return false;
}
m_bannedPlayers.swap(updatedEntries);
return true;
}
bool BanManager::AddIpBan(const BannedIpEntry &entry)
{
std::vector<BannedIpEntry> updatedEntries;
if (!SnapshotBannedIps(&updatedEntries))
{
return false;
}
BannedIpEntry normalized = entry;
normalized.ip = NetworkUtils::NormalizeIpToken(normalized.ip);
NormalizeMetadata(&normalized.metadata);
if (normalized.ip.empty())
{
return false;
}
const auto existing = std::find_if(
updatedEntries.begin(),
updatedEntries.end(),
[&normalized](const BannedIpEntry &candidate)
{
return candidate.ip == normalized.ip;
});
if (existing != updatedEntries.end())
{
// Update the existing entry in place so the stored list remains unique by normalized IP.
*existing = normalized;
if (!SaveIps(updatedEntries))
{
return false;
}
m_bannedIps.swap(updatedEntries);
return true;
}
updatedEntries.push_back(normalized);
if (!SaveIps(updatedEntries))
{
return false;
}
m_bannedIps.swap(updatedEntries);
return true;
}
bool BanManager::RemovePlayerBanByXuid(const std::string &xuid)
{
const std::string normalized = AccessStorageUtils::NormalizeXuid(xuid);
if (normalized.empty())
{
return false;
}
std::vector<BannedPlayerEntry> updatedEntries;
if (!SnapshotBannedPlayers(&updatedEntries))
{
return false;
}
size_t oldSize = updatedEntries.size();
updatedEntries.erase(
std::remove_if(
updatedEntries.begin(),
updatedEntries.end(),
[&normalized](const BannedPlayerEntry &entry) { return entry.xuid == normalized; }),
updatedEntries.end());
if (updatedEntries.size() == oldSize)
{
return false;
}
if (!SavePlayers(updatedEntries))
{
return false;
}
m_bannedPlayers.swap(updatedEntries);
return true;
}
bool BanManager::RemoveIpBan(const std::string &ip)
{
const std::string normalized = NetworkUtils::NormalizeIpToken(ip);
if (normalized.empty())
{
return false;
}
std::vector<BannedIpEntry> updatedEntries;
if (!SnapshotBannedIps(&updatedEntries))
{
return false;
}
size_t oldSize = updatedEntries.size();
updatedEntries.erase(
std::remove_if(
updatedEntries.begin(),
updatedEntries.end(),
[&normalized](const BannedIpEntry &entry) { return entry.ip == normalized; }),
updatedEntries.end());
if (updatedEntries.size() == oldSize)
{
return false;
}
if (!SaveIps(updatedEntries))
{
return false;
}
m_bannedIps.swap(updatedEntries);
return true;
}
std::string BanManager::GetBannedPlayersFilePath() const
{
return BuildPath(kBannedPlayersFileName);
}
std::string BanManager::GetBannedIpsFilePath() const
{
return BuildPath(kBannedIpsFileName);
}
BanMetadata BanManager::BuildDefaultMetadata(const char *source)
{
BanMetadata metadata;
metadata.created = StringUtils::GetCurrentUtcTimestampIso8601();
metadata.source = (source != nullptr) ? source : "Server";
metadata.expires = "";
metadata.reason = "";
return metadata;
}
void BanManager::NormalizeMetadata(BanMetadata *metadata)
{
if (metadata == nullptr)
{
return;
}
metadata->created = StringUtils::TrimAscii(metadata->created);
metadata->source = StringUtils::TrimAscii(metadata->source);
metadata->expires = StringUtils::TrimAscii(metadata->expires);
metadata->reason = StringUtils::TrimAscii(metadata->reason);
}
std::string BanManager::BuildPath(const char *fileName) const
{
return AccessStorageUtils::BuildPathFromBaseDirectory(m_baseDirectory, fileName);
}
}
}

View File

@@ -0,0 +1,106 @@
#pragma once
#include <string>
#include <vector>
namespace ServerRuntime
{
namespace Access
{
/**
* Information shared with player bans and IP bans
* プレイヤーBANとIP BANで共有する情報
*/
struct BanMetadata
{
std::string created;
std::string source;
std::string expires;
std::string reason;
};
struct BannedPlayerEntry
{
std::string xuid;
std::string name;
BanMetadata metadata;
};
struct BannedIpEntry
{
std::string ip;
BanMetadata metadata;
};
/**
* Dedicated server BAN file manager.
*
* Files:
* - banned-players.json
* - banned-ips.json
*
* This class only handles storage/caching.
* Connection-time hooks are wired separately.
*/
class BanManager
{
public:
/**
* **Create Ban Manager**
*
* Binds the manager to the directory that stores the dedicated-server access files
* Dedicated Server のアクセスファイル配置先を設定する
*/
explicit BanManager(const std::string &baseDirectory = ".");
/**
* Creates empty JSON array files when the dedicated server starts without persisted access data
* BANファイルが無い初回起動時に空JSONを用意する
*/
bool EnsureBanFilesExist() const;
bool Reload();
bool Save() const;
bool LoadPlayers(std::vector<BannedPlayerEntry> *outEntries) const;
bool LoadIps(std::vector<BannedIpEntry> *outEntries) const;
bool SavePlayers(const std::vector<BannedPlayerEntry> &entries) const;
bool SaveIps(const std::vector<BannedIpEntry> &entries) const;
const std::vector<BannedPlayerEntry> &GetBannedPlayers() const;
const std::vector<BannedIpEntry> &GetBannedIps() const;
/**
* Copies only currently active player BAN entries so expired metadata does not leak into command output
* 期限切れを除いた有効なプレイヤーBAN一覧を複製取得する
*/
bool SnapshotBannedPlayers(std::vector<BannedPlayerEntry> *outEntries) const;
/**
* Copies only currently active IP BAN entries so expired metadata does not leak into command output
* 期限切れを除いた有効なIP BAN一覧を複製取得する
*/
bool SnapshotBannedIps(std::vector<BannedIpEntry> *outEntries) const;
bool IsPlayerBannedByXuid(const std::string &xuid) const;
bool IsIpBanned(const std::string &ip) const;
bool AddPlayerBan(const BannedPlayerEntry &entry);
bool AddIpBan(const BannedIpEntry &entry);
bool RemovePlayerBanByXuid(const std::string &xuid);
bool RemoveIpBan(const std::string &ip);
std::string GetBannedPlayersFilePath() const;
std::string GetBannedIpsFilePath() const;
static BanMetadata BuildDefaultMetadata(const char *source = "Server");
private:
static void NormalizeMetadata(BanMetadata *metadata);
std::string BuildPath(const char *fileName) const;
private:
std::string m_baseDirectory;
std::vector<BannedPlayerEntry> m_bannedPlayers;
std::vector<BannedIpEntry> m_bannedIps;
};
}
}

View File

@@ -0,0 +1,297 @@
#include "stdafx.h"
#include "WhitelistManager.h"
#include "..\Common\AccessStorageUtils.h"
#include "..\Common\FileUtils.h"
#include "..\Common\StringUtils.h"
#include "..\ServerLogger.h"
#include "..\vendor\nlohmann\json.hpp"
#include <algorithm>
namespace ServerRuntime
{
namespace Access
{
using OrderedJson = nlohmann::ordered_json;
namespace
{
static const char *kWhitelistFileName = "whitelist.json";
}
WhitelistManager::WhitelistManager(const std::string &baseDirectory)
: m_baseDirectory(baseDirectory.empty() ? "." : baseDirectory)
{
}
bool WhitelistManager::EnsureWhitelistFileExists() const
{
const std::string path = GetWhitelistFilePath();
if (!AccessStorageUtils::EnsureJsonListFileExists(path))
{
LogErrorf("access", "failed to create %s", path.c_str());
return false;
}
return true;
}
bool WhitelistManager::Reload()
{
std::vector<WhitelistedPlayerEntry> players;
if (!LoadPlayers(&players))
{
return false;
}
m_whitelistedPlayers.swap(players);
return true;
}
bool WhitelistManager::Save() const
{
std::vector<WhitelistedPlayerEntry> players;
return SnapshotWhitelistedPlayers(&players) && SavePlayers(players);
}
bool WhitelistManager::LoadPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const
{
if (outEntries == nullptr)
{
return false;
}
outEntries->clear();
std::string text;
const std::string path = GetWhitelistFilePath();
if (!FileUtils::ReadTextFile(path, &text))
{
LogErrorf("access", "failed to read %s", path.c_str());
return false;
}
if (text.empty())
{
text = "[]";
}
OrderedJson root;
try
{
root = OrderedJson::parse(StringUtils::StripUtf8Bom(text));
}
catch (const nlohmann::json::exception &e)
{
LogErrorf("access", "failed to parse %s: %s", path.c_str(), e.what());
return false;
}
if (!root.is_array())
{
LogErrorf("access", "failed to parse %s: root json value is not an array", path.c_str());
return false;
}
for (const auto &object : root)
{
if (!object.is_object())
{
LogWarnf("access", "skipping whitelist entry that is not an object in %s", path.c_str());
continue;
}
std::string rawXuid;
if (!AccessStorageUtils::TryGetStringField(object, "xuid", &rawXuid))
{
LogWarnf("access", "skipping whitelist entry without xuid in %s", path.c_str());
continue;
}
WhitelistedPlayerEntry entry;
entry.xuid = AccessStorageUtils::NormalizeXuid(rawXuid);
if (entry.xuid.empty())
{
LogWarnf("access", "skipping whitelist entry with empty xuid in %s", path.c_str());
continue;
}
AccessStorageUtils::TryGetStringField(object, "name", &entry.name);
AccessStorageUtils::TryGetStringField(object, "created", &entry.metadata.created);
AccessStorageUtils::TryGetStringField(object, "source", &entry.metadata.source);
NormalizeMetadata(&entry.metadata);
outEntries->push_back(entry);
}
return true;
}
bool WhitelistManager::SavePlayers(const std::vector<WhitelistedPlayerEntry> &entries) const
{
OrderedJson root = OrderedJson::array();
for (const auto &entry : entries)
{
OrderedJson object = OrderedJson::object();
object["xuid"] = AccessStorageUtils::NormalizeXuid(entry.xuid);
object["name"] = entry.name;
object["created"] = entry.metadata.created;
object["source"] = entry.metadata.source;
root.push_back(object);
}
const std::string path = GetWhitelistFilePath();
const std::string json = root.empty() ? std::string("[]\n") : (root.dump(2) + "\n");
if (!FileUtils::WriteTextFileAtomic(path, json))
{
LogErrorf("access", "failed to write %s", path.c_str());
return false;
}
return true;
}
const std::vector<WhitelistedPlayerEntry> &WhitelistManager::GetWhitelistedPlayers() const
{
return m_whitelistedPlayers;
}
bool WhitelistManager::SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const
{
if (outEntries == nullptr)
{
return false;
}
*outEntries = m_whitelistedPlayers;
return true;
}
bool WhitelistManager::IsPlayerWhitelistedByXuid(const std::string &xuid) const
{
const auto normalized = AccessStorageUtils::NormalizeXuid(xuid);
if (normalized.empty())
{
return false;
}
return std::any_of(
m_whitelistedPlayers.begin(),
m_whitelistedPlayers.end(),
[&normalized](const WhitelistedPlayerEntry &entry)
{
return entry.xuid == normalized;
});
}
bool WhitelistManager::AddPlayer(const WhitelistedPlayerEntry &entry)
{
std::vector<WhitelistedPlayerEntry> updatedEntries;
if (!SnapshotWhitelistedPlayers(&updatedEntries))
{
return false;
}
auto normalized = entry;
normalized.xuid = AccessStorageUtils::NormalizeXuid(normalized.xuid);
NormalizeMetadata(&normalized.metadata);
if (normalized.xuid.empty())
{
return false;
}
const auto existing = std::find_if(
updatedEntries.begin(),
updatedEntries.end(),
[&normalized](const WhitelistedPlayerEntry &candidate)
{
return candidate.xuid == normalized.xuid;
});
if (existing != updatedEntries.end())
{
*existing = normalized;
if (!SavePlayers(updatedEntries))
{
return false;
}
m_whitelistedPlayers.swap(updatedEntries);
return true;
}
updatedEntries.push_back(normalized);
if (!SavePlayers(updatedEntries))
{
return false;
}
m_whitelistedPlayers.swap(updatedEntries);
return true;
}
bool WhitelistManager::RemovePlayerByXuid(const std::string &xuid)
{
const auto normalized = AccessStorageUtils::NormalizeXuid(xuid);
if (normalized.empty())
{
return false;
}
std::vector<WhitelistedPlayerEntry> updatedEntries;
if (!SnapshotWhitelistedPlayers(&updatedEntries))
{
return false;
}
const auto oldSize = updatedEntries.size();
updatedEntries.erase(
std::remove_if(
updatedEntries.begin(),
updatedEntries.end(),
[&normalized](const WhitelistedPlayerEntry &entry) { return entry.xuid == normalized; }),
updatedEntries.end());
if (updatedEntries.size() == oldSize)
{
return false;
}
if (!SavePlayers(updatedEntries))
{
return false;
}
m_whitelistedPlayers.swap(updatedEntries);
return true;
}
std::string WhitelistManager::GetWhitelistFilePath() const
{
return BuildPath(kWhitelistFileName);
}
WhitelistMetadata WhitelistManager::BuildDefaultMetadata(const char *source)
{
WhitelistMetadata metadata;
metadata.created = StringUtils::GetCurrentUtcTimestampIso8601();
metadata.source = (source != nullptr) ? source : "Server";
return metadata;
}
void WhitelistManager::NormalizeMetadata(WhitelistMetadata *metadata)
{
if (metadata == nullptr)
{
return;
}
metadata->created = StringUtils::TrimAscii(metadata->created);
metadata->source = StringUtils::TrimAscii(metadata->source);
}
std::string WhitelistManager::BuildPath(const char *fileName) const
{
return AccessStorageUtils::BuildPathFromBaseDirectory(m_baseDirectory, fileName);
}
}
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <string>
#include <vector>
namespace ServerRuntime
{
namespace Access
{
struct WhitelistMetadata
{
std::string created;
std::string source;
};
struct WhitelistedPlayerEntry
{
std::string xuid;
std::string name;
WhitelistMetadata metadata;
};
/**
* whitelist manager
*
* Files:
* - whitelist.json
*
* Stores and normalizes XUID-based allow entries.
*/
class WhitelistManager
{
public:
explicit WhitelistManager(const std::string &baseDirectory = ".");
bool EnsureWhitelistFileExists() const;
bool Reload();
bool Save() const;
bool LoadPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const;
bool SavePlayers(const std::vector<WhitelistedPlayerEntry> &entries) const;
const std::vector<WhitelistedPlayerEntry> &GetWhitelistedPlayers() const;
bool SnapshotWhitelistedPlayers(std::vector<WhitelistedPlayerEntry> *outEntries) const;
bool IsPlayerWhitelistedByXuid(const std::string &xuid) const;
bool AddPlayer(const WhitelistedPlayerEntry &entry);
bool RemovePlayerByXuid(const std::string &xuid);
std::string GetWhitelistFilePath() const;
static WhitelistMetadata BuildDefaultMetadata(const char *source = "Server");
private:
static void NormalizeMetadata(WhitelistMetadata *metadata);
std::string BuildPath(const char *fileName) const;
private:
std::string m_baseDirectory;
std::vector<WhitelistedPlayerEntry> m_whitelistedPlayers;
};
}
}

View File

@@ -0,0 +1,105 @@
#pragma once
#include "FileUtils.h"
#include "StringUtils.h"
#include "..\vendor\nlohmann\json.hpp"
#include <stdio.h>
namespace ServerRuntime
{
namespace AccessStorageUtils
{
inline bool IsRegularFile(const std::string &path)
{
const std::wstring widePath = StringUtils::Utf8ToWide(path);
if (widePath.empty())
{
return false;
}
const DWORD attributes = GetFileAttributesW(widePath.c_str());
return (attributes != INVALID_FILE_ATTRIBUTES) && ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
}
inline bool EnsureJsonListFileExists(const std::string &path)
{
return IsRegularFile(path) || FileUtils::WriteTextFileAtomic(path, "[]\n");
}
inline bool TryGetStringField(const nlohmann::ordered_json &object, const char *key, std::string *outValue)
{
if (key == nullptr || outValue == nullptr || !object.is_object())
{
return false;
}
const auto it = object.find(key);
if (it == object.end() || !it->is_string())
{
return false;
}
*outValue = it->get<std::string>();
return true;
}
inline std::string NormalizeXuid(const std::string &xuid)
{
const std::string trimmed = StringUtils::TrimAscii(xuid);
if (trimmed.empty())
{
return "";
}
unsigned long long numericXuid = 0;
if (StringUtils::TryParseUnsignedLongLong(trimmed, &numericXuid))
{
if (numericXuid == 0ULL)
{
return "";
}
char buffer[32] = {};
sprintf_s(buffer, sizeof(buffer), "0x%016llx", numericXuid);
return buffer;
}
return StringUtils::ToLowerAscii(trimmed);
}
inline std::string BuildPathFromBaseDirectory(const std::string &baseDirectory, const char *fileName)
{
if (fileName == nullptr || fileName[0] == 0)
{
return "";
}
const std::wstring wideFileName = StringUtils::Utf8ToWide(fileName);
if (wideFileName.empty())
{
return "";
}
if (baseDirectory.empty() || baseDirectory == ".")
{
return StringUtils::WideToUtf8(wideFileName);
}
const std::wstring wideBaseDirectory = StringUtils::Utf8ToWide(baseDirectory);
if (wideBaseDirectory.empty())
{
return StringUtils::WideToUtf8(wideFileName);
}
const wchar_t last = wideBaseDirectory[wideBaseDirectory.size() - 1];
if (last == L'\\' || last == L'/')
{
return StringUtils::WideToUtf8(wideBaseDirectory + wideFileName);
}
return StringUtils::WideToUtf8(wideBaseDirectory + L"\\" + wideFileName);
}
}
}

View File

@@ -0,0 +1,146 @@
#include "stdafx.h"
#include "FileUtils.h"
#include "StringUtils.h"
#include <io.h>
#include <stdio.h>
namespace ServerRuntime
{
namespace FileUtils
{
namespace
{
static std::wstring ToWidePath(const std::string &filePath)
{
return StringUtils::Utf8ToWide(filePath);
}
}
unsigned long long GetCurrentUtcFileTime()
{
FILETIME now = {};
GetSystemTimeAsFileTime(&now);
ULARGE_INTEGER value = {};
value.LowPart = now.dwLowDateTime;
value.HighPart = now.dwHighDateTime;
return value.QuadPart;
}
bool ReadTextFile(const std::string &filePath, std::string *outText)
{
if (outText == nullptr)
{
return false;
}
outText->clear();
const std::wstring widePath = ToWidePath(filePath);
if (widePath.empty())
{
return false;
}
FILE *inFile = nullptr;
if (_wfopen_s(&inFile, widePath.c_str(), L"rb") != 0 || inFile == nullptr)
{
return false;
}
if (fseek(inFile, 0, SEEK_END) != 0)
{
fclose(inFile);
return false;
}
long fileSize = ftell(inFile);
if (fileSize < 0)
{
fclose(inFile);
return false;
}
if (fseek(inFile, 0, SEEK_SET) != 0)
{
fclose(inFile);
return false;
}
if (fileSize == 0)
{
fclose(inFile);
return true;
}
outText->resize((size_t)fileSize);
size_t bytesRead = fread(&(*outText)[0], 1, (size_t)fileSize, inFile);
fclose(inFile);
if (bytesRead != (size_t)fileSize)
{
outText->clear();
return false;
}
return true;
}
bool WriteTextFileAtomic(const std::string &filePath, const std::string &text)
{
const std::wstring widePath = ToWidePath(filePath);
if (widePath.empty())
{
return false;
}
const std::wstring tmpPath = widePath + L".tmp";
FILE *outFile = nullptr;
if (_wfopen_s(&outFile, tmpPath.c_str(), L"wb") != 0 || outFile == nullptr)
{
return false;
}
if (!text.empty())
{
size_t bytesWritten = fwrite(text.data(), 1, text.size(), outFile);
if (bytesWritten != text.size())
{
fclose(outFile);
DeleteFileW(tmpPath.c_str());
return false;
}
}
if (fflush(outFile) != 0 || _commit(_fileno(outFile)) != 0)
{
fclose(outFile);
DeleteFileW(tmpPath.c_str());
return false;
}
fclose(outFile);
DWORD attrs = GetFileAttributesW(widePath.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES && ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0))
{
// Replace the destination without deleting the last known-good file first.
if (ReplaceFileW(widePath.c_str(), tmpPath.c_str(), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr))
{
return true;
}
}
if (MoveFileExW(tmpPath.c_str(), widePath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
{
return true;
}
// Keep the temp file on failure so the original file remains recoverable and the caller can inspect the write result.
return false;
}
}
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <string>
namespace ServerRuntime
{
namespace FileUtils
{
/**
* Reads the full UTF-8 path target into memory without interpreting JSON or line endings
* UTF-8パスのテキストファイル全体をそのまま読み込む
*/
bool ReadTextFile(const std::string &filePath, std::string *outText);
/**
* Writes text through a same-directory temporary file and publishes it with a single replacement step
* 同一ディレクトリの一時ファイル経由で安全に書き換える
*/
bool WriteTextFileAtomic(const std::string &filePath, const std::string &text);
/**
* Returns the current UTC timestamp encoded in Windows FILETIME units for expiry comparisons
* 期限判定用に現在UTC時刻をWindows FILETIME単位で返す
*/
unsigned long long GetCurrentUtcFileTime();
}
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "StringUtils.h"
#include <WS2tcpip.h>
namespace ServerRuntime
{
namespace NetworkUtils
{
inline std::string NormalizeIpToken(const std::string &ip)
{
return StringUtils::ToLowerAscii(StringUtils::TrimAscii(ip));
}
inline bool IsIpLiteral(const std::string &text)
{
const std::string trimmed = StringUtils::TrimAscii(text);
if (trimmed.empty())
{
return false;
}
IN_ADDR ipv4 = {};
IN6_ADDR ipv6 = {};
return InetPtonA(AF_INET, trimmed.c_str(), &ipv4) == 1 || InetPtonA(AF_INET6, trimmed.c_str(), &ipv6) == 1;
}
}
}

View File

@@ -0,0 +1,212 @@
#include "stdafx.h"
#include "StringUtils.h"
#include <cctype>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
namespace ServerRuntime
{
namespace StringUtils
{
std::string WideToUtf8(const std::wstring &value)
{
if (value.empty())
{
return std::string();
}
int charCount = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), (int)value.length(), NULL, 0, NULL, NULL);
if (charCount <= 0)
{
return std::string();
}
std::string utf8;
utf8.resize(charCount);
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), (int)value.length(), &utf8[0], charCount, NULL, NULL);
return utf8;
}
std::wstring Utf8ToWide(const char *value)
{
if (value == NULL || value[0] == 0)
{
return std::wstring();
}
int wideCount = MultiByteToWideChar(CP_UTF8, 0, value, -1, NULL, 0);
if (wideCount <= 0)
{
// Fall back to the current ANSI code page so legacy non-UTF-8 inputs remain readable.
wideCount = MultiByteToWideChar(CP_ACP, 0, value, -1, NULL, 0);
if (wideCount <= 0)
{
return std::wstring();
}
std::wstring wide;
wide.resize(wideCount - 1);
MultiByteToWideChar(CP_ACP, 0, value, -1, &wide[0], wideCount);
return wide;
}
std::wstring wide;
wide.resize(wideCount - 1);
MultiByteToWideChar(CP_UTF8, 0, value, -1, &wide[0], wideCount);
return wide;
}
std::wstring Utf8ToWide(const std::string &value)
{
return Utf8ToWide(value.c_str());
}
std::string StripUtf8Bom(const std::string &value)
{
if (value.size() >= 3 &&
(unsigned char)value[0] == 0xEF &&
(unsigned char)value[1] == 0xBB &&
(unsigned char)value[2] == 0xBF)
{
return value.substr(3);
}
return value;
}
std::string TrimAscii(const std::string &value)
{
size_t start = 0;
while (start < value.length() && std::isspace((unsigned char)value[start]))
{
++start;
}
size_t end = value.length();
while (end > start && std::isspace((unsigned char)value[end - 1]))
{
--end;
}
return value.substr(start, end - start);
}
std::string ToLowerAscii(const std::string &value)
{
std::string lowered = value;
for (size_t i = 0; i < lowered.length(); ++i)
{
lowered[i] = (char)std::tolower((unsigned char)lowered[i]);
}
return lowered;
}
std::string JoinTokens(const std::vector<std::string> &tokens, size_t startIndex, const char *separator)
{
if (startIndex >= tokens.size())
{
return std::string();
}
const auto joinSeparator = std::string((separator != nullptr) ? separator : " ");
size_t totalLength = 0;
for (size_t i = startIndex; i < tokens.size(); ++i)
{
totalLength += tokens[i].size();
}
totalLength += (tokens.size() - startIndex - 1) * joinSeparator.size();
std::string joined;
joined.reserve(totalLength);
for (size_t i = startIndex; i < tokens.size(); ++i)
{
if (!joined.empty())
{
joined += joinSeparator;
}
joined += tokens[i];
}
return joined;
}
bool StartsWithIgnoreCase(const std::string &value, const std::string &prefix)
{
if (prefix.size() > value.size())
{
return false;
}
for (size_t i = 0; i < prefix.size(); ++i)
{
unsigned char a = (unsigned char)value[i];
unsigned char b = (unsigned char)prefix[i];
if (std::tolower(a) != std::tolower(b))
{
return false;
}
}
return true;
}
bool TryParseUnsignedLongLong(const std::string &value, unsigned long long *outValue)
{
if (outValue == nullptr)
{
return false;
}
const std::string trimmed = TrimAscii(value);
if (trimmed.empty())
{
return false;
}
errno = 0;
char *end = nullptr;
const unsigned long long parsed = _strtoui64(trimmed.c_str(), &end, 0);
if (end == trimmed.c_str() || errno != 0)
{
return false;
}
while (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')
{
++end;
}
if (*end != 0)
{
return false;
}
*outValue = parsed;
return true;
}
std::string GetCurrentUtcTimestampIso8601()
{
SYSTEMTIME utc = {};
GetSystemTime(&utc);
char created[64] = {};
sprintf_s(
created,
sizeof(created),
"%04u-%02u-%02uT%02u:%02u:%02uZ",
(unsigned)utc.wYear,
(unsigned)utc.wMonth,
(unsigned)utc.wDay,
(unsigned)utc.wHour,
(unsigned)utc.wMinute,
(unsigned)utc.wSecond);
return created;
}
}
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <vector>
namespace ServerRuntime
{
namespace StringUtils
{
std::string WideToUtf8(const std::wstring &value);
std::wstring Utf8ToWide(const char *value);
std::wstring Utf8ToWide(const std::string &value);
std::string StripUtf8Bom(const std::string &value);
std::string TrimAscii(const std::string &value);
std::string ToLowerAscii(const std::string &value);
std::string JoinTokens(const std::vector<std::string> &tokens, size_t startIndex = 0, const char *separator = " ");
bool StartsWithIgnoreCase(const std::string &value, const std::string &prefix);
bool TryParseUnsignedLongLong(const std::string &value, unsigned long long *outValue);
std::string GetCurrentUtcTimestampIso8601();
}
}

View File

@@ -0,0 +1,44 @@
#include "stdafx.h"
#include "ServerCli.h"
#include "ServerCliEngine.h"
#include "ServerCliInput.h"
namespace ServerRuntime
{
ServerCli::ServerCli()
: m_engine(new ServerCliEngine())
, m_input(new ServerCliInput())
{
}
ServerCli::~ServerCli()
{
Stop();
}
void ServerCli::Start()
{
if (m_input && m_engine)
{
m_input->Start(m_engine.get());
}
}
void ServerCli::Stop()
{
if (m_input)
{
m_input->Stop();
}
}
void ServerCli::Poll()
{
if (m_engine)
{
m_engine->Poll();
}
}
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include <memory>
namespace ServerRuntime
{
class ServerCliEngine;
class ServerCliInput;
/**
* **Server CLI facade**
*
* Owns the command engine and input component, and exposes a small lifecycle API.
* CLI 全体の開始・停止・更新をまとめる窓口クラス
*/
class ServerCli
{
public:
ServerCli();
~ServerCli();
/**
* **Start console input processing**
*
* Connects input to the engine and starts background reading.
* 入力処理を開始してエンジンに接続
*/
void Start();
/**
* **Stop console input processing**
*
* Stops background input safely and detaches from the engine.
* 入力処理を安全に停止
*/
void Stop();
/**
* **Process queued command lines**
*
* Drains commands collected by input and executes them in the main loop.
* 入力キューのコマンドを実行
*/
void Poll();
private:
std::unique_ptr<ServerCliEngine> m_engine;
std::unique_ptr<ServerCliInput> m_input;
};
}

View File

@@ -0,0 +1,395 @@
#include "stdafx.h"
#include "ServerCliEngine.h"
#include "ServerCliParser.h"
#include "ServerCliRegistry.h"
#include "commands\IServerCliCommand.h"
#include "commands\ban\CliCommandBan.h"
#include "commands\ban-ip\CliCommandBanIp.h"
#include "commands\ban-list\CliCommandBanList.h"
#include "commands\defaultgamemode\CliCommandDefaultGamemode.h"
#include "commands\enchant\CliCommandEnchant.h"
#include "commands\experience\CliCommandExperience.h"
#include "commands\gamemode\CliCommandGamemode.h"
#include "commands\give\CliCommandGive.h"
#include "commands\help\CliCommandHelp.h"
#include "commands\kill\CliCommandKill.h"
#include "commands\list\CliCommandList.h"
#include "commands\pardon\CliCommandPardon.h"
#include "commands\pardon-ip\CliCommandPardonIp.h"
#include "commands\stop\CliCommandStop.h"
#include "commands\time\CliCommandTime.h"
#include "commands\tp\CliCommandTp.h"
#include "commands\weather\CliCommandWeather.h"
#include "commands\whitelist\CliCommandWhitelist.h"
#include "..\Common\StringUtils.h"
#include "..\ServerShutdown.h"
#include "..\ServerLogger.h"
#include "..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\Minecraft.Client\PlayerList.h"
#include "..\..\Minecraft.Client\ServerPlayer.h"
#include "..\..\Minecraft.World\CommandDispatcher.h"
#include "..\..\Minecraft.World\CommandSender.h"
#include "..\..\Minecraft.World\LevelSettings.h"
#include "..\..\Minecraft.World\StringHelpers.h"
#include <stdlib.h>
#include <unordered_set>
namespace ServerRuntime
{
/**
* Create an authorized Sender to make the CLI appear as a user.
* The return value can also be used to display logs.
*/
namespace
{
class ServerCliConsoleCommandSender : public CommandSender
{
public:
explicit ServerCliConsoleCommandSender(const ServerCliEngine *engine)
: m_engine(engine)
{
}
void sendMessage(const wstring &message, ChatPacket::EChatPacketMessage type, int customData, const wstring &additionalMessage) override
{
(void)type;
(void)customData;
(void)additionalMessage;
if (m_engine == nullptr)
{
return;
}
m_engine->LogInfo(StringUtils::WideToUtf8(message));
}
bool hasPermission(EGameCommand command) override
{
(void)command;
return true;
}
private:
const ServerCliEngine *m_engine;
};
}
ServerCliEngine::ServerCliEngine()
: m_registry(new ServerCliRegistry())
, m_consoleSender(std::make_shared<ServerCliConsoleCommandSender>(this))
{
RegisterDefaultCommands();
}
ServerCliEngine::~ServerCliEngine()
{
}
void ServerCliEngine::RegisterDefaultCommands()
{
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandHelp()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandStop()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandList()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandBan()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandBanIp()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandPardon()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandPardonIp()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandBanList()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandWhitelist()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandTp()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandTime()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandWeather()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandGive()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandEnchant()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandKill()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandGamemode()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandDefaultGamemode()));
m_registry->Register(std::unique_ptr<IServerCliCommand>(new CliCommandExperience()));
}
void ServerCliEngine::EnqueueCommandLine(const std::string &line)
{
std::lock_guard<std::mutex> lock(m_queueMutex);
m_pendingLines.push(line);
}
void ServerCliEngine::Poll()
{
for (;;)
{
std::string line;
{
// Keep the lock scope minimal: dequeue only, execute outside.
std::lock_guard<std::mutex> lock(m_queueMutex);
if (m_pendingLines.empty())
{
break;
}
line = m_pendingLines.front();
m_pendingLines.pop();
}
ExecuteCommandLine(line);
}
}
bool ServerCliEngine::ExecuteCommandLine(const std::string &line)
{
// Normalize user input before parsing (trim + optional leading slash).
std::wstring wide = trimString(StringUtils::Utf8ToWide(line));
if (wide.empty())
{
return true;
}
std::string normalizedLine = StringUtils::WideToUtf8(wide);
if (!normalizedLine.empty() && normalizedLine[0] == '/')
{
normalizedLine = normalizedLine.substr(1);
}
ServerCliParsedLine parsed = ServerCliParser::Parse(normalizedLine);
if (parsed.tokens.empty())
{
return true;
}
IServerCliCommand *command = m_registry->FindMutable(parsed.tokens[0]);
if (command == NULL)
{
LogWarn("Unknown command: " + parsed.tokens[0]);
return false;
}
return command->Execute(parsed, this);
}
void ServerCliEngine::BuildCompletions(const std::string &line, std::vector<std::string> *out) const
{
if (out == NULL)
{
return;
}
out->clear();
ServerCliCompletionContext context = ServerCliParser::BuildCompletionContext(line);
bool slashPrefixedCommand = false;
std::string commandToken;
if (!context.parsed.tokens.empty())
{
// Completion accepts both "tp" and "/tp" style command heads.
commandToken = context.parsed.tokens[0];
if (!commandToken.empty() && commandToken[0] == '/')
{
commandToken = commandToken.substr(1);
slashPrefixedCommand = true;
}
}
if (context.currentTokenIndex == 0)
{
std::string prefix = context.prefix;
if (!prefix.empty() && prefix[0] == '/')
{
prefix = prefix.substr(1);
slashPrefixedCommand = true;
}
std::string linePrefix = context.linePrefix;
if (slashPrefixedCommand && linePrefix.empty())
{
// Preserve leading slash when user started with "/".
linePrefix = "/";
}
m_registry->SuggestCommandNames(prefix, linePrefix, out);
}
else
{
const IServerCliCommand *command = m_registry->Find(commandToken);
if (command != NULL)
{
command->Complete(context, this, out);
}
}
std::unordered_set<std::string> seen;
std::vector<std::string> unique;
for (size_t i = 0; i < out->size(); ++i)
{
// Remove duplicates while keeping first-seen ordering.
if (seen.insert((*out)[i]).second)
{
unique.push_back((*out)[i]);
}
}
out->swap(unique);
}
void ServerCliEngine::LogInfo(const std::string &message) const
{
LogInfof("console", "%s", message.c_str());
}
void ServerCliEngine::LogWarn(const std::string &message) const
{
LogWarnf("console", "%s", message.c_str());
}
void ServerCliEngine::LogError(const std::string &message) const
{
LogErrorf("console", "%s", message.c_str());
}
void ServerCliEngine::RequestShutdown() const
{
RequestDedicatedServerShutdown();
}
std::vector<std::string> ServerCliEngine::GetOnlinePlayerNamesUtf8() const
{
std::vector<std::string> result;
MinecraftServer *server = MinecraftServer::getInstance();
if (server == NULL)
{
return result;
}
PlayerList *players = server->getPlayers();
if (players == NULL)
{
return result;
}
for (size_t i = 0; i < players->players.size(); ++i)
{
std::shared_ptr<ServerPlayer> player = players->players[i];
if (player != NULL)
{
result.push_back(StringUtils::WideToUtf8(player->getName()));
}
}
return result;
}
std::shared_ptr<ServerPlayer> ServerCliEngine::FindPlayerByNameUtf8(const std::string &name) const
{
MinecraftServer *server = MinecraftServer::getInstance();
if (server == NULL)
{
return nullptr;
}
PlayerList *players = server->getPlayers();
if (players == NULL)
{
return nullptr;
}
std::wstring target = StringUtils::Utf8ToWide(name);
for (size_t i = 0; i < players->players.size(); ++i)
{
std::shared_ptr<ServerPlayer> player = players->players[i];
if (player != NULL && equalsIgnoreCase(player->getName(), target))
{
return player;
}
}
return nullptr;
}
void ServerCliEngine::SuggestPlayers(const std::string &prefix, const std::string &linePrefix, std::vector<std::string> *out) const
{
std::vector<std::string> players = GetOnlinePlayerNamesUtf8();
std::string loweredPrefix = StringUtils::ToLowerAscii(prefix);
for (size_t i = 0; i < players.size(); ++i)
{
std::string loweredName = StringUtils::ToLowerAscii(players[i]);
if (loweredName.compare(0, loweredPrefix.size(), loweredPrefix) == 0)
{
out->push_back(linePrefix + players[i]);
}
}
}
void ServerCliEngine::SuggestGamemodes(const std::string &prefix, const std::string &linePrefix, std::vector<std::string> *out) const
{
static const char *kModes[] = { "survival", "creative", "s", "c", "0", "1" };
std::string loweredPrefix = StringUtils::ToLowerAscii(prefix);
for (size_t i = 0; i < sizeof(kModes) / sizeof(kModes[0]); ++i)
{
std::string candidate = kModes[i];
std::string loweredCandidate = StringUtils::ToLowerAscii(candidate);
if (loweredCandidate.compare(0, loweredPrefix.size(), loweredPrefix) == 0)
{
out->push_back(linePrefix + candidate);
}
}
}
GameType *ServerCliEngine::ParseGamemode(const std::string &token) const
{
std::string lowered = StringUtils::ToLowerAscii(token);
if (lowered == "survival" || lowered == "s" || lowered == "0")
{
return GameType::SURVIVAL;
}
if (lowered == "creative" || lowered == "c" || lowered == "1")
{
return GameType::CREATIVE;
}
char *end = NULL;
long id = strtol(lowered.c_str(), &end, 10);
if (end != NULL && *end == 0)
{
// Numeric fallback supports extended ids handled by level settings.
return LevelSettings::validateGameType((int)id);
}
return NULL;
}
bool ServerCliEngine::DispatchWorldCommand(EGameCommand command, byteArray commandData, const std::shared_ptr<CommandSender> &sender) const
{
MinecraftServer *server = MinecraftServer::getInstance();
if (server == NULL)
{
LogWarn("MinecraftServer instance is not available.");
return false;
}
CommandDispatcher *dispatcher = server->getCommandDispatcher();
if (dispatcher == NULL)
{
LogWarn("Command dispatcher is not available.");
return false;
}
std::shared_ptr<CommandSender> commandSender = sender;
if (commandSender == nullptr)
{
// fall back to console sender if caller did not provide one
commandSender = m_consoleSender;
}
if (commandSender == nullptr)
{
LogWarn("No command sender is available.");
return false;
}
dispatcher->performCommand(commandSender, command, commandData);
return true;
}
const ServerCliRegistry &ServerCliEngine::Registry() const
{
return *m_registry;
}
}

View File

@@ -0,0 +1,127 @@
#pragma once
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <vector>
#include "..\..\Minecraft.World\ArrayWithLength.h"
#include "..\..\Minecraft.World\CommandsEnum.h"
class GameType;
class ServerPlayer;
class CommandSender;
namespace ServerRuntime
{
class ServerCliRegistry;
/**
* **CLI execution engine**
*
* Handles parsing, command dispatch, completion suggestions, and server-side helpers.
* 解析・実行・補完エンジン
*/
class ServerCliEngine
{
public:
ServerCliEngine();
~ServerCliEngine();
/**
* **Queue one raw command line**
*
* Called by input thread; execution is deferred to `Poll()`.
* 入力行を実行キューに追加
*/
void EnqueueCommandLine(const std::string &line);
/**
* **Execute queued commands**
*
* Drains pending lines and dispatches them in order.
* キュー済みコマンドを順番に実行
*/
void Poll();
/**
* **Execute one command line immediately**
*
* Parses and dispatches a normalized line to a registered command.
* 1行を直接パースしてコマンド実行
*/
bool ExecuteCommandLine(const std::string &line);
/**
* **Build completion candidates for current line**
*
* Produces command or argument suggestions based on parser context.
* 現在入力に対する補完候補を作成
*/
void BuildCompletions(const std::string &line, std::vector<std::string> *out) const;
void LogInfo(const std::string &message) const;
void LogWarn(const std::string &message) const;
void LogError(const std::string &message) const;
void RequestShutdown() const;
/**
* **List connected players as UTF-8 names**
*
* ここら辺は分けてもいいかも
*/
std::vector<std::string> GetOnlinePlayerNamesUtf8() const;
/**
* **Find a player by UTF-8 name**
*/
std::shared_ptr<ServerPlayer> FindPlayerByNameUtf8(const std::string &name) const;
/**
* **Suggest player-name arguments**
*
* Appends matching player candidates using the given completion prefix.
* プレイヤー名の補完候補
*/
void SuggestPlayers(const std::string &prefix, const std::string &linePrefix, std::vector<std::string> *out) const;
/**
* **Suggest gamemode arguments**
*
* Appends standard gamemode aliases (survival/creative/0/1).
* ゲームモードの補完候補
*/
void SuggestGamemodes(const std::string &prefix, const std::string &linePrefix, std::vector<std::string> *out) const;
/**
* **Parse gamemode token**
*
* Supports names, short aliases, and numeric ids.
* 文字列からゲームモードを解決
*/
GameType *ParseGamemode(const std::string &token) const;
/**
* **Dispatch one Minecraft.World game command**
*
* Uses `Minecraft.World::CommandDispatcher` for actual execution.
* When `sender` is null, an internal console command sender is used.
*
* Minecraft.Worldのコマンドを実行するためのディスパッチャーのラッパー
* 内部でsenderがnullの場合はコンソールコマンド送信者を使用
*/
bool DispatchWorldCommand(EGameCommand command, byteArray commandData, const std::shared_ptr<CommandSender> &sender = nullptr) const;
const ServerCliRegistry &Registry() const;
private:
void RegisterDefaultCommands();
private:
mutable std::mutex m_queueMutex;
std::queue<std::string> m_pendingLines;
std::unique_ptr<ServerCliRegistry> m_registry;
std::shared_ptr<CommandSender> m_consoleSender;
};
}

View File

@@ -0,0 +1,285 @@
#include "stdafx.h"
#include "ServerCliInput.h"
#include "ServerCliEngine.h"
#include "..\ServerLogger.h"
#include "..\vendor\linenoise\linenoise.h"
#include <ctype.h>
#include <stdlib.h>
namespace
{
bool UseStreamInputMode()
{
const char *mode = getenv("SERVER_CLI_INPUT_MODE");
if (mode != NULL)
{
return _stricmp(mode, "stream") == 0
|| _stricmp(mode, "stdin") == 0;
}
return false;
}
int WaitForStdinReadable(HANDLE stdinHandle, DWORD waitMs)
{
if (stdinHandle == NULL || stdinHandle == INVALID_HANDLE_VALUE)
{
return -1;
}
DWORD fileType = GetFileType(stdinHandle);
if (fileType == FILE_TYPE_PIPE)
{
DWORD available = 0;
if (!PeekNamedPipe(stdinHandle, NULL, 0, NULL, &available, NULL))
{
return -1;
}
return available > 0 ? 1 : 0;
}
if (fileType == FILE_TYPE_CHAR)
{
// console/pty char handles are often not waitable across Wine+Docker.
return 1;
}
DWORD waitResult = WaitForSingleObject(stdinHandle, waitMs);
if (waitResult == WAIT_OBJECT_0)
{
return 1;
}
if (waitResult == WAIT_TIMEOUT)
{
return 0;
}
return -1;
}
}
namespace ServerRuntime
{
// C-style completion callback bridge requires a static instance pointer.
ServerCliInput *ServerCliInput::s_instance = NULL;
ServerCliInput::ServerCliInput()
: m_running(false)
, m_engine(NULL)
{
}
ServerCliInput::~ServerCliInput()
{
Stop();
}
void ServerCliInput::Start(ServerCliEngine *engine)
{
if (engine == NULL || m_running.exchange(true))
{
return;
}
m_engine = engine;
s_instance = this;
linenoiseResetStop();
linenoiseHistorySetMaxLen(128);
linenoiseSetCompletionCallback(&ServerCliInput::CompletionThunk);
m_inputThread = std::thread(&ServerCliInput::RunInputLoop, this);
LogInfo("console", "CLI input thread started.");
}
void ServerCliInput::Stop()
{
if (!m_running.exchange(false))
{
return;
}
// Ask linenoise to break out first, then join thread safely.
linenoiseRequestStop();
if (m_inputThread.joinable())
{
CancelSynchronousIo((HANDLE)m_inputThread.native_handle());
m_inputThread.join();
}
linenoiseSetCompletionCallback(NULL);
if (s_instance == this)
{
s_instance = NULL;
}
m_engine = NULL;
LogInfo("console", "CLI input thread stopped.");
}
bool ServerCliInput::IsRunning() const
{
return m_running.load();
}
void ServerCliInput::RunInputLoop()
{
if (UseStreamInputMode())
{
LogInfo("console", "CLI input mode: stream(file stdin)");
RunStreamInputLoop();
return;
}
RunLinenoiseLoop();
}
/**
* use linenoise for interactive console input, with line editing and history support
*/
void ServerCliInput::RunLinenoiseLoop()
{
while (m_running)
{
char *line = linenoise("server> ");
if (line == NULL)
{
// NULL is expected on stop request (or Ctrl+C inside linenoise).
if (!m_running)
{
break;
}
Sleep(10);
continue;
}
EnqueueLine(line);
linenoiseFree(line);
}
}
/**
* use file-based stdin reading instead of linenoise when requested or when stdin is not a console/pty (e.g. piped input or non-interactive docker)
*/
void ServerCliInput::RunStreamInputLoop()
{
HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
if (stdinHandle == NULL || stdinHandle == INVALID_HANDLE_VALUE)
{
LogWarn("console", "stream input mode requested but STDIN handle is unavailable; falling back to linenoise.");
RunLinenoiseLoop();
return;
}
std::string line;
bool skipNextLf = false;
printf("server> ");
fflush(stdout);
while (m_running)
{
int readable = WaitForStdinReadable(stdinHandle, 50);
if (readable <= 0)
{
Sleep(10);
continue;
}
char ch = 0;
DWORD bytesRead = 0;
if (!ReadFile(stdinHandle, &ch, 1, &bytesRead, NULL) || bytesRead == 0)
{
Sleep(10);
continue;
}
if (skipNextLf && ch == '\n')
{
skipNextLf = false;
continue;
}
if (ch == '\r' || ch == '\n')
{
if (ch == '\r')
{
skipNextLf = true;
}
else
{
skipNextLf = false;
}
if (!line.empty())
{
EnqueueLine(line.c_str());
line.clear();
}
printf("server> ");
fflush(stdout);
continue;
}
skipNextLf = false;
if ((unsigned char)ch == 3)
{
continue;
}
if ((unsigned char)ch == 8 || (unsigned char)ch == 127)
{
if (!line.empty())
{
line.resize(line.size() - 1);
}
continue;
}
if (isprint((unsigned char)ch) && line.size() < 4096)
{
line.push_back(ch);
}
}
}
void ServerCliInput::EnqueueLine(const char *line)
{
if (line == NULL || line[0] == 0 || m_engine == NULL)
{
return;
}
// Keep local history and forward command for main-thread execution.
linenoiseHistoryAdd(line);
m_engine->EnqueueCommandLine(line);
}
void ServerCliInput::CompletionThunk(const char *line, linenoiseCompletions *completions)
{
// Static thunk forwards callback into instance state.
if (s_instance != NULL)
{
s_instance->BuildCompletions(line, completions);
}
}
void ServerCliInput::BuildCompletions(const char *line, linenoiseCompletions *completions)
{
if (line == NULL || completions == NULL || m_engine == NULL)
{
return;
}
std::vector<std::string> suggestions;
m_engine->BuildCompletions(line, &suggestions);
for (size_t i = 0; i < suggestions.size(); ++i)
{
linenoiseAddCompletion(completions, suggestions[i].c_str());
}
}
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <atomic>
#include <thread>
struct linenoiseCompletions;
namespace ServerRuntime
{
class ServerCliEngine;
/**
* **CLI input worker**
*
* Owns the interactive input thread and bridges linenoise callbacks to the engine.
* 入力スレッドと補完コールバックを管理するクラス
*/
class ServerCliInput
{
public:
ServerCliInput();
~ServerCliInput();
/**
* **Start input loop**
*
* Binds to an engine and starts reading user input from the console.
* エンジンに接続して入力ループを開始
*/
void Start(ServerCliEngine *engine);
/**
* **Stop input loop**
*
* Requests stop and joins the input thread.
* 停止要求を出して入力スレッドを終了
*/
void Stop();
/**
* **Check running state**
*
* Returns true while the input thread is active.
* 入力処理が動作中かどうか
*/
bool IsRunning() const;
private:
void RunInputLoop();
void RunLinenoiseLoop();
void RunStreamInputLoop();
void EnqueueLine(const char *line);
static void CompletionThunk(const char *line, linenoiseCompletions *completions);
void BuildCompletions(const char *line, linenoiseCompletions *completions);
private:
std::atomic<bool> m_running;
std::thread m_inputThread;
ServerCliEngine *m_engine;
static ServerCliInput *s_instance;
};
}

View File

@@ -0,0 +1,116 @@
#include "stdafx.h"
#include "ServerCliParser.h"
namespace ServerRuntime
{
static void TokenizeLine(const std::string &line, std::vector<std::string> *tokens, bool *trailingSpace)
{
std::string current;
bool inQuotes = false;
bool escaped = false;
tokens->clear();
*trailingSpace = false;
for (size_t i = 0; i < line.size(); ++i)
{
char ch = line[i];
if (escaped)
{
// Keep escaped character literally (e.g. \" or \ ).
current.push_back(ch);
escaped = false;
continue;
}
if (ch == '\\')
{
escaped = true;
continue;
}
if (ch == '"')
{
// Double quotes group spaces into one token.
inQuotes = !inQuotes;
continue;
}
if (!inQuotes && (ch == ' ' || ch == '\t'))
{
if (!current.empty())
{
tokens->push_back(current);
current.clear();
}
continue;
}
current.push_back(ch);
}
if (!current.empty())
{
tokens->push_back(current);
}
if (!line.empty())
{
char tail = line[line.size() - 1];
// Trailing space means completion targets the next token slot.
*trailingSpace = (!inQuotes && (tail == ' ' || tail == '\t'));
}
}
ServerCliParsedLine ServerCliParser::Parse(const std::string &line)
{
ServerCliParsedLine parsed;
parsed.raw = line;
TokenizeLine(line, &parsed.tokens, &parsed.trailingSpace);
return parsed;
}
ServerCliCompletionContext ServerCliParser::BuildCompletionContext(const std::string &line)
{
ServerCliCompletionContext context;
context.parsed = Parse(line);
if (context.parsed.tokens.empty())
{
context.currentTokenIndex = 0;
context.prefix.clear();
context.linePrefix.clear();
return context;
}
if (context.parsed.trailingSpace)
{
// Cursor is after a separator, so complete a new token.
context.currentTokenIndex = context.parsed.tokens.size();
context.prefix.clear();
}
else
{
// Cursor is inside current token, so complete by its prefix.
context.currentTokenIndex = context.parsed.tokens.size() - 1;
context.prefix = context.parsed.tokens.back();
}
for (size_t i = 0; i < context.currentTokenIndex; ++i)
{
// linePrefix is the immutable left side reused by completion output.
if (!context.linePrefix.empty())
{
context.linePrefix.push_back(' ');
}
context.linePrefix += context.parsed.tokens[i];
}
if (!context.linePrefix.empty())
{
context.linePrefix.push_back(' ');
}
return context;
}
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <string>
#include <vector>
namespace ServerRuntime
{
/**
* **Parsed command line**
*/
struct ServerCliParsedLine
{
std::string raw;
std::vector<std::string> tokens;
bool trailingSpace;
ServerCliParsedLine()
: trailingSpace(false)
{
}
};
/**
* **Completion context for one input line**
*
* Indicates current token index, token prefix, and the fixed line prefix.
*/
struct ServerCliCompletionContext
{
ServerCliParsedLine parsed;
size_t currentTokenIndex;
std::string prefix;
std::string linePrefix;
ServerCliCompletionContext()
: currentTokenIndex(0)
{
}
};
/**
* **CLI parser helpers**
*
* Converts raw input text into tokenized data used by execution and completion.
*/
class ServerCliParser
{
public:
/**
* **Tokenize one command line**
*
* Supports quoted segments and escaped characters.
*/
static ServerCliParsedLine Parse(const std::string &line);
/**
* **Build completion metadata**
*
* Determines active token position and reusable prefix parts.
*/
static ServerCliCompletionContext BuildCompletionContext(const std::string &line);
};
}

View File

@@ -0,0 +1,99 @@
#include "stdafx.h"
#include "ServerCliRegistry.h"
#include "commands\IServerCliCommand.h"
#include "..\Common\StringUtils.h"
namespace ServerRuntime
{
bool ServerCliRegistry::Register(std::unique_ptr<IServerCliCommand> command)
{
if (!command)
{
return false;
}
IServerCliCommand *raw = command.get();
std::string baseName = StringUtils::ToLowerAscii(raw->Name());
// Reject empty/duplicate primary command names.
if (baseName.empty() || m_lookup.find(baseName) != m_lookup.end())
{
return false;
}
std::vector<std::string> aliases = raw->Aliases();
std::vector<std::string> normalizedAliases;
normalizedAliases.reserve(aliases.size());
for (size_t i = 0; i < aliases.size(); ++i)
{
std::string alias = StringUtils::ToLowerAscii(aliases[i]);
// Alias must also be unique across all names and aliases.
if (alias.empty() || m_lookup.find(alias) != m_lookup.end())
{
return false;
}
normalizedAliases.push_back(alias);
}
m_lookup[baseName] = raw;
for (size_t i = 0; i < normalizedAliases.size(); ++i)
{
m_lookup[normalizedAliases[i]] = raw;
}
// Command objects are owned here; lookup stores non-owning pointers.
m_commands.push_back(std::move(command));
return true;
}
const IServerCliCommand *ServerCliRegistry::Find(const std::string &name) const
{
std::string key = StringUtils::ToLowerAscii(name);
auto it = m_lookup.find(key);
if (it == m_lookup.end())
{
return NULL;
}
return it->second;
}
IServerCliCommand *ServerCliRegistry::FindMutable(const std::string &name)
{
std::string key = StringUtils::ToLowerAscii(name);
auto it = m_lookup.find(key);
if (it == m_lookup.end())
{
return NULL;
}
return it->second;
}
void ServerCliRegistry::SuggestCommandNames(const std::string &prefix, const std::string &linePrefix, std::vector<std::string> *out) const
{
for (size_t i = 0; i < m_commands.size(); ++i)
{
const IServerCliCommand *command = m_commands[i].get();
std::string name = command->Name();
if (StringUtils::StartsWithIgnoreCase(name, prefix))
{
out->push_back(linePrefix + name);
}
// Include aliases so users can discover shorthand commands.
std::vector<std::string> aliases = command->Aliases();
for (size_t aliasIndex = 0; aliasIndex < aliases.size(); ++aliasIndex)
{
if (StringUtils::StartsWithIgnoreCase(aliases[aliasIndex], prefix))
{
out->push_back(linePrefix + aliases[aliasIndex]);
}
}
}
}
const std::vector<std::unique_ptr<IServerCliCommand>> &ServerCliRegistry::Commands() const
{
return m_commands;
}
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace ServerRuntime
{
class IServerCliCommand;
/**
* **CLI command registry**
*/
class ServerCliRegistry
{
public:
/**
* **Register a command object**
*
* Validates name/aliases and adds lookup entries.
* コマンドの追加
*/
bool Register(std::unique_ptr<IServerCliCommand> command);
/**
* **Find command by name or alias (const)**
*
* Returns null when no match exists.
*/
const IServerCliCommand *Find(const std::string &name) const;
/**
* **Find mutable command by name or alias**
*
* Used by runtime dispatch path.
*/
IServerCliCommand *FindMutable(const std::string &name);
/**
* **Suggest top-level command names**
*
* Adds matching command names and aliases to the output list.
*/
void SuggestCommandNames(const std::string &prefix, const std::string &linePrefix, std::vector<std::string> *out) const;
/**
* **Get registered command list**
*
* Intended for help output and inspection.
*/
const std::vector<std::unique_ptr<IServerCliCommand>> &Commands() const;
private:
std::vector<std::unique_ptr<IServerCliCommand>> m_commands;
std::unordered_map<std::string, IServerCliCommand *> m_lookup;
};
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <cerrno>
#include <cstdlib>
#include <limits>
#include <string>
namespace ServerRuntime
{
namespace CommandParsing
{
inline bool TryParseInt(const std::string &text, int *outValue)
{
if (outValue == nullptr || text.empty())
{
return false;
}
char *end = nullptr;
errno = 0;
const long parsedValue = std::strtol(text.c_str(), &end, 10);
if (end == text.c_str() || *end != '\0')
{
return false;
}
if (errno == ERANGE)
{
return false;
}
if (parsedValue < (std::numeric_limits<int>::min)() || parsedValue > (std::numeric_limits<int>::max)())
{
return false;
}
*outValue = static_cast<int>(parsedValue);
return true;
}
}
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include <string>
#include <vector>
namespace ServerRuntime
{
class ServerCliEngine;
struct ServerCliParsedLine;
struct ServerCliCompletionContext;
/**
* **Command interface for server CLI**
*
* Implement this contract to add new commands without changing engine internals.
*/
class IServerCliCommand
{
public:
virtual ~IServerCliCommand() = default;
/** Primary command name */
virtual const char *Name() const = 0;
/** Optional aliases */
virtual std::vector<std::string> Aliases() const { return {}; }
/** Usage text for help */
virtual const char *Usage() const = 0;
/** Short command description*/
virtual const char *Description() const = 0;
/**
* **Execute command logic**
*
* Called after tokenization and command lookup.
*/
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) = 0;
/**
* **Provide argument completion candidates**
*
* Override when command-specific completion is needed.
*/
virtual void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
(void)context;
(void)engine;
(void)out;
}
};
}

View File

@@ -0,0 +1,171 @@
#include "stdafx.h"
#include "CliCommandBanIp.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Access\Access.h"
#include "..\..\..\Common\NetworkUtils.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\ServerLogManager.h"
#include "..\..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\..\Minecraft.Client\PlayerConnection.h"
#include "..\..\..\..\Minecraft.Client\PlayerList.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
#include "..\..\..\..\Minecraft.World\Connection.h"
#include "..\..\..\..\Minecraft.World\DisconnectPacket.h"
namespace ServerRuntime
{
namespace
{
// The dedicated server keeps the accepted remote IP in ServerLogManager, keyed by connection smallId.
// It's a bit strange from a responsibility standpoint, so we'll need to implement it separately.
static bool TryGetPlayerRemoteIp(const std::shared_ptr<ServerPlayer> &player, std::string *outIp)
{
if (outIp == nullptr || player == nullptr || player->connection == nullptr || player->connection->connection == nullptr || player->connection->connection->getSocket() == nullptr)
{
return false;
}
const unsigned char smallId = player->connection->connection->getSocket()->getSmallId();
if (smallId == 0)
{
return false;
}
return ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, outIp);
}
// After persisting the ban, walk a snapshot of current players so every matching session is removed.
static int DisconnectPlayersByRemoteIp(const std::string &ip)
{
auto *server = MinecraftServer::getInstance();
if (server == nullptr || server->getPlayers() == nullptr)
{
return 0;
}
const std::string normalizedIp = NetworkUtils::NormalizeIpToken(ip);
const std::vector<std::shared_ptr<ServerPlayer>> playerSnapshot = server->getPlayers()->players;
int disconnectedCount = 0;
for (const auto &player : playerSnapshot)
{
std::string playerIp;
if (!TryGetPlayerRemoteIp(player, &playerIp))
{
continue;
}
if (NetworkUtils::NormalizeIpToken(playerIp) == normalizedIp)
{
if (player != nullptr && player->connection != nullptr)
{
player->connection->disconnect(DisconnectPacket::eDisconnect_Banned);
++disconnectedCount;
}
}
}
return disconnectedCount;
}
}
const char *CliCommandBanIp::Name() const
{
return "ban-ip";
}
const char *CliCommandBanIp::Usage() const
{
return "ban-ip <address|player> [reason ...]";
}
const char *CliCommandBanIp::Description() const
{
return "Ban an IP address or a player's current IP.";
}
/**
* Resolves either a literal IP or an online player's current IP, persists the ban, and disconnects every matching connection
* IPまたは接続中プレイヤーの現在IPをBANし一致する接続を切断する
*/
bool CliCommandBanIp::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 2)
{
engine->LogWarn("Usage: ban-ip <address|player> [reason ...]");
return false;
}
if (!ServerRuntime::Access::IsInitialized())
{
engine->LogWarn("Access manager is not initialized.");
return false;
}
const std::string targetToken = line.tokens[1];
std::string remoteIp;
// Match Java Edition behavior by accepting either a literal IP or an online player name.
const auto targetPlayer = engine->FindPlayerByNameUtf8(targetToken);
if (targetPlayer != nullptr)
{
if (!TryGetPlayerRemoteIp(targetPlayer, &remoteIp))
{
engine->LogWarn("Cannot ban that player's IP because no current remote IP is available.");
return false;
}
}
else if (NetworkUtils::IsIpLiteral(targetToken))
{
remoteIp = StringUtils::TrimAscii(targetToken);
}
else
{
engine->LogWarn("Unknown player or invalid IP address: " + targetToken);
return false;
}
// Refuse duplicate bans so operators get immediate feedback instead of rewriting the same entry.
if (ServerRuntime::Access::IsIpBanned(remoteIp))
{
engine->LogWarn("That IP address is already banned.");
return false;
}
ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Console");
metadata.reason = StringUtils::JoinTokens(line.tokens, 2);
if (metadata.reason.empty())
{
metadata.reason = "Banned by an operator.";
}
// Publish the ban before disconnecting players so reconnect attempts are rejected immediately.
if (!ServerRuntime::Access::AddIpBan(remoteIp, metadata))
{
engine->LogError("Failed to write IP ban.");
return false;
}
const int disconnectedCount = DisconnectPlayersByRemoteIp(remoteIp);
// Report the resolved IP rather than the original token so player-name targets are explicit in the console.
engine->LogInfo("Banned IP address " + remoteIp + ".");
if (disconnectedCount > 0)
{
engine->LogInfo("Disconnected " + std::to_string(disconnectedCount) + " player(s) with that IP.");
}
return true;
}
/**
* Suggests online player names for the player-target form of the Java Edition command
* プレイヤー名指定時の補完候補を返す
*/
void CliCommandBanIp::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
/**
* Applies a dedicated-server IP ban using Java Edition style syntax and Access-backed persistence
*/
class CliCommandBanIp : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
virtual void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const;
};
}

View File

@@ -0,0 +1,136 @@
#include "stdafx.h"
#include "CliCommandBanList.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Access\Access.h"
#include "..\..\..\Common\StringUtils.h"
#include <algorithm>
namespace ServerRuntime
{
namespace
{
static void AppendUniqueText(const std::string &text, std::vector<std::string> *out)
{
if (out == nullptr || text.empty())
{
return;
}
if (std::find(out->begin(), out->end(), text) == out->end())
{
out->push_back(text);
}
}
static bool CompareLowerAscii(const std::string &left, const std::string &right)
{
return StringUtils::ToLowerAscii(left) < StringUtils::ToLowerAscii(right);
}
static bool LogBannedPlayers(ServerCliEngine *engine)
{
std::vector<ServerRuntime::Access::BannedPlayerEntry> entries;
if (!ServerRuntime::Access::SnapshotBannedPlayers(&entries))
{
engine->LogError("Failed to read banned players.");
return false;
}
std::vector<std::string> names;
for (const auto &entry : entries)
{
AppendUniqueText(entry.name, &names);
}
std::sort(names.begin(), names.end(), CompareLowerAscii);
engine->LogInfo("There are " + std::to_string(names.size()) + " banned player(s).");
for (const auto &name : names)
{
engine->LogInfo(" " + name);
}
return true;
}
static bool LogBannedIps(ServerCliEngine *engine)
{
std::vector<ServerRuntime::Access::BannedIpEntry> entries;
if (!ServerRuntime::Access::SnapshotBannedIps(&entries))
{
engine->LogError("Failed to read banned IPs.");
return false;
}
std::vector<std::string> ips;
for (const auto &entry : entries)
{
AppendUniqueText(entry.ip, &ips);
}
std::sort(ips.begin(), ips.end(), CompareLowerAscii);
engine->LogInfo("There are " + std::to_string(ips.size()) + " banned IP(s).");
for (const auto &ip : ips)
{
engine->LogInfo(" " + ip);
}
return true;
}
static bool LogAllBans(ServerCliEngine *engine)
{
if (!LogBannedPlayers(engine))
{
return false;
}
// Always print the IP snapshot as well so ban-ip entries are visible from the same command output.
return LogBannedIps(engine);
}
}
const char *CliCommandBanList::Name() const
{
return "banlist";
}
const char *CliCommandBanList::Usage() const
{
return "banlist";
}
const char *CliCommandBanList::Description() const
{
return "List all banned players and IPs.";
}
/**
* Reads the current Access snapshots and always prints both banned players and banned IPs
* Access の一覧を読みプレイヤーBANとIP BANをまとめて表示する
*/
bool CliCommandBanList::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() > 1)
{
engine->LogWarn("Usage: banlist");
return false;
}
if (!ServerRuntime::Access::IsInitialized())
{
engine->LogWarn("Access manager is not initialized.");
return false;
}
return LogAllBans(engine);
}
void CliCommandBanList::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
(void)context;
(void)engine;
(void)out;
}
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
/**
* **Ban List Command**
*
* Lists dedicated-server player bans and IP bans in a single command output
* 専用サーバーのプレイヤーBANとIP BANをまとめて表示する
*/
class CliCommandBanList : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
virtual void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const;
};
}

View File

@@ -0,0 +1,145 @@
#include "stdafx.h"
#include "CliCommandBan.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Access\Access.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\..\Minecraft.Client\PlayerConnection.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
#include "..\..\..\..\Minecraft.World\DisconnectPacket.h"
#include <algorithm>
namespace ServerRuntime
{
namespace
{
static void AppendUniqueXuid(PlayerUID xuid, std::vector<PlayerUID> *out)
{
if (out == nullptr || xuid == INVALID_XUID)
{
return;
}
if (std::find(out->begin(), out->end(), xuid) == out->end())
{
out->push_back(xuid);
}
}
static void CollectPlayerBanXuids(const std::shared_ptr<ServerPlayer> &player, std::vector<PlayerUID> *out)
{
if (player == nullptr || out == nullptr)
{
return;
}
// Keep both identity variants because the dedicated server checks login and online XUIDs separately.
AppendUniqueXuid(player->getXuid(), out);
AppendUniqueXuid(player->getOnlineXuid(), out);
}
}
const char *CliCommandBan::Name() const
{
return "ban";
}
const char *CliCommandBan::Usage() const
{
return "ban <player> [reason ...]";
}
const char *CliCommandBan::Description() const
{
return "Ban an online player.";
}
/**
* Resolves the live player, writes one or more Access ban entries, and disconnects the target with the banned reason
* 対象プレイヤーを解決してBANを保存し切断する
*/
bool CliCommandBan::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 2)
{
engine->LogWarn("Usage: ban <player> [reason ...]");
return false;
}
if (!ServerRuntime::Access::IsInitialized())
{
engine->LogWarn("Access manager is not initialized.");
return false;
}
const auto target = engine->FindPlayerByNameUtf8(line.tokens[1]);
if (target == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[1] + " (this server build can only ban players that are currently online).");
return false;
}
std::vector<PlayerUID> xuids;
CollectPlayerBanXuids(target, &xuids);
if (xuids.empty())
{
engine->LogWarn("Cannot ban that player because no valid XUID is available.");
return false;
}
const bool hasUnbannedIdentity = std::any_of(
xuids.begin(),
xuids.end(),
[](PlayerUID xuid) { return !ServerRuntime::Access::IsPlayerBanned(xuid); });
if (!hasUnbannedIdentity)
{
engine->LogWarn("That player is already banned.");
return false;
}
ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Console");
metadata.reason = StringUtils::JoinTokens(line.tokens, 2);
if (metadata.reason.empty())
{
metadata.reason = "Banned by an operator.";
}
const std::string playerName = StringUtils::WideToUtf8(target->getName());
for (const auto xuid : xuids)
{
if (ServerRuntime::Access::IsPlayerBanned(xuid))
{
continue;
}
if (!ServerRuntime::Access::AddPlayerBan(xuid, playerName, metadata))
{
engine->LogError("Failed to write player ban.");
return false;
}
}
if (target->connection != nullptr)
{
target->connection->disconnect(DisconnectPacket::eDisconnect_Banned);
}
engine->LogInfo("Banned player " + playerName + ".");
return true;
}
/**
* Suggests currently connected player names for the Java-style player argument
* プレイヤー引数の補完候補を返す
*/
void CliCommandBan::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
/**
* Applies a dedicated-server player ban using Java Edition style syntax and Access-backed persistence
* Java Edition 風の ban コマンドで永続プレイヤーBANを行う
*/
class CliCommandBan : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
virtual void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const;
};
}

View File

@@ -0,0 +1,117 @@
#include "stdafx.h"
#include "CliCommandDefaultGamemode.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\..\Minecraft.Client\PlayerList.h"
#include "..\..\..\..\Minecraft.Client\ServerLevel.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
#include "..\..\..\..\Minecraft.World\net.minecraft.world.level.storage.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kDefaultGamemodeUsage = "defaultgamemode <survival|creative|0|1>";
static std::string ModeLabel(GameType *mode)
{
if (mode == GameType::SURVIVAL)
{
return "survival";
}
if (mode == GameType::CREATIVE)
{
return "creative";
}
if (mode == GameType::ADVENTURE)
{
return "adventure";
}
return std::to_string(mode != nullptr ? mode->getId() : -1);
}
}
const char *CliCommandDefaultGamemode::Name() const
{
return "defaultgamemode";
}
const char *CliCommandDefaultGamemode::Usage() const
{
return kDefaultGamemodeUsage;
}
const char *CliCommandDefaultGamemode::Description() const
{
return "Set the default game mode (server-side implementation).";
}
bool CliCommandDefaultGamemode::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() != 2)
{
engine->LogWarn(std::string("Usage: ") + kDefaultGamemodeUsage);
return false;
}
GameType *mode = engine->ParseGamemode(line.tokens[1]);
if (mode == nullptr)
{
engine->LogWarn("Unknown gamemode: " + line.tokens[1]);
return false;
}
MinecraftServer *server = MinecraftServer::getInstance();
if (server == nullptr)
{
engine->LogWarn("MinecraftServer instance is not available.");
return false;
}
PlayerList *players = server->getPlayers();
if (players == nullptr)
{
engine->LogWarn("Player list is not available.");
return false;
}
players->setOverrideGameMode(mode);
for (unsigned int i = 0; i < server->levels.length; ++i)
{
ServerLevel *level = server->levels[i];
if (level != nullptr && level->getLevelData() != nullptr)
{
level->getLevelData()->setGameType(mode);
}
}
if (server->getForceGameType())
{
for (size_t i = 0; i < players->players.size(); ++i)
{
std::shared_ptr<ServerPlayer> player = players->players[i];
if (player != nullptr)
{
player->setGameMode(mode);
player->fallDistance = 0.0f;
}
}
}
engine->LogInfo("Default gamemode set to " + ModeLabel(mode) + ".");
return true;
}
void CliCommandDefaultGamemode::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestGamemodes(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandDefaultGamemode : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,87 @@
#include "stdafx.h"
#include "CliCommandEnchant.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\CommandParsing.h"
#include "..\..\..\..\Minecraft.World\GameCommandPacket.h"
#include "..\..\..\..\Minecraft.World\EnchantItemCommand.h"
#include "..\..\..\..\Minecraft.World\net.minecraft.world.entity.player.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kEnchantUsage = "enchant <player> <enchantId> [level]";
}
const char *CliCommandEnchant::Name() const
{
return "enchant";
}
const char *CliCommandEnchant::Usage() const
{
return kEnchantUsage;
}
const char *CliCommandEnchant::Description() const
{
return "Enchant held item via Minecraft.World command dispatcher.";
}
bool CliCommandEnchant::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 3 || line.tokens.size() > 4)
{
engine->LogWarn(std::string("Usage: ") + kEnchantUsage);
return false;
}
std::shared_ptr<ServerPlayer> target = engine->FindPlayerByNameUtf8(line.tokens[1]);
if (target == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[1]);
return false;
}
int enchantmentId = 0;
int enchantmentLevel = 1;
if (!CommandParsing::TryParseInt(line.tokens[2], &enchantmentId))
{
engine->LogWarn("Invalid enchantment id: " + line.tokens[2]);
return false;
}
if (line.tokens.size() >= 4 && !CommandParsing::TryParseInt(line.tokens[3], &enchantmentLevel))
{
engine->LogWarn("Invalid enchantment level: " + line.tokens[3]);
return false;
}
std::shared_ptr<Player> player = std::dynamic_pointer_cast<Player>(target);
if (player == nullptr)
{
engine->LogWarn("Cannot resolve target player entity.");
return false;
}
std::shared_ptr<GameCommandPacket> packet = EnchantItemCommand::preparePacket(player, enchantmentId, enchantmentLevel);
if (packet == nullptr)
{
engine->LogError("Failed to build enchant command packet.");
return false;
}
return engine->DispatchWorldCommand(packet->command, packet->data);
}
void CliCommandEnchant::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandEnchant : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,184 @@
#include "stdafx.h"
#include "CliCommandExperience.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\CommandParsing.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\..\Minecraft.Client\PlayerList.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
#include <limits>
namespace ServerRuntime
{
namespace
{
constexpr const char *kExperienceUsage = "xp <amount>[L] [player]";
constexpr const char *kExperienceUsageWithPlayer = "xp <amount>[L] <player>";
struct ExperienceAmount
{
int amount = 0;
bool levels = false;
bool take = false;
};
static bool TryParseExperienceAmount(const std::string &token, ExperienceAmount *outValue)
{
if (outValue == nullptr || token.empty())
{
return false;
}
ExperienceAmount parsed;
std::string numericToken = token;
const char suffix = token[token.size() - 1];
if (suffix == 'l' || suffix == 'L')
{
parsed.levels = true;
numericToken = token.substr(0, token.size() - 1);
if (numericToken.empty())
{
return false;
}
}
int signedAmount = 0;
if (!CommandParsing::TryParseInt(numericToken, &signedAmount))
{
return false;
}
if (signedAmount == (std::numeric_limits<int>::min)())
{
return false;
}
parsed.take = signedAmount < 0;
parsed.amount = parsed.take ? -signedAmount : signedAmount;
*outValue = parsed;
return true;
}
static std::shared_ptr<ServerPlayer> ResolveTargetPlayer(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() >= 3)
{
return engine->FindPlayerByNameUtf8(line.tokens[2]);
}
MinecraftServer *server = MinecraftServer::getInstance();
if (server == nullptr || server->getPlayers() == nullptr)
{
return nullptr;
}
PlayerList *players = server->getPlayers();
if (players->players.size() == 1 && players->players[0] != nullptr)
{
return players->players[0];
}
return nullptr;
}
}
const char *CliCommandExperience::Name() const
{
return "xp";
}
std::vector<std::string> CliCommandExperience::Aliases() const
{
return { "experience" };
}
const char *CliCommandExperience::Usage() const
{
return kExperienceUsage;
}
const char *CliCommandExperience::Description() const
{
return "Grant or remove experience (server-side implementation).";
}
bool CliCommandExperience::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 2 || line.tokens.size() > 3)
{
engine->LogWarn(std::string("Usage: ") + kExperienceUsage);
return false;
}
ExperienceAmount amount;
if (!TryParseExperienceAmount(line.tokens[1], &amount))
{
engine->LogWarn(std::string("Usage: ") + kExperienceUsage);
return false;
}
std::shared_ptr<ServerPlayer> target = ResolveTargetPlayer(line, engine);
if (target == nullptr)
{
if (line.tokens.size() >= 3)
{
engine->LogWarn("Unknown player: " + line.tokens[2]);
}
else
{
engine->LogWarn(std::string("Usage: ") + kExperienceUsageWithPlayer);
}
return false;
}
if (amount.levels)
{
target->giveExperienceLevels(amount.take ? -amount.amount : amount.amount);
if (amount.take)
{
engine->LogInfo("Removed " + std::to_string(amount.amount) + " level(s) from " + StringUtils::WideToUtf8(target->getName()) + ".");
}
else
{
engine->LogInfo("Added " + std::to_string(amount.amount) + " level(s) to " + StringUtils::WideToUtf8(target->getName()) + ".");
}
return true;
}
if (amount.take)
{
engine->LogWarn("Removing raw experience points is not supported. Use negative levels (example: xp -5L <player>).");
return false;
}
target->increaseXp(amount.amount);
engine->LogInfo("Added " + std::to_string(amount.amount) + " experience points to " + StringUtils::WideToUtf8(target->getName()) + ".");
return true;
}
void CliCommandExperience::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
if (StringUtils::StartsWithIgnoreCase("10", context.prefix))
{
out->push_back(context.linePrefix + "10");
}
if (StringUtils::StartsWithIgnoreCase("10L", context.prefix))
{
out->push_back(context.linePrefix + "10L");
}
if (StringUtils::StartsWithIgnoreCase("-5L", context.prefix))
{
out->push_back(context.linePrefix + "-5L");
}
}
else if (context.currentTokenIndex == 2)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandExperience : public IServerCliCommand
{
public:
const char *Name() const override;
std::vector<std::string> Aliases() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,109 @@
#include "stdafx.h"
#include "CliCommandGamemode.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\..\Minecraft.Client\PlayerList.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kGamemodeUsage = "gamemode <survival|creative|0|1> [player]";
constexpr const char *kGamemodeUsageWithPlayer = "gamemode <survival|creative|0|1> <player>";
}
const char *CliCommandGamemode::Name() const
{
return "gamemode";
}
std::vector<std::string> CliCommandGamemode::Aliases() const
{
return { "gm" };
}
const char *CliCommandGamemode::Usage() const
{
return kGamemodeUsage;
}
const char *CliCommandGamemode::Description() const
{
return "Set a player's game mode.";
}
bool CliCommandGamemode::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 2 || line.tokens.size() > 3)
{
engine->LogWarn(std::string("Usage: ") + kGamemodeUsage);
return false;
}
GameType *mode = engine->ParseGamemode(line.tokens[1]);
if (mode == nullptr)
{
engine->LogWarn("Unknown gamemode: " + line.tokens[1]);
return false;
}
std::shared_ptr<ServerPlayer> target = nullptr;
if (line.tokens.size() >= 3)
{
target = engine->FindPlayerByNameUtf8(line.tokens[2]);
if (target == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[2]);
return false;
}
}
else
{
MinecraftServer *server = MinecraftServer::getInstance();
if (server == nullptr || server->getPlayers() == nullptr)
{
engine->LogWarn("Player list is not available.");
return false;
}
PlayerList *players = server->getPlayers();
if (players->players.size() != 1 || players->players[0] == nullptr)
{
engine->LogWarn(std::string("Usage: ") + kGamemodeUsageWithPlayer);
return false;
}
target = players->players[0];
}
target->setGameMode(mode);
target->fallDistance = 0.0f;
if (line.tokens.size() >= 3)
{
engine->LogInfo("Set " + line.tokens[2] + " gamemode to " + line.tokens[1] + ".");
}
else
{
engine->LogInfo("Set gamemode to " + line.tokens[1] + ".");
}
return true;
}
void CliCommandGamemode::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestGamemodes(context.prefix, context.linePrefix, out);
}
else if (context.currentTokenIndex == 2)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandGamemode : public IServerCliCommand
{
public:
const char *Name() const override;
std::vector<std::string> Aliases() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,103 @@
#include "stdafx.h"
#include "CliCommandGive.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\CommandParsing.h"
#include "..\..\..\..\Minecraft.World\GameCommandPacket.h"
#include "..\..\..\..\Minecraft.World\GiveItemCommand.h"
#include "..\..\..\..\Minecraft.World\net.minecraft.world.entity.player.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kGiveUsage = "give <player> <itemId> [amount] [aux]";
}
const char *CliCommandGive::Name() const
{
return "give";
}
const char *CliCommandGive::Usage() const
{
return kGiveUsage;
}
const char *CliCommandGive::Description() const
{
return "Give an item via Minecraft.World command dispatcher.";
}
bool CliCommandGive::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 3 || line.tokens.size() > 5)
{
engine->LogWarn(std::string("Usage: ") + kGiveUsage);
return false;
}
std::shared_ptr<ServerPlayer> target = engine->FindPlayerByNameUtf8(line.tokens[1]);
if (target == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[1]);
return false;
}
int itemId = 0;
int amount = 1;
int aux = 0;
if (!CommandParsing::TryParseInt(line.tokens[2], &itemId))
{
engine->LogWarn("Invalid item id: " + line.tokens[2]);
return false;
}
if (itemId <= 0)
{
engine->LogWarn("Item id must be greater than 0.");
return false;
}
if (line.tokens.size() >= 4 && !CommandParsing::TryParseInt(line.tokens[3], &amount))
{
engine->LogWarn("Invalid amount: " + line.tokens[3]);
return false;
}
if (line.tokens.size() >= 5 && !CommandParsing::TryParseInt(line.tokens[4], &aux))
{
engine->LogWarn("Invalid aux value: " + line.tokens[4]);
return false;
}
if (amount <= 0)
{
engine->LogWarn("Amount must be greater than 0.");
return false;
}
std::shared_ptr<Player> player = std::dynamic_pointer_cast<Player>(target);
if (player == nullptr)
{
engine->LogWarn("Cannot resolve target player entity.");
return false;
}
std::shared_ptr<GameCommandPacket> packet = GiveItemCommand::preparePacket(player, itemId, amount, aux);
if (packet == nullptr)
{
engine->LogError("Failed to build give command packet.");
return false;
}
return engine->DispatchWorldCommand(packet->command, packet->data);
}
void CliCommandGive::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandGive : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,46 @@
#include "stdafx.h"
#include "CliCommandHelp.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliRegistry.h"
namespace ServerRuntime
{
const char *CliCommandHelp::Name() const
{
return "help";
}
std::vector<std::string> CliCommandHelp::Aliases() const
{
return { "?" };
}
const char *CliCommandHelp::Usage() const
{
return "help";
}
const char *CliCommandHelp::Description() const
{
return "Show available server console commands.";
}
bool CliCommandHelp::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
(void)line;
const std::vector<std::unique_ptr<IServerCliCommand>> &commands = engine->Registry().Commands();
engine->LogInfo("Available commands:");
for (size_t i = 0; i < commands.size(); ++i)
{
std::string row = " ";
row += commands[i]->Usage();
row += " - ";
row += commands[i]->Description();
engine->LogInfo(row);
}
return true;
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandHelp : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual std::vector<std::string> Aliases() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
};
}

View File

@@ -0,0 +1,64 @@
#include "stdafx.h"
#include "CliCommandKill.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\..\Minecraft.World\CommandSender.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kKillUsage = "kill <player>";
}
const char *CliCommandKill::Name() const
{
return "kill";
}
const char *CliCommandKill::Usage() const
{
return kKillUsage;
}
const char *CliCommandKill::Description() const
{
return "Kill a player via Minecraft.World command dispatcher.";
}
bool CliCommandKill::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() != 2)
{
engine->LogWarn(std::string("Usage: ") + kKillUsage);
return false;
}
std::shared_ptr<ServerPlayer> target = engine->FindPlayerByNameUtf8(line.tokens[1]);
if (target == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[1]);
return false;
}
std::shared_ptr<CommandSender> sender = std::dynamic_pointer_cast<CommandSender>(target);
if (sender == nullptr)
{
engine->LogWarn("Cannot resolve target command sender.");
return false;
}
return engine->DispatchWorldCommand(eGameCommand_Kill, byteArray(), sender);
}
void CliCommandKill::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandKill : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,49 @@
#include "stdafx.h"
#include "CliCommandList.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\..\Minecraft.Client\PlayerList.h"
namespace ServerRuntime
{
const char *CliCommandList::Name() const
{
return "list";
}
const char *CliCommandList::Usage() const
{
return "list";
}
const char *CliCommandList::Description() const
{
return "List connected players.";
}
bool CliCommandList::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
(void)line;
MinecraftServer *server = MinecraftServer::getInstance();
if (server == NULL || server->getPlayers() == NULL)
{
engine->LogWarn("Player list is not available.");
return false;
}
PlayerList *players = server->getPlayers();
std::string names = StringUtils::WideToUtf8(players->getPlayerNames());
if (names.empty())
{
names = "(none)";
}
engine->LogInfo("Players (" + std::to_string(players->getPlayerCount()) + "): " + names);
return true;
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandList : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
};
}

View File

@@ -0,0 +1,98 @@
#include "stdafx.h"
#include "CliCommandPardonIp.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Access\Access.h"
#include "..\..\..\Common\NetworkUtils.h"
#include "..\..\..\Common\StringUtils.h"
namespace ServerRuntime
{
const char *CliCommandPardonIp::Name() const
{
return "pardon-ip";
}
const char *CliCommandPardonIp::Usage() const
{
return "pardon-ip <address>";
}
const char *CliCommandPardonIp::Description() const
{
return "Remove an IP ban.";
}
/**
* Validates the literal IP argument and removes the matching Access IP ban entry
* リテラルIPを検証して一致するIP BANを解除する
*/
bool CliCommandPardonIp::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() != 2)
{
engine->LogWarn("Usage: pardon-ip <address>");
return false;
}
if (!ServerRuntime::Access::IsInitialized())
{
engine->LogWarn("Access manager is not initialized.");
return false;
}
// Java Edition pardon-ip only operates on a literal address, so do not resolve player names here.
const std::string ip = StringUtils::TrimAscii(line.tokens[1]);
if (!NetworkUtils::IsIpLiteral(ip))
{
engine->LogWarn("Invalid IP address: " + line.tokens[1]);
return false;
}
// Distinguish invalid input from a valid but currently unbanned address for clearer operator feedback.
if (!ServerRuntime::Access::IsIpBanned(ip))
{
engine->LogWarn("That IP address is not banned.");
return false;
}
if (!ServerRuntime::Access::RemoveIpBan(ip))
{
engine->LogError("Failed to remove IP ban.");
return false;
}
engine->LogInfo("Unbanned IP address " + ip + ".");
return true;
}
/**
* Suggests currently banned IP addresses for the Java Edition literal-IP argument
* BAN済みIPの補完候補を返す
*/
void CliCommandPardonIp::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
(void)engine;
// Complete from the persisted IP-ban snapshot because this command only accepts already-banned literals.
if (context.currentTokenIndex != 1 || out == nullptr)
{
return;
}
std::vector<ServerRuntime::Access::BannedIpEntry> entries;
if (!ServerRuntime::Access::SnapshotBannedIps(&entries))
{
return;
}
// Reuse the normalized prefix match used by other commands so completion stays case-insensitive.
for (const auto &entry : entries)
{
const std::string &candidate = entry.ip;
if (StringUtils::StartsWithIgnoreCase(candidate, context.prefix))
{
out->push_back(context.linePrefix + candidate);
}
}
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
/**
* Removes a dedicated-server IP ban using Java Edition style syntax and Access-backed persistence
*/
class CliCommandPardonIp : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
virtual void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const;
};
}

View File

@@ -0,0 +1,173 @@
#include "stdafx.h"
#include "CliCommandPardon.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Access\Access.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
#include <algorithm>
namespace ServerRuntime
{
namespace
{
static void AppendUniqueText(const std::string &text, std::vector<std::string> *out)
{
if (out == nullptr || text.empty())
{
return;
}
if (std::find(out->begin(), out->end(), text) == out->end())
{
out->push_back(text);
}
}
static void AppendUniqueXuid(PlayerUID xuid, std::vector<PlayerUID> *out)
{
if (out == nullptr || xuid == INVALID_XUID)
{
return;
}
if (std::find(out->begin(), out->end(), xuid) == out->end())
{
out->push_back(xuid);
}
}
}
const char *CliCommandPardon::Name() const
{
return "pardon";
}
const char *CliCommandPardon::Usage() const
{
return "pardon <player>";
}
const char *CliCommandPardon::Description() const
{
return "Remove a player ban.";
}
/**
* Removes every Access ban entry that matches the requested player name so dual-XUID entries are cleared together
* 名前に一致するBANをまとめて解除する
*/
bool CliCommandPardon::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() != 2)
{
engine->LogWarn("Usage: pardon <player>");
return false;
}
if (!ServerRuntime::Access::IsInitialized())
{
engine->LogWarn("Access manager is not initialized.");
return false;
}
std::vector<PlayerUID> xuidsToRemove;
std::vector<std::string> matchedNames;
std::shared_ptr<ServerPlayer> onlineTarget = engine->FindPlayerByNameUtf8(line.tokens[1]);
if (onlineTarget != nullptr)
{
if (ServerRuntime::Access::IsPlayerBanned(onlineTarget->getXuid()))
{
AppendUniqueXuid(onlineTarget->getXuid(), &xuidsToRemove);
}
if (ServerRuntime::Access::IsPlayerBanned(onlineTarget->getOnlineXuid()))
{
AppendUniqueXuid(onlineTarget->getOnlineXuid(), &xuidsToRemove);
}
}
std::vector<ServerRuntime::Access::BannedPlayerEntry> entries;
if (!ServerRuntime::Access::SnapshotBannedPlayers(&entries))
{
engine->LogError("Failed to read banned players.");
return false;
}
const std::string loweredTarget = StringUtils::ToLowerAscii(line.tokens[1]);
for (const auto &entry : entries)
{
if (StringUtils::ToLowerAscii(entry.name) == loweredTarget)
{
PlayerUID parsedXuid = INVALID_XUID;
if (ServerRuntime::Access::TryParseXuid(entry.xuid, &parsedXuid))
{
AppendUniqueXuid(parsedXuid, &xuidsToRemove);
}
AppendUniqueText(entry.name, &matchedNames);
}
}
if (xuidsToRemove.empty())
{
engine->LogWarn("That player is not banned.");
return false;
}
for (const auto xuid : xuidsToRemove)
{
if (!ServerRuntime::Access::RemovePlayerBan(xuid))
{
engine->LogError("Failed to remove player ban.");
return false;
}
}
std::string playerName = line.tokens[1];
if (!matchedNames.empty())
{
playerName = matchedNames[0];
}
else if (onlineTarget != nullptr)
{
playerName = StringUtils::WideToUtf8(onlineTarget->getName());
}
engine->LogInfo("Unbanned player " + playerName + ".");
return true;
}
/**
* Suggests currently banned player names first and then online names for convenience
* BAN済み名とオンライン名を補完候補に出す
*/
void CliCommandPardon::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex != 1 || out == nullptr)
{
return;
}
std::vector<ServerRuntime::Access::BannedPlayerEntry> entries;
if (ServerRuntime::Access::SnapshotBannedPlayers(&entries))
{
std::vector<std::string> names;
for (const auto &entry : entries)
{
AppendUniqueText(entry.name, &names);
}
for (const auto &name : names)
{
if (StringUtils::StartsWithIgnoreCase(name, context.prefix))
{
out->push_back(context.linePrefix + name);
}
}
}
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
/**
* Removes dedicated-server player bans using Java Edition style syntax and Access-backed persistence
*/
class CliCommandPardon : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
virtual void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const;
};
}

View File

@@ -0,0 +1,32 @@
#include "stdafx.h"
#include "CliCommandStop.h"
#include "..\..\ServerCliEngine.h"
namespace ServerRuntime
{
const char *CliCommandStop::Name() const
{
return "stop";
}
const char *CliCommandStop::Usage() const
{
return "stop";
}
const char *CliCommandStop::Description() const
{
return "Stop the dedicated server.";
}
bool CliCommandStop::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
(void)line;
engine->LogInfo("Stopping server...");
engine->RequestShutdown();
return true;
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandStop : public IServerCliCommand
{
public:
virtual const char *Name() const;
virtual const char *Usage() const;
virtual const char *Description() const;
virtual bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine);
};
}

View File

@@ -0,0 +1,118 @@
#include "stdafx.h"
#include "CliCommandTime.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\..\Minecraft.World\GameCommandPacket.h"
#include "..\..\..\..\Minecraft.World\TimeCommand.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kTimeUsage = "time <day|night|set day|set night>";
static bool TryResolveNightFlag(const std::vector<std::string> &tokens, bool *outNight)
{
if (outNight == nullptr)
{
return false;
}
std::string value;
if (tokens.size() == 2)
{
value = StringUtils::ToLowerAscii(tokens[1]);
}
else if (tokens.size() == 3 && StringUtils::ToLowerAscii(tokens[1]) == "set")
{
value = StringUtils::ToLowerAscii(tokens[2]);
}
else
{
return false;
}
if (value == "day")
{
*outNight = false;
return true;
}
if (value == "night")
{
*outNight = true;
return true;
}
return false;
}
static void SuggestLiteral(const char *candidate, const ServerCliCompletionContext &context, std::vector<std::string> *out)
{
if (candidate == nullptr || out == nullptr)
{
return;
}
const std::string text(candidate);
if (StringUtils::StartsWithIgnoreCase(text, context.prefix))
{
out->push_back(context.linePrefix + text);
}
}
}
const char *CliCommandTime::Name() const
{
return "time";
}
const char *CliCommandTime::Usage() const
{
return kTimeUsage;
}
const char *CliCommandTime::Description() const
{
return "Set day or night via Minecraft.World command dispatcher.";
}
bool CliCommandTime::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
bool night = false;
if (!TryResolveNightFlag(line.tokens, &night))
{
engine->LogWarn(std::string("Usage: ") + kTimeUsage);
return false;
}
std::shared_ptr<GameCommandPacket> packet = TimeCommand::preparePacket(night);
if (packet == nullptr)
{
engine->LogError("Failed to build time command packet.");
return false;
}
return engine->DispatchWorldCommand(packet->command, packet->data);
}
void CliCommandTime::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
(void)engine;
if (context.currentTokenIndex == 1)
{
SuggestLiteral("day", context, out);
SuggestLiteral("night", context, out);
SuggestLiteral("set", context, out);
}
else if (context.currentTokenIndex == 2 &&
context.parsed.tokens.size() >= 2 &&
StringUtils::ToLowerAscii(context.parsed.tokens[1]) == "set")
{
SuggestLiteral("day", context, out);
SuggestLiteral("night", context, out);
}
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandTime : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,82 @@
#include "stdafx.h"
#include "CliCommandTp.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\..\Minecraft.Client\PlayerConnection.h"
#include "..\..\..\..\Minecraft.Client\TeleportCommand.h"
#include "..\..\..\..\Minecraft.Client\ServerPlayer.h"
#include "..\..\..\..\Minecraft.World\GameCommandPacket.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kTpUsage = "tp <player> <target>";
}
const char *CliCommandTp::Name() const
{
return "tp";
}
std::vector<std::string> CliCommandTp::Aliases() const
{
return { "teleport" };
}
const char *CliCommandTp::Usage() const
{
return kTpUsage;
}
const char *CliCommandTp::Description() const
{
return "Teleport one player to another via Minecraft.World command dispatcher.";
}
bool CliCommandTp::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() != 3)
{
engine->LogWarn(std::string("Usage: ") + kTpUsage);
return false;
}
std::shared_ptr<ServerPlayer> subject = engine->FindPlayerByNameUtf8(line.tokens[1]);
std::shared_ptr<ServerPlayer> destination = engine->FindPlayerByNameUtf8(line.tokens[2]);
if (subject == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[1]);
return false;
}
if (destination == nullptr)
{
engine->LogWarn("Unknown player: " + line.tokens[2]);
return false;
}
if (subject->connection == nullptr)
{
engine->LogWarn("Cannot teleport because source player connection is inactive.");
return false;
}
std::shared_ptr<GameCommandPacket> packet = TeleportCommand::preparePacket(subject->getXuid(), destination->getXuid());
if (packet == nullptr)
{
engine->LogError("Failed to build teleport command packet.");
return false;
}
return engine->DispatchWorldCommand(packet->command, packet->data);
}
void CliCommandTp::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
if (context.currentTokenIndex == 1 || context.currentTokenIndex == 2)
{
engine->SuggestPlayers(context.prefix, context.linePrefix, out);
}
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandTp : public IServerCliCommand
{
public:
const char *Name() const override;
std::vector<std::string> Aliases() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,49 @@
#include "stdafx.h"
#include "CliCommandWeather.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\..\Minecraft.World\GameCommandPacket.h"
#include "..\..\..\..\Minecraft.World\ToggleDownfallCommand.h"
namespace ServerRuntime
{
namespace
{
constexpr const char *kWeatherUsage = "weather";
}
const char *CliCommandWeather::Name() const
{
return "weather";
}
const char *CliCommandWeather::Usage() const
{
return kWeatherUsage;
}
const char *CliCommandWeather::Description() const
{
return "Toggle weather via Minecraft.World command dispatcher.";
}
bool CliCommandWeather::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() != 1)
{
engine->LogWarn(std::string("Usage: ") + kWeatherUsage);
return false;
}
std::shared_ptr<GameCommandPacket> packet = ToggleDownfallCommand::preparePacket();
if (packet == nullptr)
{
engine->LogError("Failed to build weather command packet.");
return false;
}
return engine->DispatchWorldCommand(packet->command, packet->data);
}
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandWeather : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
};
}

View File

@@ -0,0 +1,285 @@
#include "stdafx.h"
#include "CliCommandWhitelist.h"
#include "..\..\ServerCliEngine.h"
#include "..\..\ServerCliParser.h"
#include "..\..\..\Access\Access.h"
#include "..\..\..\Common\StringUtils.h"
#include "..\..\..\ServerProperties.h"
#include <algorithm>
#include <array>
namespace ServerRuntime
{
namespace
{
static const char *kWhitelistUsage = "whitelist <on|off|list|add|remove|reload> [...]";
static bool CompareWhitelistEntries(const ServerRuntime::Access::WhitelistedPlayerEntry &left, const ServerRuntime::Access::WhitelistedPlayerEntry &right)
{
const auto leftName = StringUtils::ToLowerAscii(left.name);
const auto rightName = StringUtils::ToLowerAscii(right.name);
if (leftName != rightName)
{
return leftName < rightName;
}
return StringUtils::ToLowerAscii(left.xuid) < StringUtils::ToLowerAscii(right.xuid);
}
static bool PersistWhitelistToggle(bool enabled)
{
auto config = LoadServerPropertiesConfig();
config.whiteListEnabled = enabled;
return SaveServerPropertiesConfig(config);
}
static std::string BuildWhitelistEntryRow(const ServerRuntime::Access::WhitelistedPlayerEntry &entry)
{
std::string row = " ";
row += entry.xuid;
if (!entry.name.empty())
{
row += " - ";
row += entry.name;
}
return row;
}
static void LogWhitelistMode(ServerCliEngine *engine)
{
engine->LogInfo(std::string("Whitelist is ") + (ServerRuntime::Access::IsWhitelistEnabled() ? "enabled." : "disabled."));
}
static bool LogWhitelistEntries(ServerCliEngine *engine)
{
std::vector<ServerRuntime::Access::WhitelistedPlayerEntry> entries;
if (!ServerRuntime::Access::SnapshotWhitelistedPlayers(&entries))
{
engine->LogError("Failed to read whitelist entries.");
return false;
}
std::sort(entries.begin(), entries.end(), CompareWhitelistEntries);
LogWhitelistMode(engine);
engine->LogInfo("There are " + std::to_string(entries.size()) + " whitelisted player(s).");
for (const auto &entry : entries)
{
engine->LogInfo(BuildWhitelistEntryRow(entry));
}
return true;
}
static bool TryParseWhitelistXuid(const std::string &text, ServerCliEngine *engine, PlayerUID *outXuid)
{
if (ServerRuntime::Access::TryParseXuid(text, outXuid))
{
return true;
}
engine->LogWarn("Invalid XUID: " + text);
return false;
}
static void SuggestLiteral(const std::string &candidate, const ServerCliCompletionContext &context, std::vector<std::string> *out)
{
if (out == nullptr)
{
return;
}
if (StringUtils::StartsWithIgnoreCase(candidate, context.prefix))
{
out->push_back(context.linePrefix + candidate);
}
}
}
const char *CliCommandWhitelist::Name() const
{
return "whitelist";
}
const char *CliCommandWhitelist::Usage() const
{
return kWhitelistUsage;
}
const char *CliCommandWhitelist::Description() const
{
return "Manage the dedicated-server XUID whitelist.";
}
bool CliCommandWhitelist::Execute(const ServerCliParsedLine &line, ServerCliEngine *engine)
{
if (line.tokens.size() < 2)
{
engine->LogWarn(std::string("Usage: ") + kWhitelistUsage);
return false;
}
if (!ServerRuntime::Access::IsInitialized())
{
engine->LogWarn("Access manager is not initialized.");
return false;
}
const auto subcommand = StringUtils::ToLowerAscii(line.tokens[1]);
if (subcommand == "on" || subcommand == "off")
{
if (line.tokens.size() != 2)
{
engine->LogWarn("Usage: whitelist <on|off>");
return false;
}
const bool enabled = (subcommand == "on");
if (!PersistWhitelistToggle(enabled))
{
engine->LogError("Failed to persist whitelist mode to server.properties.");
return false;
}
ServerRuntime::Access::SetWhitelistEnabled(enabled);
engine->LogInfo(std::string("Whitelist ") + (enabled ? "enabled." : "disabled."));
return true;
}
if (subcommand == "list")
{
if (line.tokens.size() != 2)
{
engine->LogWarn("Usage: whitelist list");
return false;
}
return LogWhitelistEntries(engine);
}
if (subcommand == "reload")
{
if (line.tokens.size() != 2)
{
engine->LogWarn("Usage: whitelist reload");
return false;
}
if (!ServerRuntime::Access::ReloadWhitelist())
{
engine->LogError("Failed to reload whitelist.");
return false;
}
const auto config = LoadServerPropertiesConfig();
ServerRuntime::Access::SetWhitelistEnabled(config.whiteListEnabled);
engine->LogInfo("Reloaded whitelist from disk.");
LogWhitelistMode(engine);
return true;
}
if (subcommand == "add")
{
if (line.tokens.size() < 3)
{
engine->LogWarn("Usage: whitelist add <xuid> [name ...]");
return false;
}
PlayerUID xuid = INVALID_XUID;
if (!TryParseWhitelistXuid(line.tokens[2], engine, &xuid))
{
return false;
}
if (ServerRuntime::Access::IsPlayerWhitelisted(xuid))
{
engine->LogWarn("That XUID is already whitelisted.");
return false;
}
const auto metadata = ServerRuntime::Access::WhitelistManager::BuildDefaultMetadata("Console");
const auto name = StringUtils::JoinTokens(line.tokens, 3);
if (!ServerRuntime::Access::AddWhitelistedPlayer(xuid, name, metadata))
{
engine->LogError("Failed to write whitelist entry.");
return false;
}
std::string message = "Whitelisted XUID " + ServerRuntime::Access::FormatXuid(xuid) + ".";
if (!name.empty())
{
message += " Name: " + name;
}
engine->LogInfo(message);
return true;
}
if (subcommand == "remove")
{
if (line.tokens.size() != 3)
{
engine->LogWarn("Usage: whitelist remove <xuid>");
return false;
}
PlayerUID xuid = INVALID_XUID;
if (!TryParseWhitelistXuid(line.tokens[2], engine, &xuid))
{
return false;
}
if (!ServerRuntime::Access::IsPlayerWhitelisted(xuid))
{
engine->LogWarn("That XUID is not whitelisted.");
return false;
}
if (!ServerRuntime::Access::RemoveWhitelistedPlayer(xuid))
{
engine->LogError("Failed to remove whitelist entry.");
return false;
}
engine->LogInfo("Removed XUID " + ServerRuntime::Access::FormatXuid(xuid) + " from the whitelist.");
return true;
}
engine->LogWarn(std::string("Usage: ") + kWhitelistUsage);
return false;
}
void CliCommandWhitelist::Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const
{
(void)engine;
if (out == nullptr)
{
return;
}
if (context.currentTokenIndex == 1)
{
SuggestLiteral("on", context, out);
SuggestLiteral("off", context, out);
SuggestLiteral("list", context, out);
SuggestLiteral("add", context, out);
SuggestLiteral("remove", context, out);
SuggestLiteral("reload", context, out);
return;
}
if (context.currentTokenIndex == 2 && context.parsed.tokens.size() >= 2 && StringUtils::ToLowerAscii(context.parsed.tokens[1]) == "remove")
{
std::vector<ServerRuntime::Access::WhitelistedPlayerEntry> entries;
if (!ServerRuntime::Access::SnapshotWhitelistedPlayers(&entries))
{
return;
}
for (const auto &entry : entries)
{
SuggestLiteral(entry.xuid, context, out);
}
}
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "..\IServerCliCommand.h"
namespace ServerRuntime
{
class CliCommandWhitelist : public IServerCliCommand
{
public:
const char *Name() const override;
const char *Usage() const override;
const char *Description() const override;
bool Execute(const ServerCliParsedLine &line, ServerCliEngine *engine) override;
void Complete(const ServerCliCompletionContext &context, const ServerCliEngine *engine, std::vector<std::string> *out) const override;
};
}

View File

@@ -0,0 +1,749 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{7CB40BFC-C8E4-4293-A22E-D2041348D5AF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>MinecraftServer</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
</ImportGroup>
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\obj\MinecraftServer\</IntDir>
<TargetName>Minecraft.Server</TargetName>
<LocalDebuggerWorkingDirectory>$(OutDir)</LocalDebuggerWorkingDirectory>
<LocalDebuggerCommandArguments>-port 25565 -bind 0.0.0.0 -name DedicatedServer</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\Minecraft.Server\$(Configuration)\obj\MinecraftServer\</IntDir>
<TargetName>Minecraft.Server</TargetName>
<LocalDebuggerWorkingDirectory>$(OutDir)</LocalDebuggerWorkingDirectory>
<LocalDebuggerCommandArguments>-port 25565 -bind 0.0.0.0 -name DedicatedServer</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderOutputFile>$(OutDir)MinecraftServer.pch</PrecompiledHeaderOutputFile>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<PreprocessorDefinitions>_LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_DEBUG;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64;MINECRAFT_SERVER_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\Minecraft.Client;..\Minecraft.Client\Windows64\Iggy\include;..\Minecraft.Client\Xbox\Sentient\Include;..\Minecraft.World\x64headers;..\include;$(ProjectDir)Windows64;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<MASM>
<UseSafeExceptionHandlers>false</UseSafeExceptionHandlers>
</MASM>
<ResourceCompile>
<PreprocessorDefinitions>_WINDOWS64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\Minecraft.Client;..\Minecraft.Client\Xbox;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<SubSystem>Console</SubSystem>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>d3d11.lib;XInput9_1_0.lib;wsock32.lib;legacy_stdio_definitions.lib;..\Minecraft.World\x64_Debug\Minecraft.World.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggy_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyperfmon_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyexpruntime_w64.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Input_d.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Storage_d.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Render_PC_d.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>powershell -ExecutionPolicy Bypass -File "$(ProjectDir)Windows64\postbuild_server.ps1" -OutDir "$(OutDir)." -ProjectRoot "$(ProjectDir).." -Configuration "$(Configuration)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderOutputFile>$(OutDir)MinecraftServer.pch</PrecompiledHeaderOutputFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeTypeInfo>true</RuntimeTypeInfo>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<PreprocessorDefinitions>_LARGE_WORLDS;_DEBUG_MENUS_ENABLED;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_WINDOWS64;MINECRAFT_SERVER_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\Minecraft.Client;..\Minecraft.Client\Windows64\Iggy\include;..\Minecraft.Client\Xbox\Sentient\Include;..\Minecraft.World\x64headers;..\include;$(ProjectDir)Windows64;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<MASM>
<UseSafeExceptionHandlers>false</UseSafeExceptionHandlers>
</MASM>
<ResourceCompile>
<PreprocessorDefinitions>_WINDOWS64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\Minecraft.Client;..\Minecraft.Client\Xbox;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<SubSystem>Console</SubSystem>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>d3d11.lib;XInput9_1_0.lib;wsock32.lib;legacy_stdio_definitions.lib;..\Minecraft.World\x64_Release\Minecraft.World.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggy_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyperfmon_w64.lib;..\Minecraft.Client\Windows64\Iggy\lib\iggyexpruntime_w64.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Input.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Storage.lib;..\Minecraft.Client\Windows64\4JLibs\libs\4J_Render_PC.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>powershell -ExecutionPolicy Bypass -File "$(ProjectDir)Windows64\postbuild_server.ps1" -OutDir "$(OutDir)." -ProjectRoot "$(ProjectDir).." -Configuration "$(Configuration)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Access\Access.cpp" />
<ClCompile Include="Access\BanManager.cpp" />
<ClCompile Include="Access\WhitelistManager.cpp" />
<ClCompile Include="ServerLogManager.cpp" />
<ClCompile Include="..\Minecraft.Client\AbstractTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\AchievementPopup.cpp" />
<ClCompile Include="..\Minecraft.Client\AchievementScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\AllowAllCuller.cpp" />
<ClCompile Include="..\Minecraft.Client\ArchiveFile.cpp" />
<ClCompile Include="..\Minecraft.Client\ArrowRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BatModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BatRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BeaconRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BlazeModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BlazeRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BoatModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BoatRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BookModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BossMobGuiInfo.cpp" />
<ClCompile Include="..\Minecraft.Client\BreakingItemParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\BubbleParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\BufferedImage.cpp" />
<ClCompile Include="..\Minecraft.Client\Button.cpp" />
<ClCompile Include="..\Minecraft.Client\Camera.cpp" />
<ClCompile Include="..\Minecraft.Client\CaveSpiderRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ChatScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ChestModel.cpp" />
<ClCompile Include="..\Minecraft.Client\ChestRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ChickenModel.cpp" />
<ClCompile Include="..\Minecraft.Client\ChickenRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Chunk.cpp" />
<ClCompile Include="..\Minecraft.Client\ClientConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\ClientConstants.cpp" />
<ClCompile Include="..\Minecraft.Client\ClockTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Audio\Consoles_SoundEngine.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Audio\SoundEngine.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Audio\SoundNames.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Colours\ColourTable.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\ConsoleGameMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Console_Utils.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Consoles_App.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCAudioFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCCapeFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCColourTableFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCGameRulesFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCGameRulesHeader.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCLocalisationFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCPack.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCSkinFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCTextureFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCUIDataFile.cpp" />
<ClCompile Include="..\include\lce_filesystem\lce_filesystem.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\AddEnchantmentRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\AddItemRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\ApplySchematicRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\BiomeOverride.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\CollectItemRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\CompleteAllRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\CompoundGameRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\ConsoleGenerateStructure.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\ConsoleSchematicFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\GameRule.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\GameRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\GameRuleManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelGenerationOptions.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelGenerators.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelRules.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelRuleset.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\NamedAreaRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\StartFeature.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\UpdatePlayerRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\UseTileRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionGenerateBox.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionPlaceBlock.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionPlaceContainer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionPlaceSpawner.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Leaderboards\LeaderboardInterface.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Leaderboards\LeaderboardManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Network\GameNetworkManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Network\PlatformNetworkManagerStub.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Telemetry\TelemetryManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Trial\TrialMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\AreaConstraint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\AreaHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\AreaTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ChangeStateConstraint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ChoiceTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\CompleteUsingItemTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ControllerTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\CraftTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\DiggerItemHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\EffectChangedTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\FullTutorial.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\FullTutorialActiveTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\FullTutorialMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\HorseChoiceTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\InfoTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\InputConstraint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\LookAtEntityHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\LookAtTileHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\PickupTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ProcedureCompoundTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ProgressFlagTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\RideEntityTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\StatTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TakeItemHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\Tutorial.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\UseItemTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\UseTileTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\XuiCraftingTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_AbstractContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_AnvilMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_BeaconMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_BrewingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_CommandBlockMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_ContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_CraftingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_CreativeMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_DispenserMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_EnchantingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_FireworksMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_FurnaceMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_HUD.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_HopperMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_HorseInventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_InventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_PauseMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_StartGame.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_TradingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIBitmapFont.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Chat.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_DebugUIConsole.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_DebugUIMarketingGuide.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Logo.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_MenuBackground.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Panorama.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_PressStartToPlay.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Tooltips.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_TutorialPopup.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Base.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_BeaconEffectButton.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_BitmapIcon.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Button.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_ButtonList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_CheckBox.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Cursor.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_DLCList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_DynamicLabel.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_EnchantmentBook.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_EnchantmentButton.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_HTMLLabel.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Label.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_LeaderboardList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_MinecraftHorse.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_MinecraftPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_PlayerList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_PlayerSkinPreview.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Progress.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_SaveList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Slider.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_SlotList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_SpaceIndicatorBar.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_TextInput.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_TexturePackList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIController.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIFontData.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIGroup.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UILayer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_AbstractContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_AnvilMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_BeaconMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_BrewingStandMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ConnectingProgress.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ControlsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_CraftingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_CreateWorldMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_CreativeMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Credits.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DLCMainMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DLCOffersMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DeathMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugCreateSchematic.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugOptions.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugOverlay.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugSetCamera.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DispenserMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_EULA.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_EnchantingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_EndPoem.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_FireworksMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_FullscreenProgress.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_FurnaceMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HUD.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HelpAndOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HopperMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HorseInventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HowToPlay.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HowToPlayMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InGameHostOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InGameInfoMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InGamePlayerOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Intro.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_JoinMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Keyboard.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LanguageSelector.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LaunchMoreOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LeaderboardsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LoadMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LoadOrJoinMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_MainMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_MessageBox.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_NewUpdateMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_PauseMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_QuadrantSignin.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ReinstallMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SaveMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsAudioMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsControlMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsGraphicsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsUIMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SignEntryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SkinSelectMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_TeleportMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Timer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_TradingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_TrialExitUpsell.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIString.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UITTFFont.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\adler32.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\compress.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\crc32.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\deflate.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzclose.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzlib.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzread.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzwrite.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\infback.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\inffast.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\inflate.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\inftrees.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\trees.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\uncompr.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\Common\zlib\zutil.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\CompassTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\ConfirmScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ConsoleInput.cpp" />
<ClCompile Include="..\Minecraft.Client\ControlsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\CowModel.cpp" />
<ClCompile Include="..\Minecraft.Client\CowRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\CreateWorldScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\CreeperModel.cpp" />
<ClCompile Include="..\Minecraft.Client\CreeperRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\CritParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\CritParticle2.cpp" />
<ClCompile Include="..\Minecraft.Client\Cube.cpp" />
<ClCompile Include="..\Minecraft.Client\DLCTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\DeathScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\DefaultRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\DefaultTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\DemoUser.cpp" />
<ClCompile Include="..\Minecraft.Client\DerivedServerLevel.cpp" />
<ClCompile Include="..\Minecraft.Client\DirtyChunkSorter.cpp" />
<ClCompile Include="..\Minecraft.Client\DispenserBootstrap.cpp" />
<ClCompile Include="..\Minecraft.Client\DistanceChunkSorter.cpp" />
<ClCompile Include="..\Minecraft.Client\DragonBreathParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\DragonModel.cpp" />
<ClCompile Include="..\Minecraft.Client\DripParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\EchantmentTableParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\EditBox.cpp" />
<ClCompile Include="..\Minecraft.Client\EnchantTableRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderChestRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderCrystalModel.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderCrystalRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderDragonRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\EndermanModel.cpp" />
<ClCompile Include="..\Minecraft.Client\EndermanRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityRenderDispatcher.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityTileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityTracker.cpp" />
<ClCompile Include="..\Minecraft.Client\ErrorScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ExperienceOrbRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ExplodeParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Extrax64Stubs.cpp" />
<ClCompile Include="..\Minecraft.Client\FallingTileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\FileTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\FireballRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\FireworksParticles.cpp" />
<ClCompile Include="..\Minecraft.Client\FishingHookRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\FlameParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\FolderTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\Font.cpp" />
<ClCompile Include="..\Minecraft.Client\FootstepParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Frustum.cpp" />
<ClCompile Include="..\Minecraft.Client\FrustumCuller.cpp" />
<ClCompile Include="..\Minecraft.Client\FrustumData.cpp" />
<ClCompile Include="..\Minecraft.Client\GameRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\GhastModel.cpp" />
<ClCompile Include="..\Minecraft.Client\GhastRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\GiantMobRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Gui.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiComponent.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiParticles.cpp" />
<ClCompile Include="..\Minecraft.Client\HeartParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\HorseRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\HttpTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\HugeExplosionParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\HugeExplosionSeedParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\HumanoidMobRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\HumanoidModel.cpp" />
<ClCompile Include="..\Minecraft.Client\InBedChatScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\Input.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemFrameRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemInHandRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemSpriteRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\JoinMultiplayerScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\KeyMapping.cpp" />
<ClCompile Include="..\Minecraft.Client\LargeChestModel.cpp" />
<ClCompile Include="..\Minecraft.Client\LavaParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\LavaSlimeModel.cpp" />
<ClCompile Include="..\Minecraft.Client\LavaSlimeRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LeashKnotModel.cpp" />
<ClCompile Include="..\Minecraft.Client\LeashKnotRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LevelRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Lighting.cpp" />
<ClCompile Include="..\Minecraft.Client\LightningBoltRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LivingEntityRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LocalPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\MemTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\MemoryTracker.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecartModel.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecartRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecartSpawnerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Minecraft.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecraftServer.cpp" />
<ClCompile Include="..\Minecraft.Client\Minimap.cpp" />
<ClCompile Include="..\Minecraft.Client\MobRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\MobSkinMemTextureProcessor.cpp" />
<ClCompile Include="..\Minecraft.Client\MobSkinTextureProcessor.cpp" />
<ClCompile Include="..\Minecraft.Client\MobSpawnerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Model.cpp" />
<ClCompile Include="..\Minecraft.Client\ModelHorse.cpp" />
<ClCompile Include="..\Minecraft.Client\ModelPart.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerChunkCache.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerGameMode.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerLevel.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerLocalPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\MushroomCowRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\NameEntryScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\NetherPortalParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\NoteParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\OcelotModel.cpp" />
<ClCompile Include="..\Minecraft.Client\OcelotRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\OffsettedRenderList.cpp" />
<ClCompile Include="..\Minecraft.Client\Options.cpp" />
<ClCompile Include="..\Minecraft.Client\OptionsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\PS3\PS3Extras\ShutdownManager.cpp" />
<ClCompile Include="..\Minecraft.Client\PaintingRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Particle.cpp" />
<ClCompile Include="..\Minecraft.Client\ParticleEngine.cpp" />
<ClCompile Include="..\Minecraft.Client\PauseScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\PendingConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\PigModel.cpp" />
<ClCompile Include="..\Minecraft.Client\PigRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\PistonPieceRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerChunkMap.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerCloudParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerList.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Polygon.cpp" />
<ClCompile Include="..\Minecraft.Client\PreStitchedTextureMap.cpp" />
<ClCompile Include="..\Minecraft.Client\ProgressRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\QuadrupedModel.cpp" />
<ClCompile Include="..\Minecraft.Client\Rect2i.cpp" />
<ClCompile Include="..\Minecraft.Client\RedDustParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\RemotePlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\RenameWorldScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\Screen.cpp" />
<ClCompile Include="..\Minecraft.Client\ScreenSizeCalculator.cpp" />
<ClCompile Include="..\Minecraft.Client\ScrolledSelectionList.cpp" />
<ClCompile Include="..\Minecraft.Client\SelectWorldScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerChunkCache.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerCommandDispatcher.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerLevel.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerLevelListener.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerPlayerGameMode.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerScoreboard.cpp" />
<ClCompile Include="..\Minecraft.Client\Settings.cpp" />
<ClCompile Include="..\Minecraft.Client\SheepFurModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SheepModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SheepRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SignModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SignRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SilverfishModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SilverfishRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SimpleIcon.cpp" />
<ClCompile Include="..\Minecraft.Client\SkeletonHeadModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SkeletonModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SkeletonRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SkiModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SkullTileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SlideButton.cpp" />
<ClCompile Include="..\Minecraft.Client\SlimeModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SlimeRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SmallButton.cpp" />
<ClCompile Include="..\Minecraft.Client\SmokeParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SnowManModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SnowManRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SnowShovelParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SpellParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SpiderModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SpiderRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SplashParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SquidModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SquidRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\StatsCounter.cpp" />
<ClCompile Include="..\Minecraft.Client\StatsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\StatsSyncher.cpp" />
<ClCompile Include="..\Minecraft.Client\StitchSlot.cpp" />
<ClCompile Include="..\Minecraft.Client\StitchedTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\Stitcher.cpp" />
<ClCompile Include="..\Minecraft.Client\StringTable.cpp" />
<ClCompile Include="..\Minecraft.Client\SuspendedParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SuspendedTownParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\TakeAnimationParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\TeleportCommand.cpp" />
<ClCompile Include="..\Minecraft.Client\TerrainParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Tesselator.cpp" />
<ClCompile Include="..\Minecraft.Client\TexOffs.cpp" />
<ClCompile Include="..\Minecraft.Client\Texture.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureAtlas.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureHolder.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureManager.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureMap.cpp" />
<ClCompile Include="..\Minecraft.Client\TexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\TexturePackRepository.cpp" />
<ClCompile Include="..\Minecraft.Client\Textures.cpp" />
<ClCompile Include="..\Minecraft.Client\TheEndPortalRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TileEntityRenderDispatcher.cpp" />
<ClCompile Include="..\Minecraft.Client\TileEntityRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Timer.cpp" />
<ClCompile Include="..\Minecraft.Client\TitleScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\TntMinecartRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TntRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TrackedEntity.cpp" />
<ClCompile Include="..\Minecraft.Client\User.cpp" />
<ClCompile Include="..\Minecraft.Client\Vertex.cpp" />
<ClCompile Include="..\Minecraft.Client\VideoSettingsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ViewportCuller.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerGolemModel.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerGolemRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerModel.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerZombieModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WaterDropParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Iggy\gdraw\gdraw_d3d11.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\KeyboardMouseInput.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Leaderboards\WindowsLeaderboardManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\PostProcesser.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Windows64_App.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Windows64_Minecraft.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Windows64_UIController.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Network\WinsockNetLayer.cpp" />
<ClCompile Include="..\Minecraft.Client\WitchModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WitchRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WitherBossModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WitherBossRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WitherSkullRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WolfModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WolfRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WstringLookup.cpp" />
<ClCompile Include="..\Minecraft.Client\Xbox\Network\NetworkPlayerXbox.cpp" />
<ClCompile Include="..\Minecraft.Client\ZombieModel.cpp" />
<ClCompile Include="..\Minecraft.Client\ZombieRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\compat_shims.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="Console\ServerCli.cpp" />
<ClCompile Include="Console\ServerCliInput.cpp" />
<ClCompile Include="Console\commands\ban\CliCommandBan.cpp" />
<ClCompile Include="Console\commands\ban-ip\CliCommandBanIp.cpp" />
<ClCompile Include="Console\commands\ban-list\CliCommandBanList.cpp" />
<ClCompile Include="Console\commands\defaultgamemode\CliCommandDefaultGamemode.cpp" />
<ClCompile Include="Console\commands\enchant\CliCommandEnchant.cpp" />
<ClCompile Include="Console\commands\experience\CliCommandExperience.cpp" />
<ClCompile Include="Console\commands\gamemode\CliCommandGamemode.cpp" />
<ClCompile Include="Console\commands\give\CliCommandGive.cpp" />
<ClCompile Include="Console\commands\help\CliCommandHelp.cpp" />
<ClCompile Include="Console\commands\kill\CliCommandKill.cpp" />
<ClCompile Include="Console\commands\list\CliCommandList.cpp" />
<ClCompile Include="Console\commands\pardon\CliCommandPardon.cpp" />
<ClCompile Include="Console\commands\pardon-ip\CliCommandPardonIp.cpp" />
<ClCompile Include="Console\commands\stop\CliCommandStop.cpp" />
<ClCompile Include="Console\commands\time\CliCommandTime.cpp" />
<ClCompile Include="Console\commands\tp\CliCommandTp.cpp" />
<ClCompile Include="Console\commands\weather\CliCommandWeather.cpp" />
<ClCompile Include="Console\commands\whitelist\CliCommandWhitelist.cpp" />
<ClCompile Include="Console\ServerCliEngine.cpp" />
<ClCompile Include="Console\ServerCliParser.cpp" />
<ClCompile Include="Console\ServerCliRegistry.cpp" />
<ClCompile Include="Common\FileUtils.cpp" />
<ClCompile Include="Common\StringUtils.cpp" />
<ClCompile Include="..\Minecraft.Client\glWrapper.cpp" />
<ClCompile Include="ServerLogger.cpp" />
<ClCompile Include="ServerProperties.cpp" />
<ClCompile Include="vendor\linenoise\linenoise.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="WorldManager.cpp" />
<ClCompile Include="..\Minecraft.Client\stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\stubs.cpp" />
<ClCompile Include="Windows64\ServerMain.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Access\Access.h" />
<ClInclude Include="Access\BanManager.h" />
<ClInclude Include="Access\WhitelistManager.h" />
<ClInclude Include="Console\ServerCli.h" />
<ClInclude Include="Console\ServerCliInput.h" />
<ClInclude Include="Console\commands\ban\CliCommandBan.h" />
<ClInclude Include="Console\commands\ban-ip\CliCommandBanIp.h" />
<ClInclude Include="Console\commands\ban-list\CliCommandBanList.h" />
<ClInclude Include="Console\commands\defaultgamemode\CliCommandDefaultGamemode.h" />
<ClInclude Include="Console\commands\enchant\CliCommandEnchant.h" />
<ClInclude Include="Console\commands\experience\CliCommandExperience.h" />
<ClInclude Include="Console\commands\gamemode\CliCommandGamemode.h" />
<ClInclude Include="Console\commands\give\CliCommandGive.h" />
<ClInclude Include="Console\commands\help\CliCommandHelp.h" />
<ClInclude Include="Console\commands\CommandParsing.h" />
<ClInclude Include="Console\commands\kill\CliCommandKill.h" />
<ClInclude Include="Console\commands\list\CliCommandList.h" />
<ClInclude Include="Console\commands\pardon\CliCommandPardon.h" />
<ClInclude Include="Console\commands\pardon-ip\CliCommandPardonIp.h" />
<ClInclude Include="Console\commands\stop\CliCommandStop.h" />
<ClInclude Include="Console\commands\time\CliCommandTime.h" />
<ClInclude Include="Console\commands\tp\CliCommandTp.h" />
<ClInclude Include="Console\commands\weather\CliCommandWeather.h" />
<ClInclude Include="Console\commands\whitelist\CliCommandWhitelist.h" />
<ClInclude Include="Console\commands\IServerCliCommand.h" />
<ClInclude Include="Console\ServerCliEngine.h" />
<ClInclude Include="Console\ServerCliParser.h" />
<ClInclude Include="Console\ServerCliRegistry.h" />
<ClInclude Include="Common\FileUtils.h" />
<ClInclude Include="Common\AccessStorageUtils.h" />
<ClInclude Include="Common\NetworkUtils.h" />
<ClInclude Include="Common\StringUtils.h" />
<ClInclude Include="ServerLogger.h" />
<ClInclude Include="ServerLogManager.h" />
<ClInclude Include="ServerProperties.h" />
<ClInclude Include="vendor\linenoise\linenoise.h" />
<ClInclude Include="WorldManager.h" />
</ItemGroup>
<ItemGroup>
<MASM Include="..\Minecraft.Client\iob_shim.asm" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\Minecraft.Client\Xbox\MinecraftWindows.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Minecraft.World\Minecraft.World.vcxproj">
<Project>{F046C3CE-9749-4823-B32B-D9CC10B1A2C8}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
</Project>

View File

@@ -0,0 +1,737 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Server">
<UniqueIdentifier>{A8A47C24-66C0-4912-9D34-2CBF87F1D707}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Console">
<UniqueIdentifier>{39B037A0-9B57-454A-AF34-7D9164E22A0F}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Console\Commands">
<UniqueIdentifier>{7C28D123-0DA3-4B17-84C0-E326F5A75740}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Access">
<UniqueIdentifier>{29AB58D1-E8A9-465A-B3EA-BC5E9110A7A1}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Common">
<UniqueIdentifier>{BC6FD58B-1A40-45FE-B8D9-1A087C25126D}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Vendor">
<UniqueIdentifier>{3E4D5A41-CAB8-4A10-82B5-8B2AE2E25CB2}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ServerLogger.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="ServerLogManager.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="ServerProperties.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="WorldManager.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="Console\ServerCli.cpp">
<Filter>Server\Console</Filter>
</ClCompile>
<ClCompile Include="Console\ServerCliEngine.cpp">
<Filter>Server\Console</Filter>
</ClCompile>
<ClCompile Include="Console\ServerCliParser.cpp">
<Filter>Server\Console</Filter>
</ClCompile>
<ClCompile Include="Console\ServerCliRegistry.cpp">
<Filter>Server\Console</Filter>
</ClCompile>
<ClCompile Include="Common\StringUtils.cpp">
<Filter>Server\Common</Filter>
</ClCompile>
<ClCompile Include="vendor\linenoise\linenoise.c">
<Filter>Server\Vendor</Filter>
</ClCompile>
<ClCompile Include="Windows64\ServerMain.cpp">
<Filter>Server</Filter>
</ClCompile>
<ClCompile Include="Access\Access.cpp">
<Filter>Server\Access</Filter>
</ClCompile>
<ClCompile Include="Access\BanManager.cpp">
<Filter>Server\Access</Filter>
</ClCompile>
<ClCompile Include="Access\WhitelistManager.cpp">
<Filter>Server\Access</Filter>
</ClCompile>
<ClCompile Include="Common\FileUtils.cpp">
<Filter>Server\Common</Filter>
</ClCompile>
<ClCompile Include="..\Minecraft.Client\AbstractTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\AchievementPopup.cpp" />
<ClCompile Include="..\Minecraft.Client\AchievementScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\AllowAllCuller.cpp" />
<ClCompile Include="..\Minecraft.Client\ArchiveFile.cpp" />
<ClCompile Include="..\Minecraft.Client\ArrowRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BatModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BatRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BeaconRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BlazeModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BlazeRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BoatModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BoatRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\BookModel.cpp" />
<ClCompile Include="..\Minecraft.Client\BossMobGuiInfo.cpp" />
<ClCompile Include="..\Minecraft.Client\BreakingItemParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\BubbleParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\BufferedImage.cpp" />
<ClCompile Include="..\Minecraft.Client\Button.cpp" />
<ClCompile Include="..\Minecraft.Client\Camera.cpp" />
<ClCompile Include="..\Minecraft.Client\CaveSpiderRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ChatScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ChestModel.cpp" />
<ClCompile Include="..\Minecraft.Client\ChestRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ChickenModel.cpp" />
<ClCompile Include="..\Minecraft.Client\ChickenRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Chunk.cpp" />
<ClCompile Include="..\Minecraft.Client\ClientConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\ClientConstants.cpp" />
<ClCompile Include="..\Minecraft.Client\ClockTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Audio\Consoles_SoundEngine.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Audio\SoundEngine.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Audio\SoundNames.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Colours\ColourTable.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\ConsoleGameMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Console_Utils.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Consoles_App.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCAudioFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCCapeFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCColourTableFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCGameRulesFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCGameRulesHeader.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCLocalisationFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCPack.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCSkinFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCTextureFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\DLC\DLCUIDataFile.cpp" />
<ClCompile Include="..\include\lce_filesystem\lce_filesystem.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\AddEnchantmentRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\AddItemRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\ApplySchematicRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\BiomeOverride.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\CollectItemRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\CompleteAllRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\CompoundGameRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\ConsoleGenerateStructure.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\ConsoleSchematicFile.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\GameRule.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\GameRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\GameRuleManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelGenerationOptions.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelGenerators.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelRules.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\LevelRuleset.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\NamedAreaRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\StartFeature.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\UpdatePlayerRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\UseTileRuleDefinition.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionGenerateBox.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionPlaceBlock.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionPlaceContainer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\GameRules\XboxStructureActionPlaceSpawner.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Leaderboards\LeaderboardInterface.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Leaderboards\LeaderboardManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Network\GameNetworkManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Network\PlatformNetworkManagerStub.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Telemetry\TelemetryManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Trial\TrialMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\AreaConstraint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\AreaHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\AreaTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ChangeStateConstraint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ChoiceTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\CompleteUsingItemTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ControllerTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\CraftTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\DiggerItemHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\EffectChangedTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\FullTutorial.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\FullTutorialActiveTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\FullTutorialMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\HorseChoiceTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\InfoTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\InputConstraint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\LookAtEntityHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\LookAtTileHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\PickupTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ProcedureCompoundTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\ProgressFlagTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\RideEntityTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\StatTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TakeItemHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\Tutorial.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialHint.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialMode.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\TutorialTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\UseItemTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\UseTileTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\Tutorial\XuiCraftingTask.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_AbstractContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_AnvilMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_BeaconMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_BrewingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_CommandBlockMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_ContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_CraftingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_CreativeMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_DispenserMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_EnchantingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_FireworksMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_FurnaceMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_HUD.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_HopperMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_HorseInventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_InventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_PauseMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_StartGame.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\IUIScene_TradingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIBitmapFont.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Chat.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_DebugUIConsole.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_DebugUIMarketingGuide.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Logo.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_MenuBackground.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Panorama.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_PressStartToPlay.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_Tooltips.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIComponent_TutorialPopup.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Base.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_BeaconEffectButton.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_BitmapIcon.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Button.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_ButtonList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_CheckBox.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Cursor.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_DLCList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_DynamicLabel.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_EnchantmentBook.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_EnchantmentButton.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_HTMLLabel.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Label.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_LeaderboardList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_MinecraftHorse.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_MinecraftPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_PlayerList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_PlayerSkinPreview.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Progress.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_SaveList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_Slider.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_SlotList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_SpaceIndicatorBar.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_TextInput.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIControl_TexturePackList.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIController.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIFontData.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIGroup.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UILayer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_AbstractContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_AnvilMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_BeaconMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_BrewingStandMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ConnectingProgress.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ContainerMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ControlsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_CraftingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_CreateWorldMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_CreativeMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Credits.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DLCMainMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DLCOffersMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DeathMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugCreateSchematic.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugOptions.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugOverlay.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DebugSetCamera.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_DispenserMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_EULA.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_EnchantingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_EndPoem.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_FireworksMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_FullscreenProgress.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_FurnaceMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HUD.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HelpAndOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HopperMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HorseInventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HowToPlay.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_HowToPlayMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InGameHostOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InGameInfoMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InGamePlayerOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Intro.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_InventoryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_JoinMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Keyboard.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LanguageSelector.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LaunchMoreOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LeaderboardsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LoadMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_LoadOrJoinMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_MainMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_MessageBox.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_NewUpdateMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_PauseMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_QuadrantSignin.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_ReinstallMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SaveMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsAudioMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsControlMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsGraphicsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsOptionsMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SettingsUIMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SignEntryMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_SkinSelectMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_TeleportMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_Timer.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_TradingMenu.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIScene_TrialExitUpsell.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UIString.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\UI\UITTFFont.cpp" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\adler32.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\compress.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\crc32.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\deflate.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzclose.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzlib.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzread.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\gzwrite.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\infback.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\inffast.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\inflate.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\inftrees.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\trees.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\uncompr.c" />
<ClCompile Include="..\Minecraft.Client\Common\zlib\zutil.c" />
<ClCompile Include="..\Minecraft.Client\CompassTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\ConfirmScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ConsoleInput.cpp" />
<ClCompile Include="..\Minecraft.Client\ControlsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\CowModel.cpp" />
<ClCompile Include="..\Minecraft.Client\CowRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\CreateWorldScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\CreeperModel.cpp" />
<ClCompile Include="..\Minecraft.Client\CreeperRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\CritParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\CritParticle2.cpp" />
<ClCompile Include="..\Minecraft.Client\Cube.cpp" />
<ClCompile Include="..\Minecraft.Client\DLCTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\DeathScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\DefaultRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\DefaultTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\DemoUser.cpp" />
<ClCompile Include="..\Minecraft.Client\DerivedServerLevel.cpp" />
<ClCompile Include="..\Minecraft.Client\DirtyChunkSorter.cpp" />
<ClCompile Include="..\Minecraft.Client\DispenserBootstrap.cpp" />
<ClCompile Include="..\Minecraft.Client\DistanceChunkSorter.cpp" />
<ClCompile Include="..\Minecraft.Client\DragonBreathParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\DragonModel.cpp" />
<ClCompile Include="..\Minecraft.Client\DripParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\EchantmentTableParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\EditBox.cpp" />
<ClCompile Include="..\Minecraft.Client\EnchantTableRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderChestRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderCrystalModel.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderCrystalRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderDragonRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EnderParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\EndermanModel.cpp" />
<ClCompile Include="..\Minecraft.Client\EndermanRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityRenderDispatcher.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityTileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\EntityTracker.cpp" />
<ClCompile Include="..\Minecraft.Client\ErrorScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ExperienceOrbRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ExplodeParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Extrax64Stubs.cpp" />
<ClCompile Include="..\Minecraft.Client\FallingTileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\FileTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\FireballRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\FireworksParticles.cpp" />
<ClCompile Include="..\Minecraft.Client\FishingHookRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\FlameParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\FolderTexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\Font.cpp" />
<ClCompile Include="..\Minecraft.Client\FootstepParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Frustum.cpp" />
<ClCompile Include="..\Minecraft.Client\FrustumCuller.cpp" />
<ClCompile Include="..\Minecraft.Client\FrustumData.cpp" />
<ClCompile Include="..\Minecraft.Client\GameRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\GhastModel.cpp" />
<ClCompile Include="..\Minecraft.Client\GhastRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\GiantMobRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Gui.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiComponent.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiMessage.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\GuiParticles.cpp" />
<ClCompile Include="..\Minecraft.Client\HeartParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\HorseRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\HttpTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\HugeExplosionParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\HugeExplosionSeedParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\HumanoidMobRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\HumanoidModel.cpp" />
<ClCompile Include="..\Minecraft.Client\InBedChatScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\Input.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemFrameRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemInHandRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\ItemSpriteRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\JoinMultiplayerScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\KeyMapping.cpp" />
<ClCompile Include="..\Minecraft.Client\LargeChestModel.cpp" />
<ClCompile Include="..\Minecraft.Client\LavaParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\LavaSlimeModel.cpp" />
<ClCompile Include="..\Minecraft.Client\LavaSlimeRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LeashKnotModel.cpp" />
<ClCompile Include="..\Minecraft.Client\LeashKnotRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LevelRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Lighting.cpp" />
<ClCompile Include="..\Minecraft.Client\LightningBoltRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LivingEntityRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\LocalPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\MemTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\MemoryTracker.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecartModel.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecartRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecartSpawnerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Minecraft.cpp" />
<ClCompile Include="..\Minecraft.Client\MinecraftServer.cpp" />
<ClCompile Include="..\Minecraft.Client\Minimap.cpp" />
<ClCompile Include="..\Minecraft.Client\MobRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\MobSkinMemTextureProcessor.cpp" />
<ClCompile Include="..\Minecraft.Client\MobSkinTextureProcessor.cpp" />
<ClCompile Include="..\Minecraft.Client\MobSpawnerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Model.cpp" />
<ClCompile Include="..\Minecraft.Client\ModelHorse.cpp" />
<ClCompile Include="..\Minecraft.Client\ModelPart.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerChunkCache.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerGameMode.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerLevel.cpp" />
<ClCompile Include="..\Minecraft.Client\MultiPlayerLocalPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\MushroomCowRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\NameEntryScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\NetherPortalParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\NoteParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\OcelotModel.cpp" />
<ClCompile Include="..\Minecraft.Client\OcelotRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\OffsettedRenderList.cpp" />
<ClCompile Include="..\Minecraft.Client\Options.cpp" />
<ClCompile Include="..\Minecraft.Client\OptionsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\PS3\PS3Extras\ShutdownManager.cpp" />
<ClCompile Include="..\Minecraft.Client\PaintingRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Particle.cpp" />
<ClCompile Include="..\Minecraft.Client\ParticleEngine.cpp" />
<ClCompile Include="..\Minecraft.Client\PauseScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\PendingConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\PigModel.cpp" />
<ClCompile Include="..\Minecraft.Client\PigRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\PistonPieceRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerChunkMap.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerCloudParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerList.cpp" />
<ClCompile Include="..\Minecraft.Client\PlayerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Polygon.cpp" />
<ClCompile Include="..\Minecraft.Client\PreStitchedTextureMap.cpp" />
<ClCompile Include="..\Minecraft.Client\ProgressRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\QuadrupedModel.cpp" />
<ClCompile Include="..\Minecraft.Client\Rect2i.cpp" />
<ClCompile Include="..\Minecraft.Client\RedDustParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\RemotePlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\RenameWorldScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\Screen.cpp" />
<ClCompile Include="..\Minecraft.Client\ScreenSizeCalculator.cpp" />
<ClCompile Include="..\Minecraft.Client\ScrolledSelectionList.cpp" />
<ClCompile Include="..\Minecraft.Client\SelectWorldScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerChunkCache.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerCommandDispatcher.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerConnection.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerLevel.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerLevelListener.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerPlayer.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerPlayerGameMode.cpp" />
<ClCompile Include="..\Minecraft.Client\ServerScoreboard.cpp" />
<ClCompile Include="..\Minecraft.Client\Settings.cpp" />
<ClCompile Include="..\Minecraft.Client\SheepFurModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SheepModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SheepRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SignModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SignRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SilverfishModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SilverfishRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SimpleIcon.cpp" />
<ClCompile Include="..\Minecraft.Client\SkeletonHeadModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SkeletonModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SkeletonRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SkiModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SkullTileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SlideButton.cpp" />
<ClCompile Include="..\Minecraft.Client\SlimeModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SlimeRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SmallButton.cpp" />
<ClCompile Include="..\Minecraft.Client\SmokeParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SnowManModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SnowManRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SnowShovelParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SpellParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SpiderModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SpiderRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\SplashParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SquidModel.cpp" />
<ClCompile Include="..\Minecraft.Client\SquidRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\StatsCounter.cpp" />
<ClCompile Include="..\Minecraft.Client\StatsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\StatsSyncher.cpp" />
<ClCompile Include="..\Minecraft.Client\StitchSlot.cpp" />
<ClCompile Include="..\Minecraft.Client\StitchedTexture.cpp" />
<ClCompile Include="..\Minecraft.Client\Stitcher.cpp" />
<ClCompile Include="..\Minecraft.Client\StringTable.cpp" />
<ClCompile Include="..\Minecraft.Client\SuspendedParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\SuspendedTownParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\TakeAnimationParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\TeleportCommand.cpp" />
<ClCompile Include="..\Minecraft.Client\TerrainParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Tesselator.cpp" />
<ClCompile Include="..\Minecraft.Client\TexOffs.cpp" />
<ClCompile Include="..\Minecraft.Client\Texture.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureAtlas.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureHolder.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureManager.cpp" />
<ClCompile Include="..\Minecraft.Client\TextureMap.cpp" />
<ClCompile Include="..\Minecraft.Client\TexturePack.cpp" />
<ClCompile Include="..\Minecraft.Client\TexturePackRepository.cpp" />
<ClCompile Include="..\Minecraft.Client\Textures.cpp" />
<ClCompile Include="..\Minecraft.Client\TheEndPortalRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TileEntityRenderDispatcher.cpp" />
<ClCompile Include="..\Minecraft.Client\TileEntityRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TileRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\Timer.cpp" />
<ClCompile Include="..\Minecraft.Client\TitleScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\TntMinecartRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TntRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\TrackedEntity.cpp" />
<ClCompile Include="..\Minecraft.Client\User.cpp" />
<ClCompile Include="..\Minecraft.Client\Vertex.cpp" />
<ClCompile Include="..\Minecraft.Client\VideoSettingsScreen.cpp" />
<ClCompile Include="..\Minecraft.Client\ViewportCuller.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerGolemModel.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerGolemRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerModel.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\VillagerZombieModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WaterDropParticle.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Iggy\gdraw\gdraw_d3d11.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\KeyboardMouseInput.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Leaderboards\WindowsLeaderboardManager.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\PostProcesser.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Windows64_App.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Windows64_Minecraft.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Windows64_UIController.cpp" />
<ClCompile Include="..\Minecraft.Client\Windows64\Network\WinsockNetLayer.cpp" />
<ClCompile Include="..\Minecraft.Client\WitchModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WitchRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WitherBossModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WitherBossRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WitherSkullRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WolfModel.cpp" />
<ClCompile Include="..\Minecraft.Client\WolfRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\WstringLookup.cpp" />
<ClCompile Include="..\Minecraft.Client\Xbox\Network\NetworkPlayerXbox.cpp" />
<ClCompile Include="..\Minecraft.Client\ZombieModel.cpp" />
<ClCompile Include="..\Minecraft.Client\ZombieRenderer.cpp" />
<ClCompile Include="..\Minecraft.Client\compat_shims.cpp" />
<ClCompile Include="Console\ServerCliInput.cpp" />
<ClCompile Include="..\Minecraft.Client\glWrapper.cpp" />
<ClCompile Include="..\Minecraft.Client\stdafx.cpp" />
<ClCompile Include="..\Minecraft.Client\stubs.cpp" />
<ClCompile Include="Console\commands\ban\CliCommandBan.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\ban-ip\CliCommandBanIp.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\ban-list\CliCommandBanList.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\defaultgamemode\CliCommandDefaultGamemode.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\enchant\CliCommandEnchant.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\experience\CliCommandExperience.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\gamemode\CliCommandGamemode.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\give\CliCommandGive.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\help\CliCommandHelp.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\kill\CliCommandKill.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\list\CliCommandList.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\pardon\CliCommandPardon.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\pardon-ip\CliCommandPardonIp.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\stop\CliCommandStop.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\time\CliCommandTime.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\tp\CliCommandTp.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\weather\CliCommandWeather.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
<ClCompile Include="Console\commands\whitelist\CliCommandWhitelist.cpp">
<Filter>Server\Console\Commands</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Console\ServerCli.h">
<Filter>Server\Console</Filter>
</ClInclude>
<ClInclude Include="Console\ServerCliEngine.h">
<Filter>Server\Console</Filter>
</ClInclude>
<ClInclude Include="Console\ServerCliParser.h">
<Filter>Server\Console</Filter>
</ClInclude>
<ClInclude Include="Console\ServerCliRegistry.h">
<Filter>Server\Console</Filter>
</ClInclude>
<ClInclude Include="Access\Access.h">
<Filter>Server\Access</Filter>
</ClInclude>
<ClInclude Include="Access\BanManager.h">
<Filter>Server\Access</Filter>
</ClInclude>
<ClInclude Include="Access\WhitelistManager.h">
<Filter>Server\Access</Filter>
</ClInclude>
<ClInclude Include="Common\FileUtils.h">
<Filter>Server\Common</Filter>
</ClInclude>
<ClInclude Include="Common\AccessStorageUtils.h">
<Filter>Server\Common</Filter>
</ClInclude>
<ClInclude Include="Common\NetworkUtils.h">
<Filter>Server\Common</Filter>
</ClInclude>
<ClInclude Include="Common\StringUtils.h">
<Filter>Server\Common</Filter>
</ClInclude>
<ClInclude Include="ServerLogger.h">
<Filter>Server</Filter>
</ClInclude>
<ClInclude Include="ServerLogManager.h">
<Filter>Server</Filter>
</ClInclude>
<ClInclude Include="ServerProperties.h">
<Filter>Server</Filter>
</ClInclude>
<ClInclude Include="vendor\linenoise\linenoise.h">
<Filter>Server\Vendor</Filter>
</ClInclude>
<ClInclude Include="WorldManager.h">
<Filter>Server</Filter>
</ClInclude>
<ClInclude Include="Console\ServerCliInput.h" />
<ClInclude Include="Console\commands\ban\CliCommandBan.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\ban-ip\CliCommandBanIp.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\ban-list\CliCommandBanList.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\defaultgamemode\CliCommandDefaultGamemode.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\enchant\CliCommandEnchant.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\experience\CliCommandExperience.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\gamemode\CliCommandGamemode.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\give\CliCommandGive.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\help\CliCommandHelp.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\CommandParsing.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\kill\CliCommandKill.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\list\CliCommandList.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\pardon\CliCommandPardon.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\pardon-ip\CliCommandPardonIp.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\stop\CliCommandStop.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\time\CliCommandTime.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\tp\CliCommandTp.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\weather\CliCommandWeather.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\whitelist\CliCommandWhitelist.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
<ClInclude Include="Console\commands\IServerCliCommand.h">
<Filter>Server\Console\Commands</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\Minecraft.Client\Xbox\MinecraftWindows.rc" />
</ItemGroup>
<ItemGroup>
<MASM Include="..\Minecraft.Client\iob_shim.asm" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,402 @@
#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]);
}
}
}

View File

@@ -0,0 +1,127 @@
#pragma once
#include <string>
#include <stdarg.h>
#include "..\Minecraft.World\DisconnectPacket.h"
namespace ServerRuntime
{
namespace ServerLogManager
{
/**
* Identifies why the dedicated server rejected a TCP connection before login completed
* ログイン完了前にTCP接続を拒否した理由
*/
enum ETcpRejectReason
{
eTcpRejectReason_BannedIp = 0,
eTcpRejectReason_GameNotReady,
eTcpRejectReason_ServerFull
};
/**
* Identifies why the dedicated server rejected a player during login validation
* ログイン検証中にプレイヤーを拒否した理由
*/
enum ELoginRejectReason
{
eLoginRejectReason_BannedXuid = 0,
eLoginRejectReason_NotWhitelisted,
eLoginRejectReason_DuplicateXuid,
eLoginRejectReason_DuplicateName
};
/**
* Returns `true` when client-side debug logs should be redirected into the dedicated server logger
* dedicated server時にclient側デバッグログを転送すかどうか
*/
bool ShouldForwardClientDebugLogs();
/**
* Formats and forwards `CMinecraftApp::DebugPrintf` output through the dedicated server logger
* CMinecraftApp::DebugPrintf の出力を専用サーバーロガーへ転送
*/
void ForwardClientAppDebugLogV(const char *format, va_list args);
/**
* Formats and forwards `CMinecraftApp::DebugPrintf(int user, ...)` output through the dedicated server logger
* CMinecraftApp::DebugPrintf(int user, ...) の出力を専用サーバーロガーへ転送
*/
void ForwardClientUserDebugLogV(int user, const char *format, va_list args);
/**
* Formats and forwards legacy `DebugSpew` output through the dedicated server logger
* 従来の DebugSpew 出力を専用サーバーロガーへ転送
*/
void ForwardClientDebugSpewLogV(const char *format, va_list args);
/**
* Clears cached connection metadata before the dedicated server starts accepting players
* 接続ログ管理用のキャッシュを初期化
*/
void Initialize();
/**
* Releases cached connection metadata after the dedicated server stops
* 接続ログ管理用のキャッシュを停止時に破棄
*/
void Shutdown();
/**
* **Log Incoming TCP Connection**
*
* Emits a named log for a raw TCP accept before smallId assignment finishes
* smallId割り当て前のTCP接続を記録
*/
void OnIncomingTcpConnection(const char *ip);
/**
* Emits a named log for a TCP connection rejected before login starts
* ログイン開始前に拒否したTCP接続を記録
*/
void OnRejectedTcpConnection(const char *ip, ETcpRejectReason reason);
/**
* Stores the remote IP for the assigned smallId and logs the accepted transport connection
* 割り当て済みsmallIdに対接続IPを保存して記録
*/
void OnAcceptedTcpConnection(unsigned char smallId, const char *ip);
/**
* Associates a player name with the connection and emits the accepted login log
* 接続にプレイヤー名を関連付けてログイン成功を記録
*/
void OnAcceptedPlayerLogin(unsigned char smallId, const std::wstring &playerName);
/**
* Emits a named login rejection log and clears cached metadata for that smallId
* ログイン拒否を記録し対象smallIdのキャッシュを破棄
*/
void OnRejectedPlayerLogin(unsigned char smallId, const std::wstring &playerName, ELoginRejectReason reason);
/**
* Emits a named disconnect log using cached connection metadata and then clears that entry
* 接続キャッシュを使って切断ログを出しその後で破棄
*/
void OnPlayerDisconnected(
unsigned char smallId,
const std::wstring &playerName,
DisconnectPacket::eDisconnectReason reason,
bool initiatedByServer);
/**
* Reads the cached remote IP for a live smallId without consuming the entry
* Eventually, this should be implemented in a separate class or on the `Minecraft.Client` side instead.
*
* 指定smallIdの接続IPをキャッシュから参照する
*/
bool TryGetConnectionRemoteIp(unsigned char smallId, std::string *outIp);
/**
* Removes any remembered IP or player name for the specified smallId
* 指定smallIdに紐づく接続キャッシュを消去
*/
void ClearConnection(unsigned char smallId);
}
}

View File

@@ -0,0 +1,258 @@
#include "stdafx.h"
#include "ServerLogger.h"
#include "Common\\StringUtils.h"
#include "vendor\\linenoise\\linenoise.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
namespace ServerRuntime
{
static volatile LONG g_minLogLevel = (LONG)eServerLogLevel_Info;
static const char *NormalizeCategory(const char *category)
{
if (category == NULL || category[0] == 0)
{
return "server";
}
return category;
}
static const char *LogLevelToString(EServerLogLevel level)
{
switch (level)
{
case eServerLogLevel_Debug:
return "DEBUG";
case eServerLogLevel_Info:
return "INFO";
case eServerLogLevel_Warn:
return "WARN";
case eServerLogLevel_Error:
return "ERROR";
default:
return "INFO";
}
}
static WORD LogLevelToColor(EServerLogLevel level)
{
switch (level)
{
case eServerLogLevel_Debug:
return FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
case eServerLogLevel_Warn:
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
case eServerLogLevel_Error:
return FOREGROUND_RED | FOREGROUND_INTENSITY;
case eServerLogLevel_Info:
default:
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
}
}
static void BuildTimestamp(char *buffer, size_t bufferSize)
{
if (buffer == NULL || bufferSize == 0)
{
return;
}
SYSTEMTIME localTime;
GetLocalTime(&localTime);
sprintf_s(
buffer,
bufferSize,
"%04u-%02u-%02u %02u:%02u:%02u.%03u",
(unsigned)localTime.wYear,
(unsigned)localTime.wMonth,
(unsigned)localTime.wDay,
(unsigned)localTime.wHour,
(unsigned)localTime.wMinute,
(unsigned)localTime.wSecond,
(unsigned)localTime.wMilliseconds);
}
static bool ShouldLog(EServerLogLevel level)
{
return ((LONG)level >= g_minLogLevel);
}
static void WriteLogLine(EServerLogLevel level, const char *category, const char *message)
{
if (!ShouldLog(level))
{
return;
}
linenoiseExternalWriteBegin();
const char *safeCategory = NormalizeCategory(category);
const char *safeMessage = (message != NULL) ? message : "";
char timestamp[32] = {};
BuildTimestamp(timestamp, sizeof(timestamp));
HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO originalInfo;
bool hasColorConsole = false;
if (stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != NULL)
{
if (GetConsoleScreenBufferInfo(stdoutHandle, &originalInfo))
{
hasColorConsole = true;
SetConsoleTextAttribute(stdoutHandle, LogLevelToColor(level));
}
}
printf(
"[%s][%s][%s] %s\n",
timestamp,
LogLevelToString(level),
safeCategory,
safeMessage);
fflush(stdout);
if (hasColorConsole)
{
SetConsoleTextAttribute(stdoutHandle, originalInfo.wAttributes);
}
linenoiseExternalWriteEnd();
}
static void WriteLogLineV(EServerLogLevel level, const char *category, const char *format, va_list args)
{
char messageBuffer[2048] = {};
if (format == NULL)
{
WriteLogLine(level, category, "");
return;
}
vsnprintf_s(messageBuffer, sizeof(messageBuffer), _TRUNCATE, format, args);
WriteLogLine(level, category, messageBuffer);
}
bool TryParseServerLogLevel(const char *value, EServerLogLevel *outLevel)
{
if (value == NULL || outLevel == NULL)
{
return false;
}
if (_stricmp(value, "debug") == 0)
{
*outLevel = eServerLogLevel_Debug;
return true;
}
if (_stricmp(value, "info") == 0)
{
*outLevel = eServerLogLevel_Info;
return true;
}
if (_stricmp(value, "warn") == 0 || _stricmp(value, "warning") == 0)
{
*outLevel = eServerLogLevel_Warn;
return true;
}
if (_stricmp(value, "error") == 0)
{
*outLevel = eServerLogLevel_Error;
return true;
}
return false;
}
void SetServerLogLevel(EServerLogLevel level)
{
if (level < eServerLogLevel_Debug)
{
level = eServerLogLevel_Debug;
}
else if (level > eServerLogLevel_Error)
{
level = eServerLogLevel_Error;
}
g_minLogLevel = (LONG)level;
}
EServerLogLevel GetServerLogLevel()
{
return (EServerLogLevel)g_minLogLevel;
}
void LogDebug(const char *category, const char *message)
{
WriteLogLine(eServerLogLevel_Debug, category, message);
}
void LogInfo(const char *category, const char *message)
{
WriteLogLine(eServerLogLevel_Info, category, message);
}
void LogWarn(const char *category, const char *message)
{
WriteLogLine(eServerLogLevel_Warn, category, message);
}
void LogError(const char *category, const char *message)
{
WriteLogLine(eServerLogLevel_Error, category, message);
}
void LogDebugf(const char *category, const char *format, ...)
{
va_list args;
va_start(args, format);
WriteLogLineV(eServerLogLevel_Debug, category, format, args);
va_end(args);
}
void LogInfof(const char *category, const char *format, ...)
{
va_list args;
va_start(args, format);
WriteLogLineV(eServerLogLevel_Info, category, format, args);
va_end(args);
}
void LogWarnf(const char *category, const char *format, ...)
{
va_list args;
va_start(args, format);
WriteLogLineV(eServerLogLevel_Warn, category, format, args);
va_end(args);
}
void LogErrorf(const char *category, const char *format, ...)
{
va_list args;
va_start(args, format);
WriteLogLineV(eServerLogLevel_Error, category, format, args);
va_end(args);
}
void LogStartupStep(const char *message)
{
LogInfo("startup", message);
}
void LogWorldIO(const char *message)
{
LogInfo("world-io", message);
}
void LogWorldName(const char *prefix, const std::wstring &name)
{
std::string utf8 = StringUtils::WideToUtf8(name);
LogInfof("world-io", "%s: %s", (prefix != NULL) ? prefix : "name", utf8.c_str());
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <string>
namespace ServerRuntime
{
enum EServerLogLevel
{
eServerLogLevel_Debug = 0,
eServerLogLevel_Info = 1,
eServerLogLevel_Warn = 2,
eServerLogLevel_Error = 3
};
/**
* **Parse Log Level String**
*
* Converts a string value into log level (`debug`/`info`/`warn`/`error`)
* ログレベル文字列の変換処理
*
* @param value Source string
* @param outLevel Output location for parsed level
* @return `true` when conversion succeeds
*/
bool TryParseServerLogLevel(const char *value, EServerLogLevel *outLevel);
void SetServerLogLevel(EServerLogLevel level);
EServerLogLevel GetServerLogLevel();
void LogDebug(const char *category, const char *message);
void LogInfo(const char *category, const char *message);
void LogWarn(const char *category, const char *message);
void LogError(const char *category, const char *message);
/** Emit formatted log output with the specified level and category */
void LogDebugf(const char *category, const char *format, ...);
void LogInfof(const char *category, const char *format, ...);
void LogWarnf(const char *category, const char *format, ...);
void LogErrorf(const char *category, const char *format, ...);
void LogStartupStep(const char *message);
void LogWorldIO(const char *message);
void LogWorldName(const char *prefix, const std::wstring &name);
}

View File

@@ -0,0 +1,930 @@
#include "stdafx.h"
#include "ServerProperties.h"
#include "ServerLogger.h"
#include "Common\\StringUtils.h"
#include "Common\\FileUtils.h"
#include "..\\Minecraft.World\\ChunkSource.h"
#include <cctype>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <unordered_map>
namespace ServerRuntime
{
using StringUtils::ToLowerAscii;
using StringUtils::TrimAscii;
using StringUtils::StripUtf8Bom;
using StringUtils::Utf8ToWide;
using StringUtils::WideToUtf8;
struct ServerPropertyDefault
{
const char *key;
const char *value;
};
static const char *kServerPropertiesPath = "server.properties";
static const size_t kMaxSaveIdLength = 31;
static const int kDefaultServerPort = 25565;
static const int kDefaultMaxPlayers = 16;
static const int kMaxDedicatedPlayers = 256;
static const int kDefaultAutosaveIntervalSeconds = 60;
static const char *kLanAdvertisePropertyKey = "lan-advertise";
static const ServerPropertyDefault kServerPropertyDefaults[] =
{
{ "allow-flight", "true" },
{ "allow-nether", "true" },
{ "autosave-interval", "60" },
{ "bedrock-fog", "true" },
{ "bonus-chest", "false" },
{ "difficulty", "1" },
{ "disable-saving", "false" },
{ "do-daylight-cycle", "true" },
{ "do-mob-loot", "true" },
{ "do-mob-spawning", "true" },
{ "do-tile-drops", "true" },
{ "fire-spreads", "true" },
{ "friends-of-friends", "false" },
{ "gamemode", "0" },
{ "gamertags", "true" },
{ "generate-structures", "true" },
{ "host-can-be-invisible", "true" },
{ "host-can-change-hunger", "true" },
{ "host-can-fly", "true" },
{ "keep-inventory", "false" },
{ "level-id", "world" },
{ "level-name", "world" },
{ "level-seed", "" },
{ "level-type", "default" },
{ "world-size", "classic" },
{ "spawn-protection", "0" },
{ "log-level", "info" },
{ "max-build-height", "256" },
{ "max-players", "16" },
{ "mob-griefing", "true" },
{ "motd", "A Minecraft Server" },
{ "natural-regeneration", "true" },
{ "pvp", "true" },
{ "server-ip", "0.0.0.0" },
{ "server-name", "DedicatedServer" },
{ "server-port", "25565" },
{ "white-list", "false" },
{ "lan-advertise", "false" },
{ "spawn-animals", "true" },
{ "spawn-monsters", "true" },
{ "spawn-npcs", "true" },
{ "tnt", "true" },
{ "trust-players", "true" }
};
static std::string BoolToString(bool value)
{
return value ? "true" : "false";
}
static std::string IntToString(int value)
{
char buffer[32] = {};
sprintf_s(buffer, sizeof(buffer), "%d", value);
return std::string(buffer);
}
static std::string Int64ToString(__int64 value)
{
char buffer[64] = {};
_i64toa_s(value, buffer, sizeof(buffer), 10);
return std::string(buffer);
}
static int ClampInt(int value, int minValue, int maxValue)
{
if (value < minValue)
{
return minValue;
}
if (value > maxValue)
{
return maxValue;
}
return value;
}
static bool TryParseBool(const std::string &value, bool *outValue)
{
if (outValue == NULL)
{
return false;
}
std::string lowered = ToLowerAscii(TrimAscii(value));
if (lowered == "true" || lowered == "1" || lowered == "yes" || lowered == "on")
{
*outValue = true;
return true;
}
if (lowered == "false" || lowered == "0" || lowered == "no" || lowered == "off")
{
*outValue = false;
return true;
}
return false;
}
static bool TryParseInt(const std::string &value, int *outValue)
{
if (outValue == NULL)
{
return false;
}
std::string trimmed = TrimAscii(value);
if (trimmed.empty())
{
return false;
}
char *end = NULL;
long parsed = strtol(trimmed.c_str(), &end, 10);
if (end == trimmed.c_str() || *end != 0)
{
return false;
}
*outValue = (int)parsed;
return true;
}
static bool TryParseInt64(const std::string &value, __int64 *outValue)
{
if (outValue == NULL)
{
return false;
}
std::string trimmed = TrimAscii(value);
if (trimmed.empty())
{
return false;
}
char *end = NULL;
__int64 parsed = _strtoi64(trimmed.c_str(), &end, 10);
if (end == trimmed.c_str() || *end != 0)
{
return false;
}
*outValue = parsed;
return true;
}
static std::string LogLevelToPropertyValue(EServerLogLevel level)
{
switch (level)
{
case eServerLogLevel_Debug:
return "debug";
case eServerLogLevel_Warn:
return "warn";
case eServerLogLevel_Error:
return "error";
case eServerLogLevel_Info:
default:
return "info";
}
}
/**
* **Normalize Save ID**
*
* Normalizes an arbitrary string into a safe save destination ID
* Conversion rules:
* - Lowercase alphabetic characters
* - Keep only `[a-z0-9_.-]`
* - Replace spaces and unsupported characters with `_`
* - Fallback to `world` when empty
* - Enforce max length to match storage constraints
* 保存先IDの正規化処理
*/
static std::string NormalizeSaveId(const std::string &source)
{
std::string out;
out.reserve(source.length());
// Normalize into a character set that is safe for storage save IDs
// Replace invalid characters with '_' and fold letter case to reduce collisions
for (size_t i = 0; i < source.length(); ++i)
{
unsigned char ch = (unsigned char)source[i];
if (ch >= 'A' && ch <= 'Z')
{
ch = (unsigned char)(ch - 'A' + 'a');
}
const bool alnum = (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');
const bool passthrough = (ch == '_') || (ch == '-') || (ch == '.');
if (alnum || passthrough)
{
out.push_back((char)ch);
}
else if (std::isspace(ch))
{
out.push_back('_');
}
else if (ch < 0x80)
{
out.push_back('_');
}
}
if (out.empty())
{
out = "world";
}
// Add a prefix when needed to avoid awkward leading characters
if (!((out[0] >= 'a' && out[0] <= 'z') || (out[0] >= '0' && out[0] <= '9')))
{
out = std::string("w_") + out;
}
// Clamp length to the 4J-side filename buffer constraint
if (out.length() > kMaxSaveIdLength)
{
out.resize(kMaxSaveIdLength);
}
return out;
}
static void ApplyDefaultServerProperties(std::unordered_map<std::string, std::string> *properties)
{
if (properties == NULL)
{
return;
}
const size_t defaultCount = sizeof(kServerPropertyDefaults) / sizeof(kServerPropertyDefaults[0]);
for (size_t i = 0; i < defaultCount; ++i)
{
(*properties)[kServerPropertyDefaults[i].key] = kServerPropertyDefaults[i].value;
}
}
/**
* **Parse server.properties Text**
*
* Extracts key/value pairs from `server.properties` format text
* - Ignores lines starting with `#` or `!` as comments
* - Accepts `=` or `:` as separators
* - Skips invalid lines and continues
* server.propertiesのパース処理
*/
static bool ReadServerPropertiesFile(const char *filePath, std::unordered_map<std::string, std::string> *properties, int *outParsedCount)
{
if (properties == NULL)
{
return false;
}
std::string text;
if (filePath == NULL || !FileUtils::ReadTextFile(filePath, &text))
{
return false;
}
text = StripUtf8Bom(text);
int parsedCount = 0;
for (size_t start = 0; start <= text.length();)
{
size_t end = text.find_first_of("\r\n", start);
size_t nextStart = text.length() + 1;
if (end != std::string::npos)
{
nextStart = end + 1;
if (text[end] == '\r' && nextStart < text.length() && text[nextStart] == '\n')
{
++nextStart;
}
}
std::string line;
if (end == std::string::npos)
{
line = text.substr(start);
}
else
{
line = text.substr(start, end - start);
}
std::string trimmedLine = TrimAscii(line);
if (trimmedLine.empty())
{
start = nextStart;
continue;
}
if (trimmedLine[0] == '#' || trimmedLine[0] == '!')
{
start = nextStart;
continue;
}
size_t eqPos = trimmedLine.find('=');
size_t colonPos = trimmedLine.find(':');
size_t sepPos = std::string::npos;
if (eqPos == std::string::npos)
{
sepPos = colonPos;
}
else if (colonPos == std::string::npos)
{
sepPos = eqPos;
}
else
{
sepPos = (eqPos < colonPos) ? eqPos : colonPos;
}
if (sepPos == std::string::npos)
{
start = nextStart;
continue;
}
std::string key = TrimAscii(trimmedLine.substr(0, sepPos));
if (key.empty())
{
start = nextStart;
continue;
}
std::string value = TrimAscii(trimmedLine.substr(sepPos + 1));
(*properties)[key] = value;
++parsedCount;
start = nextStart;
}
if (outParsedCount != NULL)
{
*outParsedCount = parsedCount;
}
return true;
}
/**
* **Write server.properties Text**
*
* Writes key/value data back as `server.properties`
* Sorts keys before writing to keep output order stable
* server.propertiesの書き戻し処理
*/
static bool WriteServerPropertiesFile(const char *filePath, const std::unordered_map<std::string, std::string> &properties)
{
if (filePath == NULL)
{
return false;
}
std::string text;
text += "# Minecraft server properties\n";
text += "# Auto-generated and normalized when missing\n";
std::map<std::string, std::string> sortedProperties(properties.begin(), properties.end());
for (std::map<std::string, std::string>::const_iterator it = sortedProperties.begin(); it != sortedProperties.end(); ++it)
{
text += it->first;
text += "=";
text += it->second;
text += "\n";
}
return FileUtils::WriteTextFileAtomic(filePath, text);
}
static bool ReadNormalizedBoolProperty(
std::unordered_map<std::string, std::string> *properties,
const char *key,
bool defaultValue,
bool *shouldWrite)
{
std::string raw = TrimAscii((*properties)[key]);
bool value = defaultValue;
if (!TryParseBool(raw, &value))
{
value = defaultValue;
}
std::string normalized = BoolToString(value);
if (raw != normalized)
{
(*properties)[key] = normalized;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
return value;
}
static int ReadNormalizedIntProperty(
std::unordered_map<std::string, std::string> *properties,
const char *key,
int defaultValue,
int minValue,
int maxValue,
bool *shouldWrite)
{
std::string raw = TrimAscii((*properties)[key]);
int value = defaultValue;
if (!TryParseInt(raw, &value))
{
value = defaultValue;
}
value = ClampInt(value, minValue, maxValue);
std::string normalized = IntToString(value);
if (raw != normalized)
{
(*properties)[key] = normalized;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
return value;
}
static std::string ReadNormalizedStringProperty(
std::unordered_map<std::string, std::string> *properties,
const char *key,
const std::string &defaultValue,
size_t maxLength,
bool *shouldWrite)
{
std::string value = TrimAscii((*properties)[key]);
if (value.empty())
{
value = defaultValue;
}
if (maxLength > 0 && value.length() > maxLength)
{
value.resize(maxLength);
}
if (value != (*properties)[key])
{
(*properties)[key] = value;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
return value;
}
static bool ReadNormalizedOptionalInt64Property(
std::unordered_map<std::string, std::string> *properties,
const char *key,
__int64 *outValue,
bool *shouldWrite)
{
std::string raw = TrimAscii((*properties)[key]);
if (raw.empty())
{
if ((*properties)[key] != "")
{
(*properties)[key] = "";
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
return false;
}
__int64 parsed = 0;
if (!TryParseInt64(raw, &parsed))
{
(*properties)[key] = "";
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
return false;
}
std::string normalized = Int64ToString(parsed);
if (raw != normalized)
{
(*properties)[key] = normalized;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
if (outValue != NULL)
{
*outValue = parsed;
}
return true;
}
static EServerLogLevel ReadNormalizedLogLevelProperty(
std::unordered_map<std::string, std::string> *properties,
const char *key,
EServerLogLevel defaultValue,
bool *shouldWrite)
{
std::string raw = TrimAscii((*properties)[key]);
EServerLogLevel value = defaultValue;
if (!TryParseServerLogLevel(raw.c_str(), &value))
{
value = defaultValue;
}
std::string normalized = LogLevelToPropertyValue(value);
if (raw != normalized)
{
(*properties)[key] = normalized;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
return value;
}
static std::string ReadNormalizedLevelTypeProperty(
std::unordered_map<std::string, std::string> *properties,
const char *key,
bool *outIsFlat,
bool *shouldWrite)
{
std::string raw = TrimAscii((*properties)[key]);
std::string lowered = ToLowerAscii(raw);
bool isFlat = false;
std::string normalized = "default";
if (lowered == "flat" || lowered == "superflat" || lowered == "1")
{
isFlat = true;
normalized = "flat";
}
else if (lowered == "default" || lowered == "normal" || lowered == "0")
{
isFlat = false;
normalized = "default";
}
if (raw != normalized)
{
(*properties)[key] = normalized;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
if (outIsFlat != NULL)
{
*outIsFlat = isFlat;
}
return normalized;
}
static std::string WorldSizeToPropertyValue(int worldSize)
{
switch (worldSize)
{
case e_worldSize_Small:
return "small";
case e_worldSize_Medium:
return "medium";
case e_worldSize_Large:
return "large";
case e_worldSize_Classic:
default:
return "classic";
}
}
static int WorldSizeToXzChunks(int worldSize)
{
switch (worldSize)
{
case e_worldSize_Small:
return LEVEL_WIDTH_SMALL;
case e_worldSize_Medium:
return LEVEL_WIDTH_MEDIUM;
case e_worldSize_Large:
return LEVEL_WIDTH_LARGE;
case e_worldSize_Classic:
default:
return LEVEL_WIDTH_CLASSIC;
}
}
static int WorldSizeToHellScale(int worldSize)
{
switch (worldSize)
{
case e_worldSize_Small:
return HELL_LEVEL_SCALE_SMALL;
case e_worldSize_Medium:
return HELL_LEVEL_SCALE_MEDIUM;
case e_worldSize_Large:
return HELL_LEVEL_SCALE_LARGE;
case e_worldSize_Classic:
default:
return HELL_LEVEL_SCALE_CLASSIC;
}
}
static bool TryParseWorldSize(const std::string &lowered, int *outWorldSize)
{
if (outWorldSize == NULL)
{
return false;
}
if (lowered == "classic" || lowered == "54" || lowered == "1")
{
*outWorldSize = e_worldSize_Classic;
return true;
}
if (lowered == "small" || lowered == "64" || lowered == "2")
{
*outWorldSize = e_worldSize_Small;
return true;
}
if (lowered == "medium" || lowered == "192" || lowered == "3")
{
*outWorldSize = e_worldSize_Medium;
return true;
}
if (lowered == "large" || lowered == "320" || lowered == "4")
{
*outWorldSize = e_worldSize_Large;
return true;
}
return false;
}
static int ReadNormalizedWorldSizeProperty(
std::unordered_map<std::string, std::string> *properties,
const char *key,
int defaultWorldSize,
int *outXzChunks,
int *outHellScale,
bool *shouldWrite)
{
std::string raw = TrimAscii((*properties)[key]);
std::string lowered = ToLowerAscii(raw);
int worldSize = defaultWorldSize;
if (!raw.empty())
{
int parsedWorldSize = defaultWorldSize;
if (TryParseWorldSize(lowered, &parsedWorldSize))
{
worldSize = parsedWorldSize;
}
}
std::string normalized = WorldSizeToPropertyValue(worldSize);
if (raw != normalized)
{
(*properties)[key] = normalized;
if (shouldWrite != NULL)
{
*shouldWrite = true;
}
}
if (outXzChunks != NULL)
{
*outXzChunks = WorldSizeToXzChunks(worldSize);
}
if (outHellScale != NULL)
{
*outHellScale = WorldSizeToHellScale(worldSize);
}
return worldSize;
}
/**
* **Load Effective Server Properties Config**
*
* Loads effective world settings, repairs missing or invalid values, and returns normalized config
* - Creates defaults when file is missing
* - Fills required keys when absent
* - Normalizes `level-id` to a safe format
* - Auto-saves when any fix is applied
* 実効設定の読み込みと補正処理
*/
ServerPropertiesConfig LoadServerPropertiesConfig()
{
ServerPropertiesConfig config;
std::unordered_map<std::string, std::string> defaults;
std::unordered_map<std::string, std::string> loaded;
ApplyDefaultServerProperties(&defaults);
int parsedCount = 0;
bool readSuccess = ReadServerPropertiesFile(kServerPropertiesPath, &loaded, &parsedCount);
std::unordered_map<std::string, std::string> merged = defaults;
bool shouldWrite = false;
if (!readSuccess)
{
LogWorldIO("server.properties not found or unreadable; creating defaults");
shouldWrite = true;
}
else
{
if (parsedCount == 0)
{
LogWorldIO("server.properties has no properties; applying defaults");
shouldWrite = true;
}
const size_t defaultCount = sizeof(kServerPropertyDefaults) / sizeof(kServerPropertyDefaults[0]);
for (size_t i = 0; i < defaultCount; ++i)
{
if (loaded.find(kServerPropertyDefaults[i].key) == loaded.end())
{
shouldWrite = true;
break;
}
}
}
for (std::unordered_map<std::string, std::string>::const_iterator it = loaded.begin(); it != loaded.end(); ++it)
{
// Merge loaded values over defaults and keep unknown keys whenever possible
merged[it->first] = it->second;
}
std::string worldName = TrimAscii(merged["level-name"]);
if (worldName.empty())
{
worldName = "world";
shouldWrite = true;
}
std::string worldSaveId = TrimAscii(merged["level-id"]);
if (worldSaveId.empty())
{
// If level-id is missing, derive it from level-name to lock save destination
worldSaveId = NormalizeSaveId(worldName);
shouldWrite = true;
}
else
{
// Normalize existing level-id as well to avoid future inconsistencies
std::string normalized = NormalizeSaveId(worldSaveId);
if (normalized != worldSaveId)
{
worldSaveId = normalized;
shouldWrite = true;
}
}
merged["level-name"] = worldName;
merged["level-id"] = worldSaveId;
config.worldName = Utf8ToWide(worldName.c_str());
config.worldSaveId = worldSaveId;
config.serverPort = ReadNormalizedIntProperty(&merged, "server-port", kDefaultServerPort, 1, 65535, &shouldWrite);
config.serverIp = ReadNormalizedStringProperty(&merged, "server-ip", "0.0.0.0", 255, &shouldWrite);
config.lanAdvertise = ReadNormalizedBoolProperty(&merged, kLanAdvertisePropertyKey, false, &shouldWrite);
config.whiteListEnabled = ReadNormalizedBoolProperty(&merged, "white-list", false, &shouldWrite);
config.serverName = ReadNormalizedStringProperty(&merged, "server-name", "DedicatedServer", 16, &shouldWrite);
config.maxPlayers = ReadNormalizedIntProperty(&merged, "max-players", kDefaultMaxPlayers, 1, kMaxDedicatedPlayers, &shouldWrite);
config.seed = 0;
config.hasSeed = ReadNormalizedOptionalInt64Property(&merged, "level-seed", &config.seed, &shouldWrite);
config.logLevel = ReadNormalizedLogLevelProperty(&merged, "log-level", eServerLogLevel_Info, &shouldWrite);
config.autosaveIntervalSeconds = ReadNormalizedIntProperty(&merged, "autosave-interval", kDefaultAutosaveIntervalSeconds, 5, 3600, &shouldWrite);
config.difficulty = ReadNormalizedIntProperty(&merged, "difficulty", 1, 0, 3, &shouldWrite);
config.gameMode = ReadNormalizedIntProperty(&merged, "gamemode", 0, 0, 1, &shouldWrite);
config.worldSize = ReadNormalizedWorldSizeProperty(
&merged,
"world-size",
e_worldSize_Classic,
&config.worldSizeChunks,
&config.worldHellScale,
&shouldWrite);
config.levelType = ReadNormalizedLevelTypeProperty(&merged, "level-type", &config.levelTypeFlat, &shouldWrite);
config.spawnProtectionRadius = ReadNormalizedIntProperty(&merged, "spawn-protection", 0, 0, 256, &shouldWrite);
config.generateStructures = ReadNormalizedBoolProperty(&merged, "generate-structures", true, &shouldWrite);
config.bonusChest = ReadNormalizedBoolProperty(&merged, "bonus-chest", false, &shouldWrite);
config.pvp = ReadNormalizedBoolProperty(&merged, "pvp", true, &shouldWrite);
config.trustPlayers = ReadNormalizedBoolProperty(&merged, "trust-players", true, &shouldWrite);
config.fireSpreads = ReadNormalizedBoolProperty(&merged, "fire-spreads", true, &shouldWrite);
config.tnt = ReadNormalizedBoolProperty(&merged, "tnt", true, &shouldWrite);
config.spawnAnimals = ReadNormalizedBoolProperty(&merged, "spawn-animals", true, &shouldWrite);
config.spawnNpcs = ReadNormalizedBoolProperty(&merged, "spawn-npcs", true, &shouldWrite);
config.spawnMonsters = ReadNormalizedBoolProperty(&merged, "spawn-monsters", true, &shouldWrite);
config.allowFlight = ReadNormalizedBoolProperty(&merged, "allow-flight", true, &shouldWrite);
config.allowNether = ReadNormalizedBoolProperty(&merged, "allow-nether", true, &shouldWrite);
config.friendsOfFriends = ReadNormalizedBoolProperty(&merged, "friends-of-friends", false, &shouldWrite);
config.gamertags = ReadNormalizedBoolProperty(&merged, "gamertags", true, &shouldWrite);
config.bedrockFog = ReadNormalizedBoolProperty(&merged, "bedrock-fog", true, &shouldWrite);
config.hostCanFly = ReadNormalizedBoolProperty(&merged, "host-can-fly", true, &shouldWrite);
config.hostCanChangeHunger = ReadNormalizedBoolProperty(&merged, "host-can-change-hunger", true, &shouldWrite);
config.hostCanBeInvisible = ReadNormalizedBoolProperty(&merged, "host-can-be-invisible", true, &shouldWrite);
config.disableSaving = ReadNormalizedBoolProperty(&merged, "disable-saving", false, &shouldWrite);
config.mobGriefing = ReadNormalizedBoolProperty(&merged, "mob-griefing", true, &shouldWrite);
config.keepInventory = ReadNormalizedBoolProperty(&merged, "keep-inventory", false, &shouldWrite);
config.doMobSpawning = ReadNormalizedBoolProperty(&merged, "do-mob-spawning", true, &shouldWrite);
config.doMobLoot = ReadNormalizedBoolProperty(&merged, "do-mob-loot", true, &shouldWrite);
config.doTileDrops = ReadNormalizedBoolProperty(&merged, "do-tile-drops", true, &shouldWrite);
config.naturalRegeneration = ReadNormalizedBoolProperty(&merged, "natural-regeneration", true, &shouldWrite);
config.doDaylightCycle = ReadNormalizedBoolProperty(&merged, "do-daylight-cycle", true, &shouldWrite);
config.maxBuildHeight = ReadNormalizedIntProperty(&merged, "max-build-height", 256, 64, 256, &shouldWrite);
config.motd = ReadNormalizedStringProperty(&merged, "motd", "A Minecraft Server", 255, &shouldWrite);
if (shouldWrite)
{
if (WriteServerPropertiesFile(kServerPropertiesPath, merged))
{
LogWorldIO("wrote server.properties");
}
else
{
LogWorldIO("failed to write server.properties");
}
}
return config;
}
/**
* **Save World Identity While Preserving Other Keys**
*
* Saves world identity fields while preserving as many other settings as possible
* - Reads existing file and merges including unknown keys
* - Updates `level-name`, `level-id`, and `white-list` before writing back
* ワールド識別情報の保存処理
*/
bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config)
{
std::unordered_map<std::string, std::string> merged;
ApplyDefaultServerProperties(&merged);
std::unordered_map<std::string, std::string> loaded;
int parsedCount = 0;
if (ReadServerPropertiesFile(kServerPropertiesPath, &loaded, &parsedCount))
{
for (std::unordered_map<std::string, std::string>::const_iterator it = loaded.begin(); it != loaded.end(); ++it)
{
// Keep existing content so keys untouched by caller are not dropped
merged[it->first] = it->second;
}
}
std::string worldName = TrimAscii(WideToUtf8(config.worldName));
if (worldName.empty())
{
worldName = "world"; // Default world name
}
std::string worldSaveId = TrimAscii(config.worldSaveId);
if (worldSaveId.empty())
{
worldSaveId = NormalizeSaveId(worldName);
}
else
{
worldSaveId = NormalizeSaveId(worldSaveId);
}
merged["level-name"] = worldName;
merged["level-id"] = worldSaveId;
merged["white-list"] = BoolToString(config.whiteListEnabled);
return WriteServerPropertiesFile(kServerPropertiesPath, merged);
}
}

View File

@@ -0,0 +1,105 @@
#pragma once
#include <string>
#include "ServerLogger.h"
namespace ServerRuntime
{
/**
* `server.properties`
*/
struct ServerPropertiesConfig
{
/** world name `level-name` */
std::wstring worldName;
/** world save id `level-id` */
std::string worldSaveId;
/** `server-port` */
int serverPort;
/** `server-ip` */
std::string serverIp;
/** `lan-advertise` */
bool lanAdvertise;
/** `white-list` */
bool whiteListEnabled;
/** `server-name` (max 16 chars at runtime) */
std::string serverName;
/** `max-players` */
int maxPlayers;
/** `level-seed` is explicitly set */
bool hasSeed;
/** `level-seed` */
__int64 seed;
/** `log-level` */
EServerLogLevel logLevel;
/** `autosave-interval` (seconds) */
int autosaveIntervalSeconds;
/** host options / game settings */
int difficulty;
int gameMode;
/** `world-size` preset (`classic` / `small` / `medium` / `large`) */
int worldSize;
/** Overworld chunk width derived from `world-size` */
int worldSizeChunks;
/** Nether scale derived from `world-size` */
int worldHellScale;
bool levelTypeFlat;
/** `spawn-protection` radius in blocks (0 disables protection) */
int spawnProtectionRadius;
bool generateStructures;
bool bonusChest;
bool pvp;
bool trustPlayers;
bool fireSpreads;
bool tnt;
bool spawnAnimals;
bool spawnNpcs;
bool spawnMonsters;
bool allowFlight;
bool allowNether;
bool friendsOfFriends;
bool gamertags;
bool bedrockFog;
bool hostCanFly;
bool hostCanChangeHunger;
bool hostCanBeInvisible;
bool disableSaving;
bool mobGriefing;
bool keepInventory;
bool doMobSpawning;
bool doMobLoot;
bool doTileDrops;
bool naturalRegeneration;
bool doDaylightCycle;
/** other MinecraftServer runtime settings */
int maxBuildHeight;
std::string levelType;
std::string motd;
};
/**
* server.properties loader
*
* - ファイル欠損時はデフォルト値で新規作成
* - 必須キー不足時は補完して再保存
* - `level-id` は保存先として安全な形式へ正規化
*
* @return `WorldManager` が利用するワールド設定
*/
ServerPropertiesConfig LoadServerPropertiesConfig();
/**
* server.properties saver
*
* - `level-name` と `level-id` を更新
* - `white-list` を更新
* - それ以外の既存キーは極力保持
*
* @param config 保存するワールド識別情報と永続化対象設定
* @return 書き込み成功時 `true`
*/
bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config);
}

View File

@@ -0,0 +1,6 @@
#pragma once
namespace ServerRuntime
{
void RequestDedicatedServerShutdown();
}

View File

@@ -0,0 +1,718 @@
#include "stdafx.h"
#include "Common/App_Defines.h"
#include "Common/Network/GameNetworkManager.h"
#include "Input.h"
#include "Minecraft.h"
#include "MinecraftServer.h"
#include "..\Access\Access.h"
#include "..\Common\StringUtils.h"
#include "..\ServerLogger.h"
#include "..\ServerLogManager.h"
#include "..\ServerProperties.h"
#include "..\ServerShutdown.h"
#include "..\WorldManager.h"
#include "..\Console\ServerCli.h"
#include "Tesselator.h"
#include "Windows64/4JLibs/inc/4J_Render.h"
#include "Windows64/GameConfig/Minecraft.spa.h"
#include "Windows64/KeyboardMouseInput.h"
#include "Windows64/Network/WinsockNetLayer.h"
#include "Windows64/Windows64_UIController.h"
#include "Common/UI/UI.h"
#include "../../Minecraft.World/AABB.h"
#include "../../Minecraft.World/Vec3.h"
#include "../../Minecraft.World/IntCache.h"
#include "../../Minecraft.World/ChunkSource.h"
#include "../../Minecraft.World/TilePos.h"
#include "../../Minecraft.World/compression.h"
#include "../../Minecraft.World/OldChunkStorage.h"
#include "../../Minecraft.World/net.minecraft.world.level.tile.h"
#include "../../Minecraft.World/Random.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atomic>
extern ATOM MyRegisterClass(HINSTANCE hInstance);
extern BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);
extern HRESULT InitDevice();
extern void CleanupDevice();
extern void DefineActions(void);
extern HWND g_hWnd;
extern int g_iScreenWidth;
extern int g_iScreenHeight;
extern char g_Win64Username[17];
extern wchar_t g_Win64UsernameW[17];
extern ID3D11Device* g_pd3dDevice;
extern ID3D11DeviceContext* g_pImmediateContext;
extern IDXGISwapChain* g_pSwapChain;
extern ID3D11RenderTargetView* g_pRenderTargetView;
extern ID3D11DepthStencilView* g_pDepthStencilView;
extern DWORD dwProfileSettingsA[];
static const int kProfileValueCount = 5;
static const int kProfileSettingCount = 4;
struct DedicatedServerConfig
{
int port;
char bindIP[256];
char name[17];
int maxPlayers;
int worldSize;
int worldSizeChunks;
int worldHellScale;
__int64 seed;
ServerRuntime::EServerLogLevel logLevel;
bool hasSeed;
bool showHelp;
};
static std::atomic<bool> g_shutdownRequested(false);
static const DWORD kDefaultAutosaveIntervalMs = 60 * 1000;
static const int kServerActionPad = 0;
static bool IsShutdownRequested()
{
return g_shutdownRequested.load();
}
namespace ServerRuntime
{
void RequestDedicatedServerShutdown()
{
g_shutdownRequested.store(true);
}
}
/**
* Calls Access::Shutdown automatically once dedicated access control was initialized successfully
* アクセス制御初期化後のShutdownを自動化する
*/
class AccessShutdownGuard
{
public:
AccessShutdownGuard()
: m_active(false)
{
}
void Activate()
{
m_active = true;
}
~AccessShutdownGuard()
{
if (m_active)
{
ServerRuntime::Access::Shutdown();
}
}
private:
bool m_active;
};
static BOOL WINAPI ConsoleCtrlHandlerProc(DWORD ctrlType)
{
switch (ctrlType)
{
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_SHUTDOWN_EVENT:
ServerRuntime::RequestDedicatedServerShutdown();
return TRUE;
default:
return FALSE;
}
}
/**
* **Wait For Server Stopped Signal**
*
* Thread entry used during shutdown to wait until the network layer reports server stop completion
* 停止通知待機用の終了スレッド処理
*/
static int WaitForServerStoppedThreadProc(void *)
{
if (g_NetworkManager.ServerStoppedValid())
{
g_NetworkManager.ServerStoppedWait();
}
return 0;
}
static void PrintUsage()
{
ServerRuntime::LogInfo("usage", "Minecraft.Server.exe [options]");
ServerRuntime::LogInfo("usage", " -port <1-65535> Listen TCP port (default: server.properties:server-port)");
ServerRuntime::LogInfo("usage", " -ip <addr> Bind address (default: server.properties:server-ip)");
ServerRuntime::LogInfo("usage", " -bind <addr> Alias of -ip");
ServerRuntime::LogInfo("usage", " -name <name> Host display name (max 16 chars, default: server.properties:server-name)");
ServerRuntime::LogInfo("usage", " -maxplayers <1-8> Public slots (default: server.properties:max-players)");
ServerRuntime::LogInfo("usage", " -seed <int64> World seed (overrides server.properties:level-seed)");
ServerRuntime::LogInfo("usage", " -loglevel <level> debug|info|warn|error (default: server.properties:log-level)");
ServerRuntime::LogInfo("usage", " -help Show this help");
}
using ServerRuntime::LoadServerPropertiesConfig;
using ServerRuntime::LogError;
using ServerRuntime::LogErrorf;
using ServerRuntime::LogInfof;
using ServerRuntime::LogDebugf;
using ServerRuntime::LogStartupStep;
using ServerRuntime::LogWarn;
using ServerRuntime::LogWorldIO;
using ServerRuntime::SaveServerPropertiesConfig;
using ServerRuntime::SetServerLogLevel;
using ServerRuntime::ServerPropertiesConfig;
using ServerRuntime::TryParseServerLogLevel;
using ServerRuntime::StringUtils::WideToUtf8;
using ServerRuntime::BootstrapWorldForServer;
using ServerRuntime::eWorldBootstrap_CreatedNew;
using ServerRuntime::eWorldBootstrap_Failed;
using ServerRuntime::eWorldBootstrap_Loaded;
using ServerRuntime::WaitForWorldActionIdle;
using ServerRuntime::WorldBootstrapResult;
static bool ParseIntArg(const char *value, int *outValue)
{
if (value == NULL || *value == 0)
return false;
char *end = NULL;
long parsed = strtol(value, &end, 10);
if (end == value || *end != 0)
return false;
*outValue = (int)parsed;
return true;
}
static bool ParseInt64Arg(const char *value, __int64 *outValue)
{
if (value == NULL || *value == 0)
return false;
char *end = NULL;
__int64 parsed = _strtoi64(value, &end, 10);
if (end == value || *end != 0)
return false;
*outValue = parsed;
return true;
}
static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *config)
{
for (int i = 1; i < argc; ++i)
{
const char *arg = argv[i];
if (_stricmp(arg, "-help") == 0 || _stricmp(arg, "--help") == 0 || _stricmp(arg, "-h") == 0)
{
config->showHelp = true;
return true;
}
else if ((_stricmp(arg, "-port") == 0) && (i + 1 < argc))
{
int port = 0;
if (!ParseIntArg(argv[++i], &port) || port <= 0 || port > 65535)
{
LogError("startup", "Invalid -port value.");
return false;
}
config->port = port;
}
else if ((_stricmp(arg, "-ip") == 0 || _stricmp(arg, "-bind") == 0) && (i + 1 < argc))
{
strncpy_s(config->bindIP, sizeof(config->bindIP), argv[++i], _TRUNCATE);
}
else if ((_stricmp(arg, "-name") == 0) && (i + 1 < argc))
{
strncpy_s(config->name, sizeof(config->name), argv[++i], _TRUNCATE);
}
else if ((_stricmp(arg, "-maxplayers") == 0) && (i + 1 < argc))
{
int maxPlayers = 0;
if (!ParseIntArg(argv[++i], &maxPlayers) || maxPlayers <= 0 || maxPlayers > MINECRAFT_NET_MAX_PLAYERS)
{
LogError("startup", "Invalid -maxplayers value.");
return false;
}
config->maxPlayers = maxPlayers;
}
else if ((_stricmp(arg, "-seed") == 0) && (i + 1 < argc))
{
if (!ParseInt64Arg(argv[++i], &config->seed))
{
LogError("startup", "Invalid -seed value.");
return false;
}
config->hasSeed = true;
}
else if ((_stricmp(arg, "-loglevel") == 0) && (i + 1 < argc))
{
if (!TryParseServerLogLevel(argv[++i], &config->logLevel))
{
LogError("startup", "Invalid -loglevel value. Use debug/info/warn/error.");
return false;
}
}
else
{
LogErrorf("startup", "Unknown or incomplete argument: %s", arg);
return false;
}
}
return true;
}
static void SetExeWorkingDirectory()
{
char exePath[MAX_PATH] = {};
GetModuleFileNameA(NULL, exePath, MAX_PATH);
char *slash = strrchr(exePath, '\\');
if (slash != NULL)
{
*(slash + 1) = 0;
SetCurrentDirectoryA(exePath);
}
}
static void ApplyServerPropertiesToDedicatedConfig(const ServerPropertiesConfig &serverProperties, DedicatedServerConfig *config)
{
if (config == NULL)
{
return;
}
config->port = serverProperties.serverPort;
strncpy_s(
config->bindIP,
sizeof(config->bindIP),
serverProperties.serverIp.empty() ? "0.0.0.0" : serverProperties.serverIp.c_str(),
_TRUNCATE);
strncpy_s(
config->name,
sizeof(config->name),
serverProperties.serverName.empty() ? "DedicatedServer" : serverProperties.serverName.c_str(),
_TRUNCATE);
config->maxPlayers = serverProperties.maxPlayers;
config->worldSize = serverProperties.worldSize;
config->worldSizeChunks = serverProperties.worldSizeChunks;
config->worldHellScale = serverProperties.worldHellScale;
config->logLevel = serverProperties.logLevel;
config->hasSeed = serverProperties.hasSeed;
config->seed = serverProperties.seed;
}
/**
* **Tick Core Async Subsystems**
*
* Advances core subsystems for one frame to keep async processing alive
* Call continuously even inside wait loops to avoid stalling storage/profile/network work
* 非同期進行維持のためのティック処理
*/
static void TickCoreSystems()
{
g_NetworkManager.DoWork();
ProfileManager.Tick();
StorageManager.Tick();
}
/**
* **Handle Queued XUI Server Action Once**
*
* Processes queued XUI/server action once
* XUIアクションの単発処理
*/
static void HandleXuiActions()
{
app.HandleXuiActions();
}
/**
* Dedicated Server Entory Point
*
* 主な責務:
* - プロセス/描画/ネットワークの初期化
* - `WorldManager` によるワールドロードまたは新規作成
* - メインループと定期オートセーブ実行
* - 終了時の最終保存と各サブシステムの安全停止
*/
int main(int argc, char **argv)
{
DedicatedServerConfig config;
config.port = WIN64_NET_DEFAULT_PORT;
strncpy_s(config.bindIP, sizeof(config.bindIP), "0.0.0.0", _TRUNCATE);
strncpy_s(config.name, sizeof(config.name), "DedicatedServer", _TRUNCATE);
config.maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
config.worldSize = e_worldSize_Classic;
config.worldSizeChunks = LEVEL_WIDTH_CLASSIC;
config.worldHellScale = HELL_LEVEL_SCALE_CLASSIC;
config.seed = 0;
config.logLevel = ServerRuntime::eServerLogLevel_Info;
config.hasSeed = false;
config.showHelp = false;
SetConsoleCtrlHandler(ConsoleCtrlHandlerProc, TRUE);
SetExeWorkingDirectory();
// Load base settings from server.properties, then override with CLI values when provided
ServerPropertiesConfig serverProperties = LoadServerPropertiesConfig();
ApplyServerPropertiesToDedicatedConfig(serverProperties, &config);
if (!ParseCommandLine(argc, argv, &config))
{
PrintUsage();
return 1;
}
if (config.showHelp)
{
PrintUsage();
return 0;
}
SetServerLogLevel(config.logLevel);
LogStartupStep("initializing process state");
AccessShutdownGuard accessShutdownGuard;
g_iScreenWidth = 1280;
g_iScreenHeight = 720;
strncpy_s(g_Win64Username, sizeof(g_Win64Username), config.name, _TRUNCATE);
MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17);
g_Win64MultiplayerHost = true;
g_Win64MultiplayerJoin = false;
g_Win64MultiplayerPort = config.port;
strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), config.bindIP, _TRUNCATE);
g_Win64DedicatedServer = true;
g_Win64DedicatedServerPort = config.port;
strncpy_s(g_Win64DedicatedServerBindIP, sizeof(g_Win64DedicatedServerBindIP), config.bindIP, _TRUNCATE);
g_Win64DedicatedServerLanAdvertise = serverProperties.lanAdvertise;
LogStartupStep("initializing server log manager");
ServerRuntime::ServerLogManager::Initialize();
LogStartupStep("initializing dedicated access control");
if (!ServerRuntime::Access::Initialize(".", serverProperties.whiteListEnabled))
{
LogError("startup", "Failed to initialize dedicated server access control.");
return 2;
}
accessShutdownGuard.Activate();
LogInfof("startup", "LAN advertise: %s", serverProperties.lanAdvertise ? "enabled" : "disabled");
LogInfof("startup", "Whitelist: %s", serverProperties.whiteListEnabled ? "enabled" : "disabled");
LogInfof("startup", "Spawn protection radius: %d", serverProperties.spawnProtectionRadius);
#ifdef _LARGE_WORLDS
LogInfof(
"startup",
"World size preset: %d (xz=%d, hell-scale=%d)",
config.worldSize,
config.worldSizeChunks,
config.worldHellScale);
#endif
LogStartupStep("registering hidden window class");
HINSTANCE hInstance = GetModuleHandle(NULL);
MyRegisterClass(hInstance);
LogStartupStep("creating hidden window");
if (!InitInstance(hInstance, SW_HIDE))
{
LogError("startup", "Failed to create window instance.");
return 2;
}
ShowWindow(g_hWnd, SW_HIDE);
LogStartupStep("initializing graphics device wrappers");
if (FAILED(InitDevice()))
{
LogError("startup", "Failed to initialize D3D device.");
CleanupDevice();
return 2;
}
LogStartupStep("loading media/string tables");
app.loadMediaArchive();
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
app.loadStringTable();
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
g_KBMInput.Init();
DefineActions();
InputManager.SetJoypadMapVal(0, 0);
InputManager.SetKeyRepeatRate(0.3f, 0.2f);
ProfileManager.Initialise(
TITLEID_MINECRAFT,
app.m_dwOfferID,
PROFILE_VERSION_10,
kProfileValueCount,
kProfileSettingCount,
dwProfileSettingsA,
app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT,
&app.uiGameDefinedDataChangedBitmask);
ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app);
ProfileManager.SetDebugFullOverride(true);
LogStartupStep("initializing network manager");
g_NetworkManager.Initialise();
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i)
{
IQNet::m_player[i].m_smallId = (BYTE)i;
IQNet::m_player[i].m_isRemote = false;
IQNet::m_player[i].m_isHostPlayer = (i == 0);
swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
}
wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
WinsockNetLayer::Initialize();
Tesselator::CreateNewThreadStorage(1024 * 1024);
AABB::CreateNewThreadStorage();
Vec3::CreateNewThreadStorage();
IntCache::CreateNewThreadStorage();
Compression::CreateNewThreadStorage();
OldChunkStorage::CreateNewThreadStorage();
Level::enableLightingCache();
Tile::CreateNewThreadStorage();
LogStartupStep("creating Minecraft singleton");
Minecraft::main();
Minecraft *minecraft = Minecraft::GetInstance();
if (minecraft == NULL)
{
LogError("startup", "Minecraft initialization failed.");
CleanupDevice();
return 3;
}
app.InitGameSettings();
MinecraftServer::resetFlags();
app.SetTutorialMode(false);
app.SetCorruptSaveDeleted(false);
app.SetGameHostOption(eGameHostOption_Difficulty, serverProperties.difficulty);
app.SetGameHostOption(eGameHostOption_FriendsOfFriends, serverProperties.friendsOfFriends ? 1 : 0);
app.SetGameHostOption(eGameHostOption_Gamertags, serverProperties.gamertags ? 1 : 0);
app.SetGameHostOption(eGameHostOption_BedrockFog, serverProperties.bedrockFog ? 1 : 0);
app.SetGameHostOption(eGameHostOption_GameType, serverProperties.gameMode);
app.SetGameHostOption(eGameHostOption_LevelType, serverProperties.levelTypeFlat ? 1 : 0);
app.SetGameHostOption(eGameHostOption_Structures, serverProperties.generateStructures ? 1 : 0);
app.SetGameHostOption(eGameHostOption_BonusChest, serverProperties.bonusChest ? 1 : 0);
app.SetGameHostOption(eGameHostOption_PvP, serverProperties.pvp ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TrustPlayers, serverProperties.trustPlayers ? 1 : 0);
app.SetGameHostOption(eGameHostOption_FireSpreads, serverProperties.fireSpreads ? 1 : 0);
app.SetGameHostOption(eGameHostOption_TNT, serverProperties.tnt ? 1 : 0);
app.SetGameHostOption(
eGameHostOption_CheatsEnabled,
(serverProperties.hostCanFly || serverProperties.hostCanChangeHunger || serverProperties.hostCanBeInvisible) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_HostCanFly, serverProperties.hostCanFly ? 1 : 0);
app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, serverProperties.hostCanChangeHunger ? 1 : 0);
app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, serverProperties.hostCanBeInvisible ? 1 : 0);
app.SetGameHostOption(eGameHostOption_DisableSaving, serverProperties.disableSaving ? 1 : 0);
app.SetGameHostOption(eGameHostOption_MobGriefing, serverProperties.mobGriefing ? 1 : 0);
app.SetGameHostOption(eGameHostOption_KeepInventory, serverProperties.keepInventory ? 1 : 0);
app.SetGameHostOption(eGameHostOption_DoMobSpawning, serverProperties.doMobSpawning ? 1 : 0);
app.SetGameHostOption(eGameHostOption_DoMobLoot, serverProperties.doMobLoot ? 1 : 0);
app.SetGameHostOption(eGameHostOption_DoTileDrops, serverProperties.doTileDrops ? 1 : 0);
app.SetGameHostOption(eGameHostOption_NaturalRegeneration, serverProperties.naturalRegeneration ? 1 : 0);
app.SetGameHostOption(eGameHostOption_DoDaylightCycle, serverProperties.doDaylightCycle ? 1 : 0);
#ifdef _LARGE_WORLDS
app.SetGameHostOption(eGameHostOption_WorldSize, serverProperties.worldSize);
// Apply desired target size for loading existing worlds.
// Expansion happens only when target size is larger than current level size.
app.SetGameNewWorldSize(serverProperties.worldSizeChunks, true);
app.SetGameNewHellScale(serverProperties.worldHellScale);
#endif
StorageManager.SetSaveDisabled(serverProperties.disableSaving);
// Read world name and fixed save-id from server.properties
// Delegate load-vs-create decision to WorldManager
std::wstring targetWorldName = serverProperties.worldName;
if (targetWorldName.empty())
{
targetWorldName = L"world"; // Default world name
}
WorldBootstrapResult worldBootstrap = BootstrapWorldForServer(serverProperties, kServerActionPad, &TickCoreSystems);
if (worldBootstrap.status == eWorldBootstrap_Loaded)
{
const std::string &loadedSaveFilename = worldBootstrap.resolvedSaveId;
if (!loadedSaveFilename.empty() && _stricmp(loadedSaveFilename.c_str(), serverProperties.worldSaveId.c_str()) != 0)
{
// Persist the actually loaded save-id back to config
// Keep lookup keys aligned for next startup
LogWorldIO("updating level-id to loaded save filename");
serverProperties.worldSaveId = loadedSaveFilename;
if (!SaveServerPropertiesConfig(serverProperties))
{
LogWorldIO("failed to persist updated level-id");
}
}
}
else if (worldBootstrap.status == eWorldBootstrap_Failed)
{
LogErrorf("world-io", "Failed to load configured world \"%s\".", WideToUtf8(targetWorldName).c_str());
WinsockNetLayer::Shutdown();
g_NetworkManager.Terminate();
CleanupDevice();
return 4;
}
NetworkGameInitData *param = new NetworkGameInitData();
if (config.hasSeed)
{
param->seed = config.seed;
}
else
{
param->seed = (new Random())->nextLong();
}
#ifdef _LARGE_WORLDS
param->xzSize = (unsigned int)config.worldSizeChunks;
param->hellScale = (unsigned char)config.worldHellScale;
#endif
param->saveData = worldBootstrap.saveData;
param->settings = app.GetGameHostOption(eGameHostOption_All);
param->dedicatedNoLocalHostPlayer = true;
LogStartupStep("starting hosted network game thread");
g_NetworkManager.HostGame(0, true, false, (unsigned char)config.maxPlayers, 0);
g_NetworkManager.FakeLocalPlayerJoined();
C4JThread *startThread = new C4JThread(&CGameNetworkManager::RunNetworkGameThreadProc, (LPVOID)param, "RunNetworkGame");
startThread->Run();
while (startThread->isRunning() && !IsShutdownRequested())
{
TickCoreSystems();
Sleep(10);
}
startThread->WaitForCompletion(INFINITE);
int startupResult = startThread->GetExitCode();
delete startThread;
if (startupResult != 0)
{
LogErrorf("startup", "Failed to start dedicated server (code %d).", startupResult);
WinsockNetLayer::Shutdown();
g_NetworkManager.Terminate();
CleanupDevice();
return 4;
}
LogStartupStep("server startup complete");
LogInfof("startup", "Dedicated server listening on %s:%d", g_Win64MultiplayerIP, g_Win64MultiplayerPort);
if (worldBootstrap.status == eWorldBootstrap_CreatedNew && !IsShutdownRequested() && !app.m_bShutdown)
{
// Windows64 suppresses saveToDisc right after new world creation
// Dedicated Server explicitly runs the initial save here
LogWorldIO("requesting initial save for newly created world");
WaitForWorldActionIdle(kServerActionPad, 5000, &TickCoreSystems, &HandleXuiActions);
app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
if (!WaitForWorldActionIdle(kServerActionPad, 30000, &TickCoreSystems, &HandleXuiActions))
{
LogWorldIO("initial save timed out");
LogWarn("world-io", "Timed out waiting for initial save action to finish.");
}
else
{
LogWorldIO("initial save completed");
}
}
DWORD autosaveIntervalMs = kDefaultAutosaveIntervalMs;
if (serverProperties.autosaveIntervalSeconds > 0)
{
autosaveIntervalMs = (DWORD)(serverProperties.autosaveIntervalSeconds * 1000);
}
DWORD nextAutosaveTick = GetTickCount() + autosaveIntervalMs;
bool autosaveRequested = false;
ServerRuntime::ServerCli serverCli;
serverCli.Start();
while (!IsShutdownRequested() && !app.m_bShutdown)
{
TickCoreSystems();
HandleXuiActions();
serverCli.Poll();
if (IsShutdownRequested() || app.m_bShutdown)
{
break;
}
if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle)
{
LogWorldIO("autosave completed");
autosaveRequested = false;
}
if (MinecraftServer::serverHalted())
{
break;
}
DWORD now = GetTickCount();
if ((LONG)(now - nextAutosaveTick) >= 0)
{
if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle)
{
LogWorldIO("requesting autosave");
app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
autosaveRequested = true;
}
nextAutosaveTick = now + autosaveIntervalMs;
}
Sleep(10);
}
serverCli.Stop();
app.m_bShutdown = true;
LogInfof("shutdown", "Dedicated server stopped");
MinecraftServer *server = MinecraftServer::getInstance();
if (server != NULL)
{
server->setSaveOnExit(true);
}
if (server != NULL)
{
LogWorldIO("requesting save before shutdown");
LogWorldIO("using saveOnExit for shutdown");
}
MinecraftServer::HaltServer();
if (g_NetworkManager.ServerStoppedValid())
{
C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped");
waitThread.Run();
waitThread.WaitForCompletion(INFINITE);
}
LogInfof("shutdown", "Cleaning up and exiting.");
WinsockNetLayer::Shutdown();
LogDebugf("shutdown", "Network layer shutdown complete.");
g_NetworkManager.Terminate();
LogDebugf("shutdown", "Network manager terminated.");
ServerRuntime::ServerLogManager::Shutdown();
CleanupDevice();
return 0;
}

View File

@@ -0,0 +1,65 @@
param(
[string]$OutDir,
[string]$ProjectRoot,
[string]$Configuration
)
if ([string]::IsNullOrWhiteSpace($OutDir)) {
throw "OutDir is required."
}
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
$ProjectRoot = Resolve-Path (Join-Path $PSScriptRoot "..\\..")
}
if ([string]::IsNullOrWhiteSpace($Configuration)) {
$Configuration = "Debug"
}
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
$ProjectRoot = [System.IO.Path]::GetFullPath($ProjectRoot)
$ClientRoot = Join-Path $ProjectRoot "Minecraft.Client"
Write-Host "Server post-build started. OutDir: $OutDir"
function Ensure-Dir([string]$path) {
if (-not (Test-Path $path)) {
New-Item -ItemType Directory -Path $path -Force | Out-Null
}
}
function Copy-Tree-IfExists([string]$src, [string]$dst) {
if (Test-Path $src) {
Ensure-Dir $dst
xcopy /q /y /i /s /e /d "$src" "$dst" 2>$null | Out-Null
}
}
function Copy-File-IfExists([string]$src, [string]$dst) {
if (Test-Path $src) {
$dstDir = Split-Path -Parent $dst
Ensure-Dir $dstDir
xcopy /q /y /d "$src" "$dstDir" 2>$null | Out-Null
}
}
function Copy-FirstExisting([string[]]$candidates, [string]$dstFile) {
foreach ($candidate in $candidates) {
if (Test-Path $candidate) {
Copy-File-IfExists $candidate $dstFile
return
}
}
}
# Dedicated server only needs core resources for current startup path.
Copy-File-IfExists (Join-Path $ClientRoot "Common\\Media\\MediaWindows64.arc") (Join-Path $OutDir "Common\\Media\\MediaWindows64.arc")
Copy-Tree-IfExists (Join-Path $ClientRoot "Common\\res") (Join-Path $OutDir "Common\\res")
Copy-Tree-IfExists (Join-Path $ClientRoot "Windows64\\GameHDD") (Join-Path $OutDir "Windows64\\GameHDD")
# Runtime DLLs.
Copy-FirstExisting @(
(Join-Path $ClientRoot "Windows64\\Iggy\\lib\\redist64\\iggy_w64.dll"),
(Join-Path $ProjectRoot ("x64\\{0}\\iggy_w64.dll" -f $Configuration))
) (Join-Path $OutDir "iggy_w64.dll")

View File

@@ -0,0 +1,641 @@
#include "stdafx.h"
#include "WorldManager.h"
#include "Minecraft.h"
#include "MinecraftServer.h"
#include "ServerLogger.h"
#include "Common\\StringUtils.h"
#include <stdio.h>
#include <string.h>
namespace ServerRuntime
{
using StringUtils::Utf8ToWide;
using StringUtils::WideToUtf8;
enum EWorldSaveLoadResult
{
eWorldSaveLoad_Loaded,
eWorldSaveLoad_NotFound,
eWorldSaveLoad_Failed
};
struct SaveInfoQueryContext
{
bool done;
bool success;
SAVE_DETAILS *details;
SaveInfoQueryContext()
: done(false)
, success(false)
, details(NULL)
{
}
};
struct SaveDataLoadContext
{
bool done;
bool isCorrupt;
bool isOwner;
SaveDataLoadContext()
: done(false)
, isCorrupt(true)
, isOwner(false)
{
}
};
/**
* **Apply Save ID To StorageManager**
*
* Applies the configured save destination ID (`level-id`) to `StorageManager`
* - Re-applies the same ID at startup and before save to avoid destination drift
* - Ignores empty values as invalid
* - For some reason, a date-based world file occasionally appears after a debug build, but the cause is unknown.
* 保存先IDの適用処理
*
* @param saveFilename Normalized save destination ID
*/
static void SetStorageSaveUniqueFilename(const std::string &saveFilename)
{
if (saveFilename.empty())
{
return;
}
char filenameBuffer[64] = {};
strncpy_s(filenameBuffer, sizeof(filenameBuffer), saveFilename.c_str(), _TRUNCATE);
StorageManager.SetSaveUniqueFilename(filenameBuffer);
}
static void LogSaveFilename(const char *prefix, const std::string &saveFilename)
{
LogInfof("world-io", "%s: %s", (prefix != NULL) ? prefix : "save-filename", saveFilename.c_str());
}
/**
* Verifies a directory exists and creates it when missing
* - Treats an existing non-directory path as failure
* - Returns whether the directory had to be created
* ディレクトリ存在保証の補助処理
*/
static bool EnsureDirectoryExists(const std::wstring &directoryPath, bool *outCreated)
{
if (outCreated != NULL)
{
*outCreated = false;
}
if (directoryPath.empty())
{
return false;
}
DWORD attrs = GetFileAttributesW(directoryPath.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES)
{
if ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
return true;
}
LogErrorf("world-io", "path exists but is not a directory: %s", WideToUtf8(directoryPath).c_str());
return false;
}
if (CreateDirectoryW(directoryPath.c_str(), NULL))
{
if (outCreated != NULL)
{
*outCreated = true;
}
return true;
}
DWORD error = GetLastError();
if (error == ERROR_ALREADY_EXISTS)
{
attrs = GetFileAttributesW(directoryPath.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES && ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0))
{
return true;
}
}
LogErrorf(
"world-io",
"failed to create directory %s (error=%lu)",
WideToUtf8(directoryPath).c_str(),
(unsigned long)error);
return false;
}
/**
* Prepares the save root used by the Windows64 storage layout
* - Creates `Windows64` first because `CreateDirectoryW` is not recursive
* - Creates `Windows64\\GameHDD` when missing before world bootstrap starts
* Windows64用保存先ディレクトリの存在保証
*/
static bool EnsureGameHddRootExists()
{
bool windows64Created = false;
if (!EnsureDirectoryExists(L"Windows64", &windows64Created))
{
return false;
}
bool gameHddCreated = false;
if (!EnsureDirectoryExists(L"Windows64\\GameHDD", &gameHddCreated))
{
return false;
}
if (windows64Created || gameHddCreated)
{
LogWorldIO("created missing Windows64\\GameHDD storage directories");
}
return true;
}
static void LogEnumeratedSaveInfo(int index, const SAVE_INFO &saveInfo)
{
std::wstring title = Utf8ToWide(saveInfo.UTF8SaveTitle);
std::wstring filename = Utf8ToWide(saveInfo.UTF8SaveFilename);
std::string titleUtf8 = WideToUtf8(title);
std::string filenameUtf8 = WideToUtf8(filename);
char logLine[512] = {};
sprintf_s(
logLine,
sizeof(logLine),
"save[%d] title=\"%s\" filename=\"%s\"",
index,
titleUtf8.c_str(),
filenameUtf8.c_str());
LogDebug("world-io", logLine);
}
/**
* **Save List Callback**
*
* Captures async save-list results into `SaveInfoQueryContext` and marks completion for the waiter
* セーブ一覧取得の完了通知
*/
static int GetSavesInfoCallbackProc(LPVOID lpParam, SAVE_DETAILS *pSaveDetails, const bool bRes)
{
SaveInfoQueryContext *context = (SaveInfoQueryContext *)lpParam;
if (context != NULL)
{
context->details = pSaveDetails;
context->success = bRes;
context->done = true;
}
return 0;
}
/**
* **Save Data Load Callback**
*
* Writes load results such as corruption status into `SaveDataLoadContext`
* セーブ読み込み結果の反映
*/
static int LoadSaveDataCallbackProc(LPVOID lpParam, const bool bIsCorrupt, const bool bIsOwner)
{
SaveDataLoadContext *context = (SaveDataLoadContext *)lpParam;
if (context != NULL)
{
context->isCorrupt = bIsCorrupt;
context->isOwner = bIsOwner;
context->done = true;
}
return 0;
}
/**
* **Wait For Save List Completion**
*
* Waits until save-list retrieval completes
* - Prefers callback completion as the primary signal
* - Also falls back to polling because some environments populate `ReturnSavesInfo()` before callback
* セーブ一覧待機の補助処理
*
* @return `true` when completion is detected
*/
static bool WaitForSaveInfoResult(SaveInfoQueryContext *context, DWORD timeoutMs, WorldManagerTickProc tickProc)
{
DWORD start = GetTickCount();
while ((GetTickCount() - start) < timeoutMs)
{
if (context->done)
{
return true;
}
if (context->details == NULL)
{
// Some implementations fill ReturnSavesInfo before the callback
// Keep polling as a fallback instead of relying only on callback completion
SAVE_DETAILS *details = StorageManager.ReturnSavesInfo();
if (details != NULL)
{
context->details = details;
context->success = true;
context->done = true;
return true;
}
}
if (tickProc != NULL)
{
tickProc();
}
Sleep(10);
}
return context->done;
}
/**
* **Wait For Save Data Load Completion**
*
* Waits for the save-data load callback to complete
* セーブ本体の読み込み待機
*
* @return `true` when callback is reached, `false` on timeout
*/
static bool WaitForSaveLoadResult(SaveDataLoadContext *context, DWORD timeoutMs, WorldManagerTickProc tickProc)
{
DWORD start = GetTickCount();
while ((GetTickCount() - start) < timeoutMs)
{
if (context->done)
{
return true;
}
if (tickProc != NULL)
{
tickProc();
}
Sleep(10);
}
return context->done;
}
/**
* **Match SAVE_INFO By World Name**
*
* Compares both save title and save filename against the target world name
* ワールド名一致判定
*/
static bool SaveInfoMatchesWorldName(const SAVE_INFO &saveInfo, const std::wstring &targetWorldName)
{
if (targetWorldName.empty())
{
return false;
}
std::wstring saveTitle = Utf8ToWide(saveInfo.UTF8SaveTitle);
std::wstring saveFilename = Utf8ToWide(saveInfo.UTF8SaveFilename);
if (!saveTitle.empty() && (_wcsicmp(saveTitle.c_str(), targetWorldName.c_str()) == 0))
{
return true;
}
if (!saveFilename.empty() && (_wcsicmp(saveFilename.c_str(), targetWorldName.c_str()) == 0))
{
return true;
}
return false;
}
/**
* **Match SAVE_INFO By Save Filename**
*
* Checks whether `SAVE_INFO` matches by save destination ID (`UTF8SaveFilename`)
* 保存先ID一致判定
*/
static bool SaveInfoMatchesSaveFilename(const SAVE_INFO &saveInfo, const std::string &targetSaveFilename)
{
if (targetSaveFilename.empty() || saveInfo.UTF8SaveFilename[0] == 0)
{
return false;
}
return (_stricmp(saveInfo.UTF8SaveFilename, targetSaveFilename.c_str()) == 0);
}
/**
* **Apply World Identity To Storage**
*
* Applies world identity (`level-name` + `level-id`) to storage
* - Always sets both display name and ID to avoid partial configuration
* - Helps prevent unintended new save destinations across environment differences
* 保存先と表示名の同期処理
*/
static void ApplyWorldStorageTarget(const std::wstring &worldName, const std::string &saveId)
{
// Set both title (display name) and save ID (actual folder name) explicitly
// Setting only one side can create unexpected new save targets in some environments
StorageManager.SetSaveTitle(worldName.c_str());
SetStorageSaveUniqueFilename(saveId);
}
/**
* **Prepare World Save Data For Startup**
*
* Searches for a save matching the target world and extracts startup payload when found
* Match priority:
* 1. Exact match by `level-id` (`UTF8SaveFilename`)
* 2. Fallback match by `level-name` against title or filename
* ワールド一致セーブの探索処理
*
* @return
* - `eWorldSaveLoad_Loaded`: Existing save loaded successfully
* - `eWorldSaveLoad_NotFound`: No matching save found
* - `eWorldSaveLoad_Failed`: API failure, corruption, or invalid data
*/
static EWorldSaveLoadResult PrepareWorldSaveData(
const std::wstring &targetWorldName,
const std::string &targetSaveFilename,
int actionPad,
WorldManagerTickProc tickProc,
LoadSaveDataThreadParam **outSaveData,
std::string *outResolvedSaveFilename)
{
if (outSaveData == NULL)
{
return eWorldSaveLoad_Failed;
}
*outSaveData = NULL;
if (outResolvedSaveFilename != NULL)
{
outResolvedSaveFilename->clear();
}
LogWorldIO("enumerating saves for configured world");
StorageManager.ClearSavesInfo();
SaveInfoQueryContext infoContext;
int infoState = StorageManager.GetSavesInfo(actionPad, &GetSavesInfoCallbackProc, &infoContext, "save");
if (infoState == C4JStorage::ESaveGame_Idle)
{
infoContext.done = true;
infoContext.success = true;
infoContext.details = StorageManager.ReturnSavesInfo();
}
else if (infoState != C4JStorage::ESaveGame_GetSavesInfo)
{
LogWorldIO("GetSavesInfo failed to start");
return eWorldSaveLoad_Failed;
}
if (!WaitForSaveInfoResult(&infoContext, 10000, tickProc))
{
LogWorldIO("timed out waiting for save list");
return eWorldSaveLoad_Failed;
}
if (infoContext.details == NULL)
{
infoContext.details = StorageManager.ReturnSavesInfo();
}
if (infoContext.details == NULL)
{
LogWorldIO("failed to retrieve save list");
return eWorldSaveLoad_Failed;
}
int matchedIndex = -1;
if (!targetSaveFilename.empty())
{
// 1) If save ID is provided, search by it first
// This is the most stable way to reuse the same world target
for (int i = 0; i < infoContext.details->iSaveC; ++i)
{
LogEnumeratedSaveInfo(i, infoContext.details->SaveInfoA[i]);
if (SaveInfoMatchesSaveFilename(infoContext.details->SaveInfoA[i], targetSaveFilename))
{
matchedIndex = i;
break;
}
}
}
if (matchedIndex < 0 && targetSaveFilename.empty())
{
for (int i = 0; i < infoContext.details->iSaveC; ++i)
{
LogEnumeratedSaveInfo(i, infoContext.details->SaveInfoA[i]);
}
}
for (int i = 0; i < infoContext.details->iSaveC; ++i)
{
// 2) If no save matched by ID, try compatibility fallback
// Match worldName against save title or save filename
if (matchedIndex >= 0)
{
break;
}
if (SaveInfoMatchesWorldName(infoContext.details->SaveInfoA[i], targetWorldName))
{
matchedIndex = i;
break;
}
}
if (matchedIndex < 0)
{
LogWorldIO("no save matched configured world name");
return eWorldSaveLoad_NotFound;
}
std::wstring matchedTitle = Utf8ToWide(infoContext.details->SaveInfoA[matchedIndex].UTF8SaveTitle);
if (matchedTitle.empty())
{
matchedTitle = targetWorldName;
}
LogWorldName("matched save title", matchedTitle);
SAVE_INFO *matchedSaveInfo = &infoContext.details->SaveInfoA[matchedIndex];
std::wstring matchedFilename = Utf8ToWide(matchedSaveInfo->UTF8SaveFilename);
if (!matchedFilename.empty())
{
LogWorldName("matched save filename", matchedFilename);
}
ApplyWorldStorageTarget(targetWorldName, targetSaveFilename);
std::string resolvedSaveFilename;
if (matchedSaveInfo->UTF8SaveFilename[0] != 0)
{
// Prefer the save ID that was actually matched, then keep using it for future saves
resolvedSaveFilename = matchedSaveInfo->UTF8SaveFilename;
SetStorageSaveUniqueFilename(resolvedSaveFilename);
}
else if (!targetSaveFilename.empty())
{
resolvedSaveFilename = targetSaveFilename;
}
if (outResolvedSaveFilename != NULL)
{
*outResolvedSaveFilename = resolvedSaveFilename;
}
SaveDataLoadContext loadContext;
int loadState = StorageManager.LoadSaveData(matchedSaveInfo, &LoadSaveDataCallbackProc, &loadContext);
if (loadState != C4JStorage::ESaveGame_Load && loadState != C4JStorage::ESaveGame_Idle)
{
LogWorldIO("LoadSaveData failed to start");
return eWorldSaveLoad_Failed;
}
if (loadState == C4JStorage::ESaveGame_Load)
{
if (!WaitForSaveLoadResult(&loadContext, 15000, tickProc))
{
LogWorldIO("timed out waiting for save data load");
return eWorldSaveLoad_Failed;
}
if (loadContext.isCorrupt)
{
LogWorldIO("target save is corrupt; aborting load");
return eWorldSaveLoad_Failed;
}
}
unsigned int saveSize = StorageManager.GetSaveSize();
if (saveSize == 0)
{
// Treat zero-byte payload as failure even when load API reports success
LogWorldIO("loaded save has zero size");
return eWorldSaveLoad_Failed;
}
byteArray loadedSaveData(saveSize, false);
unsigned int loadedSize = saveSize;
StorageManager.GetSaveData(loadedSaveData.data, &loadedSize);
if (loadedSize == 0)
{
LogWorldIO("failed to copy loaded save data from storage manager");
return eWorldSaveLoad_Failed;
}
*outSaveData = new LoadSaveDataThreadParam(loadedSaveData.data, loadedSize, matchedTitle);
LogWorldIO("prepared save data payload for server startup");
return eWorldSaveLoad_Loaded;
}
/**
* **Bootstrap World State For Server Startup**
*
* Determines final world startup state
* - Returns loaded save data when an existing save is found
* - Prepares a new world context when not found
* - Returns `Failed` when startup should be aborted
* サーバー起動時のワールド確定処理
*/
WorldBootstrapResult BootstrapWorldForServer(
const ServerPropertiesConfig &config,
int actionPad,
WorldManagerTickProc tickProc)
{
WorldBootstrapResult result;
if (!EnsureGameHddRootExists())
{
LogWorldIO("failed to prepare Windows64\\GameHDD storage root");
return result;
}
std::wstring targetWorldName = config.worldName;
std::string targetSaveFilename = config.worldSaveId;
if (targetWorldName.empty())
{
targetWorldName = L"world";
}
LogWorldName("configured level-name", targetWorldName);
if (!targetSaveFilename.empty())
{
LogSaveFilename("configured level-id", targetSaveFilename);
}
ApplyWorldStorageTarget(targetWorldName, targetSaveFilename);
std::string loadedSaveFilename;
EWorldSaveLoadResult worldLoadResult = PrepareWorldSaveData(
targetWorldName,
targetSaveFilename,
actionPad,
tickProc,
&result.saveData,
&loadedSaveFilename);
if (worldLoadResult == eWorldSaveLoad_Loaded)
{
result.status = eWorldBootstrap_Loaded;
result.resolvedSaveId = loadedSaveFilename;
LogStartupStep("loading configured world from save data");
}
else if (worldLoadResult == eWorldSaveLoad_NotFound)
{
// Create a new context only when no matching save exists
// Fix saveId here so the next startup writes to the same location
result.status = eWorldBootstrap_CreatedNew;
result.resolvedSaveId = targetSaveFilename;
LogStartupStep("configured world not found; creating new world");
LogWorldIO("creating new world save context");
StorageManager.ResetSaveData();
ApplyWorldStorageTarget(targetWorldName, targetSaveFilename);
}
else
{
result.status = eWorldBootstrap_Failed;
}
return result;
}
/**
* **Wait Until Server XUI Action Is Idle**
*
* Keeps tick/handle running during save action so async processing does not stall
* XUIアクション待機中の進行維持処理
*/
bool WaitForWorldActionIdle(
int actionPad,
DWORD timeoutMs,
WorldManagerTickProc tickProc,
WorldManagerHandleActionsProc handleActionsProc)
{
DWORD start = GetTickCount();
while (app.GetXuiServerAction(actionPad) != eXuiServerAction_Idle && !MinecraftServer::serverHalted())
{
// Keep network and storage progressing while waiting
// If this stops, save action itself may stall and time out
if (tickProc != NULL)
{
tickProc();
}
if (handleActionsProc != NULL)
{
handleActionsProc();
}
if ((GetTickCount() - start) >= timeoutMs)
{
return false;
}
Sleep(10);
}
return (app.GetXuiServerAction(actionPad) == eXuiServerAction_Idle);
}
}

View File

@@ -0,0 +1,92 @@
#pragma once
#include <string>
#include <windows.h>
#include "ServerProperties.h"
struct _LoadSaveDataThreadParam;
typedef struct _LoadSaveDataThreadParam LoadSaveDataThreadParam;
namespace ServerRuntime
{
/** Tick callback used while waiting on async storage/network work */
typedef void (*WorldManagerTickProc)();
/** Optional action handler used while waiting for server actions */
typedef void (*WorldManagerHandleActionsProc)();
/**
* **World Bootstrap Status**
*
* Result type for world startup preparation, either loading an existing world or creating a new one
* ワールド起動準備の結果種別
*/
enum EWorldBootstrapStatus
{
/** Found and loaded an existing world */
eWorldBootstrap_Loaded,
/** No matching save was found, created a new world context */
eWorldBootstrap_CreatedNew,
/** Bootstrap failed and server startup should be aborted */
eWorldBootstrap_Failed
};
/**
* **World Bootstrap Result**
*
* Output payload returned by world startup preparation
* ワールド起動準備の出力データ
*/
struct WorldBootstrapResult
{
/** Bootstrap status */
EWorldBootstrapStatus status;
/** Save data used for server initialization, `NULL` when creating a new world */
LoadSaveDataThreadParam *saveData;
/** Save ID that was actually selected */
std::string resolvedSaveId;
WorldBootstrapResult()
: status(eWorldBootstrap_Failed)
, saveData(NULL)
{
}
};
/**
* **Bootstrap Target World For Server Startup**
*
* Resolves whether the target world should be loaded from an existing save or created as new
* - Applies `level-name` and `level-id` from `server.properties`
* - Loads when a matching save exists
* - Creates a new world context only when no save matches
* サーバー起動時のロードか新規作成かを確定する
*
* @param config Normalized `server.properties` values
* @param actionPad padId used by async storage APIs
* @param tickProc Tick callback run while waiting for async completion
* @return Bootstrap result including whether save data was loaded
*/
WorldBootstrapResult BootstrapWorldForServer(
const ServerPropertiesConfig &config,
int actionPad,
WorldManagerTickProc tickProc);
/**
* **Wait Until Server Action Returns To Idle**
*
* Waits until server action state reaches `Idle`
* サーバーアクションの待機処理
*
* @param actionPad padId to monitor
* @param timeoutMs Timeout in milliseconds
* @param tickProc Tick callback run inside the wait loop
* @param handleActionsProc Optional action handler callback
* @return `true` when `Idle` is reached before timeout
*/
bool WaitForWorldActionIdle(
int actionPad,
DWORD timeoutMs,
WorldManagerTickProc tickProc,
WorldManagerHandleActionsProc handleActionsProc);
}

View File

@@ -0,0 +1,284 @@
# Minecraft.Server Developer Guide (English)
This document is for contributors who are new to `Minecraft.Server` and need a practical map for adding or modifying features safely.
## 1. What This Server Does
`Minecraft.Server` is the dedicated-server executable entry for this codebase.
Core responsibilities:
- Switch the process working directory to the executable folder before relative file I/O
- Load, normalize, and repair `server.properties`
- Initialize dedicated runtime systems, connection logging, and access control
- Load or create the target world and keep `level-id` aligned with the actual save destination
- Run the dedicated main loop (network tick, XUI actions, autosave, CLI input)
- Maintain operator-facing access files such as `banned-players.json` and `banned-ips.json`
- Perform an initial save for newly created worlds and then shut down safely
## 2. Important Files
### Startup and Runtime
- `Windows64/ServerMain.cpp`
- `PrintUsage()` and `ParseCommandLine()`
- `SetExeWorkingDirectory()`
- Runtime setup and shutdown flow
- Initial save path for newly created worlds
- Main loop, autosave scheduler, and CLI polling
### World Selection and Save Load
- `WorldManager.h`
- `WorldManager.cpp`
- Finds matching save by `level-id` first, then world-name fallback
- Applies storage title + save ID consistently
- Wait helpers for async storage/server action completion
### Server Properties
- `ServerProperties.h`
- `ServerProperties.cpp`
- Default values and normalization ranges
- Parse/repair/write `server.properties`
- Exposes `ServerPropertiesConfig`
- `SaveServerPropertiesConfig()` rewrites `level-name`, `level-id`, and `white-list`
### Access Control, Ban, and Whitelist Storage
- `Access/Access.h`
- `Access/Access.cpp`
- Process-wide access-control facade
- Published snapshot model used by console commands and login checks
- `Access/BanManager.h`
- `Access/BanManager.cpp`
- Reads/writes `banned-players.json` and `banned-ips.json`
- Normalizes identifiers and filters expired entries from snapshots
- `Access/WhitelistManager.h`
- `Access/WhitelistManager.cpp`
- Reads/writes `whitelist.json`
- Normalizes XUID-based whitelist entries used by login validation and CLI commands
### Logging and Connection Audit
- `ServerLogger.h`
- `ServerLogger.cpp`
- Log level parsing
- Colored/timestamped console logs
- General categories such as `startup`, `world-io`, `console`, `access`, `network`, and `shutdown`
- `ServerLogManager.h`
- `ServerLogManager.cpp`
- Accepted/rejected TCP connection logs
- Login/disconnect audit logs
- Remote-IP cache used by `ban-ip <player>`
### Console Command System
- `Console/ServerCli.cpp` (facade)
- `Console/ServerCliInput.cpp` (linenoise input thread + completion bridge)
- `Console/ServerCliParser.cpp` (tokenization, quoted args, completion context)
- `Console/ServerCliEngine.cpp` (dispatch, completion, helpers)
- `Console/ServerCliRegistry.cpp` (command registration + lookup)
- `Console/commands/*` (individual commands)
## 3. End-to-End Startup Flow
Main flow in `Windows64/ServerMain.cpp`:
1. `SetExeWorkingDirectory()` switches the current directory to the executable folder.
2. Load and normalize `server.properties` via `LoadServerPropertiesConfig()`.
3. Copy config into `DedicatedServerConfig`, then apply CLI overrides (`-port`, `-ip`/`-bind`, `-name`, `-maxplayers`, `-seed`, `-loglevel`, `-help`/`--help`/`-h`).
4. Initialize process state, `ServerLogManager`, and `Access::Initialize(".")`.
5. Initialize window/device/profile/network/thread-local systems.
6. Set host/game options from `ServerPropertiesConfig`.
7. Bootstrap world with `BootstrapWorldForServer(...)`.
8. If world bootstrap resolves a different normalized save ID, persist it with `SaveServerPropertiesConfig()`.
9. Start hosted game thread (`RunNetworkGameThreadProc`).
10. If a brand-new world was created, explicitly request one initial save.
11. Enter the main loop:
- `TickCoreSystems()`
- `HandleXuiActions()`
- `serverCli.Poll()`
- autosave scheduling
12. On shutdown:
- stop CLI input
- request save-on-exit / halt server
- wait for network shutdown completion
- terminate log, access, network, and device systems
## 4. Current Operator Surface
### 4.1 Launch Arguments
- `-port <1-65535>`
- `-ip <addr>` or `-bind <addr>`
- `-name <name>` (runtime max 16 chars)
- `-maxplayers <1-8>`
- `-seed <int64>`
- `-loglevel <debug|info|warn|error>`
- `-help`, `--help`, `-h`
Notes:
- CLI overrides affect only the current process.
- The only values currently written back by the server are `level-name` and `level-id`, and that happens when world bootstrap resolves identity changes.
### 4.2 Built-in Console Commands
- `help` / `?`
- `stop`
- `list`
- `ban <player> [reason ...]`
- currently requires the target player to be online
- `ban-ip <address|player> [reason ...]`
- accepts a literal IPv4/IPv6 address or an online player's current remote IP
- `pardon <player>`
- `pardon-ip <address>`
- only accepts a literal address
- `banlist`
- `tp <player> <target>` / `teleport`
- `gamemode <survival|creative|0|1> [player]` / `gm`
CLI behavior notes:
- Command parsing accepts both `cmd` and `/cmd`.
- Quoted arguments are supported by `ServerCliParser`.
- Completion is implemented per command via `Complete(...)`.
### 4.3 Files Written Next to the Executable
- `server.properties`
- `banned-players.json`
- `banned-ips.json`
This follows from `SetExeWorkingDirectory()`, so these files are resolved relative to `Minecraft.Server.exe`, not the shell directory you launched from.
## 5. Common Development Tasks
### 5.1 Add a New CLI Command
Use this pattern when adding commands like `/kick`, `/time`, etc.
1. Add files under `Console/commands/`
- `CliCommandYourCommand.h`
- `CliCommandYourCommand.cpp`
2. Implement `IServerCliCommand`
- `Name()`, `Usage()`, `Description()`, `Execute(...)`
- optional: `Aliases()` and `Complete(...)`
3. Register the command in `ServerCliEngine::RegisterDefaultCommands()`.
4. Add source/header to build definitions:
- `CMakeLists.txt` (`MINECRAFT_SERVER_SOURCES`)
- `Minecraft.Server/Minecraft.Server.vcxproj` (`<ClCompile>` / `<ClInclude>`)
5. Manual verify:
- command appears in `help`
- command executes correctly
- completion works for both `cmd` and `/cmd`
- quoted arguments behave as expected
Implementation references:
- `CliCommandHelp.cpp` for a simple no-arg command
- `CliCommandTp.cpp` for multi-arg + completion + runtime checks
- `CliCommandGamemode.cpp` for argument parsing and aliases
- `CliCommandBanIp.cpp` for access-backed behavior with connection metadata
### 5.2 Add or Change a `server.properties` Key
1. Add/update the field in `ServerPropertiesConfig` (`ServerProperties.h`).
2. Add a default entry to `kServerPropertyDefaults` (`ServerProperties.cpp`).
3. Load and normalize the value in `LoadServerPropertiesConfig()`.
- Use existing helpers for bool/int/string/int64/log level/level type.
4. If this value should be written back, update `SaveServerPropertiesConfig()`.
- Note: today that function intentionally only persists world identity.
5. Apply it to runtime where needed:
- `ApplyServerPropertiesToDedicatedConfig(...)`
- host options in `ServerMain.cpp` (`app.SetGameHostOption(...)`)
- `PrintUsage()` / `ParseCommandLine()` if the key also gets a CLI override
6. Manual verify:
- missing key regeneration
- invalid value normalization
- clamped ranges still make sense
- runtime behavior reflects the new value
Normalization details worth remembering:
- `level-id` is normalized to a safe save ID and length-limited.
- `server-name` is capped to 16 runtime chars.
- `max-players` is clamped to `1..8`.
- `autosave-interval` is clamped to `5..3600`.
- `level-type` normalizes to `default` or `flat`.
### 5.3 Change Ban / Access Behavior
Primary code lives in `Access/Access.cpp`, `Access/BanManager.cpp`, and `ServerLogManager.cpp`.
When changing this area:
- Keep `BanManager` responsible for storage/caching, not live-network policy.
- Keep the clone-and-publish snapshot pattern in `Access.cpp` so readers never block on disk I/O.
- Remember that `ban-ip <player>` depends on `ServerLogManager::TryGetConnectionRemoteIp(...)`.
- Keep expired entries out of `SnapshotBannedPlayers()` / `SnapshotBannedIps()` output.
- Verify:
- clean boot creates empty ban files when missing
- `ban`, `ban-ip`, `pardon`, `pardon-ip`, and `banlist` still work
- online bans disconnect live targets immediately
- manual edits still reload safely if you later add or extend reload paths
### 5.4 Change World Load/Create Behavior
Primary code is in `WorldManager.cpp`.
Current matching policy:
1. Match by `level-id` (`UTF8SaveFilename`) first.
2. Fall back to world-name match on title/file name.
When changing this logic:
- Keep `ApplyWorldStorageTarget(...)` usage consistent (title + save ID together).
- Preserve periodic ticking in wait loops (`tickProc`) to avoid async deadlocks.
- Keep timeout/error logs specific enough for diagnosis.
- Verify:
- existing world is reused correctly
- no accidental new save directory creation
- shutdown save still succeeds
- newly created worlds still get the explicit initial save from `ServerMain.cpp`
### 5.5 Add Logging for New Feature Work
Use `ServerLogger` helpers:
- `LogDebug`, `LogInfo`, `LogWarn`, `LogError`
- formatted variants `LogDebugf`, `LogInfof`, etc.
Use `ServerLogManager` when the event is specifically part of the transport/login/disconnect lifecycle.
Recommended categories:
- `startup` for init/shutdown lifecycle
- `world-io` for save/world operations
- `console` for CLI command handling
- `access` for ban/access control state
- `network` for connection/login audit
## 6. Build and Run
From repository root:
```powershell
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Debug --target MinecraftServer
cd .\build\Debug
.\Minecraft.Server.exe -port 25565 -bind 0.0.0.0 -maxplayers 8 -name DedicatedServer
```
Notes:
- The process switches its working directory to the executable directory at startup.
- `server.properties`, `banned-players.json`, and `banned-ips.json` are therefore read/written next to the executable.
- For Visual Studio workflow, see root `COMPILE.md`.
## 7. Safety Checklist Before Commit
- the server starts without crash when `server.properties` is missing or sparse
- missing access files are recreated on a clean boot
- existing world loads by expected `level-id`
- new world creation still performs the explicit initial save
- CLI input and completion remain responsive
- `banlist` output stays sane after adding/removing bans
- no busy-wait path removed from async wait loops
- both CMake and `.vcxproj` include newly added source files
## 8. Quick Troubleshooting
- Unknown command:
- check `RegisterDefaultCommands()` and build-file entries
- `server.properties` or ban files seem to load from the wrong folder:
- remember `SetExeWorkingDirectory()` moves the working directory to the executable folder
- Autosave or shutdown save timing out:
- confirm wait loops still call `TickCoreSystems()` and `HandleXuiActions()` where required
- World not reused on restart:
- inspect `level-id` normalization and matching logic in `WorldManager.cpp`
- `ban-ip <player>` cannot resolve an address:
- confirm the player is currently online and `ServerLogManager` has a cached remote IP for that connection
- Settings not applied:
- confirm the value is loaded into `ServerPropertiesConfig`, optionally copied into `DedicatedServerConfig`, and then applied in `ServerMain.cpp`

View File

@@ -0,0 +1,286 @@
# Minecraft.Server 開発ガイド (日本語)
この文書は、`Minecraft.Server` に新しく入る開発者が、安全に機能追加や改修を行うための実践的な地図として使うことを想定しています
## 1. このサーバーが担うこと
`Minecraft.Server` は、このコードベースにおける専用サーバー実行ファイルのエントリーポイントです
主な責務:
- 相対パスのファイル I/O を行う前に、カレントディレクトリを実行ファイルのあるフォルダへ切り替える
- `server.properties` を読み込み、正規化し、不足や不正値を補完する
- 専用サーバー向けランタイム、接続ログ、アクセス制御を初期化する
- 対象ワールドをロードまたは新規作成し、実際のセーブ先と `level-id` を整合させる
- 専用サーバーのメインループを回す (network tick, XUI actions, autosave, CLI input)
- `banned-players.json``banned-ips.json` など運用向けファイルを維持する
- 新規ワールドの初回保存を実行し、その後安全にシャットダウンする
## 2. 重要ファイル
### 起動とランタイム
- `Windows64/ServerMain.cpp`
- `PrintUsage()``ParseCommandLine()`
- `SetExeWorkingDirectory()`
- 起動/終了フロー
- 新規ワールド初回保存の経路
- メインループ、オートセーブ、CLI ポーリング
### ワールド選択とセーブ読込
- `WorldManager.h`
- `WorldManager.cpp`
- `level-id` 優先、その後 world 名フォールバックでセーブ探索
- storage title と save ID を常にセットで適用
- 非同期 storage/server action 完了待ちの helper を提供
### サーバー設定
- `ServerProperties.h`
- `ServerProperties.cpp`
- 既定値と正規化レンジ
- `server.properties` の読込/補修/書込
- `ServerPropertiesConfig` の提供
- `SaveServerPropertiesConfig()``level-name` / `level-id` / `white-list` を書き換える
### アクセス制御と BAN / Whitelist 永続化
- `Access/Access.h`
- `Access/Access.cpp`
- プロセス全体で使うアクセス制御 facade
- コンソールコマンドとログイン判定から参照される公開スナップショット管理
- `Access/BanManager.h`
- `Access/BanManager.cpp`
- `banned-players.json``banned-ips.json` の読込/書込
- 識別子の正規化と、期限切れエントリを除いた snapshot 出力
- `Access/WhitelistManager.h`
- `Access/WhitelistManager.cpp`
- `whitelist.json` の読込/書込
- ログイン判定と CLI で使う XUID whitelist の正規化管理
### ログと接続監査
- `ServerLogger.h`
- `ServerLogger.cpp`
- ログレベル解釈
- 色付き/タイムスタンプ付きコンソールログ
- `startup`, `world-io`, `console`, `access`, `network`, `shutdown` などのカテゴリ
- `ServerLogManager.h`
- `ServerLogManager.cpp`
- TCP 接続 accept/reject ログ
- ログイン/切断の監査ログ
- `ban-ip <player>` が使う remote IP キャッシュ
### コンソールコマンドシステム
- `Console/ServerCli.cpp` (facade)
- `Console/ServerCliInput.cpp` (linenoise 入力スレッド + completion bridge)
- `Console/ServerCliParser.cpp` (トークン分解、クォート、補完コンテキスト)
- `Console/ServerCliEngine.cpp` (実行ディスパッチ、補完、共通ヘルパー)
- `Console/ServerCliRegistry.cpp` (登録と名前解決)
- `Console/commands/*` (各コマンド実装)
## 3. 起動フロー全体
`Windows64/ServerMain.cpp` の主な流れ:
1. `SetExeWorkingDirectory()` でカレントディレクトリを実行ファイルのフォルダへ切り替える
2. `LoadServerPropertiesConfig()``server.properties` を読み込み、正規化する
3. `DedicatedServerConfig` へ反映したあと、CLI 引数で上書きする (`-port`, `-ip`/`-bind`, `-name`, `-maxplayers`, `-seed`, `-loglevel`, `-help`/`--help`/`-h`)
4. プロセス状態、`ServerLogManager``Access::Initialize(".")` を初期化する
5. window/device/profile/network/thread-local 系を初期化する
6. `ServerPropertiesConfig` をゲームホスト設定へ反映する
7. `BootstrapWorldForServer(...)` でワールドを決定する
8. 読み込まれたセーブ ID が正規化後に変わった場合は、`SaveServerPropertiesConfig()` で書き戻す
9. `RunNetworkGameThreadProc` でホストゲームスレッドを起動する
10. 新規ワールドが作成された場合は、専用サーバー側で明示的に初回保存を要求する
11. メインループに入る:
- `TickCoreSystems()`
- `HandleXuiActions()`
- `serverCli.Poll()`
- オートセーブスケジュール
12. 終了時:
- CLI 入力を停止
- save-on-exit を要求してサーバー停止
- ネットワーク停止完了を待機
- ログ/アクセス制御/ネットワーク/デバイスを終了
## 4. 現在の運用インターフェース
### 4.1 起動引数
- `-port <1-65535>`
- `-ip <addr>` または `-bind <addr>`
- `-name <name>` (実行時上限 16 文字)
- `-maxplayers <1-8>`
- `-seed <int64>`
- `-loglevel <debug|info|warn|error>`
- `-help`, `--help`, `-h`
補足:
- CLI による上書きは、その起動中のプロセスにだけ効きます
- 現在サーバーが書き戻す値は `level-name``level-id` だけで、ワールド解決時に識別情報が変わった場合に限られます
### 4.2 組み込みコンソールコマンド
- `help` / `?`
- `stop`
- `list`
- `ban <player> [reason ...]`
- 現状では対象プレイヤーがオンラインである必要があります
- `ban-ip <address|player> [reason ...]`
- リテラル IPv4/IPv6 か、オンラインプレイヤーの現在 IP を対象にできます
- `pardon <player>`
- `pardon-ip <address>`
- リテラルアドレスのみ受け付けます
- `banlist`
- `tp <player> <target>` / `teleport`
- `gamemode <survival|creative|0|1> [player]` / `gm`
CLI 挙動の補足:
- `cmd``/cmd` の両方を受け付けます
- `ServerCliParser` により引用符付き引数を扱えます
- 補完は各コマンドの `Complete(...)` で実装します
### 4.3 実行ファイル横に書かれるファイル
- `server.properties`
- `banned-players.json`
- `banned-ips.json`
これは `SetExeWorkingDirectory()` による挙動ですつまり、これらのファイルはシェル上の起動場所ではなく `Minecraft.Server.exe` 基準で解決されます
## 5. よくある開発作業
### 5.1 CLI コマンドを追加する
`/kick``/time` のようなコマンド追加時の基本手順:
1. `Console/commands/` にファイルを追加
- `CliCommandYourCommand.h`
- `CliCommandYourCommand.cpp`
2. `IServerCliCommand` を実装
- `Name()`, `Usage()`, `Description()`, `Execute(...)`
- 必要なら `Aliases()``Complete(...)`
3. `ServerCliEngine::RegisterDefaultCommands()` に登録する
4. ビルド定義に追加する
- `CMakeLists.txt` (`MINECRAFT_SERVER_SOURCES`)
- `Minecraft.Server/Minecraft.Server.vcxproj` (`<ClCompile>` / `<ClInclude>`)
5. 手動確認
- `help` に表示される
- 実行結果が期待通り
- 補完が `cmd``/cmd` の両方で動く
- 引用符付き引数が期待通り処理される
参考実装:
- `CliCommandHelp.cpp` (単純コマンド)
- `CliCommandTp.cpp` (複数引数 + 補完 + 実行時チェック)
- `CliCommandGamemode.cpp` (引数解釈 + エイリアス)
- `CliCommandBanIp.cpp` (接続メタデータを使うアクセス制御系コマンド)
### 5.2 `server.properties` キーを追加/変更する
1. `ServerProperties.h``ServerPropertiesConfig` にフィールドを追加/更新する
2. `ServerProperties.cpp``kServerPropertyDefaults` に既定値を追加する
3. `LoadServerPropertiesConfig()` で読み込みと正規化を実装する
- bool/int/string/int64/log level/level type 用の既存 helper を使う
4. 書き戻し対象にしたいなら `SaveServerPropertiesConfig()` を更新する
- ただし現状この関数は、意図的にワールド識別情報だけを永続化します
5. 実行時反映箇所を更新する:
- `ApplyServerPropertiesToDedicatedConfig(...)`
- `ServerMain.cpp``app.SetGameHostOption(...)`
- CLI 上書きも持たせるなら `PrintUsage()` / `ParseCommandLine()`
6. 手動確認:
- 欠損キーの自動補完
- 不正値の正規化
- clamp 範囲が妥当か
- 実行時挙動に反映されるか
> 覚えておくと良い正規化ポイント
- `level-id` は安全な save ID に正規化され、長さ制限も掛かる
- `server-name` は実行時 16 文字まで
- `max-players``1..8` に clamp される(あとで増やす必要あり)
- `autosave-interval``5..3600` に clamp される
- `level-type``default` または `flat` に正規化される
### 5.3 BAN / アクセス制御挙動を変更する
主な実装は `Access/Access.cpp`, `Access/BanManager.cpp`, `ServerLogManager.cpp` にあります
変更時の注意:
- `BanManager` は storage/caching に責務を寄せ、 live-network policy を持ち込みすぎない
- `Access.cpp` の clone-and-publish スナップショット方式を保ち、読取側がディスク I/O で止まらないようにする
- `ban-ip <player>``ServerLogManager::TryGetConnectionRemoteIp(...)` に依存することを忘れない
- `SnapshotBannedPlayers()` / `SnapshotBannedIps()` には期限切れエントリを混ぜない
- 確認項目:
- 欠損時に空の BAN ファイルが初回起動で生成される
- `ban`, `ban-ip`, `pardon`, `pardon-ip`, `banlist` が動く
- オンライン対象の BAN が即時切断まで到達する
- 将来 reload 経路を増やしても手動編集が安全に再読込できる
### 5.4 ワールドロード/新規作成ロジックを変更する
主な実装は `WorldManager.cpp` にあります
現在の探索ポリシー:
1. `level-id` (`UTF8SaveFilename`) 完全一致を優先
2. 失敗時に world 名一致へフォールバック
変更時の注意:
- `ApplyWorldStorageTarget(...)` で title と save ID を常にセットで扱う
- 待機ループで `tickProc` を回し続ける
- これを止めると非同期進行が止まり、タイムアウトしやすくなる
- タイムアウトや失敗ログは具体的に残す
- 確認項目
- 既存ワールドを正しく再利用できるか
- 意図しない新規セーブ先が増えていないか
- 終了時保存が成功するか
- 新規ワールド時の明示的初回保存が `ServerMain.cpp` から維持されているか
### 5.5 ログを追加する
`ServerLogger` の API を利用:
- `LogDebug`, `LogInfo`, `LogWarn`, `LogError`
- フォーマット付きは `LogDebugf`, `LogInfof` など
transport/login/disconnect ライフサイクルに属するイベントなら `ServerLogManager` 側を使います
推奨カテゴリ:
- `startup`: 起動ライフサイクル
- `shutdown`: 停止ライフサイクル
- `world-io`: ワールド/保存処理
- `console`: CLI コマンド処理
- `access`: BAN/アクセス制御状態
- `network`: 接続/ログイン監査
## 6. ビルドと実行
リポジトリルートで実行:
```powershell
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Debug --target MinecraftServer
cd .\build\Debug
.\Minecraft.Server.exe -port 25565 -bind 0.0.0.0 -name DedicatedServer
```
補足:
- プロセスは起動時にカレントディレクトリを実行ファイルの場所へ切り替えます
- `server.properties`, `banned-players.json`, `banned-ips.json` はそのため実行ファイル横に読み書きされます
- Visual Studio ワークフローはルートの `COMPILE.md` を参照してください
## 7. 変更前チェックリスト
- `server.properties` が欠損または疎でもクラッシュせず起動できる
- 欠損したアクセス制御ファイルがクリーンブート時に再生成される
- 既存ワールドが期待した `level-id` でロードされる
- 新規ワールド作成時の明示的初回保存が維持される
- CLI 入力と補完が引き続き応答する
- `banlist` 出力が BAN 追加/解除後も破綻しない
- 非同期待機ループから `TickCoreSystems()` など busy-wait 防止用ティックを消していない
- 新規追加したソースが CMake と `.vcxproj` の両方に入っている
## 8. クイックトラブルシュート
- コマンドが認識されない:
- `RegisterDefaultCommands()` とビルド定義を確認する
- `server.properties` や BAN ファイルの読込先が想定と違う:
- `SetExeWorkingDirectory()` により実行ファイルのフォルダへ移動していることを確認する
- オートセーブ/終了時保存がタイムアウトする:
- 待機ループ内で `TickCoreSystems()``HandleXuiActions()` を回しているか確認する
- 再起動時に同じワールドを使わない:
- `level-id` の正規化と `WorldManager.cpp` の一致判定を確認する
- `ban-ip <player>` で IP を解決できない:
- 対象プレイヤーがオンラインで、`ServerLogManager` に接続 IP がキャッシュされているか確認する
- 設定変更が効かない:
- 値が `ServerPropertiesConfig` にロードされ、必要なら `DedicatedServerConfig` にコピーされ、その後 `ServerMain.cpp` で反映されているか確認する

View File

@@ -0,0 +1,25 @@
This vendored component is based on the linenoise project idea/API.
Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,649 @@
#include "linenoise.h"
#include <conio.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define LINENOISE_MAX_LINE 4096
#define LINENOISE_MAX_PROMPT 128
typedef struct linenoiseHistory {
char **items;
int len;
int cap;
int maxLen;
} linenoiseHistory;
static linenoiseCompletionCallback *g_completionCallback = NULL;
static volatile LONG g_stopRequested = 0;
static linenoiseHistory g_history = { NULL, 0, 0, 128 };
/* Guards redraw/log interleaving so prompt and log lines do not overlap. */
static CRITICAL_SECTION g_ioLock;
static volatile LONG g_ioLockState = 0; /* 0=not init, 1=init in progress, 2=ready */
/* Snapshot of current editor line used to restore prompt after external output. */
static volatile LONG g_editorActive = 0;
static char g_editorPrompt[LINENOISE_MAX_PROMPT] = { 0 };
static char g_editorBuf[LINENOISE_MAX_LINE] = { 0 };
static int g_editorLen = 0;
static int g_editorPos = 0;
static int g_editorPrevLen = 0;
/**
* Lazily initialize the console I/O critical section.
* This avoids static init order issues and keeps startup cost minimal.
*/
static void linenoiseEnsureIoLockInit(void)
{
LONG state = InterlockedCompareExchange(&g_ioLockState, 0, 0);
if (state == 2)
return;
if (state == 0 && InterlockedCompareExchange(&g_ioLockState, 1, 0) == 0)
{
InitializeCriticalSection(&g_ioLock);
InterlockedExchange(&g_ioLockState, 2);
return;
}
while (InterlockedCompareExchange(&g_ioLockState, 0, 0) != 2)
{
Sleep(0);
}
}
static void linenoiseLockIo(void)
{
linenoiseEnsureIoLockInit();
EnterCriticalSection(&g_ioLock);
}
static void linenoiseUnlockIo(void)
{
LeaveCriticalSection(&g_ioLock);
}
/**
* Save current prompt/buffer/cursor state for later redraw.
* Called after each redraw while editor is active.
*/
static void linenoiseUpdateEditorState(const char *prompt, const char *buf, int len, int pos, int prevLen)
{
if (prompt == NULL)
prompt = "";
if (buf == NULL)
buf = "";
strncpy_s(g_editorPrompt, sizeof(g_editorPrompt), prompt, _TRUNCATE);
strncpy_s(g_editorBuf, sizeof(g_editorBuf), buf, _TRUNCATE);
g_editorLen = len;
g_editorPos = pos;
g_editorPrevLen = prevLen;
InterlockedExchange(&g_editorActive, 1);
}
static void linenoiseDeactivateEditorState(void)
{
InterlockedExchange(&g_editorActive, 0);
g_editorPrompt[0] = 0;
g_editorBuf[0] = 0;
g_editorLen = 0;
g_editorPos = 0;
g_editorPrevLen = 0;
}
static char *linenoiseStrdup(const char *src)
{
size_t n = strlen(src) + 1;
char *out = (char *)malloc(n);
if (out == NULL)
return NULL;
memcpy(out, src, n);
return out;
}
static void linenoiseEnsureHistoryCapacity(int wanted)
{
if (wanted <= g_history.cap)
return;
int newCap = g_history.cap == 0 ? 32 : g_history.cap;
while (newCap < wanted)
newCap *= 2;
char **newItems = (char **)realloc(g_history.items, sizeof(char *) * (size_t)newCap);
if (newItems == NULL)
return;
g_history.items = newItems;
g_history.cap = newCap;
}
static void linenoiseClearCompletions(linenoiseCompletions *lc)
{
size_t i = 0;
for (i = 0; i < lc->len; ++i)
{
free(lc->cvec[i]);
}
free(lc->cvec);
lc->cvec = NULL;
lc->len = 0;
}
void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str)
{
char **newVec = (char **)realloc(lc->cvec, sizeof(char *) * (lc->len + 1));
if (newVec == NULL)
return;
lc->cvec = newVec;
lc->cvec[lc->len] = linenoiseStrdup(str);
if (lc->cvec[lc->len] == NULL)
return;
lc->len += 1;
}
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn)
{
g_completionCallback = fn;
}
void linenoiseFree(void *ptr)
{
free(ptr);
}
int linenoiseHistorySetMaxLen(int len)
{
if (len <= 0)
return 0;
g_history.maxLen = len;
while (g_history.len > g_history.maxLen)
{
free(g_history.items[0]);
memmove(g_history.items, g_history.items + 1, sizeof(char *) * (size_t)(g_history.len - 1));
g_history.len -= 1;
}
return 1;
}
int linenoiseHistoryAdd(const char *line)
{
if (line == NULL || line[0] == 0)
return 0;
if (g_history.len > 0)
{
const char *last = g_history.items[g_history.len - 1];
if (last != NULL && strcmp(last, line) == 0)
return 1;
}
linenoiseEnsureHistoryCapacity(g_history.len + 1);
if (g_history.cap <= g_history.len)
return 0;
g_history.items[g_history.len] = linenoiseStrdup(line);
if (g_history.items[g_history.len] == NULL)
return 0;
g_history.len += 1;
while (g_history.len > g_history.maxLen)
{
free(g_history.items[0]);
memmove(g_history.items, g_history.items + 1, sizeof(char *) * (size_t)(g_history.len - 1));
g_history.len -= 1;
}
return 1;
}
void linenoiseRequestStop(void)
{
InterlockedExchange(&g_stopRequested, 1);
}
void linenoiseResetStop(void)
{
InterlockedExchange(&g_stopRequested, 0);
}
static int linenoiseIsStopRequested(void)
{
return InterlockedCompareExchange(&g_stopRequested, 0, 0) != 0;
}
static void linenoiseWriteHint(const char *hint, size_t hintLen)
{
HANDLE stdoutHandle;
CONSOLE_SCREEN_BUFFER_INFO originalInfo;
int hasColorConsole = 0;
if (hint == NULL || hintLen == 0)
{
return;
}
stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
if (stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != NULL)
{
if (GetConsoleScreenBufferInfo(stdoutHandle, &originalInfo))
{
hasColorConsole = 1;
/* Draw predictive tail in dim gray, then restore original console colors. */
SetConsoleTextAttribute(stdoutHandle, FOREGROUND_INTENSITY);
}
}
fwrite(hint, 1, hintLen, stdout);
if (hasColorConsole)
{
SetConsoleTextAttribute(stdoutHandle, originalInfo.wAttributes);
}
}
static int linenoiseStartsWithIgnoreCase(const char *full, const char *prefix)
{
while (*prefix != 0)
{
if (tolower((unsigned char)*full) != tolower((unsigned char)*prefix))
{
return 0;
}
++full;
++prefix;
}
return 1;
}
static size_t linenoiseBuildHint(const char *buf, char *hint, size_t hintSize)
{
linenoiseCompletions lc;
size_t inputLen = 0;
size_t i = 0;
if (hint == NULL || hintSize == 0)
{
return 0;
}
hint[0] = 0;
if (buf == NULL || buf[0] == 0 || g_completionCallback == NULL)
{
return 0;
}
lc.len = 0;
lc.cvec = NULL;
/* Reuse the completion callback and derive a "ghost text" suffix from the first extending match. */
g_completionCallback(buf, &lc);
inputLen = strlen(buf);
for (i = 0; i < lc.len; ++i)
{
const char *candidate = lc.cvec[i];
if (candidate == NULL)
{
continue;
}
if (strlen(candidate) <= inputLen)
{
continue;
}
if (!linenoiseStartsWithIgnoreCase(candidate, buf))
{
continue;
}
/* Keep only the part not yet typed by the user (rendered as hint text). */
strncpy_s(hint, hintSize, candidate + inputLen, _TRUNCATE);
break;
}
linenoiseClearCompletions(&lc);
return strlen(hint);
}
static void linenoiseRedrawUnsafe(const char *prompt, const char *buf, int len, int pos, int *prevLen)
{
int i;
char hint[LINENOISE_MAX_LINE] = {0};
int renderedLen = len;
/* Hint length contributes to the rendered width so stale tail characters can be cleared correctly. */
int hintLen = (int)linenoiseBuildHint(buf, hint, sizeof(hint));
if (hintLen > 0)
{
renderedLen += hintLen;
}
fputc('\r', stdout);
fputs(prompt, stdout);
if (len > 0)
{
fwrite(buf, 1, (size_t)len, stdout);
}
if (hintLen > 0)
{
linenoiseWriteHint(hint, (size_t)hintLen);
}
if (*prevLen > renderedLen)
{
for (i = renderedLen; i < *prevLen; ++i)
{
fputc(' ', stdout);
}
}
fputc('\r', stdout);
fputs(prompt, stdout);
if (pos > 0)
{
/* Cursor positioning reflects only real user input, not the ghost hint suffix. */
fwrite(buf, 1, (size_t)pos, stdout);
}
fflush(stdout);
*prevLen = renderedLen;
linenoiseUpdateEditorState(prompt, buf, len, pos, *prevLen);
}
static void linenoiseRedraw(const char *prompt, const char *buf, int len, int pos, int *prevLen)
{
linenoiseLockIo();
linenoiseRedrawUnsafe(prompt, buf, len, pos, prevLen);
linenoiseUnlockIo();
}
static int linenoiseStartsWith(const char *full, const char *prefix)
{
while (*prefix != 0)
{
if (*full != *prefix)
return 0;
++full;
++prefix;
}
return 1;
}
static int linenoiseComputeCommonPrefix(const linenoiseCompletions *lc, const char *seed, char *out, size_t outSize)
{
size_t commonLen = 0;
size_t i;
if (lc->len == 0 || outSize == 0)
return 0;
strncpy_s(out, outSize, lc->cvec[0], _TRUNCATE);
commonLen = strlen(out);
for (i = 1; i < lc->len; ++i)
{
const char *candidate = lc->cvec[i];
size_t j = 0;
while (j < commonLen && out[j] != 0 && candidate[j] != 0 && out[j] == candidate[j])
++j;
commonLen = j;
out[commonLen] = 0;
if (commonLen == 0)
break;
}
if (strlen(out) <= strlen(seed))
return 0;
return linenoiseStartsWith(out, seed);
}
static void linenoiseApplyCompletion(const char *prompt, char *buf, int *len, int *pos, int *prevLen)
{
linenoiseCompletions lc;
int i;
if (g_completionCallback == NULL)
{
Beep(750, 15);
return;
}
lc.len = 0;
lc.cvec = NULL;
g_completionCallback(buf, &lc);
if (lc.len == 0)
{
Beep(750, 15);
linenoiseClearCompletions(&lc);
return;
}
if (lc.len == 1)
{
strncpy_s(buf, LINENOISE_MAX_LINE, lc.cvec[0], _TRUNCATE);
*len = (int)strlen(buf);
*pos = *len;
linenoiseRedraw(prompt, buf, *len, *pos, prevLen);
linenoiseClearCompletions(&lc);
return;
}
{
char common[LINENOISE_MAX_LINE] = { 0 };
if (linenoiseComputeCommonPrefix(&lc, buf, common, sizeof(common)))
{
strncpy_s(buf, LINENOISE_MAX_LINE, common, _TRUNCATE);
*len = (int)strlen(buf);
*pos = *len;
linenoiseRedraw(prompt, buf, *len, *pos, prevLen);
}
}
linenoiseLockIo();
fputc('\n', stdout);
for (i = 0; i < (int)lc.len; ++i)
{
fputs(lc.cvec[i], stdout);
fputs(" ", stdout);
}
fputc('\n', stdout);
linenoiseRedrawUnsafe(prompt, buf, *len, *pos, prevLen);
linenoiseUnlockIo();
linenoiseClearCompletions(&lc);
}
char *linenoise(const char *prompt)
{
char buf[LINENOISE_MAX_LINE];
int len = 0;
int pos = 0;
int prevLen = 0;
int historyIndex = g_history.len;
if (prompt == NULL)
prompt = "";
buf[0] = 0;
linenoiseLockIo();
linenoiseUpdateEditorState(prompt, buf, len, pos, prevLen);
fputs(prompt, stdout);
fflush(stdout);
linenoiseUnlockIo();
while (!linenoiseIsStopRequested())
{
if (!_kbhit())
{
Sleep(10);
continue;
}
{
int c = _getwch();
if (c == 0 || c == 224)
{
int ext = _getwch();
if (ext == 72)
{
if (g_history.len > 0 && historyIndex > 0)
{
historyIndex -= 1;
strncpy_s(buf, sizeof(buf), g_history.items[historyIndex], _TRUNCATE);
len = (int)strlen(buf);
pos = len;
linenoiseRedraw(prompt, buf, len, pos, &prevLen);
}
}
else if (ext == 80)
{
if (g_history.len > 0 && historyIndex < g_history.len)
{
historyIndex += 1;
if (historyIndex == g_history.len)
buf[0] = 0;
else
strncpy_s(buf, sizeof(buf), g_history.items[historyIndex], _TRUNCATE);
len = (int)strlen(buf);
pos = len;
linenoiseRedraw(prompt, buf, len, pos, &prevLen);
}
}
else if (ext == 75)
{
if (pos > 0)
{
pos -= 1;
linenoiseRedraw(prompt, buf, len, pos, &prevLen);
}
}
else if (ext == 77)
{
if (pos < len)
{
pos += 1;
linenoiseRedraw(prompt, buf, len, pos, &prevLen);
}
}
continue;
}
if (c == 3)
{
linenoiseLockIo();
linenoiseDeactivateEditorState();
fputc('\n', stdout);
fflush(stdout);
linenoiseUnlockIo();
return NULL;
}
if (c == '\r' || c == '\n')
{
char *out;
linenoiseLockIo();
linenoiseDeactivateEditorState();
fputc('\n', stdout);
fflush(stdout);
linenoiseUnlockIo();
out = linenoiseStrdup(buf);
return out;
}
if (c == '\t')
{
linenoiseApplyCompletion(prompt, buf, &len, &pos, &prevLen);
continue;
}
if (c == 8)
{
if (pos > 0 && len > 0)
{
memmove(buf + pos - 1, buf + pos, (size_t)(len - pos + 1));
pos -= 1;
len -= 1;
linenoiseRedraw(prompt, buf, len, pos, &prevLen);
}
continue;
}
if (isprint((unsigned char)c) && len < LINENOISE_MAX_LINE - 1)
{
if (pos == len)
{
buf[pos++] = (char)c;
len += 1;
buf[len] = 0;
}
else
{
memmove(buf + pos + 1, buf + pos, (size_t)(len - pos + 1));
buf[pos] = (char)c;
pos += 1;
len += 1;
}
linenoiseRedraw(prompt, buf, len, pos, &prevLen);
}
}
}
linenoiseLockIo();
linenoiseDeactivateEditorState();
fputc('\n', stdout);
fflush(stdout);
linenoiseUnlockIo();
return NULL;
}
void linenoiseExternalWriteBegin(void)
{
int i;
int totalChars = 0;
/* Lock shared console state and clear current prompt area before external output. */
linenoiseLockIo();
if (InterlockedCompareExchange(&g_editorActive, 0, 0) == 0)
{
return;
}
totalChars = (int)strlen(g_editorPrompt) + g_editorPrevLen;
if (totalChars < 0)
{
totalChars = 0;
}
fputc('\r', stdout);
for (i = 0; i < totalChars; ++i)
{
fputc(' ', stdout);
}
fputc('\r', stdout);
fflush(stdout);
}
void linenoiseExternalWriteEnd(void)
{
/* Restore prompt line after external output has been printed. */
if (InterlockedCompareExchange(&g_editorActive, 0, 0) != 0)
{
int prevLen = g_editorPrevLen;
linenoiseRedrawUnsafe(g_editorPrompt, g_editorBuf, g_editorLen, g_editorPos, &prevLen);
g_editorPrevLen = prevLen;
}
linenoiseUnlockIo();
}

View File

@@ -0,0 +1,37 @@
#ifndef VENDORED_LINENOISE_H
#define VENDORED_LINENOISE_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
typedef void(linenoiseCompletionCallback)(const char *buf, linenoiseCompletions *lc);
char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn);
void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
void linenoiseRequestStop(void);
void linenoiseResetStop(void);
/* Wrap external stdout/stderr writes so active prompt can be cleared/restored safely. */
void linenoiseExternalWriteBegin(void);
void linenoiseExternalWriteEnd(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2025 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

25526
Minecraft.Server/vendor/nlohmann/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff