mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-05-13 18:11:59 +00:00
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 initialization1dc8a005ed* fix: change virtual screen resolution to 1920x1080(HD) Since 31881af56936aeef38ff322b975fd0 , `skinHud.swf` for 720 is not included in `MediaWindows64.arc`, the app crashes unless the virtual screen is set to HD. * fix: dedicated server build settings for miniaudio migration and missing sources - remove stale Windows64 Miles (mss64) link/copy references from server build - add Common/Filesystem/Filesystem.cpp to Minecraft.Server.vcxproj - add Windows64/PostProcesser.cpp to Minecraft.Server.vcxproj - fix unresolved externals (PostProcesser::*, FileExists) in dedicated server build * update: changed the virtual screen to 720p Since the crash caused by the 720p `skinHud.swf` not being included in `MediaWindows64.arc` has been resolved, switching back to 720p to reduce resource usage. * add: add Docker support for Dedicated Server add with entrypoint and build scripts * fix: add initial save for newly created worlds in dedicated server on the server side, I fixed the behavior introduced after commitaadb511, where newly created worlds are intentionally not saved to disk immediately. * update: add basically all configuration options that are implemented in the classes to `server.properties` * update: add LAN advertising configuration for server.properties LAN-Discovery, which isn’t needed in server mode and could potentially be a security risk, has also been disabled(only server mode). * add: add implementing interactive command line using linenoise - Integrated linenoise library for line editing and completion in the server console. - Updated ServerLogger to handle external writes safely during logging. - Modified ServerMain to initialize and manage the ServerCli for command input. - The implementation is separate from everything else, so it doesn't affect anything else. - The command input section and execution section are separated into threads. * update: enhance command line completion with predictive hints Like most command line tools, it highlights predictions in gray. * add: implement `StringUtils` for string manipulation and refactor usages Unified the scattered utility functions. * fix: send DisconnectPacket on shutdown and fix Win64 recv-thread teardown race Before this change, server/host shutdown closed sockets directly in ServerConnection::stop(), which bypassed the normal disconnect flow. As a result, clients could be dropped without receiving a proper DisconnectPacket during stop/kill/world-close paths. Also, WinsockNetLayer::Shutdown() could destroy synchronization objects while host-side recv threads were still exiting, causing a crash in RecvThreadProc (access violation on world close in host mode). * fix: return client to menus when Win64 host connection drops - Add client-side host disconnect handling in CPlatformNetworkManagerStub::DoWork() for _WINDOWS64. - When in QNET_STATE_GAME_PLAY as a non-host and WinsockNetLayer::IsConnected() becomes false, trigger g_NetworkManager.HandleDisconnect(false) to enter the normal disconnect/UI flow. - Use m_bLeaveGameOnTick as a one-shot guard to prevent repeated disconnect handling while the link remains down. - Reset m_bLeaveGameOnTick on LeaveGame(), HostGame(), and JoinGame() to avoid stale state across sessions. * update: converted Japanese comments to English * add: create `Minecraft.Server` developer guide in English and Japanese * update: add note about issue * add: add `nlohmann/json` json lib * add: add FileUtils Moved file operations to `utils`. * add: Dedicated Server BAN access manager with persistent player and IP bans - add Access frontend that publishes thread-safe ban manager snapshots for dedicated server use - add BanManager storage for banned-players.json and banned-ips.json with load/save/update flows - add persistent player and IP ban checks during dedicated server connection handling - add UTF-8 BOM-safe JSON parsing and shared file helpers backed by nlohmann/json - add Unicode-safe ban file read/write and safer atomic replacement behavior on Windows - add active-ban snapshot APIs and expiry-aware filtering for expires metadata - add RAII-based dedicated access shutdown handling during server startup and teardown * update: changed file read/write operations to use `FileUtils`. - As a side effect, saving has become faster! * fix: Re-added the source that had somehow disappeared. * add: significantly improved the dedicated server logging system - add ServerLogManager to Minecraft.Server as the single entry point for dedicated-server log output - forward CMinecraftApp logger output to the server logger when running with g_Win64DedicatedServer - add named network logs for incoming, accepted, rejected, and disconnected connections - cache connection metadata by smallId so player name and remote IP remain available for disconnect logs - keep Minecraft.Client changes minimal by using lightweight hook points and handling log orchestration on the server side * fix: added the updated library source * add: add `ban` and `pardon` commands for Player and IP * fix: fix stop command shutdown process add dedicated server shutdown request handling * fix: fixed the save logic during server shutdown Removed redundant repeated saves and eliminated the risks of async writes. * update: added new sever files to Docker entrypoint * fix: replace shutdown flag with atomic variable for thread safety * update: update Dedicated Server developer guide English is machine translated. Please forgive me. * update: check for the existence of `GameHDD` and create * add: add Whitelist to Dedicated Server * refactor: clean up and refactor the code - unify duplicated implementations that were copied repeatedly - update outdated patterns to more modern ones * fix: include UI header (new update fix) * fix: fix the detection range for excessive logging `getHighestNonEmptyY()` returning `-1` occurs normally when the chunk is entirely air. The caller (`Minecraft.World/LevelChunk.cpp:2400`) normalizes `-1` to `0`. * update: add world size config to dedicated server properties * update: update README add explanation of `server.properties` & launch arguments * update: add nightly release workflow for dedicated server and client builds to Actions * fix: update name for workflow * add random seed generation * add: add Docker nightly workflow for Dedicated Server publish to GitHub Container Registry * fix: ghost player when clients disconnect out of order #4 * fix: fix 7zip option * fix: fix Docker workflow for Dedicated Server artifact handling * add: add no build Dedicated Server startup scripts and Docker Compose * update: add README for Docker Dedicated Server setup with no local build * refactor: refactor command path structure As the number of commands has increased and become harder to navigate, each command has been organized into separate folders. * update: support stream(file stdin) input mode for server CLI Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux. * add: add new CLI Console Commands for Dedicated Server Most of these commands are executed using the command dispatcher implemented on the `Minecraft.World` side. When registering them with the dispatcher, the sender uses a permission-enabled configuration that treats the CLI as a player. - default game. - enchant - experience. - give - kill(currently, getting a permission error for some reason) - time - weather. - update tp & gamemode command * fix: change player map icon to random select * update: increase the player limit * add: restore the basic anti-cheat implementation and add spawn protection Added the following anti-cheat measures and add spawn protection to `server.properties`. - instant break - speed - reach * fix: fix Docker image tag --------- Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
This commit is contained in:
39
Minecraft.Server/Console/commands/CommandParsing.h
Normal file
39
Minecraft.Server/Console/commands/CommandParsing.h
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Minecraft.Server/Console/commands/IServerCliCommand.h
Normal file
50
Minecraft.Server/Console/commands/IServerCliCommand.h
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
171
Minecraft.Server/Console/commands/ban-ip/CliCommandBanIp.cpp
Normal file
171
Minecraft.Server/Console/commands/ban-ip/CliCommandBanIp.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Minecraft.Server/Console/commands/ban-ip/CliCommandBanIp.h
Normal file
19
Minecraft.Server/Console/commands/ban-ip/CliCommandBanIp.h
Normal 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;
|
||||
};
|
||||
}
|
||||
136
Minecraft.Server/Console/commands/ban-list/CliCommandBanList.cpp
Normal file
136
Minecraft.Server/Console/commands/ban-list/CliCommandBanList.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
145
Minecraft.Server/Console/commands/ban/CliCommandBan.cpp
Normal file
145
Minecraft.Server/Console/commands/ban/CliCommandBan.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
Minecraft.Server/Console/commands/ban/CliCommandBan.h
Normal file
20
Minecraft.Server/Console/commands/ban/CliCommandBan.h
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
103
Minecraft.Server/Console/commands/give/CliCommandGive.cpp
Normal file
103
Minecraft.Server/Console/commands/give/CliCommandGive.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Minecraft.Server/Console/commands/give/CliCommandGive.h
Normal file
16
Minecraft.Server/Console/commands/give/CliCommandGive.h
Normal 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;
|
||||
};
|
||||
}
|
||||
46
Minecraft.Server/Console/commands/help/CliCommandHelp.cpp
Normal file
46
Minecraft.Server/Console/commands/help/CliCommandHelp.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
17
Minecraft.Server/Console/commands/help/CliCommandHelp.h
Normal file
17
Minecraft.Server/Console/commands/help/CliCommandHelp.h
Normal 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);
|
||||
};
|
||||
}
|
||||
|
||||
64
Minecraft.Server/Console/commands/kill/CliCommandKill.cpp
Normal file
64
Minecraft.Server/Console/commands/kill/CliCommandKill.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Minecraft.Server/Console/commands/kill/CliCommandKill.h
Normal file
16
Minecraft.Server/Console/commands/kill/CliCommandKill.h
Normal 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;
|
||||
};
|
||||
}
|
||||
49
Minecraft.Server/Console/commands/list/CliCommandList.cpp
Normal file
49
Minecraft.Server/Console/commands/list/CliCommandList.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
Minecraft.Server/Console/commands/list/CliCommandList.h
Normal file
16
Minecraft.Server/Console/commands/list/CliCommandList.h
Normal 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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
173
Minecraft.Server/Console/commands/pardon/CliCommandPardon.cpp
Normal file
173
Minecraft.Server/Console/commands/pardon/CliCommandPardon.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
19
Minecraft.Server/Console/commands/pardon/CliCommandPardon.h
Normal file
19
Minecraft.Server/Console/commands/pardon/CliCommandPardon.h
Normal 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;
|
||||
};
|
||||
}
|
||||
32
Minecraft.Server/Console/commands/stop/CliCommandStop.cpp
Normal file
32
Minecraft.Server/Console/commands/stop/CliCommandStop.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
16
Minecraft.Server/Console/commands/stop/CliCommandStop.h
Normal file
16
Minecraft.Server/Console/commands/stop/CliCommandStop.h
Normal 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);
|
||||
};
|
||||
}
|
||||
|
||||
118
Minecraft.Server/Console/commands/time/CliCommandTime.cpp
Normal file
118
Minecraft.Server/Console/commands/time/CliCommandTime.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Minecraft.Server/Console/commands/time/CliCommandTime.h
Normal file
16
Minecraft.Server/Console/commands/time/CliCommandTime.h
Normal 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;
|
||||
};
|
||||
}
|
||||
82
Minecraft.Server/Console/commands/tp/CliCommandTp.cpp
Normal file
82
Minecraft.Server/Console/commands/tp/CliCommandTp.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
Minecraft.Server/Console/commands/tp/CliCommandTp.h
Normal file
18
Minecraft.Server/Console/commands/tp/CliCommandTp.h
Normal 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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user