forked from cafeberry/cafeberry
feat: add smartcmd dedicated server
major bug: when connecting to the server a second time, it gets softlocked and keeps repeating ticking connection A\n in debug logs until it times out.
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
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);
|
||||
PublishWhitelistManagerSnapshot(std::shared_ptr<WhitelistManager>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
#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/rapidjson/document.h"
|
||||
#include "../vendor/rapidjson/error/en.h"
|
||||
#include "../vendor/rapidjson/prettywriter.h"
|
||||
#include "../vendor/rapidjson/stringbuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
namespace Access
|
||||
{
|
||||
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 = "[]";
|
||||
}
|
||||
|
||||
// Strip an optional UTF-8 BOM because some editors prepend it when rewriting JSON files.
|
||||
const std::string stripped = StringUtils::StripUtf8Bom(text);
|
||||
|
||||
rapidjson::Document root;
|
||||
const rapidjson::ParseResult parseResult = root.Parse(stripped.c_str());
|
||||
if (!parseResult)
|
||||
{
|
||||
LogErrorf(
|
||||
"access",
|
||||
"failed to parse %s: %s (offset %zu)",
|
||||
path.c_str(),
|
||||
rapidjson::GetParseError_En(parseResult.Code()),
|
||||
parseResult.Offset());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!root.IsArray())
|
||||
{
|
||||
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.GetArray())
|
||||
{
|
||||
if (!object.IsObject())
|
||||
{
|
||||
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 = "[]";
|
||||
}
|
||||
|
||||
// Strip an optional UTF-8 BOM because some editors prepend it when rewriting JSON files.
|
||||
const std::string stripped = StringUtils::StripUtf8Bom(text);
|
||||
|
||||
rapidjson::Document root;
|
||||
const rapidjson::ParseResult parseResult = root.Parse(stripped.c_str());
|
||||
if (!parseResult)
|
||||
{
|
||||
LogErrorf(
|
||||
"access",
|
||||
"failed to parse %s: %s (offset %zu)",
|
||||
path.c_str(),
|
||||
rapidjson::GetParseError_En(parseResult.Code()),
|
||||
parseResult.Offset());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!root.IsArray())
|
||||
{
|
||||
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.GetArray())
|
||||
{
|
||||
if (!object.IsObject())
|
||||
{
|
||||
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
|
||||
{
|
||||
rapidjson::Document root(rapidjson::kArrayType);
|
||||
auto &allocator = root.GetAllocator();
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
const std::string normalizedXuid = AccessStorageUtils::NormalizeXuid(entry.xuid);
|
||||
|
||||
rapidjson::Value object(rapidjson::kObjectType);
|
||||
object.AddMember(
|
||||
"xuid", rapidjson::Value(normalizedXuid.c_str(), static_cast<rapidjson::SizeType>(normalizedXuid.size()), allocator), allocator);
|
||||
object.AddMember(
|
||||
"name", rapidjson::Value(entry.name.c_str(), static_cast<rapidjson::SizeType>(entry.name.size()), allocator), allocator);
|
||||
object.AddMember(
|
||||
"created",
|
||||
rapidjson::Value(entry.metadata.created.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.created.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"source",
|
||||
rapidjson::Value(entry.metadata.source.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.source.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"expires",
|
||||
rapidjson::Value(entry.metadata.expires.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.expires.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"reason",
|
||||
rapidjson::Value(entry.metadata.reason.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.reason.size()), allocator),
|
||||
allocator);
|
||||
root.PushBack(object, allocator);
|
||||
}
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
||||
writer.SetIndent(' ', 2);
|
||||
root.Accept(writer);
|
||||
|
||||
const std::string path = GetBannedPlayersFilePath();
|
||||
const std::string json = root.Empty() ? std::string("[]\n") : (std::string(buffer.GetString(), buffer.GetSize()) + "\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
|
||||
{
|
||||
rapidjson::Document root(rapidjson::kArrayType);
|
||||
auto &allocator = root.GetAllocator();
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
const std::string normalizedIp = NetworkUtils::NormalizeIpToken(entry.ip);
|
||||
|
||||
rapidjson::Value object(rapidjson::kObjectType);
|
||||
object.AddMember(
|
||||
"ip", rapidjson::Value(normalizedIp.c_str(), static_cast<rapidjson::SizeType>(normalizedIp.size()), allocator), allocator);
|
||||
object.AddMember(
|
||||
"created",
|
||||
rapidjson::Value(entry.metadata.created.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.created.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"source",
|
||||
rapidjson::Value(entry.metadata.source.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.source.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"expires",
|
||||
rapidjson::Value(entry.metadata.expires.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.expires.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"reason",
|
||||
rapidjson::Value(entry.metadata.reason.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.reason.size()), allocator),
|
||||
allocator);
|
||||
root.PushBack(object, allocator);
|
||||
}
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
||||
writer.SetIndent(' ', 2);
|
||||
root.Accept(writer);
|
||||
|
||||
const std::string path = GetBannedIpsFilePath();
|
||||
const std::string json = root.Empty() ? std::string("[]\n") : (std::string(buffer.GetString(), buffer.GetSize()) + "\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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "WhitelistManager.h"
|
||||
|
||||
#include "../Common/AccessStorageUtils.h"
|
||||
#include "../Common/FileUtils.h"
|
||||
#include "../Common/StringUtils.h"
|
||||
#include "../ServerLogger.h"
|
||||
#include "../vendor/rapidjson/document.h"
|
||||
#include "../vendor/rapidjson/error/en.h"
|
||||
#include "../vendor/rapidjson/prettywriter.h"
|
||||
#include "../vendor/rapidjson/stringbuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
namespace Access
|
||||
{
|
||||
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 = "[]";
|
||||
}
|
||||
|
||||
const std::string stripped = StringUtils::StripUtf8Bom(text);
|
||||
|
||||
rapidjson::Document root;
|
||||
const rapidjson::ParseResult parseResult = root.Parse(stripped.c_str());
|
||||
if (!parseResult)
|
||||
{
|
||||
LogErrorf(
|
||||
"access",
|
||||
"failed to parse %s: %s (offset %zu)",
|
||||
path.c_str(),
|
||||
rapidjson::GetParseError_En(parseResult.Code()),
|
||||
parseResult.Offset());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!root.IsArray())
|
||||
{
|
||||
LogErrorf("access", "failed to parse %s: root json value is not an array", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &object : root.GetArray())
|
||||
{
|
||||
if (!object.IsObject())
|
||||
{
|
||||
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
|
||||
{
|
||||
rapidjson::Document root(rapidjson::kArrayType);
|
||||
auto &allocator = root.GetAllocator();
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
const std::string normalizedXuid = AccessStorageUtils::NormalizeXuid(entry.xuid);
|
||||
|
||||
rapidjson::Value object(rapidjson::kObjectType);
|
||||
object.AddMember(
|
||||
"xuid", rapidjson::Value(normalizedXuid.c_str(), static_cast<rapidjson::SizeType>(normalizedXuid.size()), allocator), allocator);
|
||||
object.AddMember(
|
||||
"name", rapidjson::Value(entry.name.c_str(), static_cast<rapidjson::SizeType>(entry.name.size()), allocator), allocator);
|
||||
object.AddMember(
|
||||
"created",
|
||||
rapidjson::Value(entry.metadata.created.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.created.size()), allocator),
|
||||
allocator);
|
||||
object.AddMember(
|
||||
"source",
|
||||
rapidjson::Value(entry.metadata.source.c_str(), static_cast<rapidjson::SizeType>(entry.metadata.source.size()), allocator),
|
||||
allocator);
|
||||
root.PushBack(object, allocator);
|
||||
}
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
||||
writer.SetIndent(' ', 2);
|
||||
root.Accept(writer);
|
||||
|
||||
const std::string path = GetWhitelistFilePath();
|
||||
const std::string json = root.Empty() ? std::string("[]\n") : (std::string(buffer.GetString(), buffer.GetSize()) + "\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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "../vendor/rapidjson/document.h"
|
||||
|
||||
#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 rapidjson::Value &object, const char *key, std::string *outValue)
|
||||
{
|
||||
if (key == nullptr || outValue == nullptr || !object.IsObject())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto it = object.FindMember(key);
|
||||
if (it == object.MemberEnd() || !it->value.IsString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*outValue = it->value.GetString();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#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() {}
|
||||
|
||||
/** Primary command name */
|
||||
virtual const char *Name() const = 0;
|
||||
/** Optional aliases */
|
||||
virtual std::vector<std::string> Aliases() const { return std::vector<std::string>(); }
|
||||
/** 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)engine;
|
||||
(void)out;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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
|
||||
{
|
||||
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,192 @@
|
||||
#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
|
||||
{
|
||||
const char *kExperienceUsage = "xp <amount>[L] [player]";
|
||||
const char *kExperienceUsageWithPlayer = "xp <amount>[L] <player>";
|
||||
|
||||
struct ExperienceAmount
|
||||
{
|
||||
int amount;
|
||||
bool levels;
|
||||
bool take;
|
||||
};
|
||||
|
||||
static bool TryParseExperienceAmount(const std::string &token, ExperienceAmount *outValue)
|
||||
{
|
||||
if (outValue == nullptr || token.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ExperienceAmount parsed;
|
||||
parsed.amount = 0;
|
||||
parsed.levels = false;
|
||||
parsed.take = false;
|
||||
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
|
||||
{
|
||||
std::vector<std::string> aliases;
|
||||
aliases.push_back("experience");
|
||||
return aliases;
|
||||
}
|
||||
|
||||
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;
|
||||
amount.amount = 0;
|
||||
amount.levels = false;
|
||||
amount.take = false;
|
||||
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,111 @@
|
||||
#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
|
||||
{
|
||||
const char *kGamemodeUsage = "gamemode <survival|creative|0|1> [player]";
|
||||
const char *kGamemodeUsageWithPlayer = "gamemode <survival|creative|0|1> <player>";
|
||||
}
|
||||
|
||||
const char *CliCommandGamemode::Name() const
|
||||
{
|
||||
return "gamemode";
|
||||
}
|
||||
|
||||
std::vector<std::string> CliCommandGamemode::Aliases() const
|
||||
{
|
||||
std::vector<std::string> aliases;
|
||||
aliases.push_back("gm");
|
||||
return aliases;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "CliCommandHelp.h"
|
||||
|
||||
#include "../../ServerCliEngine.h"
|
||||
#include "../../ServerCliRegistry.h"
|
||||
#include "../../ServerCliParser.h"
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
const char *CliCommandHelp::Name() const
|
||||
{
|
||||
return "help";
|
||||
}
|
||||
|
||||
std::vector<std::string> CliCommandHelp::Aliases() const
|
||||
{
|
||||
std::vector<std::string> aliases;
|
||||
aliases.push_back("?");
|
||||
return aliases;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "CliCommandList.h"
|
||||
|
||||
#include "../../ServerCliEngine.h"
|
||||
#include "../../../Common/StringUtils.h"
|
||||
#include "../../../../Minecraft.Client/MinecraftServer.h"
|
||||
#include "../../../../Minecraft.Client/PlayerList.h"
|
||||
#include "../../ServerCliParser.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "CliCommandStop.h"
|
||||
|
||||
#include "../../ServerCliEngine.h"
|
||||
#include "../../ServerCliParser.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#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
|
||||
{
|
||||
const char *kTpUsage = "tp <player> <target>";
|
||||
}
|
||||
|
||||
const char *CliCommandTp::Name() const
|
||||
{
|
||||
return "tp";
|
||||
}
|
||||
|
||||
std::vector<std::string> CliCommandTp::Aliases() const
|
||||
{
|
||||
std::vector<std::string> aliases;
|
||||
aliases.push_back("teleport");
|
||||
return aliases;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
<?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>{FF2DDBF5-8A27-417A-A01C-B98A25C59760}</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>v110</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v110</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)\Server\</OutDir>
|
||||
<IntDir>$(Platform)\Server\</IntDir>
|
||||
<TargetName>Minecraft.Server</TargetName>
|
||||
<LocalDebuggerWorkingDirectory>$(OutDir)</LocalDebuggerWorkingDirectory>
|
||||
<LocalDebuggerCommandArguments>-port 25565 -bind 0.0.0.0 -name DedicatedServer</LocalDebuggerCommandArguments>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<IncludePath>..\Minecraft.World\x64headers;..\Minecraft.Client\Windows64\Sentient\Include;$(IncludePath)</IncludePath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderOutputFile>Server\$(ProjectName).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;..\Minecraft.Client\x64_Server\Minecraft.Client.lib;..\Minecraft.World\x64_$(Configuration)\Minecraft.World.lib;XInput9_1_0.lib;Windows64\Iggy\lib\iggy_w64.lib;;XInput9_1_0.lib;Windows64\Miles\Lib\mss64.lib;wsock32.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>Server\$(ProjectName).pch</PrecompiledHeaderOutputFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<ExceptionHandling>Sync</ExceptionHandling>
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<PreprocessorDefinitions>MINECRAFT_SERVER_BUILD;_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;..\Minecraft.Client\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;..\Minecraft.Client\x64_Server\Minecraft.Client.lib;..\Minecraft.World\x64_$(Configuration)\Minecraft.World.lib;XInput9_1_0.lib;Windows64\Iggy\lib\iggy_w64.lib;;XInput9_1_0.lib;Windows64\Miles\Lib\mss64.lib;wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>powershell -ExecutionPolicy Bypass -File "../Minecraft.Client/Common/Postbuilds/Server-postbuild.ps1" -OutDir "$(OutDir)/" -ProjectDir "$(ProjectDir)/"</Command>
|
||||
<Message>Run Server postbuild script</Message>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Access\Access.cpp" />
|
||||
<ClCompile Include="Access\BanManager.cpp" />
|
||||
<ClCompile Include="Access\WhitelistManager.cpp" />
|
||||
<ClCompile Include="Common\FileUtils.cpp" />
|
||||
<ClCompile Include="Common\StringUtils.cpp" />
|
||||
<ClCompile Include="Console\commands\ban-ip\CliCommandBanIp.cpp" />
|
||||
<ClCompile Include="Console\commands\ban-list\CliCommandBanList.cpp" />
|
||||
<ClCompile Include="Console\commands\ban\CliCommandBan.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-ip\CliCommandPardonIp.cpp" />
|
||||
<ClCompile Include="Console\commands\pardon\CliCommandPardon.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\ServerCli.cpp" />
|
||||
<ClCompile Include="Console\ServerCliEngine.cpp" />
|
||||
<ClCompile Include="Console\ServerCliInput.cpp" />
|
||||
<ClCompile Include="Console\ServerCliParser.cpp" />
|
||||
<ClCompile Include="Console\ServerCliRegistry.cpp" />
|
||||
<ClCompile Include="ServerLogger.cpp" />
|
||||
<ClCompile Include="ServerLogManager.cpp" />
|
||||
<ClCompile Include="ServerProperties.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vendor\linenoise\linenoise.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows64\ServerMain.cpp" />
|
||||
<ClCompile Include="WorldManager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\bck\ServerLogger.h" />
|
||||
<ClInclude Include="..\bck\ServerLogManager.h" />
|
||||
<ClInclude Include="..\bck\ServerProperties.h" />
|
||||
<ClInclude Include="..\bck\ServerShutdown.h" />
|
||||
<ClInclude Include="..\bck\WorldManager.h" />
|
||||
<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">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\allocators.h" />
|
||||
<ClInclude Include="vendor\rapidjson\cursorstreamwrapper.h" />
|
||||
<ClInclude Include="vendor\rapidjson\document.h" />
|
||||
<ClInclude Include="vendor\rapidjson\encodedstream.h" />
|
||||
<ClInclude Include="vendor\rapidjson\encodings.h" />
|
||||
<ClInclude Include="vendor\rapidjson\error\en.h" />
|
||||
<ClInclude Include="vendor\rapidjson\error\error.h" />
|
||||
<ClInclude Include="vendor\rapidjson\filereadstream.h" />
|
||||
<ClInclude Include="vendor\rapidjson\filewritestream.h" />
|
||||
<ClInclude Include="vendor\rapidjson\fwd.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\biginteger.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\clzll.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\diyfp.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\dtoa.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\ieee754.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\itoa.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\meta.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\pow10.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\regex.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\stack.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\strfunc.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\strtod.h" />
|
||||
<ClInclude Include="vendor\rapidjson\internal\swap.h" />
|
||||
<ClInclude Include="vendor\rapidjson\istreamwrapper.h" />
|
||||
<ClInclude Include="vendor\rapidjson\memorybuffer.h" />
|
||||
<ClInclude Include="vendor\rapidjson\memorystream.h" />
|
||||
<ClInclude Include="vendor\rapidjson\msinttypes\inttypes.h" />
|
||||
<ClInclude Include="vendor\rapidjson\msinttypes\stdint.h" />
|
||||
<ClInclude Include="vendor\rapidjson\ostreamwrapper.h" />
|
||||
<ClInclude Include="vendor\rapidjson\pointer.h" />
|
||||
<ClInclude Include="vendor\rapidjson\prettywriter.h" />
|
||||
<ClInclude Include="vendor\rapidjson\rapidjson.h" />
|
||||
<ClInclude Include="vendor\rapidjson\reader.h" />
|
||||
<ClInclude Include="vendor\rapidjson\schema.h" />
|
||||
<ClInclude Include="vendor\rapidjson\stream.h" />
|
||||
<ClInclude Include="vendor\rapidjson\stringbuffer.h" />
|
||||
<ClInclude Include="vendor\rapidjson\uri.h" />
|
||||
<ClInclude Include="vendor\rapidjson\writer.h" />
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Input.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Profile.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Render.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Storage.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WorldManager.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Minecraft.World\Minecraft.World.vcxproj">
|
||||
<Project>{F046C3CE-9749-4823-B32B-D9CC10B1A2C8}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="docs\DEVELOPMENT.en.md" />
|
||||
<None Include="docs\DEVELOPMENT.ja.md" />
|
||||
<None Include="vendor\linenoise\LICENSE" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Input.lib">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Input_d.lib">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Profile.lib" />
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Render_PC.lib">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Render_PC_d.lib">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Storage.lib">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Storage_d.lib">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</Library>
|
||||
<Library Include="Windows64\Iggy\lib\iggyexpruntime_w64.lib" />
|
||||
<Library Include="Windows64\Iggy\lib\iggyperfmon_w64.lib" />
|
||||
<Library Include="Windows64\Iggy\lib\iggy_w64.lib" />
|
||||
<Library Include="Windows64\Miles\lib\mss64.lib" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,529 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Access">
|
||||
<UniqueIdentifier>{51610b47-4ac2-4dde-87e2-aceebf26989c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Common">
|
||||
<UniqueIdentifier>{c148b1e2-1f21-4aa0-b174-cc8c24c8951a}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console">
|
||||
<UniqueIdentifier>{24c18f03-9685-42cc-8e58-e4b12e4c174c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands">
|
||||
<UniqueIdentifier>{029a5d10-ef33-4a25-9bcd-9f39944dd5f1}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\ban">
|
||||
<UniqueIdentifier>{5a30b59a-62d3-4f1e-b967-b77db2cec47b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\ban-ip">
|
||||
<UniqueIdentifier>{01b98446-8ff2-495d-bc87-32b2681a7df3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\ban-list">
|
||||
<UniqueIdentifier>{89569093-5317-40c0-be12-a19e4a3249a8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\defaultgamemode">
|
||||
<UniqueIdentifier>{e313f52b-43b9-4dda-9559-af5a245d73b2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\enchant">
|
||||
<UniqueIdentifier>{a6863c9c-ed24-4571-8004-e8ed4cd900a7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\experience">
|
||||
<UniqueIdentifier>{af0f8212-558d-4182-821e-dc5c6af8765d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\gamemode">
|
||||
<UniqueIdentifier>{ea6bea52-2793-4618-86b5-c737d1c0b873}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\give">
|
||||
<UniqueIdentifier>{a6301a38-475d-47f2-9450-37a3c135b826}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\help">
|
||||
<UniqueIdentifier>{bb281a8e-a612-459b-91c3-6f3bf8ede342}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\kill">
|
||||
<UniqueIdentifier>{efa1f6f1-ab08-4256-a8ab-1ad7eea0d10a}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\list">
|
||||
<UniqueIdentifier>{83df2dae-2a78-476c-b708-05659e444219}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\pardon">
|
||||
<UniqueIdentifier>{ae702713-2488-411d-8141-fd5d5a348ffa}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\pardon-ip">
|
||||
<UniqueIdentifier>{5f7f200c-d8a9-4b30-98a7-4f71e4d000f9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\stop">
|
||||
<UniqueIdentifier>{81a36a77-c03d-459f-a951-dc00a7d760d7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\time">
|
||||
<UniqueIdentifier>{eb779138-4cc1-4ee4-b212-337d56e109ee}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\tp">
|
||||
<UniqueIdentifier>{7bdf9604-e397-4e03-bbf1-2fda3cd851f9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\weather">
|
||||
<UniqueIdentifier>{dbda2ebe-0841-4b02-b6a0-9f77f0dd88ea}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Console\commands\whitelist">
|
||||
<UniqueIdentifier>{ec72e562-e436-4e4d-afd1-96662ff66149}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="docs">
|
||||
<UniqueIdentifier>{6b18e686-df1f-467d-a533-75dca2ac6f68}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="vendor">
|
||||
<UniqueIdentifier>{59579052-3978-492b-acb4-c31a0bd63bcd}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="vendor\linenoise">
|
||||
<UniqueIdentifier>{4d7261d2-3fae-4386-bbdd-8d4dad156f59}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64">
|
||||
<UniqueIdentifier>{08e4175f-35b8-4f37-9335-5f460a62ba2c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="vendor\rapidjson">
|
||||
<UniqueIdentifier>{227f9fab-7527-4e66-bb0a-77634e3c1c63}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="vendor\rapidjson\error">
|
||||
<UniqueIdentifier>{fafde418-a3b2-4986-82a8-46fb199d0bd5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="vendor\rapidjson\internal">
|
||||
<UniqueIdentifier>{3d43b9b8-1c52-4d65-b93d-d16b8d610800}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="vendor\rapidjson\msinttypes">
|
||||
<UniqueIdentifier>{a7c5b6eb-46cc-4cc0-ac7b-88a2def415fb}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\4JLibs">
|
||||
<UniqueIdentifier>{4a22e8a3-ce95-4389-9199-0c728fe25f8e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\4JLibs\inc">
|
||||
<UniqueIdentifier>{bd464b7d-b9ee-471a-ba0a-66c8cd67daa8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\4JLibs\libs">
|
||||
<UniqueIdentifier>{99a0ecc1-cdc6-4077-9988-88a17add709b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\4JLibs\Media">
|
||||
<UniqueIdentifier>{4ca625a8-aae3-4121-a9e3-61a71490928f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Iggy">
|
||||
<UniqueIdentifier>{b4403f41-77f4-49c6-8948-8ec7afd744c7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Iggy\lib">
|
||||
<UniqueIdentifier>{ad11c0f5-63b0-49cb-a426-dd5c2e93b0e3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Miles Sound System">
|
||||
<UniqueIdentifier>{a0a5cd4d-bc9b-450f-a2c1-c67e7739e52c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Miles Sound System\dll">
|
||||
<UniqueIdentifier>{fd6ceca9-2e6d-4b2d-b6aa-8c103049a8e7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Miles Sound System\dll\Debug">
|
||||
<UniqueIdentifier>{005fdd55-6b11-40bc-bc7a-59afa39d5782}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Miles Sound System\dll\Release">
|
||||
<UniqueIdentifier>{6976df84-e36b-4d48-b2f8-35d2ac4a9495}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Windows64\Miles Sound System\lib">
|
||||
<UniqueIdentifier>{19759ef7-84d7-4625-8cf5-65ce2b44c8e1}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Access\Access.cpp">
|
||||
<Filter>Access</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Access\BanManager.cpp">
|
||||
<Filter>Access</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Access\WhitelistManager.cpp">
|
||||
<Filter>Access</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Common\FileUtils.cpp">
|
||||
<Filter>Common</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Common\StringUtils.cpp">
|
||||
<Filter>Common</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\ServerCli.cpp">
|
||||
<Filter>Console</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\ServerCliEngine.cpp">
|
||||
<Filter>Console</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\ServerCliInput.cpp">
|
||||
<Filter>Console</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\ServerCliParser.cpp">
|
||||
<Filter>Console</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\ServerCliRegistry.cpp">
|
||||
<Filter>Console</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\ban\CliCommandBan.cpp">
|
||||
<Filter>Console\commands\ban</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\ban-ip\CliCommandBanIp.cpp">
|
||||
<Filter>Console\commands\ban-ip</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\ban-list\CliCommandBanList.cpp">
|
||||
<Filter>Console\commands\ban-list</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\defaultgamemode\CliCommandDefaultGamemode.cpp">
|
||||
<Filter>Console\commands\defaultgamemode</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\enchant\CliCommandEnchant.cpp">
|
||||
<Filter>Console\commands\enchant</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\experience\CliCommandExperience.cpp">
|
||||
<Filter>Console\commands\experience</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\gamemode\CliCommandGamemode.cpp">
|
||||
<Filter>Console\commands\gamemode</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\give\CliCommandGive.cpp">
|
||||
<Filter>Console\commands\give</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\help\CliCommandHelp.cpp">
|
||||
<Filter>Console\commands\help</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\kill\CliCommandKill.cpp">
|
||||
<Filter>Console\commands\kill</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\list\CliCommandList.cpp">
|
||||
<Filter>Console\commands\list</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\whitelist\CliCommandWhitelist.cpp">
|
||||
<Filter>Console\commands\whitelist</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\weather\CliCommandWeather.cpp">
|
||||
<Filter>Console\commands\weather</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\tp\CliCommandTp.cpp">
|
||||
<Filter>Console\commands\tp</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\time\CliCommandTime.cpp">
|
||||
<Filter>Console\commands\time</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\stop\CliCommandStop.cpp">
|
||||
<Filter>Console\commands\stop</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\pardon-ip\CliCommandPardonIp.cpp">
|
||||
<Filter>Console\commands\pardon-ip</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console\commands\pardon\CliCommandPardon.cpp">
|
||||
<Filter>Console\commands\pardon</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vendor\linenoise\linenoise.c">
|
||||
<Filter>vendor\linenoise</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows64\ServerMain.cpp">
|
||||
<Filter>Windows64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
<ClCompile Include="ServerLogger.cpp" />
|
||||
<ClCompile Include="ServerLogManager.cpp" />
|
||||
<ClCompile Include="ServerProperties.cpp" />
|
||||
<ClCompile Include="WorldManager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common\AccessStorageUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Common\FileUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Common\NetworkUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Common\StringUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\ban\CliCommandBan.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\ban-ip\CliCommandBanIp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\ban-list\CliCommandBanList.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\CommandParsing.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\defaultgamemode\CliCommandDefaultGamemode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\enchant\CliCommandEnchant.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\experience\CliCommandExperience.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\gamemode\CliCommandGamemode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\give\CliCommandGive.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\help\CliCommandHelp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\IServerCliCommand.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\kill\CliCommandKill.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\list\CliCommandList.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\pardon\CliCommandPardon.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\pardon-ip\CliCommandPardonIp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\stop\CliCommandStop.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\time\CliCommandTime.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\tp\CliCommandTp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\weather\CliCommandWeather.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\commands\whitelist\CliCommandWhitelist.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\ServerCli.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\ServerCliEngine.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\ServerCliInput.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\ServerCliParser.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console\ServerCliRegistry.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\linenoise\linenoise.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ServerLogger.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ServerLogManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ServerProperties.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WorldManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Access\Access.h">
|
||||
<Filter>Access</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Access\BanManager.h">
|
||||
<Filter>Access</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Access\WhitelistManager.h">
|
||||
<Filter>Access</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\bck\ServerLogger.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\bck\ServerLogManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\bck\ServerProperties.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\bck\ServerShutdown.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\bck\WorldManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\allocators.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\cursorstreamwrapper.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\document.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\encodedstream.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\encodings.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\filereadstream.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\filewritestream.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\fwd.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\istreamwrapper.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\memorybuffer.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\memorystream.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\ostreamwrapper.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\pointer.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\prettywriter.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\rapidjson.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\reader.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\schema.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\stream.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\stringbuffer.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\uri.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\writer.h">
|
||||
<Filter>vendor\rapidjson</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\error\en.h">
|
||||
<Filter>vendor\rapidjson\error</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\error\error.h">
|
||||
<Filter>vendor\rapidjson\error</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\biginteger.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\clzll.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\diyfp.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\dtoa.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\ieee754.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\itoa.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\meta.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\pow10.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\regex.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\stack.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\strfunc.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\strtod.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\internal\swap.h">
|
||||
<Filter>vendor\rapidjson\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\msinttypes\inttypes.h">
|
||||
<Filter>vendor\rapidjson\msinttypes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vendor\rapidjson\msinttypes\stdint.h">
|
||||
<Filter>vendor\rapidjson\msinttypes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Input.h">
|
||||
<Filter>Windows64\4JLibs\inc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Profile.h">
|
||||
<Filter>Windows64\4JLibs\inc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Render.h">
|
||||
<Filter>Windows64\4JLibs\inc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Windows64\4JLibs\inc\4J_Storage.h">
|
||||
<Filter>Windows64\4JLibs\inc</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="docs\DEVELOPMENT.en.md">
|
||||
<Filter>docs</Filter>
|
||||
</None>
|
||||
<None Include="docs\DEVELOPMENT.ja.md">
|
||||
<Filter>docs</Filter>
|
||||
</None>
|
||||
<None Include="vendor\linenoise\LICENSE">
|
||||
<Filter>vendor\linenoise</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Input.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Input_d.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Profile.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Render_PC.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Render_PC_d.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Storage.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\4JLibs\libs\4J_Storage_d.lib">
|
||||
<Filter>Windows64\4JLibs\libs</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\Iggy\lib\iggy_w64.lib">
|
||||
<Filter>Windows64\Iggy\lib</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\Iggy\lib\iggyexpruntime_w64.lib">
|
||||
<Filter>Windows64\Iggy\lib</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\Iggy\lib\iggyperfmon_w64.lib">
|
||||
<Filter>Windows64\Iggy\lib</Filter>
|
||||
</Library>
|
||||
<Library Include="Windows64\Miles\lib\mss64.lib">
|
||||
<Filter>Windows64\Miles Sound System\lib</Filter>
|
||||
</Library>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,401 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "ServerLogManager.h"
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "ServerLogger.h"
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
|
||||
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()
|
||||
{
|
||||
// PIEBOT: Minecraft.Server only compiles if the build config is set to Server
|
||||
return true;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
void RequestDedicatedServerShutdown();
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,730 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Common/App_Defines.h"
|
||||
#include "Common/Network/GameNetworkManager.h"
|
||||
#include "Input.h"
|
||||
#include "Minecraft.h"
|
||||
#include "MinecraftServer.h"
|
||||
#include "../Access/Access.h"
|
||||
#include "../Common/StringUtils.h"
|
||||
#include "../ServerLogger.h"
|
||||
#include "../ServerLogManager.h"
|
||||
#include "../ServerProperties.h"
|
||||
#include "../ServerShutdown.h"
|
||||
#include "../WorldManager.h"
|
||||
#include "../Console/ServerCli.h"
|
||||
#include "Tesselator.h"
|
||||
#include "Windows64/4JLibs/inc/4J_Render.h"
|
||||
#include "Windows64/GameConfig/Minecraft.spa.h"
|
||||
#include "Common/Network/NetworkSocketLayer.h"
|
||||
#include "Windows64/Windows64_UIController.h"
|
||||
#include "Common/UI/UI.h"
|
||||
|
||||
#include "../../Minecraft.World/AABB.h"
|
||||
#include "../../Minecraft.World/Vec3.h"
|
||||
#include "../../Minecraft.World/IntCache.h"
|
||||
#include "../../Minecraft.World/ChunkSource.h"
|
||||
#include "../../Minecraft.World/TilePos.h"
|
||||
#include "../../Minecraft.World/compression.h"
|
||||
#include "../../Minecraft.World/OldChunkStorage.h"
|
||||
#include "../../Minecraft.World/ConsoleSaveFileOriginal.h"
|
||||
#include "../../Minecraft.World/net.minecraft.world.level.tile.h"
|
||||
#include "../../Minecraft.World/Random.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
extern ATOM MyRegisterClass(HINSTANCE hInstance);
|
||||
extern BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);
|
||||
extern HRESULT InitDevice();
|
||||
extern void CleanupDevice();
|
||||
extern void DefineActions(void);
|
||||
|
||||
extern HWND g_hWnd;
|
||||
extern int g_iScreenWidth;
|
||||
extern int g_iScreenHeight;
|
||||
extern char g_Win64Username[17];
|
||||
extern wchar_t g_Win64UsernameW[17];
|
||||
extern ID3D11Device* g_pd3dDevice;
|
||||
extern ID3D11DeviceContext* g_pImmediateContext;
|
||||
extern IDXGISwapChain* g_pSwapChain;
|
||||
extern ID3D11RenderTargetView* g_pRenderTargetView;
|
||||
extern ID3D11DepthStencilView* g_pDepthStencilView;
|
||||
extern DWORD dwProfileSettingsA[];
|
||||
|
||||
static const int kProfileValueCount = 5;
|
||||
static const int kProfileSettingCount = 4;
|
||||
|
||||
struct DedicatedServerConfig
|
||||
{
|
||||
int port;
|
||||
char bindIP[256];
|
||||
char name[17];
|
||||
int maxPlayers;
|
||||
int worldSize;
|
||||
int worldSizeChunks;
|
||||
int worldHellScale;
|
||||
__int64 seed;
|
||||
ServerRuntime::EServerLogLevel logLevel;
|
||||
bool hasSeed;
|
||||
bool showHelp;
|
||||
};
|
||||
|
||||
static std::atomic<bool> g_shutdownRequested(false);
|
||||
static const DWORD kDefaultAutosaveIntervalMs = 60 * 1000;
|
||||
static const int kServerActionPad = 0;
|
||||
|
||||
static bool IsShutdownRequested()
|
||||
{
|
||||
return g_shutdownRequested.load();
|
||||
}
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
void RequestDedicatedServerShutdown()
|
||||
{
|
||||
g_shutdownRequested.store(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Access::Shutdown automatically once dedicated access control was initialized successfully
|
||||
* アクセス制御初期化後のShutdownを自動化する
|
||||
*/
|
||||
class AccessShutdownGuard
|
||||
{
|
||||
public:
|
||||
AccessShutdownGuard()
|
||||
: m_active(false)
|
||||
{
|
||||
}
|
||||
|
||||
void Activate()
|
||||
{
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
~AccessShutdownGuard()
|
||||
{
|
||||
if (m_active)
|
||||
{
|
||||
ServerRuntime::Access::Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_active;
|
||||
};
|
||||
static BOOL WINAPI ConsoleCtrlHandlerProc(DWORD ctrlType)
|
||||
{
|
||||
switch (ctrlType)
|
||||
{
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
case CTRL_CLOSE_EVENT:
|
||||
case CTRL_SHUTDOWN_EVENT:
|
||||
ServerRuntime::RequestDedicatedServerShutdown();
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **Wait For Server Stopped Signal**
|
||||
*
|
||||
* Thread entry used during shutdown to wait until the network layer reports server stop completion
|
||||
* 停止通知待機用の終了スレッド処理
|
||||
*/
|
||||
static int WaitForServerStoppedThreadProc(void *)
|
||||
{
|
||||
if (g_NetworkManager.ServerStoppedValid())
|
||||
{
|
||||
g_NetworkManager.ServerStoppedWait();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void PrintUsage()
|
||||
{
|
||||
ServerRuntime::LogInfo("usage", "Minecraft.Server.exe [options]");
|
||||
ServerRuntime::LogInfo("usage", " -port <1-65535> Listen TCP port (default: server.properties:server-port)");
|
||||
ServerRuntime::LogInfo("usage", " -ip <addr> Bind address (default: server.properties:server-ip)");
|
||||
ServerRuntime::LogInfo("usage", " -bind <addr> Alias of -ip");
|
||||
ServerRuntime::LogInfo("usage", " -name <name> Host display name (max 16 chars, default: server.properties:server-name)");
|
||||
ServerRuntime::LogInfo("usage", " -maxplayers <1-8> Public slots (default: server.properties:max-players)");
|
||||
ServerRuntime::LogInfo("usage", " -seed <int64> World seed (overrides server.properties:level-seed)");
|
||||
ServerRuntime::LogInfo("usage", " -loglevel <level> debug|info|warn|error (default: server.properties:log-level)");
|
||||
ServerRuntime::LogInfo("usage", " -help Show this help");
|
||||
}
|
||||
|
||||
using ServerRuntime::LoadServerPropertiesConfig;
|
||||
using ServerRuntime::LogError;
|
||||
using ServerRuntime::LogErrorf;
|
||||
using ServerRuntime::LogInfof;
|
||||
using ServerRuntime::LogDebugf;
|
||||
using ServerRuntime::LogStartupStep;
|
||||
using ServerRuntime::LogWarn;
|
||||
using ServerRuntime::LogWorldIO;
|
||||
using ServerRuntime::SaveServerPropertiesConfig;
|
||||
using ServerRuntime::SetServerLogLevel;
|
||||
using ServerRuntime::ServerPropertiesConfig;
|
||||
using ServerRuntime::TryParseServerLogLevel;
|
||||
using ServerRuntime::StringUtils::WideToUtf8;
|
||||
using ServerRuntime::BootstrapWorldForServer;
|
||||
using ServerRuntime::eWorldBootstrap_CreatedNew;
|
||||
using ServerRuntime::eWorldBootstrap_Failed;
|
||||
using ServerRuntime::eWorldBootstrap_Loaded;
|
||||
using ServerRuntime::WaitForWorldActionIdle;
|
||||
using ServerRuntime::WorldBootstrapResult;
|
||||
|
||||
static bool ParseIntArg(const char *value, int *outValue)
|
||||
{
|
||||
if (value == NULL || *value == 0)
|
||||
return false;
|
||||
|
||||
char *end = NULL;
|
||||
long parsed = strtol(value, &end, 10);
|
||||
if (end == value || *end != 0)
|
||||
return false;
|
||||
|
||||
*outValue = (int)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseInt64Arg(const char *value, __int64 *outValue)
|
||||
{
|
||||
if (value == NULL || *value == 0)
|
||||
return false;
|
||||
|
||||
char *end = NULL;
|
||||
__int64 parsed = _strtoi64(value, &end, 10);
|
||||
if (end == value || *end != 0)
|
||||
return false;
|
||||
|
||||
*outValue = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *config)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
const char *arg = argv[i];
|
||||
if (_stricmp(arg, "-help") == 0 || _stricmp(arg, "--help") == 0 || _stricmp(arg, "-h") == 0)
|
||||
{
|
||||
config->showHelp = true;
|
||||
return true;
|
||||
}
|
||||
else if ((_stricmp(arg, "-port") == 0) && (i + 1 < argc))
|
||||
{
|
||||
int port = 0;
|
||||
if (!ParseIntArg(argv[++i], &port) || port <= 0 || port > 65535)
|
||||
{
|
||||
LogError("startup", "Invalid -port value.");
|
||||
return false;
|
||||
}
|
||||
config->port = port;
|
||||
}
|
||||
else if ((_stricmp(arg, "-ip") == 0 || _stricmp(arg, "-bind") == 0) && (i + 1 < argc))
|
||||
{
|
||||
strncpy_s(config->bindIP, sizeof(config->bindIP), argv[++i], _TRUNCATE);
|
||||
}
|
||||
else if ((_stricmp(arg, "-name") == 0) && (i + 1 < argc))
|
||||
{
|
||||
strncpy_s(config->name, sizeof(config->name), argv[++i], _TRUNCATE);
|
||||
}
|
||||
else if ((_stricmp(arg, "-maxplayers") == 0) && (i + 1 < argc))
|
||||
{
|
||||
int maxPlayers = 0;
|
||||
if (!ParseIntArg(argv[++i], &maxPlayers) || maxPlayers <= 0 || maxPlayers > MINECRAFT_NET_MAX_PLAYERS)
|
||||
{
|
||||
LogError("startup", "Invalid -maxplayers value.");
|
||||
return false;
|
||||
}
|
||||
config->maxPlayers = maxPlayers;
|
||||
}
|
||||
else if ((_stricmp(arg, "-seed") == 0) && (i + 1 < argc))
|
||||
{
|
||||
if (!ParseInt64Arg(argv[++i], &config->seed))
|
||||
{
|
||||
LogError("startup", "Invalid -seed value.");
|
||||
return false;
|
||||
}
|
||||
config->hasSeed = true;
|
||||
}
|
||||
else if ((_stricmp(arg, "-loglevel") == 0) && (i + 1 < argc))
|
||||
{
|
||||
if (!TryParseServerLogLevel(argv[++i], &config->logLevel))
|
||||
{
|
||||
LogError("startup", "Invalid -loglevel value. Use debug/info/warn/error.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogErrorf("startup", "Unknown or incomplete argument: %s", arg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SetExeWorkingDirectory()
|
||||
{
|
||||
char exePath[MAX_PATH] = {};
|
||||
GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||||
char *slash = strrchr(exePath, '\\');
|
||||
if (slash != NULL)
|
||||
{
|
||||
*(slash + 1) = 0;
|
||||
SetCurrentDirectoryA(exePath);
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyServerPropertiesToDedicatedConfig(const ServerPropertiesConfig &serverProperties, DedicatedServerConfig *config)
|
||||
{
|
||||
if (config == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->port = serverProperties.serverPort;
|
||||
strncpy_s(
|
||||
config->bindIP,
|
||||
sizeof(config->bindIP),
|
||||
serverProperties.serverIp.empty() ? "0.0.0.0" : serverProperties.serverIp.c_str(),
|
||||
_TRUNCATE);
|
||||
strncpy_s(
|
||||
config->name,
|
||||
sizeof(config->name),
|
||||
serverProperties.serverName.empty() ? "DedicatedServer" : serverProperties.serverName.c_str(),
|
||||
_TRUNCATE);
|
||||
config->maxPlayers = serverProperties.maxPlayers;
|
||||
config->worldSize = serverProperties.worldSize;
|
||||
config->worldSizeChunks = serverProperties.worldSizeChunks;
|
||||
config->worldHellScale = serverProperties.worldHellScale;
|
||||
config->logLevel = serverProperties.logLevel;
|
||||
config->hasSeed = serverProperties.hasSeed;
|
||||
config->seed = serverProperties.seed;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Tick Core Async Subsystems**
|
||||
*
|
||||
* Advances core subsystems for one frame to keep async processing alive
|
||||
* Call continuously even inside wait loops to avoid stalling storage/profile/network work
|
||||
* 非同期進行維持のためのティック処理
|
||||
*/
|
||||
static void TickCoreSystems()
|
||||
{
|
||||
g_NetworkManager.DoWork();
|
||||
ProfileManager.Tick();
|
||||
StorageManager.Tick();
|
||||
//ConsoleSaveFileOriginal::flushPendingBackgroundSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* **Handle Queued XUI Server Action Once**
|
||||
*
|
||||
* Processes queued XUI/server action once
|
||||
* XUIアクションの単発処理
|
||||
*/
|
||||
static void HandleXuiActions()
|
||||
{
|
||||
app.HandleXuiActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated Server Entory Point
|
||||
*
|
||||
* 主な責務:
|
||||
* - プロセス/描画/ネットワークの初期化
|
||||
* - `WorldManager` によるワールドロードまたは新規作成
|
||||
* - メインループと定期オートセーブ実行
|
||||
* - 終了時の最終保存と各サブシステムの安全停止
|
||||
*/
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
DedicatedServerConfig config;
|
||||
config.port = NETWORK_LAN_DEFAULT_PORT;
|
||||
strncpy_s(config.bindIP, sizeof(config.bindIP), "0.0.0.0", _TRUNCATE);
|
||||
strncpy_s(config.name, sizeof(config.name), "DedicatedServer", _TRUNCATE);
|
||||
config.maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
|
||||
config.worldSize = e_worldSize_Classic;
|
||||
config.worldSizeChunks = LEVEL_WIDTH_CLASSIC;
|
||||
config.worldHellScale = HELL_LEVEL_SCALE_CLASSIC;
|
||||
config.seed = 0;
|
||||
config.logLevel = ServerRuntime::eServerLogLevel_Info;
|
||||
config.hasSeed = false;
|
||||
config.showHelp = false;
|
||||
|
||||
SetConsoleCtrlHandler(ConsoleCtrlHandlerProc, TRUE);
|
||||
SetExeWorkingDirectory();
|
||||
|
||||
// Load base settings from server.properties, then override with CLI values when provided
|
||||
ServerPropertiesConfig serverProperties = LoadServerPropertiesConfig();
|
||||
ApplyServerPropertiesToDedicatedConfig(serverProperties, &config);
|
||||
|
||||
if (!ParseCommandLine(argc, argv, &config))
|
||||
{
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
if (config.showHelp)
|
||||
{
|
||||
PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
SetServerLogLevel(config.logLevel);
|
||||
LogStartupStep("initializing process state");
|
||||
AccessShutdownGuard accessShutdownGuard;
|
||||
|
||||
g_iScreenWidth = 1280;
|
||||
g_iScreenHeight = 720;
|
||||
|
||||
strncpy_s(g_Win64Username, sizeof(g_Win64Username), config.name, _TRUNCATE);
|
||||
MultiByteToWideChar(CP_ACP, 0, g_Win64Username, -1, g_Win64UsernameW, 17);
|
||||
|
||||
g_MultiplayerHost = true;
|
||||
g_MultiplayerJoin = false;
|
||||
g_MultiplayerPort = config.port;
|
||||
strncpy_s(g_MultiplayerIP, sizeof(g_MultiplayerIP), config.bindIP, _TRUNCATE);
|
||||
g_DedicatedServerPort = config.port;
|
||||
strncpy_s(g_DedicatedServerBindIP, sizeof(g_DedicatedServerBindIP), config.bindIP, _TRUNCATE);
|
||||
g_DedicatedServerLanAdvertise = serverProperties.lanAdvertise;
|
||||
LogStartupStep("initializing server log manager");
|
||||
ServerRuntime::ServerLogManager::Initialize();
|
||||
LogStartupStep("initializing dedicated access control");
|
||||
if (!ServerRuntime::Access::Initialize(".", serverProperties.whiteListEnabled))
|
||||
{
|
||||
LogError("startup", "Failed to initialize dedicated server access control.");
|
||||
return 2;
|
||||
}
|
||||
accessShutdownGuard.Activate();
|
||||
LogInfof("startup", "LAN advertise: %s", serverProperties.lanAdvertise ? "enabled" : "disabled");
|
||||
LogInfof("startup", "Whitelist: %s", serverProperties.whiteListEnabled ? "enabled" : "disabled");
|
||||
LogInfof("startup", "Spawn protection radius: %d", serverProperties.spawnProtectionRadius);
|
||||
#ifdef _LARGE_WORLDS
|
||||
LogInfof(
|
||||
"startup",
|
||||
"World size preset: %d (xz=%d, hell-scale=%d)",
|
||||
config.worldSize,
|
||||
config.worldSizeChunks,
|
||||
config.worldHellScale);
|
||||
#endif
|
||||
|
||||
LogStartupStep("registering hidden window class");
|
||||
HINSTANCE hInstance = GetModuleHandle(NULL);
|
||||
MyRegisterClass(hInstance);
|
||||
|
||||
LogStartupStep("creating hidden window");
|
||||
if (!InitInstance(hInstance, SW_HIDE))
|
||||
{
|
||||
LogError("startup", "Failed to create window instance.");
|
||||
|
||||
return 2;
|
||||
}
|
||||
ShowWindow(g_hWnd, SW_HIDE);
|
||||
|
||||
LogStartupStep("initializing graphics device wrappers");
|
||||
if (FAILED(InitDevice()))
|
||||
{
|
||||
LogError("startup", "Failed to initialize D3D device.");
|
||||
CleanupDevice();
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
LogStartupStep("loading media/string tables");
|
||||
app.loadMediaArchive();
|
||||
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
|
||||
app.loadStringTable();
|
||||
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
|
||||
|
||||
InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
|
||||
DefineActions();
|
||||
InputManager.SetJoypadMapVal(0, 0);
|
||||
InputManager.SetKeyRepeatRate(0.3f, 0.2f);
|
||||
|
||||
ProfileManager.Initialise(
|
||||
TITLEID_MINECRAFT,
|
||||
app.m_dwOfferID,
|
||||
PROFILE_VERSION_10,
|
||||
kProfileValueCount,
|
||||
kProfileSettingCount,
|
||||
dwProfileSettingsA,
|
||||
app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT,
|
||||
&app.uiGameDefinedDataChangedBitmask);
|
||||
ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app);
|
||||
ProfileManager.SetDebugFullOverride(true);
|
||||
|
||||
LogStartupStep("initializing network manager");
|
||||
g_NetworkManager.Initialise();
|
||||
|
||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i)
|
||||
{
|
||||
IQNet::m_player[i].m_smallId = (BYTE)i;
|
||||
IQNet::m_player[i].m_isRemote = false;
|
||||
IQNet::m_player[i].m_isHostPlayer = (i == 0);
|
||||
swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
|
||||
}
|
||||
wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
|
||||
NetworkSocketLayer::Initialize();
|
||||
|
||||
Tesselator::CreateNewThreadStorage(1024 * 1024);
|
||||
AABB::CreateNewThreadStorage();
|
||||
Vec3::CreateNewThreadStorage();
|
||||
IntCache::CreateNewThreadStorage();
|
||||
Compression::CreateNewThreadStorage();
|
||||
OldChunkStorage::CreateNewThreadStorage();
|
||||
Level::enableLightingCache();
|
||||
Tile::CreateNewThreadStorage();
|
||||
|
||||
LogStartupStep("creating Minecraft singleton");
|
||||
Minecraft::main();
|
||||
Minecraft *minecraft = Minecraft::GetInstance();
|
||||
if (minecraft == NULL)
|
||||
{
|
||||
LogError("startup", "Minecraft initialization failed.");
|
||||
CleanupDevice();
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
app.InitGameSettings();
|
||||
|
||||
MinecraftServer::resetFlags();
|
||||
app.SetTutorialMode(false);
|
||||
app.SetCorruptSaveDeleted(false);
|
||||
app.SetGameHostOption(eGameHostOption_Difficulty, serverProperties.difficulty);
|
||||
app.SetGameHostOption(eGameHostOption_FriendsOfFriends, serverProperties.friendsOfFriends ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_Gamertags, serverProperties.gamertags ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_BedrockFog, serverProperties.bedrockFog ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_GameType, serverProperties.gameMode);
|
||||
app.SetGameHostOption(eGameHostOption_LevelType, serverProperties.levelTypeFlat ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_Structures, serverProperties.generateStructures ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_BonusChest, serverProperties.bonusChest ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_PvP, serverProperties.pvp ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_TrustPlayers, serverProperties.trustPlayers ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_FireSpreads, serverProperties.fireSpreads ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_TNT, serverProperties.tnt ? 1 : 0);
|
||||
app.SetGameHostOption(
|
||||
eGameHostOption_CheatsEnabled,
|
||||
(serverProperties.hostCanFly || serverProperties.hostCanChangeHunger || serverProperties.hostCanBeInvisible) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_HostCanFly, serverProperties.hostCanFly ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, serverProperties.hostCanChangeHunger ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, serverProperties.hostCanBeInvisible ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_DisableSaving, serverProperties.disableSaving ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_MobGriefing, serverProperties.mobGriefing ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_KeepInventory, serverProperties.keepInventory ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_DoMobSpawning, serverProperties.doMobSpawning ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_DoMobLoot, serverProperties.doMobLoot ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_DoTileDrops, serverProperties.doTileDrops ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_NaturalRegeneration, serverProperties.naturalRegeneration ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_DoDaylightCycle, serverProperties.doDaylightCycle ? 1 : 0);
|
||||
#ifdef _LARGE_WORLDS
|
||||
app.SetGameHostOption(eGameHostOption_WorldSize, serverProperties.worldSize);
|
||||
// Apply desired target size for loading existing worlds.
|
||||
// Expansion happens only when target size is larger than current level size.
|
||||
app.SetGameNewWorldSize(serverProperties.worldSizeChunks, true);
|
||||
app.SetGameNewHellScale(serverProperties.worldHellScale);
|
||||
#endif
|
||||
|
||||
StorageManager.SetSaveDisabled(serverProperties.disableSaving);
|
||||
// Read world name and fixed save-id from server.properties
|
||||
// Delegate load-vs-create decision to WorldManager
|
||||
std::wstring targetWorldName = serverProperties.worldName;
|
||||
if (targetWorldName.empty())
|
||||
{
|
||||
targetWorldName = L"world"; // Default world name
|
||||
}
|
||||
WorldBootstrapResult worldBootstrap = BootstrapWorldForServer(serverProperties, kServerActionPad, &TickCoreSystems);
|
||||
if (worldBootstrap.status == eWorldBootstrap_Loaded)
|
||||
{
|
||||
const std::string &loadedSaveFilename = worldBootstrap.resolvedSaveId;
|
||||
if (!loadedSaveFilename.empty() && _stricmp(loadedSaveFilename.c_str(), serverProperties.worldSaveId.c_str()) != 0)
|
||||
{
|
||||
// Persist the actually loaded save-id back to config
|
||||
// Keep lookup keys aligned for next startup
|
||||
LogWorldIO("updating level-id to loaded save filename");
|
||||
serverProperties.worldSaveId = loadedSaveFilename;
|
||||
if (!SaveServerPropertiesConfig(serverProperties))
|
||||
{
|
||||
LogWorldIO("failed to persist updated level-id");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (worldBootstrap.status == eWorldBootstrap_Failed)
|
||||
{
|
||||
LogErrorf("world-io", "Failed to load configured world \"%s\".", WideToUtf8(targetWorldName).c_str());
|
||||
NetworkSocketLayer::Shutdown();
|
||||
g_NetworkManager.Terminate();
|
||||
CleanupDevice();
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
NetworkGameInitData *param = new NetworkGameInitData();
|
||||
if (config.hasSeed)
|
||||
{
|
||||
param->seed = config.seed;
|
||||
}
|
||||
else
|
||||
{
|
||||
param->seed = (new Random())->nextLong();
|
||||
}
|
||||
#ifdef _LARGE_WORLDS
|
||||
param->xzSize = (unsigned int)config.worldSizeChunks;
|
||||
param->hellScale = (unsigned char)config.worldHellScale;
|
||||
#endif
|
||||
param->saveData = worldBootstrap.saveData;
|
||||
param->settings = app.GetGameHostOption(eGameHostOption_All);
|
||||
param->dedicatedNoLocalHostPlayer = true;
|
||||
|
||||
LogStartupStep("starting hosted network game thread");
|
||||
g_NetworkManager.HostGame(0, true, false, (unsigned char)config.maxPlayers, 0);
|
||||
g_NetworkManager.FakeLocalPlayerJoined();
|
||||
|
||||
C4JThread *startThread = new C4JThread(&CGameNetworkManager::RunNetworkGameThreadProc, (LPVOID)param, "RunNetworkGame");
|
||||
startThread->Run();
|
||||
|
||||
while (startThread->isRunning() && !IsShutdownRequested())
|
||||
{
|
||||
TickCoreSystems();
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
startThread->WaitForCompletion(INFINITE);
|
||||
int startupResult = startThread->GetExitCode();
|
||||
delete startThread;
|
||||
|
||||
if (startupResult != 0)
|
||||
{
|
||||
LogErrorf("startup", "Failed to start dedicated server (code %d).", startupResult);
|
||||
NetworkSocketLayer::Shutdown();
|
||||
g_NetworkManager.Terminate();
|
||||
CleanupDevice();
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
LogStartupStep("server startup complete");
|
||||
LogInfof("startup", "Dedicated server listening on %s:%d", g_MultiplayerIP, g_MultiplayerPort);
|
||||
if (worldBootstrap.status == eWorldBootstrap_CreatedNew && !IsShutdownRequested()/* && !app.m_bShutdown*/)
|
||||
{
|
||||
// Windows64 suppresses saveToDisc right after new world creation
|
||||
// Dedicated Server explicitly runs the initial save here
|
||||
LogWorldIO("requesting initial save for newly created world");
|
||||
WaitForWorldActionIdle(kServerActionPad, 5000, &TickCoreSystems, &HandleXuiActions);
|
||||
app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
|
||||
if (!WaitForWorldActionIdle(kServerActionPad, 30000, &TickCoreSystems, &HandleXuiActions))
|
||||
{
|
||||
LogWorldIO("initial save timed out");
|
||||
LogWarn("world-io", "Timed out waiting for initial save action to finish.");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWorldIO("initial save completed");
|
||||
}
|
||||
}
|
||||
DWORD autosaveIntervalMs = kDefaultAutosaveIntervalMs;
|
||||
if (serverProperties.autosaveIntervalSeconds > 0)
|
||||
{
|
||||
autosaveIntervalMs = (DWORD)(serverProperties.autosaveIntervalSeconds * 1000);
|
||||
}
|
||||
DWORD nextAutosaveTick = GetTickCount() + autosaveIntervalMs;
|
||||
bool autosaveRequested = false;
|
||||
ServerRuntime::ServerCli serverCli;
|
||||
serverCli.Start();
|
||||
|
||||
while (!IsShutdownRequested() /*&& !app.m_bShutdown*/)
|
||||
{
|
||||
TickCoreSystems();
|
||||
HandleXuiActions();
|
||||
serverCli.Poll();
|
||||
|
||||
if (IsShutdownRequested()/* || app.m_bShutdown*/)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle/* && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()*/)
|
||||
{
|
||||
LogWorldIO("autosave completed");
|
||||
autosaveRequested = false;
|
||||
}
|
||||
|
||||
if (MinecraftServer::serverHalted())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD now = GetTickCount();
|
||||
if ((LONG)(now - nextAutosaveTick) >= 0)
|
||||
{
|
||||
if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle/* && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()*/)
|
||||
{
|
||||
LogWorldIO("requesting autosave");
|
||||
app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
|
||||
autosaveRequested = true;
|
||||
}
|
||||
nextAutosaveTick = now + autosaveIntervalMs;
|
||||
}
|
||||
|
||||
Sleep(10);
|
||||
}
|
||||
serverCli.Stop();
|
||||
/*app.m_bShutdown = true;*/
|
||||
|
||||
LogInfof("shutdown", "Dedicated server stopped");
|
||||
MinecraftServer *server = MinecraftServer::getInstance();
|
||||
if (server != NULL/* && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()*/)
|
||||
{
|
||||
server->setSaveOnExit(true);
|
||||
LogWorldIO("requesting save before shutdown");
|
||||
LogWorldIO("using saveOnExit for shutdown");
|
||||
}
|
||||
|
||||
/*if (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
{
|
||||
LogWorldIO("Waiting for autosave to complete...");
|
||||
}*/
|
||||
|
||||
MinecraftServer::HaltServer();
|
||||
|
||||
if (g_NetworkManager.ServerStoppedValid())
|
||||
{
|
||||
C4JThread waitThread(&WaitForServerStoppedThreadProc, NULL, "WaitServerStopped");
|
||||
waitThread.Run();
|
||||
while (waitThread.isRunning())
|
||||
{
|
||||
TickCoreSystems();
|
||||
Sleep(10);
|
||||
}
|
||||
waitThread.WaitForCompletion(INFINITE);
|
||||
}
|
||||
|
||||
/*while (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
{
|
||||
TickCoreSystems();
|
||||
Sleep(10);
|
||||
}*/
|
||||
|
||||
LogInfof("shutdown", "Cleaning up and exiting.");
|
||||
NetworkSocketLayer::Shutdown();
|
||||
LogDebugf("shutdown", "Network layer shutdown complete.");
|
||||
g_NetworkManager.Terminate();
|
||||
LogDebugf("shutdown", "Network manager terminated.");
|
||||
ServerRuntime::ServerLogManager::Shutdown();
|
||||
CleanupDevice();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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`
|
||||
@@ -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` で反映されているか確認する
|
||||
@@ -0,0 +1,8 @@
|
||||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// Minecraft.Server.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// TODO: reference any additional headers you need in STDAFX.H
|
||||
// and not in this file
|
||||
+25
@@ -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.
|
||||
+652
@@ -0,0 +1,652 @@
|
||||
#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)
|
||||
{
|
||||
int newCap;
|
||||
char **newItems;
|
||||
|
||||
if (wanted <= g_history.cap)
|
||||
return;
|
||||
|
||||
newCap = g_history.cap == 0 ? 32 : g_history.cap;
|
||||
while (newCap < wanted)
|
||||
newCap *= 2;
|
||||
|
||||
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();
|
||||
}
|
||||
+37
@@ -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
|
||||
+693
@@ -0,0 +1,693 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_ALLOCATORS_H_
|
||||
#define RAPIDJSON_ALLOCATORS_H_
|
||||
|
||||
#include "rapidjson.h"
|
||||
#include "internal/meta.h"
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
#if RAPIDJSON_HAS_CXX11
|
||||
#include <type_traits>
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Allocator
|
||||
|
||||
/*! \class rapidjson::Allocator
|
||||
\brief Concept for allocating, resizing and freeing memory block.
|
||||
|
||||
Note that Malloc() and Realloc() are non-static but Free() is static.
|
||||
|
||||
So if an allocator need to support Free(), it needs to put its pointer in
|
||||
the header of memory block.
|
||||
|
||||
\code
|
||||
concept Allocator {
|
||||
static const bool kNeedFree; //!< Whether this allocator needs to call Free().
|
||||
|
||||
// Allocate a memory block.
|
||||
// \param size of the memory block in bytes.
|
||||
// \returns pointer to the memory block.
|
||||
void* Malloc(size_t size);
|
||||
|
||||
// Resize a memory block.
|
||||
// \param originalPtr The pointer to current memory block. Null pointer is permitted.
|
||||
// \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.)
|
||||
// \param newSize the new size in bytes.
|
||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize);
|
||||
|
||||
// Free a memory block.
|
||||
// \param pointer to the memory block. Null pointer is permitted.
|
||||
static void Free(void *ptr);
|
||||
};
|
||||
\endcode
|
||||
*/
|
||||
|
||||
|
||||
/*! \def RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY
|
||||
\ingroup RAPIDJSON_CONFIG
|
||||
\brief User-defined kDefaultChunkCapacity definition.
|
||||
|
||||
User can define this as any \c size that is a power of 2.
|
||||
*/
|
||||
|
||||
#ifndef RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY
|
||||
#define RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY (64 * 1024)
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CrtAllocator
|
||||
|
||||
//! C-runtime library allocator.
|
||||
/*! This class is just wrapper for standard C library memory routines.
|
||||
\note implements Allocator concept
|
||||
*/
|
||||
class CrtAllocator {
|
||||
public:
|
||||
static const bool kNeedFree = true;
|
||||
void* Malloc(size_t size) {
|
||||
if (size) // behavior of malloc(0) is implementation defined.
|
||||
return RAPIDJSON_MALLOC(size);
|
||||
else
|
||||
return NULL; // standardize to returning NULL.
|
||||
}
|
||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) {
|
||||
(void)originalSize;
|
||||
if (newSize == 0) {
|
||||
RAPIDJSON_FREE(originalPtr);
|
||||
return NULL;
|
||||
}
|
||||
return RAPIDJSON_REALLOC(originalPtr, newSize);
|
||||
}
|
||||
static void Free(void *ptr) RAPIDJSON_NOEXCEPT { RAPIDJSON_FREE(ptr); }
|
||||
|
||||
bool operator==(const CrtAllocator&) const RAPIDJSON_NOEXCEPT {
|
||||
return true;
|
||||
}
|
||||
bool operator!=(const CrtAllocator&) const RAPIDJSON_NOEXCEPT {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MemoryPoolAllocator
|
||||
|
||||
//! Default memory allocator used by the parser and DOM.
|
||||
/*! This allocator allocate memory blocks from pre-allocated memory chunks.
|
||||
|
||||
It does not free memory blocks. And Realloc() only allocate new memory.
|
||||
|
||||
The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default.
|
||||
|
||||
User may also supply a buffer as the first chunk.
|
||||
|
||||
If the user-buffer is full then additional chunks are allocated by BaseAllocator.
|
||||
|
||||
The user-buffer is not deallocated by this allocator.
|
||||
|
||||
\tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator.
|
||||
\note implements Allocator concept
|
||||
*/
|
||||
template <typename BaseAllocator = CrtAllocator>
|
||||
class MemoryPoolAllocator {
|
||||
//! Chunk header for perpending to each chunk.
|
||||
/*! Chunks are stored as a singly linked list.
|
||||
*/
|
||||
struct ChunkHeader {
|
||||
size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself).
|
||||
size_t size; //!< Current size of allocated memory in bytes.
|
||||
ChunkHeader *next; //!< Next chunk in the linked list.
|
||||
};
|
||||
|
||||
struct SharedData {
|
||||
ChunkHeader *chunkHead; //!< Head of the chunk linked-list. Only the head chunk serves allocation.
|
||||
BaseAllocator* ownBaseAllocator; //!< base allocator created by this object.
|
||||
size_t refcount;
|
||||
bool ownBuffer;
|
||||
};
|
||||
|
||||
static const size_t SIZEOF_SHARED_DATA = RAPIDJSON_ALIGN(sizeof(SharedData));
|
||||
static const size_t SIZEOF_CHUNK_HEADER = RAPIDJSON_ALIGN(sizeof(ChunkHeader));
|
||||
|
||||
static inline ChunkHeader *GetChunkHead(SharedData *shared)
|
||||
{
|
||||
return reinterpret_cast<ChunkHeader*>(reinterpret_cast<uint8_t*>(shared) + SIZEOF_SHARED_DATA);
|
||||
}
|
||||
static inline uint8_t *GetChunkBuffer(SharedData *shared)
|
||||
{
|
||||
return reinterpret_cast<uint8_t*>(shared->chunkHead) + SIZEOF_CHUNK_HEADER;
|
||||
}
|
||||
|
||||
static const size_t kDefaultChunkCapacity = RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY; //!< Default chunk capacity.
|
||||
|
||||
public:
|
||||
static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator)
|
||||
static const bool kRefCounted = true; //!< Tell users that this allocator is reference counted on copy
|
||||
|
||||
//! Constructor with chunkSize.
|
||||
/*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize.
|
||||
\param baseAllocator The allocator for allocating memory chunks.
|
||||
*/
|
||||
explicit
|
||||
MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) :
|
||||
chunk_capacity_(chunkSize),
|
||||
baseAllocator_(baseAllocator ? baseAllocator : RAPIDJSON_NEW(BaseAllocator)()),
|
||||
shared_(static_cast<SharedData*>(baseAllocator_ ? baseAllocator_->Malloc(SIZEOF_SHARED_DATA + SIZEOF_CHUNK_HEADER) : 0))
|
||||
{
|
||||
RAPIDJSON_ASSERT(baseAllocator_ != 0);
|
||||
RAPIDJSON_ASSERT(shared_ != 0);
|
||||
if (baseAllocator) {
|
||||
shared_->ownBaseAllocator = 0;
|
||||
}
|
||||
else {
|
||||
shared_->ownBaseAllocator = baseAllocator_;
|
||||
}
|
||||
shared_->chunkHead = GetChunkHead(shared_);
|
||||
shared_->chunkHead->capacity = 0;
|
||||
shared_->chunkHead->size = 0;
|
||||
shared_->chunkHead->next = 0;
|
||||
shared_->ownBuffer = true;
|
||||
shared_->refcount = 1;
|
||||
}
|
||||
|
||||
//! Constructor with user-supplied buffer.
|
||||
/*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size.
|
||||
|
||||
The user buffer will not be deallocated when this allocator is destructed.
|
||||
|
||||
\param buffer User supplied buffer.
|
||||
\param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader).
|
||||
\param chunkSize The size of memory chunk. The default is kDefaultChunkSize.
|
||||
\param baseAllocator The allocator for allocating memory chunks.
|
||||
*/
|
||||
MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) :
|
||||
chunk_capacity_(chunkSize),
|
||||
baseAllocator_(baseAllocator),
|
||||
shared_(static_cast<SharedData*>(AlignBuffer(buffer, size)))
|
||||
{
|
||||
RAPIDJSON_ASSERT(size >= SIZEOF_SHARED_DATA + SIZEOF_CHUNK_HEADER);
|
||||
shared_->chunkHead = GetChunkHead(shared_);
|
||||
shared_->chunkHead->capacity = size - SIZEOF_SHARED_DATA - SIZEOF_CHUNK_HEADER;
|
||||
shared_->chunkHead->size = 0;
|
||||
shared_->chunkHead->next = 0;
|
||||
shared_->ownBaseAllocator = 0;
|
||||
shared_->ownBuffer = false;
|
||||
shared_->refcount = 1;
|
||||
}
|
||||
|
||||
MemoryPoolAllocator(const MemoryPoolAllocator& rhs) RAPIDJSON_NOEXCEPT :
|
||||
chunk_capacity_(rhs.chunk_capacity_),
|
||||
baseAllocator_(rhs.baseAllocator_),
|
||||
shared_(rhs.shared_)
|
||||
{
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
++shared_->refcount;
|
||||
}
|
||||
MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(rhs.shared_->refcount > 0);
|
||||
++rhs.shared_->refcount;
|
||||
this->~MemoryPoolAllocator();
|
||||
baseAllocator_ = rhs.baseAllocator_;
|
||||
chunk_capacity_ = rhs.chunk_capacity_;
|
||||
shared_ = rhs.shared_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if RAPIDJSON_HAS_CXX11_RVALUE_REFS
|
||||
MemoryPoolAllocator(MemoryPoolAllocator&& rhs) RAPIDJSON_NOEXCEPT :
|
||||
chunk_capacity_(rhs.chunk_capacity_),
|
||||
baseAllocator_(rhs.baseAllocator_),
|
||||
shared_(rhs.shared_)
|
||||
{
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(rhs.shared_->refcount > 0);
|
||||
rhs.shared_ = 0;
|
||||
}
|
||||
MemoryPoolAllocator& operator=(MemoryPoolAllocator&& rhs) RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(rhs.shared_->refcount > 0);
|
||||
this->~MemoryPoolAllocator();
|
||||
baseAllocator_ = rhs.baseAllocator_;
|
||||
chunk_capacity_ = rhs.chunk_capacity_;
|
||||
shared_ = rhs.shared_;
|
||||
rhs.shared_ = 0;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
//! Destructor.
|
||||
/*! This deallocates all memory chunks, excluding the user-supplied buffer.
|
||||
*/
|
||||
~MemoryPoolAllocator() RAPIDJSON_NOEXCEPT {
|
||||
if (!shared_) {
|
||||
// do nothing if moved
|
||||
return;
|
||||
}
|
||||
if (shared_->refcount > 1) {
|
||||
--shared_->refcount;
|
||||
return;
|
||||
}
|
||||
Clear();
|
||||
BaseAllocator *a = shared_->ownBaseAllocator;
|
||||
if (shared_->ownBuffer) {
|
||||
baseAllocator_->Free(shared_);
|
||||
}
|
||||
RAPIDJSON_DELETE(a);
|
||||
}
|
||||
|
||||
//! Deallocates all memory chunks, excluding the first/user one.
|
||||
void Clear() RAPIDJSON_NOEXCEPT {
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
for (;;) {
|
||||
ChunkHeader* c = shared_->chunkHead;
|
||||
if (!c->next) {
|
||||
break;
|
||||
}
|
||||
shared_->chunkHead = c->next;
|
||||
baseAllocator_->Free(c);
|
||||
}
|
||||
shared_->chunkHead->size = 0;
|
||||
}
|
||||
|
||||
//! Computes the total capacity of allocated memory chunks.
|
||||
/*! \return total capacity in bytes.
|
||||
*/
|
||||
size_t Capacity() const RAPIDJSON_NOEXCEPT {
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
size_t capacity = 0;
|
||||
for (ChunkHeader* c = shared_->chunkHead; c != 0; c = c->next)
|
||||
capacity += c->capacity;
|
||||
return capacity;
|
||||
}
|
||||
|
||||
//! Computes the memory blocks allocated.
|
||||
/*! \return total used bytes.
|
||||
*/
|
||||
size_t Size() const RAPIDJSON_NOEXCEPT {
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
size_t size = 0;
|
||||
for (ChunkHeader* c = shared_->chunkHead; c != 0; c = c->next)
|
||||
size += c->size;
|
||||
return size;
|
||||
}
|
||||
|
||||
//! Whether the allocator is shared.
|
||||
/*! \return true or false.
|
||||
*/
|
||||
bool Shared() const RAPIDJSON_NOEXCEPT {
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
return shared_->refcount > 1;
|
||||
}
|
||||
|
||||
//! Allocates a memory block. (concept Allocator)
|
||||
void* Malloc(size_t size) {
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
if (!size)
|
||||
return NULL;
|
||||
|
||||
size = RAPIDJSON_ALIGN(size);
|
||||
if (RAPIDJSON_UNLIKELY(shared_->chunkHead->size + size > shared_->chunkHead->capacity))
|
||||
if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size))
|
||||
return NULL;
|
||||
|
||||
void *buffer = GetChunkBuffer(shared_) + shared_->chunkHead->size;
|
||||
shared_->chunkHead->size += size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//! Resizes a memory block (concept Allocator)
|
||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) {
|
||||
if (originalPtr == 0)
|
||||
return Malloc(newSize);
|
||||
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
if (newSize == 0)
|
||||
return NULL;
|
||||
|
||||
originalSize = RAPIDJSON_ALIGN(originalSize);
|
||||
newSize = RAPIDJSON_ALIGN(newSize);
|
||||
|
||||
// Do not shrink if new size is smaller than original
|
||||
if (originalSize >= newSize)
|
||||
return originalPtr;
|
||||
|
||||
// Simply expand it if it is the last allocation and there is sufficient space
|
||||
if (originalPtr == GetChunkBuffer(shared_) + shared_->chunkHead->size - originalSize) {
|
||||
size_t increment = static_cast<size_t>(newSize - originalSize);
|
||||
if (shared_->chunkHead->size + increment <= shared_->chunkHead->capacity) {
|
||||
shared_->chunkHead->size += increment;
|
||||
return originalPtr;
|
||||
}
|
||||
}
|
||||
|
||||
// Realloc process: allocate and copy memory, do not free original buffer.
|
||||
if (void* newBuffer = Malloc(newSize)) {
|
||||
if (originalSize)
|
||||
std::memcpy(newBuffer, originalPtr, originalSize);
|
||||
return newBuffer;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//! Frees a memory block (concept Allocator)
|
||||
static void Free(void *ptr) RAPIDJSON_NOEXCEPT { (void)ptr; } // Do nothing
|
||||
|
||||
//! Compare (equality) with another MemoryPoolAllocator
|
||||
bool operator==(const MemoryPoolAllocator& rhs) const RAPIDJSON_NOEXCEPT {
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(shared_->refcount > 0);
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(rhs.shared_->refcount > 0);
|
||||
return shared_ == rhs.shared_;
|
||||
}
|
||||
//! Compare (inequality) with another MemoryPoolAllocator
|
||||
bool operator!=(const MemoryPoolAllocator& rhs) const RAPIDJSON_NOEXCEPT {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
//! Creates a new chunk.
|
||||
/*! \param capacity Capacity of the chunk in bytes.
|
||||
\return true if success.
|
||||
*/
|
||||
bool AddChunk(size_t capacity) {
|
||||
if (!baseAllocator_)
|
||||
shared_->ownBaseAllocator = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)();
|
||||
if (ChunkHeader* chunk = static_cast<ChunkHeader*>(baseAllocator_->Malloc(SIZEOF_CHUNK_HEADER + capacity))) {
|
||||
chunk->capacity = capacity;
|
||||
chunk->size = 0;
|
||||
chunk->next = shared_->chunkHead;
|
||||
shared_->chunkHead = chunk;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void* AlignBuffer(void* buf, size_t &size)
|
||||
{
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(buf != 0);
|
||||
const uintptr_t mask = sizeof(void*) - 1;
|
||||
const uintptr_t ubuf = reinterpret_cast<uintptr_t>(buf);
|
||||
if (RAPIDJSON_UNLIKELY(ubuf & mask)) {
|
||||
const uintptr_t abuf = (ubuf + mask) & ~mask;
|
||||
RAPIDJSON_ASSERT(size >= abuf - ubuf);
|
||||
buf = reinterpret_cast<void*>(abuf);
|
||||
size -= abuf - ubuf;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated.
|
||||
BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks.
|
||||
SharedData *shared_; //!< The shared data of the allocator
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
template<typename, typename = void>
|
||||
struct IsRefCounted :
|
||||
public FalseType
|
||||
{ };
|
||||
template<typename T>
|
||||
struct IsRefCounted<T, typename internal::EnableIfCond<T::kRefCounted>::Type> :
|
||||
public TrueType
|
||||
{ };
|
||||
}
|
||||
|
||||
template<typename T, typename A>
|
||||
inline T* Realloc(A& a, T* old_p, size_t old_n, size_t new_n)
|
||||
{
|
||||
RAPIDJSON_NOEXCEPT_ASSERT(old_n <= (std::numeric_limits<size_t>::max)() / sizeof(T) && new_n <= (std::numeric_limits<size_t>::max)() / sizeof(T));
|
||||
return static_cast<T*>(a.Realloc(old_p, old_n * sizeof(T), new_n * sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T, typename A>
|
||||
inline T *Malloc(A& a, size_t n = 1)
|
||||
{
|
||||
return Realloc<T, A>(a, NULL, 0, n);
|
||||
}
|
||||
|
||||
template<typename T, typename A>
|
||||
inline void Free(A& a, T *p, size_t n = 1)
|
||||
{
|
||||
static_cast<void>(Realloc<T, A>(a, p, n, 0));
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++) // std::allocator can safely be inherited
|
||||
#endif
|
||||
|
||||
template <typename T, typename BaseAllocator = CrtAllocator>
|
||||
class StdAllocator :
|
||||
public std::allocator<T>
|
||||
{
|
||||
typedef std::allocator<T> allocator_type;
|
||||
#if RAPIDJSON_HAS_CXX11
|
||||
typedef std::allocator_traits<allocator_type> traits_type;
|
||||
#else
|
||||
typedef allocator_type traits_type;
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef BaseAllocator BaseAllocatorType;
|
||||
|
||||
StdAllocator() RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(),
|
||||
baseAllocator_()
|
||||
{ }
|
||||
|
||||
StdAllocator(const StdAllocator& rhs) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(rhs),
|
||||
baseAllocator_(rhs.baseAllocator_)
|
||||
{ }
|
||||
|
||||
template<typename U>
|
||||
StdAllocator(const StdAllocator<U, BaseAllocator>& rhs) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(rhs),
|
||||
baseAllocator_(rhs.baseAllocator_)
|
||||
{ }
|
||||
|
||||
#if RAPIDJSON_HAS_CXX11_RVALUE_REFS
|
||||
StdAllocator(StdAllocator&& rhs) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(std::move(rhs)),
|
||||
baseAllocator_(std::move(rhs.baseAllocator_))
|
||||
{ }
|
||||
#endif
|
||||
#if RAPIDJSON_HAS_CXX11
|
||||
using propagate_on_container_move_assignment = std::true_type;
|
||||
using propagate_on_container_swap = std::true_type;
|
||||
#endif
|
||||
|
||||
/* implicit */
|
||||
StdAllocator(const BaseAllocator& baseAllocator) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(),
|
||||
baseAllocator_(baseAllocator)
|
||||
{ }
|
||||
|
||||
~StdAllocator() RAPIDJSON_NOEXCEPT
|
||||
{ }
|
||||
|
||||
template<typename U>
|
||||
struct rebind {
|
||||
typedef StdAllocator<U, BaseAllocator> other;
|
||||
};
|
||||
|
||||
typedef typename traits_type::size_type size_type;
|
||||
typedef typename traits_type::difference_type difference_type;
|
||||
|
||||
typedef typename traits_type::value_type value_type;
|
||||
typedef typename traits_type::pointer pointer;
|
||||
typedef typename traits_type::const_pointer const_pointer;
|
||||
|
||||
#if RAPIDJSON_HAS_CXX11
|
||||
|
||||
typedef typename std::add_lvalue_reference<value_type>::type &reference;
|
||||
typedef typename std::add_lvalue_reference<typename std::add_const<value_type>::type>::type &const_reference;
|
||||
|
||||
pointer address(reference r) const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return std::addressof(r);
|
||||
}
|
||||
const_pointer address(const_reference r) const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return std::addressof(r);
|
||||
}
|
||||
|
||||
size_type max_size() const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return traits_type::max_size(*this);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
void construct(pointer p, Args&&... args)
|
||||
{
|
||||
traits_type::construct(*this, p, std::forward<Args>(args)...);
|
||||
}
|
||||
void destroy(pointer p)
|
||||
{
|
||||
traits_type::destroy(*this, p);
|
||||
}
|
||||
|
||||
#else // !RAPIDJSON_HAS_CXX11
|
||||
|
||||
typedef typename allocator_type::reference reference;
|
||||
typedef typename allocator_type::const_reference const_reference;
|
||||
|
||||
pointer address(reference r) const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return allocator_type::address(r);
|
||||
}
|
||||
const_pointer address(const_reference r) const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return allocator_type::address(r);
|
||||
}
|
||||
|
||||
size_type max_size() const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return allocator_type::max_size();
|
||||
}
|
||||
|
||||
void construct(pointer p, const_reference r)
|
||||
{
|
||||
allocator_type::construct(p, r);
|
||||
}
|
||||
void destroy(pointer p)
|
||||
{
|
||||
allocator_type::destroy(p);
|
||||
}
|
||||
|
||||
#endif // !RAPIDJSON_HAS_CXX11
|
||||
|
||||
template <typename U>
|
||||
U* allocate(size_type n = 1, const void* = 0)
|
||||
{
|
||||
return RAPIDJSON_NAMESPACE::Malloc<U>(baseAllocator_, n);
|
||||
}
|
||||
template <typename U>
|
||||
void deallocate(U* p, size_type n = 1)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Free<U>(baseAllocator_, p, n);
|
||||
}
|
||||
|
||||
pointer allocate(size_type n = 1, const void* = 0)
|
||||
{
|
||||
return allocate<value_type>(n);
|
||||
}
|
||||
void deallocate(pointer p, size_type n = 1)
|
||||
{
|
||||
deallocate<value_type>(p, n);
|
||||
}
|
||||
|
||||
#if RAPIDJSON_HAS_CXX11
|
||||
using is_always_equal = std::is_empty<BaseAllocator>;
|
||||
#endif
|
||||
|
||||
template<typename U>
|
||||
bool operator==(const StdAllocator<U, BaseAllocator>& rhs) const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return baseAllocator_ == rhs.baseAllocator_;
|
||||
}
|
||||
template<typename U>
|
||||
bool operator!=(const StdAllocator<U, BaseAllocator>& rhs) const RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
//! rapidjson Allocator concept
|
||||
static const bool kNeedFree = BaseAllocator::kNeedFree;
|
||||
static const bool kRefCounted = internal::IsRefCounted<BaseAllocator>::Value;
|
||||
void* Malloc(size_t size)
|
||||
{
|
||||
return baseAllocator_.Malloc(size);
|
||||
}
|
||||
void* Realloc(void* originalPtr, size_t originalSize, size_t newSize)
|
||||
{
|
||||
return baseAllocator_.Realloc(originalPtr, originalSize, newSize);
|
||||
}
|
||||
static void Free(void *ptr) RAPIDJSON_NOEXCEPT
|
||||
{
|
||||
BaseAllocator::Free(ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename>
|
||||
friend class StdAllocator; // access to StdAllocator<!T>.*
|
||||
|
||||
BaseAllocator baseAllocator_;
|
||||
};
|
||||
|
||||
#if !RAPIDJSON_HAS_CXX17 // std::allocator<void> deprecated in C++17
|
||||
template <typename BaseAllocator>
|
||||
class StdAllocator<void, BaseAllocator> :
|
||||
public std::allocator<void>
|
||||
{
|
||||
typedef std::allocator<void> allocator_type;
|
||||
|
||||
public:
|
||||
typedef BaseAllocator BaseAllocatorType;
|
||||
|
||||
StdAllocator() RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(),
|
||||
baseAllocator_()
|
||||
{ }
|
||||
|
||||
StdAllocator(const StdAllocator& rhs) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(rhs),
|
||||
baseAllocator_(rhs.baseAllocator_)
|
||||
{ }
|
||||
|
||||
template<typename U>
|
||||
StdAllocator(const StdAllocator<U, BaseAllocator>& rhs) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(rhs),
|
||||
baseAllocator_(rhs.baseAllocator_)
|
||||
{ }
|
||||
|
||||
/* implicit */
|
||||
StdAllocator(const BaseAllocator& baseAllocator) RAPIDJSON_NOEXCEPT :
|
||||
allocator_type(),
|
||||
baseAllocator_(baseAllocator)
|
||||
{ }
|
||||
|
||||
~StdAllocator() RAPIDJSON_NOEXCEPT
|
||||
{ }
|
||||
|
||||
template<typename U>
|
||||
struct rebind {
|
||||
typedef StdAllocator<U, BaseAllocator> other;
|
||||
};
|
||||
|
||||
typedef typename allocator_type::value_type value_type;
|
||||
|
||||
private:
|
||||
template <typename, typename>
|
||||
friend class StdAllocator; // access to StdAllocator<!T>.*
|
||||
|
||||
BaseAllocator baseAllocator_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_ENCODINGS_H_
|
||||
@@ -0,0 +1,78 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_
|
||||
#define RAPIDJSON_CURSORSTREAMWRAPPER_H_
|
||||
|
||||
#include "stream.h"
|
||||
|
||||
#if defined(__GNUC__)
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++)
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(4702) // unreachable code
|
||||
RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
//! Cursor stream wrapper for counting line and column number if error exists.
|
||||
/*!
|
||||
\tparam InputStream Any stream that implements Stream Concept
|
||||
*/
|
||||
template <typename InputStream, typename Encoding = UTF8<> >
|
||||
class CursorStreamWrapper : public GenericStreamWrapper<InputStream, Encoding> {
|
||||
public:
|
||||
typedef typename Encoding::Ch Ch;
|
||||
|
||||
CursorStreamWrapper(InputStream& is):
|
||||
GenericStreamWrapper<InputStream, Encoding>(is), line_(1), col_(0) {}
|
||||
|
||||
// counting line and column number
|
||||
Ch Take() {
|
||||
Ch ch = this->is_.Take();
|
||||
if(ch == '\n') {
|
||||
line_ ++;
|
||||
col_ = 0;
|
||||
} else {
|
||||
col_ ++;
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
//! Get the error line number, if error exists.
|
||||
size_t GetLine() const { return line_; }
|
||||
//! Get the error column number, if error exists.
|
||||
size_t GetColumn() const { return col_; }
|
||||
|
||||
private:
|
||||
size_t line_; //!< Current Line
|
||||
size_t col_; //!< Current Column
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_
|
||||
+3044
File diff suppressed because it is too large
Load Diff
+299
@@ -0,0 +1,299 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_ENCODEDSTREAM_H_
|
||||
#define RAPIDJSON_ENCODEDSTREAM_H_
|
||||
|
||||
#include "stream.h"
|
||||
#include "memorystream.h"
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++)
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(padded)
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
//! Input byte stream wrapper with a statically bound encoding.
|
||||
/*!
|
||||
\tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE.
|
||||
\tparam InputByteStream Type of input byte stream. For example, FileReadStream.
|
||||
*/
|
||||
template <typename Encoding, typename InputByteStream>
|
||||
class EncodedInputStream {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
public:
|
||||
typedef typename Encoding::Ch Ch;
|
||||
|
||||
EncodedInputStream(InputByteStream& is) : is_(is) {
|
||||
current_ = Encoding::TakeBOM(is_);
|
||||
}
|
||||
|
||||
Ch Peek() const { return current_; }
|
||||
Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; }
|
||||
size_t Tell() const { return is_.Tell(); }
|
||||
|
||||
// Not implemented
|
||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
private:
|
||||
EncodedInputStream(const EncodedInputStream&);
|
||||
EncodedInputStream& operator=(const EncodedInputStream&);
|
||||
|
||||
InputByteStream& is_;
|
||||
Ch current_;
|
||||
};
|
||||
|
||||
//! Specialized for UTF8 MemoryStream.
|
||||
template <>
|
||||
class EncodedInputStream<UTF8<>, MemoryStream> {
|
||||
public:
|
||||
typedef UTF8<>::Ch Ch;
|
||||
|
||||
EncodedInputStream(MemoryStream& is) : is_(is) {
|
||||
if (static_cast<unsigned char>(is_.Peek()) == 0xEFu) is_.Take();
|
||||
if (static_cast<unsigned char>(is_.Peek()) == 0xBBu) is_.Take();
|
||||
if (static_cast<unsigned char>(is_.Peek()) == 0xBFu) is_.Take();
|
||||
}
|
||||
Ch Peek() const { return is_.Peek(); }
|
||||
Ch Take() { return is_.Take(); }
|
||||
size_t Tell() const { return is_.Tell(); }
|
||||
|
||||
// Not implemented
|
||||
void Put(Ch) {}
|
||||
void Flush() {}
|
||||
Ch* PutBegin() { return 0; }
|
||||
size_t PutEnd(Ch*) { return 0; }
|
||||
|
||||
MemoryStream& is_;
|
||||
|
||||
private:
|
||||
EncodedInputStream(const EncodedInputStream&);
|
||||
EncodedInputStream& operator=(const EncodedInputStream&);
|
||||
};
|
||||
|
||||
//! Output byte stream wrapper with statically bound encoding.
|
||||
/*!
|
||||
\tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE.
|
||||
\tparam OutputByteStream Type of input byte stream. For example, FileWriteStream.
|
||||
*/
|
||||
template <typename Encoding, typename OutputByteStream>
|
||||
class EncodedOutputStream {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
public:
|
||||
typedef typename Encoding::Ch Ch;
|
||||
|
||||
EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) {
|
||||
if (putBOM)
|
||||
Encoding::PutBOM(os_);
|
||||
}
|
||||
|
||||
void Put(Ch c) { Encoding::Put(os_, c); }
|
||||
void Flush() { os_.Flush(); }
|
||||
|
||||
// Not implemented
|
||||
Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;}
|
||||
Ch Take() { RAPIDJSON_ASSERT(false); return 0;}
|
||||
size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; }
|
||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
private:
|
||||
EncodedOutputStream(const EncodedOutputStream&);
|
||||
EncodedOutputStream& operator=(const EncodedOutputStream&);
|
||||
|
||||
OutputByteStream& os_;
|
||||
};
|
||||
|
||||
#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8<Ch>::x, UTF16LE<Ch>::x, UTF16BE<Ch>::x, UTF32LE<Ch>::x, UTF32BE<Ch>::x
|
||||
|
||||
//! Input stream wrapper with dynamically bound encoding and automatic encoding detection.
|
||||
/*!
|
||||
\tparam CharType Type of character for reading.
|
||||
\tparam InputByteStream type of input byte stream to be wrapped.
|
||||
*/
|
||||
template <typename CharType, typename InputByteStream>
|
||||
class AutoUTFInputStream {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
public:
|
||||
typedef CharType Ch;
|
||||
|
||||
//! Constructor.
|
||||
/*!
|
||||
\param is input stream to be wrapped.
|
||||
\param type UTF encoding type if it is not detected from the stream.
|
||||
*/
|
||||
AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) {
|
||||
RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE);
|
||||
DetectType();
|
||||
static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) };
|
||||
takeFunc_ = f[type_];
|
||||
current_ = takeFunc_(*is_);
|
||||
}
|
||||
|
||||
UTFType GetType() const { return type_; }
|
||||
bool HasBOM() const { return hasBOM_; }
|
||||
|
||||
Ch Peek() const { return current_; }
|
||||
Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; }
|
||||
size_t Tell() const { return is_->Tell(); }
|
||||
|
||||
// Not implemented
|
||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
private:
|
||||
AutoUTFInputStream(const AutoUTFInputStream&);
|
||||
AutoUTFInputStream& operator=(const AutoUTFInputStream&);
|
||||
|
||||
// Detect encoding type with BOM or RFC 4627
|
||||
void DetectType() {
|
||||
// BOM (Byte Order Mark):
|
||||
// 00 00 FE FF UTF-32BE
|
||||
// FF FE 00 00 UTF-32LE
|
||||
// FE FF UTF-16BE
|
||||
// FF FE UTF-16LE
|
||||
// EF BB BF UTF-8
|
||||
|
||||
const unsigned char* c = reinterpret_cast<const unsigned char *>(is_->Peek4());
|
||||
if (!c)
|
||||
return;
|
||||
|
||||
unsigned bom = static_cast<unsigned>(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24));
|
||||
hasBOM_ = false;
|
||||
if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); }
|
||||
else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); }
|
||||
else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); }
|
||||
else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); }
|
||||
else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); }
|
||||
|
||||
// RFC 4627: Section 3
|
||||
// "Since the first two characters of a JSON text will always be ASCII
|
||||
// characters [RFC0020], it is possible to determine whether an octet
|
||||
// stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
|
||||
// at the pattern of nulls in the first four octets."
|
||||
// 00 00 00 xx UTF-32BE
|
||||
// 00 xx 00 xx UTF-16BE
|
||||
// xx 00 00 00 UTF-32LE
|
||||
// xx 00 xx 00 UTF-16LE
|
||||
// xx xx xx xx UTF-8
|
||||
|
||||
if (!hasBOM_) {
|
||||
int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0);
|
||||
switch (pattern) {
|
||||
case 0x08: type_ = kUTF32BE; break;
|
||||
case 0x0A: type_ = kUTF16BE; break;
|
||||
case 0x01: type_ = kUTF32LE; break;
|
||||
case 0x05: type_ = kUTF16LE; break;
|
||||
case 0x0F: type_ = kUTF8; break;
|
||||
default: break; // Use type defined by user.
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime check whether the size of character type is sufficient. It only perform checks with assertion.
|
||||
if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2);
|
||||
if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4);
|
||||
}
|
||||
|
||||
typedef Ch (*TakeFunc)(InputByteStream& is);
|
||||
InputByteStream* is_;
|
||||
UTFType type_;
|
||||
Ch current_;
|
||||
TakeFunc takeFunc_;
|
||||
bool hasBOM_;
|
||||
};
|
||||
|
||||
//! Output stream wrapper with dynamically bound encoding and automatic encoding detection.
|
||||
/*!
|
||||
\tparam CharType Type of character for writing.
|
||||
\tparam OutputByteStream type of output byte stream to be wrapped.
|
||||
*/
|
||||
template <typename CharType, typename OutputByteStream>
|
||||
class AutoUTFOutputStream {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
public:
|
||||
typedef CharType Ch;
|
||||
|
||||
//! Constructor.
|
||||
/*!
|
||||
\param os output stream to be wrapped.
|
||||
\param type UTF encoding type.
|
||||
\param putBOM Whether to write BOM at the beginning of the stream.
|
||||
*/
|
||||
AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) {
|
||||
RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE);
|
||||
|
||||
// Runtime check whether the size of character type is sufficient. It only perform checks with assertion.
|
||||
if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2);
|
||||
if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4);
|
||||
|
||||
static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) };
|
||||
putFunc_ = f[type_];
|
||||
|
||||
if (putBOM)
|
||||
PutBOM();
|
||||
}
|
||||
|
||||
UTFType GetType() const { return type_; }
|
||||
|
||||
void Put(Ch c) { putFunc_(*os_, c); }
|
||||
void Flush() { os_->Flush(); }
|
||||
|
||||
// Not implemented
|
||||
Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;}
|
||||
Ch Take() { RAPIDJSON_ASSERT(false); return 0;}
|
||||
size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; }
|
||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
private:
|
||||
AutoUTFOutputStream(const AutoUTFOutputStream&);
|
||||
AutoUTFOutputStream& operator=(const AutoUTFOutputStream&);
|
||||
|
||||
void PutBOM() {
|
||||
typedef void (*PutBOMFunc)(OutputByteStream&);
|
||||
static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) };
|
||||
f[type_](*os_);
|
||||
}
|
||||
|
||||
typedef void (*PutFunc)(OutputByteStream&, Ch);
|
||||
|
||||
OutputByteStream* os_;
|
||||
UTFType type_;
|
||||
PutFunc putFunc_;
|
||||
};
|
||||
|
||||
#undef RAPIDJSON_ENCODINGS_FUNC
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_FILESTREAM_H_
|
||||
+716
@@ -0,0 +1,716 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_ENCODINGS_H_
|
||||
#define RAPIDJSON_ENCODINGS_H_
|
||||
|
||||
#include "rapidjson.h"
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data
|
||||
RAPIDJSON_DIAG_OFF(4702) // unreachable code
|
||||
#elif defined(__GNUC__)
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++)
|
||||
RAPIDJSON_DIAG_OFF(overflow)
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Encoding
|
||||
|
||||
/*! \class rapidjson::Encoding
|
||||
\brief Concept for encoding of Unicode characters.
|
||||
|
||||
\code
|
||||
concept Encoding {
|
||||
typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition.
|
||||
|
||||
enum { supportUnicode = 1 }; // or 0 if not supporting unicode
|
||||
|
||||
//! \brief Encode a Unicode codepoint to an output stream.
|
||||
//! \param os Output stream.
|
||||
//! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively.
|
||||
template<typename OutputStream>
|
||||
static void Encode(OutputStream& os, unsigned codepoint);
|
||||
|
||||
//! \brief Decode a Unicode codepoint from an input stream.
|
||||
//! \param is Input stream.
|
||||
//! \param codepoint Output of the unicode codepoint.
|
||||
//! \return true if a valid codepoint can be decoded from the stream.
|
||||
template <typename InputStream>
|
||||
static bool Decode(InputStream& is, unsigned* codepoint);
|
||||
|
||||
//! \brief Validate one Unicode codepoint from an encoded stream.
|
||||
//! \param is Input stream to obtain codepoint.
|
||||
//! \param os Output for copying one codepoint.
|
||||
//! \return true if it is valid.
|
||||
//! \note This function just validating and copying the codepoint without actually decode it.
|
||||
template <typename InputStream, typename OutputStream>
|
||||
static bool Validate(InputStream& is, OutputStream& os);
|
||||
|
||||
// The following functions are deal with byte streams.
|
||||
|
||||
//! Take a character from input byte stream, skip BOM if exist.
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is);
|
||||
|
||||
//! Take a character from input byte stream.
|
||||
template <typename InputByteStream>
|
||||
static Ch Take(InputByteStream& is);
|
||||
|
||||
//! Put BOM to output byte stream.
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os);
|
||||
|
||||
//! Put a character to output byte stream.
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, Ch c);
|
||||
};
|
||||
\endcode
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UTF8
|
||||
|
||||
//! UTF-8 encoding.
|
||||
/*! http://en.wikipedia.org/wiki/UTF-8
|
||||
http://tools.ietf.org/html/rfc3629
|
||||
\tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char.
|
||||
\note implements Encoding concept
|
||||
*/
|
||||
template<typename CharType = char>
|
||||
struct UTF8 {
|
||||
typedef CharType Ch;
|
||||
|
||||
enum { supportUnicode = 1 };
|
||||
|
||||
template<typename OutputStream>
|
||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
||||
if (codepoint <= 0x7F)
|
||||
os.Put(static_cast<Ch>(codepoint & 0xFF));
|
||||
else if (codepoint <= 0x7FF) {
|
||||
os.Put(static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF)));
|
||||
os.Put(static_cast<Ch>(0x80 | ((codepoint & 0x3F))));
|
||||
}
|
||||
else if (codepoint <= 0xFFFF) {
|
||||
os.Put(static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF)));
|
||||
os.Put(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
os.Put(static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
||||
}
|
||||
else {
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
||||
os.Put(static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF)));
|
||||
os.Put(static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||
os.Put(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
os.Put(static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename OutputStream>
|
||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
||||
if (codepoint <= 0x7F)
|
||||
PutUnsafe(os, static_cast<Ch>(codepoint & 0xFF));
|
||||
else if (codepoint <= 0x7FF) {
|
||||
PutUnsafe(os, static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF)));
|
||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint & 0x3F))));
|
||||
}
|
||||
else if (codepoint <= 0xFFFF) {
|
||||
PutUnsafe(os, static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF)));
|
||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
PutUnsafe(os, static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
||||
}
|
||||
else {
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
||||
PutUnsafe(os, static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF)));
|
||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||
PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
PutUnsafe(os, static_cast<Ch>(0x80 | (codepoint & 0x3F)));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InputStream>
|
||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
||||
#define RAPIDJSON_COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast<unsigned char>(c) & 0x3Fu)
|
||||
#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0)
|
||||
#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70)
|
||||
typename InputStream::Ch c = is.Take();
|
||||
if (!(c & 0x80)) {
|
||||
*codepoint = static_cast<unsigned char>(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned char type = GetRange(static_cast<unsigned char>(c));
|
||||
if (type >= 32) {
|
||||
*codepoint = 0;
|
||||
} else {
|
||||
*codepoint = (0xFFu >> type) & static_cast<unsigned char>(c);
|
||||
}
|
||||
bool result = true;
|
||||
switch (type) {
|
||||
case 2: RAPIDJSON_TAIL(); return result;
|
||||
case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result;
|
||||
case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result;
|
||||
case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
default: return false;
|
||||
}
|
||||
#undef RAPIDJSON_COPY
|
||||
#undef RAPIDJSON_TRANS
|
||||
#undef RAPIDJSON_TAIL
|
||||
}
|
||||
|
||||
template <typename InputStream, typename OutputStream>
|
||||
static bool Validate(InputStream& is, OutputStream& os) {
|
||||
#define RAPIDJSON_COPY() if (c != '\0') os.Put(c = is.Take())
|
||||
#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0)
|
||||
#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70)
|
||||
Ch c = static_cast<Ch>(-1);
|
||||
RAPIDJSON_COPY();
|
||||
if (!(c & 0x80))
|
||||
return true;
|
||||
|
||||
bool result = true;
|
||||
switch (GetRange(static_cast<unsigned char>(c))) {
|
||||
case 2: RAPIDJSON_TAIL(); return result;
|
||||
case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result;
|
||||
case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result;
|
||||
case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result;
|
||||
default: return false;
|
||||
}
|
||||
#undef RAPIDJSON_COPY
|
||||
#undef RAPIDJSON_TRANS
|
||||
#undef RAPIDJSON_TAIL
|
||||
}
|
||||
|
||||
static unsigned char GetRange(unsigned char c) {
|
||||
// Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
// With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types.
|
||||
static const unsigned char type[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
|
||||
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
|
||||
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
|
||||
};
|
||||
return type[c];
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
typename InputByteStream::Ch c = Take(is);
|
||||
if (static_cast<unsigned char>(c) != 0xEFu) return c;
|
||||
c = is.Take();
|
||||
if (static_cast<unsigned char>(c) != 0xBBu) return c;
|
||||
c = is.Take();
|
||||
if (static_cast<unsigned char>(c) != 0xBFu) return c;
|
||||
c = is.Take();
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static Ch Take(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
return static_cast<Ch>(is.Take());
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xEFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xBBu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xBFu));
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, Ch c) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(c));
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UTF16
|
||||
|
||||
//! UTF-16 encoding.
|
||||
/*! http://en.wikipedia.org/wiki/UTF-16
|
||||
http://tools.ietf.org/html/rfc2781
|
||||
\tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead.
|
||||
\note implements Encoding concept
|
||||
|
||||
\note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness.
|
||||
For streaming, use UTF16LE and UTF16BE, which handle endianness.
|
||||
*/
|
||||
template<typename CharType = wchar_t>
|
||||
struct UTF16 {
|
||||
typedef CharType Ch;
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2);
|
||||
|
||||
enum { supportUnicode = 1 };
|
||||
|
||||
template<typename OutputStream>
|
||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2);
|
||||
if (codepoint <= 0xFFFF) {
|
||||
RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair
|
||||
os.Put(static_cast<typename OutputStream::Ch>(codepoint));
|
||||
}
|
||||
else {
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
||||
unsigned v = codepoint - 0x10000;
|
||||
os.Put(static_cast<typename OutputStream::Ch>((v >> 10) | 0xD800));
|
||||
os.Put(static_cast<typename OutputStream::Ch>((v & 0x3FF) | 0xDC00));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename OutputStream>
|
||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2);
|
||||
if (codepoint <= 0xFFFF) {
|
||||
RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair
|
||||
PutUnsafe(os, static_cast<typename OutputStream::Ch>(codepoint));
|
||||
}
|
||||
else {
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
||||
unsigned v = codepoint - 0x10000;
|
||||
PutUnsafe(os, static_cast<typename OutputStream::Ch>((v >> 10) | 0xD800));
|
||||
PutUnsafe(os, static_cast<typename OutputStream::Ch>((v & 0x3FF) | 0xDC00));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InputStream>
|
||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2);
|
||||
typename InputStream::Ch c = is.Take();
|
||||
if (c < 0xD800 || c > 0xDFFF) {
|
||||
*codepoint = static_cast<unsigned>(c);
|
||||
return true;
|
||||
}
|
||||
else if (c <= 0xDBFF) {
|
||||
*codepoint = (static_cast<unsigned>(c) & 0x3FF) << 10;
|
||||
c = is.Take();
|
||||
*codepoint |= (static_cast<unsigned>(c) & 0x3FF);
|
||||
*codepoint += 0x10000;
|
||||
return c >= 0xDC00 && c <= 0xDFFF;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename InputStream, typename OutputStream>
|
||||
static bool Validate(InputStream& is, OutputStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2);
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2);
|
||||
typename InputStream::Ch c;
|
||||
os.Put(static_cast<typename OutputStream::Ch>(c = is.Take()));
|
||||
if (c < 0xD800 || c > 0xDFFF)
|
||||
return true;
|
||||
else if (c <= 0xDBFF) {
|
||||
os.Put(c = is.Take());
|
||||
return c >= 0xDC00 && c <= 0xDFFF;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//! UTF-16 little endian encoding.
|
||||
template<typename CharType = wchar_t>
|
||||
struct UTF16LE : UTF16<CharType> {
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
CharType c = Take(is);
|
||||
return static_cast<uint16_t>(c) == 0xFEFFu ? Take(is) : c;
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static CharType Take(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
unsigned c = static_cast<uint8_t>(is.Take());
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
||||
return static_cast<CharType>(c);
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, CharType c) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(static_cast<unsigned>(c) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((static_cast<unsigned>(c) >> 8) & 0xFFu));
|
||||
}
|
||||
};
|
||||
|
||||
//! UTF-16 big endian encoding.
|
||||
template<typename CharType = wchar_t>
|
||||
struct UTF16BE : UTF16<CharType> {
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
CharType c = Take(is);
|
||||
return static_cast<uint16_t>(c) == 0xFEFFu ? Take(is) : c;
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static CharType Take(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
unsigned c = static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take()));
|
||||
return static_cast<CharType>(c);
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, CharType c) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((static_cast<unsigned>(c) >> 8) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(static_cast<unsigned>(c) & 0xFFu));
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UTF32
|
||||
|
||||
//! UTF-32 encoding.
|
||||
/*! http://en.wikipedia.org/wiki/UTF-32
|
||||
\tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead.
|
||||
\note implements Encoding concept
|
||||
|
||||
\note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness.
|
||||
For streaming, use UTF32LE and UTF32BE, which handle endianness.
|
||||
*/
|
||||
template<typename CharType = unsigned>
|
||||
struct UTF32 {
|
||||
typedef CharType Ch;
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4);
|
||||
|
||||
enum { supportUnicode = 1 };
|
||||
|
||||
template<typename OutputStream>
|
||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4);
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
||||
os.Put(codepoint);
|
||||
}
|
||||
|
||||
template<typename OutputStream>
|
||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4);
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x10FFFF);
|
||||
PutUnsafe(os, codepoint);
|
||||
}
|
||||
|
||||
template <typename InputStream>
|
||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4);
|
||||
Ch c = is.Take();
|
||||
*codepoint = c;
|
||||
return c <= 0x10FFFF;
|
||||
}
|
||||
|
||||
template <typename InputStream, typename OutputStream>
|
||||
static bool Validate(InputStream& is, OutputStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4);
|
||||
Ch c;
|
||||
os.Put(c = is.Take());
|
||||
return c <= 0x10FFFF;
|
||||
}
|
||||
};
|
||||
|
||||
//! UTF-32 little endian enocoding.
|
||||
template<typename CharType = unsigned>
|
||||
struct UTF32LE : UTF32<CharType> {
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
CharType c = Take(is);
|
||||
return static_cast<uint32_t>(c) == 0x0000FEFFu ? Take(is) : c;
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static CharType Take(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
unsigned c = static_cast<uint8_t>(is.Take());
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 16;
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 24;
|
||||
return static_cast<CharType>(c);
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, CharType c) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(c & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 8) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 16) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 24) & 0xFFu));
|
||||
}
|
||||
};
|
||||
|
||||
//! UTF-32 big endian encoding.
|
||||
template<typename CharType = unsigned>
|
||||
struct UTF32BE : UTF32<CharType> {
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
CharType c = Take(is);
|
||||
return static_cast<uint32_t>(c) == 0x0000FEFFu ? Take(is) : c;
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static CharType Take(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
unsigned c = static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 24;
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 16;
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8;
|
||||
c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take()));
|
||||
return static_cast<CharType>(c);
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0x00u));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu));
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, CharType c) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 24) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 16) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>((c >> 8) & 0xFFu));
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(c & 0xFFu));
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ASCII
|
||||
|
||||
//! ASCII encoding.
|
||||
/*! http://en.wikipedia.org/wiki/ASCII
|
||||
\tparam CharType Code unit for storing 7-bit ASCII data. Default is char.
|
||||
\note implements Encoding concept
|
||||
*/
|
||||
template<typename CharType = char>
|
||||
struct ASCII {
|
||||
typedef CharType Ch;
|
||||
|
||||
enum { supportUnicode = 0 };
|
||||
|
||||
template<typename OutputStream>
|
||||
static void Encode(OutputStream& os, unsigned codepoint) {
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x7F);
|
||||
os.Put(static_cast<Ch>(codepoint & 0xFF));
|
||||
}
|
||||
|
||||
template<typename OutputStream>
|
||||
static void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
||||
RAPIDJSON_ASSERT(codepoint <= 0x7F);
|
||||
PutUnsafe(os, static_cast<Ch>(codepoint & 0xFF));
|
||||
}
|
||||
|
||||
template <typename InputStream>
|
||||
static bool Decode(InputStream& is, unsigned* codepoint) {
|
||||
uint8_t c = static_cast<uint8_t>(is.Take());
|
||||
*codepoint = c;
|
||||
return c <= 0X7F;
|
||||
}
|
||||
|
||||
template <typename InputStream, typename OutputStream>
|
||||
static bool Validate(InputStream& is, OutputStream& os) {
|
||||
uint8_t c = static_cast<uint8_t>(is.Take());
|
||||
os.Put(static_cast<typename OutputStream::Ch>(c));
|
||||
return c <= 0x7F;
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static CharType TakeBOM(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
uint8_t c = static_cast<uint8_t>(Take(is));
|
||||
return static_cast<Ch>(c);
|
||||
}
|
||||
|
||||
template <typename InputByteStream>
|
||||
static Ch Take(InputByteStream& is) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1);
|
||||
return static_cast<Ch>(is.Take());
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void PutBOM(OutputByteStream& os) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
(void)os;
|
||||
}
|
||||
|
||||
template <typename OutputByteStream>
|
||||
static void Put(OutputByteStream& os, Ch c) {
|
||||
RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1);
|
||||
os.Put(static_cast<typename OutputByteStream::Ch>(c));
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// AutoUTF
|
||||
|
||||
//! Runtime-specified UTF encoding type of a stream.
|
||||
enum UTFType {
|
||||
kUTF8 = 0, //!< UTF-8.
|
||||
kUTF16LE = 1, //!< UTF-16 little endian.
|
||||
kUTF16BE = 2, //!< UTF-16 big endian.
|
||||
kUTF32LE = 3, //!< UTF-32 little endian.
|
||||
kUTF32BE = 4 //!< UTF-32 big endian.
|
||||
};
|
||||
|
||||
//! Dynamically select encoding according to stream's runtime-specified UTF encoding type.
|
||||
/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType().
|
||||
*/
|
||||
template<typename CharType>
|
||||
struct AutoUTF {
|
||||
typedef CharType Ch;
|
||||
|
||||
enum { supportUnicode = 1 };
|
||||
|
||||
#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8<Ch>::x, UTF16LE<Ch>::x, UTF16BE<Ch>::x, UTF32LE<Ch>::x, UTF32BE<Ch>::x
|
||||
|
||||
template<typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) {
|
||||
typedef void (*EncodeFunc)(OutputStream&, unsigned);
|
||||
static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) };
|
||||
(*f[os.GetType()])(os, codepoint);
|
||||
}
|
||||
|
||||
template<typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) {
|
||||
typedef void (*EncodeFunc)(OutputStream&, unsigned);
|
||||
static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) };
|
||||
(*f[os.GetType()])(os, codepoint);
|
||||
}
|
||||
|
||||
template <typename InputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) {
|
||||
typedef bool (*DecodeFunc)(InputStream&, unsigned*);
|
||||
static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) };
|
||||
return (*f[is.GetType()])(is, codepoint);
|
||||
}
|
||||
|
||||
template <typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) {
|
||||
typedef bool (*ValidateFunc)(InputStream&, OutputStream&);
|
||||
static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) };
|
||||
return (*f[is.GetType()])(is, os);
|
||||
}
|
||||
|
||||
#undef RAPIDJSON_ENCODINGS_FUNC
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Transcoder
|
||||
|
||||
//! Encoding conversion.
|
||||
template<typename SourceEncoding, typename TargetEncoding>
|
||||
struct Transcoder {
|
||||
//! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream.
|
||||
template<typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) {
|
||||
unsigned codepoint;
|
||||
if (!SourceEncoding::Decode(is, &codepoint))
|
||||
return false;
|
||||
TargetEncoding::Encode(os, codepoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) {
|
||||
unsigned codepoint;
|
||||
if (!SourceEncoding::Decode(is, &codepoint))
|
||||
return false;
|
||||
TargetEncoding::EncodeUnsafe(os, codepoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Validate one Unicode codepoint from an encoded stream.
|
||||
template<typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) {
|
||||
return Transcode(is, os); // Since source/target encoding is different, must transcode.
|
||||
}
|
||||
};
|
||||
|
||||
// Forward declaration.
|
||||
template<typename Stream>
|
||||
inline void PutUnsafe(Stream& stream, typename Stream::Ch c);
|
||||
|
||||
//! Specialization of Transcoder with same source and target encoding.
|
||||
template<typename Encoding>
|
||||
struct Transcoder<Encoding, Encoding> {
|
||||
template<typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) {
|
||||
os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class.
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) {
|
||||
PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class.
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename InputStream, typename OutputStream>
|
||||
static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) {
|
||||
return Encoding::Validate(is, os); // source/target encoding are the same
|
||||
}
|
||||
};
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__))
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_ENCODINGS_H_
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_ERROR_EN_H_
|
||||
#define RAPIDJSON_ERROR_EN_H_
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(switch-enum)
|
||||
RAPIDJSON_DIAG_OFF(covered-switch-default)
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
//! Maps error code of parsing into error message.
|
||||
/*!
|
||||
\ingroup RAPIDJSON_ERRORS
|
||||
\param parseErrorCode Error code obtained in parsing.
|
||||
\return the error message.
|
||||
\note User can make a copy of this function for localization.
|
||||
Using switch-case is safer for future modification of error codes.
|
||||
*/
|
||||
inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) {
|
||||
switch (parseErrorCode) {
|
||||
case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
|
||||
|
||||
case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty.");
|
||||
case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values.");
|
||||
|
||||
case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value.");
|
||||
|
||||
case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member.");
|
||||
case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member.");
|
||||
case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member.");
|
||||
|
||||
case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element.");
|
||||
|
||||
case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string.");
|
||||
case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid.");
|
||||
case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string.");
|
||||
case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string.");
|
||||
case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string.");
|
||||
|
||||
case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double.");
|
||||
case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number.");
|
||||
case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number.");
|
||||
|
||||
case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error.");
|
||||
case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error.");
|
||||
|
||||
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
|
||||
}
|
||||
}
|
||||
|
||||
//! Maps error code of validation into error message.
|
||||
/*!
|
||||
\ingroup RAPIDJSON_ERRORS
|
||||
\param validateErrorCode Error code obtained from validator.
|
||||
\return the error message.
|
||||
\note User can make a copy of this function for localization.
|
||||
Using switch-case is safer for future modification of error codes.
|
||||
*/
|
||||
inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode validateErrorCode) {
|
||||
switch (validateErrorCode) {
|
||||
case kValidateErrors: return RAPIDJSON_ERROR_STRING("One or more validation errors have occurred");
|
||||
case kValidateErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
|
||||
|
||||
case kValidateErrorMultipleOf: return RAPIDJSON_ERROR_STRING("Number '%actual' is not a multiple of the 'multipleOf' value '%expected'.");
|
||||
case kValidateErrorMaximum: return RAPIDJSON_ERROR_STRING("Number '%actual' is greater than the 'maximum' value '%expected'.");
|
||||
case kValidateErrorExclusiveMaximum: return RAPIDJSON_ERROR_STRING("Number '%actual' is greater than or equal to the 'exclusiveMaximum' value '%expected'.");
|
||||
case kValidateErrorMinimum: return RAPIDJSON_ERROR_STRING("Number '%actual' is less than the 'minimum' value '%expected'.");
|
||||
case kValidateErrorExclusiveMinimum: return RAPIDJSON_ERROR_STRING("Number '%actual' is less than or equal to the 'exclusiveMinimum' value '%expected'.");
|
||||
|
||||
case kValidateErrorMaxLength: return RAPIDJSON_ERROR_STRING("String '%actual' is longer than the 'maxLength' value '%expected'.");
|
||||
case kValidateErrorMinLength: return RAPIDJSON_ERROR_STRING("String '%actual' is shorter than the 'minLength' value '%expected'.");
|
||||
case kValidateErrorPattern: return RAPIDJSON_ERROR_STRING("String '%actual' does not match the 'pattern' regular expression.");
|
||||
|
||||
case kValidateErrorMaxItems: return RAPIDJSON_ERROR_STRING("Array of length '%actual' is longer than the 'maxItems' value '%expected'.");
|
||||
case kValidateErrorMinItems: return RAPIDJSON_ERROR_STRING("Array of length '%actual' is shorter than the 'minItems' value '%expected'.");
|
||||
case kValidateErrorUniqueItems: return RAPIDJSON_ERROR_STRING("Array has duplicate items at indices '%duplicates' but 'uniqueItems' is true.");
|
||||
case kValidateErrorAdditionalItems: return RAPIDJSON_ERROR_STRING("Array has an additional item at index '%disallowed' that is not allowed by the schema.");
|
||||
|
||||
case kValidateErrorMaxProperties: return RAPIDJSON_ERROR_STRING("Object has '%actual' members which is more than 'maxProperties' value '%expected'.");
|
||||
case kValidateErrorMinProperties: return RAPIDJSON_ERROR_STRING("Object has '%actual' members which is less than 'minProperties' value '%expected'.");
|
||||
case kValidateErrorRequired: return RAPIDJSON_ERROR_STRING("Object is missing the following members required by the schema: '%missing'.");
|
||||
case kValidateErrorAdditionalProperties: return RAPIDJSON_ERROR_STRING("Object has an additional member '%disallowed' that is not allowed by the schema.");
|
||||
case kValidateErrorPatternProperties: return RAPIDJSON_ERROR_STRING("Object has 'patternProperties' that are not allowed by the schema.");
|
||||
case kValidateErrorDependencies: return RAPIDJSON_ERROR_STRING("Object has missing property or schema dependencies, refer to following errors.");
|
||||
|
||||
case kValidateErrorEnum: return RAPIDJSON_ERROR_STRING("Property has a value that is not one of its allowed enumerated values.");
|
||||
case kValidateErrorType: return RAPIDJSON_ERROR_STRING("Property has a type '%actual' that is not in the following list: '%expected'.");
|
||||
|
||||
case kValidateErrorOneOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'oneOf', refer to following errors.");
|
||||
case kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf', indices '%matches'.");
|
||||
case kValidateErrorAllOf: return RAPIDJSON_ERROR_STRING("Property did not match all of the sub-schemas specified by 'allOf', refer to following errors.");
|
||||
case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors.");
|
||||
case kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'.");
|
||||
|
||||
case kValidateErrorReadOnly: return RAPIDJSON_ERROR_STRING("Property is read-only but has been provided when validation is for writing.");
|
||||
case kValidateErrorWriteOnly: return RAPIDJSON_ERROR_STRING("Property is write-only but has been provided when validation is for reading.");
|
||||
|
||||
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
|
||||
}
|
||||
}
|
||||
|
||||
//! Maps error code of schema document compilation into error message.
|
||||
/*!
|
||||
\ingroup RAPIDJSON_ERRORS
|
||||
\param schemaErrorCode Error code obtained from compiling the schema document.
|
||||
\return the error message.
|
||||
\note User can make a copy of this function for localization.
|
||||
Using switch-case is safer for future modification of error codes.
|
||||
*/
|
||||
inline const RAPIDJSON_ERROR_CHARTYPE* GetSchemaError_En(SchemaErrorCode schemaErrorCode) {
|
||||
switch (schemaErrorCode) {
|
||||
case kSchemaErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
|
||||
|
||||
case kSchemaErrorStartUnknown: return RAPIDJSON_ERROR_STRING("Pointer '%value' to start of schema does not resolve to a location in the document.");
|
||||
case kSchemaErrorRefPlainName: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' must be a JSON pointer.");
|
||||
case kSchemaErrorRefInvalid: return RAPIDJSON_ERROR_STRING("$ref must not be an empty string.");
|
||||
case kSchemaErrorRefPointerInvalid: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' is not a valid JSON pointer at offset '%offset'.");
|
||||
case kSchemaErrorRefUnknown: return RAPIDJSON_ERROR_STRING("$ref '%value' does not resolve to a location in the target document.");
|
||||
case kSchemaErrorRefCyclical: return RAPIDJSON_ERROR_STRING("$ref '%value' is cyclical.");
|
||||
case kSchemaErrorRefNoRemoteProvider: return RAPIDJSON_ERROR_STRING("$ref is remote but there is no remote provider.");
|
||||
case kSchemaErrorRefNoRemoteSchema: return RAPIDJSON_ERROR_STRING("$ref '%value' is remote but the remote provider did not return a schema.");
|
||||
case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'.");
|
||||
case kSchemaErrorSpecUnknown: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not recognized.");
|
||||
case kSchemaErrorSpecUnsupported: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not supported.");
|
||||
case kSchemaErrorSpecIllegal: return RAPIDJSON_ERROR_STRING("Both JSON schema draft and OpenAPI version found in document.");
|
||||
case kSchemaErrorReadOnlyAndWriteOnly: return RAPIDJSON_ERROR_STRING("Property must not be both 'readOnly' and 'writeOnly'.");
|
||||
|
||||
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
|
||||
}
|
||||
}
|
||||
|
||||
//! Maps error code of pointer parse into error message.
|
||||
/*!
|
||||
\ingroup RAPIDJSON_ERRORS
|
||||
\param pointerParseErrorCode Error code obtained from pointer parse.
|
||||
\return the error message.
|
||||
\note User can make a copy of this function for localization.
|
||||
Using switch-case is safer for future modification of error codes.
|
||||
*/
|
||||
inline const RAPIDJSON_ERROR_CHARTYPE* GetPointerParseError_En(PointerParseErrorCode pointerParseErrorCode) {
|
||||
switch (pointerParseErrorCode) {
|
||||
case kPointerParseErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
|
||||
|
||||
case kPointerParseErrorTokenMustBeginWithSolidus: return RAPIDJSON_ERROR_STRING("A token must begin with a '/'.");
|
||||
case kPointerParseErrorInvalidEscape: return RAPIDJSON_ERROR_STRING("Invalid escape.");
|
||||
case kPointerParseErrorInvalidPercentEncoding: return RAPIDJSON_ERROR_STRING("Invalid percent encoding in URI fragment.");
|
||||
case kPointerParseErrorCharacterMustPercentEncode: return RAPIDJSON_ERROR_STRING("A character must be percent encoded in a URI fragment.");
|
||||
|
||||
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
|
||||
}
|
||||
}
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_ERROR_EN_H_
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_ERROR_ERROR_H_
|
||||
#define RAPIDJSON_ERROR_ERROR_H_
|
||||
|
||||
#include "../rapidjson.h"
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(padded)
|
||||
#endif
|
||||
|
||||
/*! \file error.h */
|
||||
|
||||
/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RAPIDJSON_ERROR_CHARTYPE
|
||||
|
||||
//! Character type of error messages.
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
The default character type is \c char.
|
||||
On Windows, user can define this macro as \c TCHAR for supporting both
|
||||
unicode/non-unicode settings.
|
||||
*/
|
||||
#ifndef RAPIDJSON_ERROR_CHARTYPE
|
||||
#define RAPIDJSON_ERROR_CHARTYPE char
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// RAPIDJSON_ERROR_STRING
|
||||
|
||||
//! Macro for converting string literal to \ref RAPIDJSON_ERROR_CHARTYPE[].
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
By default this conversion macro does nothing.
|
||||
On Windows, user can define this macro as \c _T(x) for supporting both
|
||||
unicode/non-unicode settings.
|
||||
*/
|
||||
#ifndef RAPIDJSON_ERROR_STRING
|
||||
#define RAPIDJSON_ERROR_STRING(x) x
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ParseErrorCode
|
||||
|
||||
//! Error code of parsing.
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
\see GenericReader::Parse, GenericReader::GetParseErrorCode
|
||||
*/
|
||||
enum ParseErrorCode {
|
||||
kParseErrorNone = 0, //!< No error.
|
||||
|
||||
kParseErrorDocumentEmpty, //!< The document is empty.
|
||||
kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values.
|
||||
|
||||
kParseErrorValueInvalid, //!< Invalid value.
|
||||
|
||||
kParseErrorObjectMissName, //!< Missing a name for object member.
|
||||
kParseErrorObjectMissColon, //!< Missing a colon after a name of object member.
|
||||
kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member.
|
||||
|
||||
kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element.
|
||||
|
||||
kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string.
|
||||
kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid.
|
||||
kParseErrorStringEscapeInvalid, //!< Invalid escape character in string.
|
||||
kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string.
|
||||
kParseErrorStringInvalidEncoding, //!< Invalid encoding in string.
|
||||
|
||||
kParseErrorNumberTooBig, //!< Number too big to be stored in double.
|
||||
kParseErrorNumberMissFraction, //!< Miss fraction part in number.
|
||||
kParseErrorNumberMissExponent, //!< Miss exponent in number.
|
||||
|
||||
kParseErrorTermination, //!< Parsing was terminated.
|
||||
kParseErrorUnspecificSyntaxError //!< Unspecific syntax error.
|
||||
};
|
||||
|
||||
//! Result of parsing (wraps ParseErrorCode)
|
||||
/*!
|
||||
\ingroup RAPIDJSON_ERRORS
|
||||
\code
|
||||
Document doc;
|
||||
ParseResult ok = doc.Parse("[42]");
|
||||
if (!ok) {
|
||||
fprintf(stderr, "JSON parse error: %s (%u)",
|
||||
GetParseError_En(ok.Code()), ok.Offset());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
\endcode
|
||||
\see GenericReader::Parse, GenericDocument::Parse
|
||||
*/
|
||||
struct ParseResult {
|
||||
//!! Unspecified boolean type
|
||||
typedef bool (ParseResult::*BooleanType)() const;
|
||||
public:
|
||||
//! Default constructor, no error.
|
||||
ParseResult() : code_(kParseErrorNone), offset_(0) {}
|
||||
//! Constructor to set an error.
|
||||
ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {}
|
||||
|
||||
//! Get the error code.
|
||||
ParseErrorCode Code() const { return code_; }
|
||||
//! Get the error offset, if \ref IsError(), 0 otherwise.
|
||||
size_t Offset() const { return offset_; }
|
||||
|
||||
//! Explicit conversion to \c bool, returns \c true, iff !\ref IsError().
|
||||
operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; }
|
||||
//! Whether the result is an error.
|
||||
bool IsError() const { return code_ != kParseErrorNone; }
|
||||
|
||||
bool operator==(const ParseResult& that) const { return code_ == that.code_; }
|
||||
bool operator==(ParseErrorCode code) const { return code_ == code; }
|
||||
friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; }
|
||||
|
||||
bool operator!=(const ParseResult& that) const { return !(*this == that); }
|
||||
bool operator!=(ParseErrorCode code) const { return !(*this == code); }
|
||||
friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; }
|
||||
|
||||
//! Reset error code.
|
||||
void Clear() { Set(kParseErrorNone); }
|
||||
//! Update error code and offset.
|
||||
void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; }
|
||||
|
||||
private:
|
||||
ParseErrorCode code_;
|
||||
size_t offset_;
|
||||
};
|
||||
|
||||
//! Function pointer type of GetParseError().
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
|
||||
This is the prototype for \c GetParseError_X(), where \c X is a locale.
|
||||
User can dynamically change locale in runtime, e.g.:
|
||||
\code
|
||||
GetParseErrorFunc GetParseError = GetParseError_En; // or whatever
|
||||
const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode());
|
||||
\endcode
|
||||
*/
|
||||
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ValidateErrorCode
|
||||
|
||||
//! Error codes when validating.
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
\see GenericSchemaValidator
|
||||
*/
|
||||
enum ValidateErrorCode {
|
||||
kValidateErrors = -1, //!< Top level error code when kValidateContinueOnErrorsFlag set.
|
||||
kValidateErrorNone = 0, //!< No error.
|
||||
|
||||
kValidateErrorMultipleOf, //!< Number is not a multiple of the 'multipleOf' value.
|
||||
kValidateErrorMaximum, //!< Number is greater than the 'maximum' value.
|
||||
kValidateErrorExclusiveMaximum, //!< Number is greater than or equal to the 'maximum' value.
|
||||
kValidateErrorMinimum, //!< Number is less than the 'minimum' value.
|
||||
kValidateErrorExclusiveMinimum, //!< Number is less than or equal to the 'minimum' value.
|
||||
|
||||
kValidateErrorMaxLength, //!< String is longer than the 'maxLength' value.
|
||||
kValidateErrorMinLength, //!< String is longer than the 'maxLength' value.
|
||||
kValidateErrorPattern, //!< String does not match the 'pattern' regular expression.
|
||||
|
||||
kValidateErrorMaxItems, //!< Array is longer than the 'maxItems' value.
|
||||
kValidateErrorMinItems, //!< Array is shorter than the 'minItems' value.
|
||||
kValidateErrorUniqueItems, //!< Array has duplicate items but 'uniqueItems' is true.
|
||||
kValidateErrorAdditionalItems, //!< Array has additional items that are not allowed by the schema.
|
||||
|
||||
kValidateErrorMaxProperties, //!< Object has more members than 'maxProperties' value.
|
||||
kValidateErrorMinProperties, //!< Object has less members than 'minProperties' value.
|
||||
kValidateErrorRequired, //!< Object is missing one or more members required by the schema.
|
||||
kValidateErrorAdditionalProperties, //!< Object has additional members that are not allowed by the schema.
|
||||
kValidateErrorPatternProperties, //!< See other errors.
|
||||
kValidateErrorDependencies, //!< Object has missing property or schema dependencies.
|
||||
|
||||
kValidateErrorEnum, //!< Property has a value that is not one of its allowed enumerated values.
|
||||
kValidateErrorType, //!< Property has a type that is not allowed by the schema.
|
||||
|
||||
kValidateErrorOneOf, //!< Property did not match any of the sub-schemas specified by 'oneOf'.
|
||||
kValidateErrorOneOfMatch, //!< Property matched more than one of the sub-schemas specified by 'oneOf'.
|
||||
kValidateErrorAllOf, //!< Property did not match all of the sub-schemas specified by 'allOf'.
|
||||
kValidateErrorAnyOf, //!< Property did not match any of the sub-schemas specified by 'anyOf'.
|
||||
kValidateErrorNot, //!< Property matched the sub-schema specified by 'not'.
|
||||
|
||||
kValidateErrorReadOnly, //!< Property is read-only but has been provided when validation is for writing
|
||||
kValidateErrorWriteOnly //!< Property is write-only but has been provided when validation is for reading
|
||||
};
|
||||
|
||||
//! Function pointer type of GetValidateError().
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
|
||||
This is the prototype for \c GetValidateError_X(), where \c X is a locale.
|
||||
User can dynamically change locale in runtime, e.g.:
|
||||
\code
|
||||
GetValidateErrorFunc GetValidateError = GetValidateError_En; // or whatever
|
||||
const RAPIDJSON_ERROR_CHARTYPE* s = GetValidateError(validator.GetInvalidSchemaCode());
|
||||
\endcode
|
||||
*/
|
||||
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCode);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SchemaErrorCode
|
||||
|
||||
//! Error codes when validating.
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
\see GenericSchemaValidator
|
||||
*/
|
||||
enum SchemaErrorCode {
|
||||
kSchemaErrorNone = 0, //!< No error.
|
||||
|
||||
kSchemaErrorStartUnknown, //!< Pointer to start of schema does not resolve to a location in the document
|
||||
kSchemaErrorRefPlainName, //!< $ref fragment must be a JSON pointer
|
||||
kSchemaErrorRefInvalid, //!< $ref must not be an empty string
|
||||
kSchemaErrorRefPointerInvalid, //!< $ref fragment is not a valid JSON pointer at offset
|
||||
kSchemaErrorRefUnknown, //!< $ref does not resolve to a location in the target document
|
||||
kSchemaErrorRefCyclical, //!< $ref is cyclical
|
||||
kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider
|
||||
kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema
|
||||
kSchemaErrorRegexInvalid, //!< Invalid regular expression in 'pattern' or 'patternProperties'
|
||||
kSchemaErrorSpecUnknown, //!< JSON schema draft or OpenAPI version is not recognized
|
||||
kSchemaErrorSpecUnsupported, //!< JSON schema draft or OpenAPI version is not supported
|
||||
kSchemaErrorSpecIllegal, //!< Both JSON schema draft and OpenAPI version found in document
|
||||
kSchemaErrorReadOnlyAndWriteOnly //!< Property must not be both 'readOnly' and 'writeOnly'
|
||||
};
|
||||
|
||||
//! Function pointer type of GetSchemaError().
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
|
||||
This is the prototype for \c GetSchemaError_X(), where \c X is a locale.
|
||||
User can dynamically change locale in runtime, e.g.:
|
||||
\code
|
||||
GetSchemaErrorFunc GetSchemaError = GetSchemaError_En; // or whatever
|
||||
const RAPIDJSON_ERROR_CHARTYPE* s = GetSchemaError(validator.GetInvalidSchemaCode());
|
||||
\endcode
|
||||
*/
|
||||
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetSchemaErrorFunc)(SchemaErrorCode);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// PointerParseErrorCode
|
||||
|
||||
//! Error code of JSON pointer parsing.
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
\see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode
|
||||
*/
|
||||
enum PointerParseErrorCode {
|
||||
kPointerParseErrorNone = 0, //!< The parse is successful
|
||||
|
||||
kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/'
|
||||
kPointerParseErrorInvalidEscape, //!< Invalid escape
|
||||
kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment
|
||||
kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment
|
||||
};
|
||||
|
||||
//! Function pointer type of GetPointerParseError().
|
||||
/*! \ingroup RAPIDJSON_ERRORS
|
||||
|
||||
This is the prototype for \c GetPointerParseError_X(), where \c X is a locale.
|
||||
User can dynamically change locale in runtime, e.g.:
|
||||
\code
|
||||
GetPointerParseErrorFunc GetPointerParseError = GetPointerParseError_En; // or whatever
|
||||
const RAPIDJSON_ERROR_CHARTYPE* s = GetPointerParseError(pointer.GetParseErrorCode());
|
||||
\endcode
|
||||
*/
|
||||
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetPointerParseErrorFunc)(PointerParseErrorCode);
|
||||
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_ERROR_ERROR_H_
|
||||
@@ -0,0 +1,99 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_FILEREADSTREAM_H_
|
||||
#define RAPIDJSON_FILEREADSTREAM_H_
|
||||
|
||||
#include "stream.h"
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(padded)
|
||||
RAPIDJSON_DIAG_OFF(unreachable-code)
|
||||
RAPIDJSON_DIAG_OFF(missing-noreturn)
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
//! File byte stream for input using fread().
|
||||
/*!
|
||||
\note implements Stream concept
|
||||
*/
|
||||
class FileReadStream {
|
||||
public:
|
||||
typedef char Ch; //!< Character type (byte).
|
||||
|
||||
//! Constructor.
|
||||
/*!
|
||||
\param fp File pointer opened for read.
|
||||
\param buffer user-supplied buffer.
|
||||
\param bufferSize size of buffer in bytes. Must >=4 bytes.
|
||||
*/
|
||||
FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) {
|
||||
RAPIDJSON_ASSERT(fp_ != 0);
|
||||
RAPIDJSON_ASSERT(bufferSize >= 4);
|
||||
Read();
|
||||
}
|
||||
|
||||
Ch Peek() const { return *current_; }
|
||||
Ch Take() { Ch c = *current_; Read(); return c; }
|
||||
size_t Tell() const { return count_ + static_cast<size_t>(current_ - buffer_); }
|
||||
|
||||
// Not implemented
|
||||
void Put(Ch) { RAPIDJSON_ASSERT(false); }
|
||||
void Flush() { RAPIDJSON_ASSERT(false); }
|
||||
Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
// For encoding detection only.
|
||||
const Ch* Peek4() const {
|
||||
return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void Read() {
|
||||
if (current_ < bufferLast_)
|
||||
++current_;
|
||||
else if (!eof_) {
|
||||
count_ += readCount_;
|
||||
readCount_ = std::fread(buffer_, 1, bufferSize_, fp_);
|
||||
bufferLast_ = buffer_ + readCount_ - 1;
|
||||
current_ = buffer_;
|
||||
|
||||
if (readCount_ < bufferSize_) {
|
||||
buffer_[readCount_] = '\0';
|
||||
++bufferLast_;
|
||||
eof_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::FILE* fp_;
|
||||
Ch *buffer_;
|
||||
size_t bufferSize_;
|
||||
Ch *bufferLast_;
|
||||
Ch *current_;
|
||||
size_t readCount_;
|
||||
size_t count_; //!< Number of characters read
|
||||
bool eof_;
|
||||
};
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_FILESTREAM_H_
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_FILEWRITESTREAM_H_
|
||||
#define RAPIDJSON_FILEWRITESTREAM_H_
|
||||
|
||||
#include "stream.h"
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(unreachable-code)
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
//! Wrapper of C file stream for output using fwrite().
|
||||
/*!
|
||||
\note implements Stream concept
|
||||
*/
|
||||
class FileWriteStream {
|
||||
public:
|
||||
typedef char Ch; //!< Character type. Only support char.
|
||||
|
||||
FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) {
|
||||
RAPIDJSON_ASSERT(fp_ != 0);
|
||||
}
|
||||
|
||||
void Put(char c) {
|
||||
if (current_ >= bufferEnd_)
|
||||
Flush();
|
||||
|
||||
*current_++ = c;
|
||||
}
|
||||
|
||||
void PutN(char c, size_t n) {
|
||||
size_t avail = static_cast<size_t>(bufferEnd_ - current_);
|
||||
while (n > avail) {
|
||||
std::memset(current_, c, avail);
|
||||
current_ += avail;
|
||||
Flush();
|
||||
n -= avail;
|
||||
avail = static_cast<size_t>(bufferEnd_ - current_);
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
std::memset(current_, c, n);
|
||||
current_ += n;
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
if (current_ != buffer_) {
|
||||
size_t result = std::fwrite(buffer_, 1, static_cast<size_t>(current_ - buffer_), fp_);
|
||||
if (result < static_cast<size_t>(current_ - buffer_)) {
|
||||
// failure deliberately ignored at this time
|
||||
// added to avoid warn_unused_result build errors
|
||||
}
|
||||
current_ = buffer_;
|
||||
}
|
||||
}
|
||||
|
||||
// Not implemented
|
||||
char Peek() const { RAPIDJSON_ASSERT(false); return 0; }
|
||||
char Take() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; }
|
||||
char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; }
|
||||
size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; }
|
||||
|
||||
private:
|
||||
// Prohibit copy constructor & assignment operator.
|
||||
FileWriteStream(const FileWriteStream&);
|
||||
FileWriteStream& operator=(const FileWriteStream&);
|
||||
|
||||
std::FILE* fp_;
|
||||
char *buffer_;
|
||||
char *bufferEnd_;
|
||||
char *current_;
|
||||
};
|
||||
|
||||
//! Implement specialized version of PutN() with memset() for better performance.
|
||||
template<>
|
||||
inline void PutN(FileWriteStream& stream, char c, size_t n) {
|
||||
stream.PutN(c, n);
|
||||
}
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#endif // RAPIDJSON_FILESTREAM_H_
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_FWD_H_
|
||||
#define RAPIDJSON_FWD_H_
|
||||
|
||||
#include "rapidjson.h"
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
|
||||
// encodings.h
|
||||
|
||||
template<typename CharType> struct UTF8;
|
||||
template<typename CharType> struct UTF16;
|
||||
template<typename CharType> struct UTF16BE;
|
||||
template<typename CharType> struct UTF16LE;
|
||||
template<typename CharType> struct UTF32;
|
||||
template<typename CharType> struct UTF32BE;
|
||||
template<typename CharType> struct UTF32LE;
|
||||
template<typename CharType> struct ASCII;
|
||||
template<typename CharType> struct AutoUTF;
|
||||
|
||||
template<typename SourceEncoding, typename TargetEncoding>
|
||||
struct Transcoder;
|
||||
|
||||
// allocators.h
|
||||
|
||||
class CrtAllocator;
|
||||
|
||||
template <typename BaseAllocator>
|
||||
class MemoryPoolAllocator;
|
||||
|
||||
// stream.h
|
||||
|
||||
template <typename Encoding>
|
||||
struct GenericStringStream;
|
||||
|
||||
typedef GenericStringStream<UTF8<char> > StringStream;
|
||||
|
||||
template <typename Encoding>
|
||||
struct GenericInsituStringStream;
|
||||
|
||||
typedef GenericInsituStringStream<UTF8<char> > InsituStringStream;
|
||||
|
||||
// stringbuffer.h
|
||||
|
||||
template <typename Encoding, typename Allocator>
|
||||
class GenericStringBuffer;
|
||||
|
||||
typedef GenericStringBuffer<UTF8<char>, CrtAllocator> StringBuffer;
|
||||
|
||||
// filereadstream.h
|
||||
|
||||
class FileReadStream;
|
||||
|
||||
// filewritestream.h
|
||||
|
||||
class FileWriteStream;
|
||||
|
||||
// memorybuffer.h
|
||||
|
||||
template <typename Allocator>
|
||||
struct GenericMemoryBuffer;
|
||||
|
||||
typedef GenericMemoryBuffer<CrtAllocator> MemoryBuffer;
|
||||
|
||||
// memorystream.h
|
||||
|
||||
struct MemoryStream;
|
||||
|
||||
// reader.h
|
||||
|
||||
template<typename Encoding, typename Derived>
|
||||
struct BaseReaderHandler;
|
||||
|
||||
template <typename SourceEncoding, typename TargetEncoding, typename StackAllocator>
|
||||
class GenericReader;
|
||||
|
||||
typedef GenericReader<UTF8<char>, UTF8<char>, CrtAllocator> Reader;
|
||||
|
||||
// writer.h
|
||||
|
||||
template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, typename StackAllocator, unsigned writeFlags>
|
||||
class Writer;
|
||||
|
||||
// prettywriter.h
|
||||
|
||||
template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, typename StackAllocator, unsigned writeFlags>
|
||||
class PrettyWriter;
|
||||
|
||||
// document.h
|
||||
|
||||
template <typename Encoding, typename Allocator>
|
||||
class GenericMember;
|
||||
|
||||
template <bool Const, typename Encoding, typename Allocator>
|
||||
class GenericMemberIterator;
|
||||
|
||||
template<typename CharType>
|
||||
struct GenericStringRef;
|
||||
|
||||
template <typename Encoding, typename Allocator>
|
||||
class GenericValue;
|
||||
|
||||
typedef GenericValue<UTF8<char>, MemoryPoolAllocator<CrtAllocator> > Value;
|
||||
|
||||
template <typename Encoding, typename Allocator, typename StackAllocator>
|
||||
class GenericDocument;
|
||||
|
||||
typedef GenericDocument<UTF8<char>, MemoryPoolAllocator<CrtAllocator>, CrtAllocator> Document;
|
||||
|
||||
// pointer.h
|
||||
|
||||
template <typename ValueType, typename Allocator>
|
||||
class GenericPointer;
|
||||
|
||||
typedef GenericPointer<Value, CrtAllocator> Pointer;
|
||||
|
||||
// schema.h
|
||||
|
||||
template <typename SchemaDocumentType>
|
||||
class IGenericRemoteSchemaDocumentProvider;
|
||||
|
||||
template <typename ValueT, typename Allocator>
|
||||
class GenericSchemaDocument;
|
||||
|
||||
typedef GenericSchemaDocument<Value, CrtAllocator> SchemaDocument;
|
||||
typedef IGenericRemoteSchemaDocumentProvider<SchemaDocument> IRemoteSchemaDocumentProvider;
|
||||
|
||||
template <
|
||||
typename SchemaDocumentType,
|
||||
typename OutputHandler,
|
||||
typename StateAllocator>
|
||||
class GenericSchemaValidator;
|
||||
|
||||
typedef GenericSchemaValidator<SchemaDocument, BaseReaderHandler<UTF8<char>, void>, CrtAllocator> SchemaValidator;
|
||||
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_RAPIDJSONFWD_H_
|
||||
@@ -0,0 +1,297 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_BIGINTEGER_H_
|
||||
#define RAPIDJSON_BIGINTEGER_H_
|
||||
|
||||
#include "../rapidjson.h"
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && defined(_M_AMD64)
|
||||
#include <intrin.h> // for _umul128
|
||||
#if !defined(_ARM64EC_)
|
||||
#pragma intrinsic(_umul128)
|
||||
#else
|
||||
#pragma comment(lib,"softintrin")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
namespace internal {
|
||||
|
||||
class BigInteger {
|
||||
public:
|
||||
typedef uint64_t Type;
|
||||
|
||||
BigInteger(const BigInteger& rhs) : count_(rhs.count_) {
|
||||
std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type));
|
||||
}
|
||||
|
||||
explicit BigInteger(uint64_t u) : count_(1) {
|
||||
digits_[0] = u;
|
||||
}
|
||||
|
||||
template<typename Ch>
|
||||
BigInteger(const Ch* decimals, size_t length) : count_(1) {
|
||||
RAPIDJSON_ASSERT(length > 0);
|
||||
digits_[0] = 0;
|
||||
size_t i = 0;
|
||||
const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19
|
||||
while (length >= kMaxDigitPerIteration) {
|
||||
AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration);
|
||||
length -= kMaxDigitPerIteration;
|
||||
i += kMaxDigitPerIteration;
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
AppendDecimal64(decimals + i, decimals + i + length);
|
||||
}
|
||||
|
||||
BigInteger& operator=(const BigInteger &rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
count_ = rhs.count_;
|
||||
std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigInteger& operator=(uint64_t u) {
|
||||
digits_[0] = u;
|
||||
count_ = 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigInteger& operator+=(uint64_t u) {
|
||||
Type backup = digits_[0];
|
||||
digits_[0] += u;
|
||||
for (size_t i = 0; i < count_ - 1; i++) {
|
||||
if (digits_[i] >= backup)
|
||||
return *this; // no carry
|
||||
backup = digits_[i + 1];
|
||||
digits_[i + 1] += 1;
|
||||
}
|
||||
|
||||
// Last carry
|
||||
if (digits_[count_ - 1] < backup)
|
||||
PushBack(1);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigInteger& operator*=(uint64_t u) {
|
||||
if (u == 0) return *this = 0;
|
||||
if (u == 1) return *this;
|
||||
if (*this == 1) return *this = u;
|
||||
|
||||
uint64_t k = 0;
|
||||
for (size_t i = 0; i < count_; i++) {
|
||||
uint64_t hi;
|
||||
digits_[i] = MulAdd64(digits_[i], u, k, &hi);
|
||||
k = hi;
|
||||
}
|
||||
|
||||
if (k > 0)
|
||||
PushBack(k);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigInteger& operator*=(uint32_t u) {
|
||||
if (u == 0) return *this = 0;
|
||||
if (u == 1) return *this;
|
||||
if (*this == 1) return *this = u;
|
||||
|
||||
uint64_t k = 0;
|
||||
for (size_t i = 0; i < count_; i++) {
|
||||
const uint64_t c = digits_[i] >> 32;
|
||||
const uint64_t d = digits_[i] & 0xFFFFFFFF;
|
||||
const uint64_t uc = u * c;
|
||||
const uint64_t ud = u * d;
|
||||
const uint64_t p0 = ud + k;
|
||||
const uint64_t p1 = uc + (p0 >> 32);
|
||||
digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32);
|
||||
k = p1 >> 32;
|
||||
}
|
||||
|
||||
if (k > 0)
|
||||
PushBack(k);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigInteger& operator<<=(size_t shift) {
|
||||
if (IsZero() || shift == 0) return *this;
|
||||
|
||||
size_t offset = shift / kTypeBit;
|
||||
size_t interShift = shift % kTypeBit;
|
||||
RAPIDJSON_ASSERT(count_ + offset <= kCapacity);
|
||||
|
||||
if (interShift == 0) {
|
||||
std::memmove(digits_ + offset, digits_, count_ * sizeof(Type));
|
||||
count_ += offset;
|
||||
}
|
||||
else {
|
||||
digits_[count_] = 0;
|
||||
for (size_t i = count_; i > 0; i--)
|
||||
digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift));
|
||||
digits_[offset] = digits_[0] << interShift;
|
||||
count_ += offset;
|
||||
if (digits_[count_])
|
||||
count_++;
|
||||
}
|
||||
|
||||
std::memset(digits_, 0, offset * sizeof(Type));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const BigInteger& rhs) const {
|
||||
return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0;
|
||||
}
|
||||
|
||||
bool operator==(const Type rhs) const {
|
||||
return count_ == 1 && digits_[0] == rhs;
|
||||
}
|
||||
|
||||
BigInteger& MultiplyPow5(unsigned exp) {
|
||||
static const uint32_t kPow5[12] = {
|
||||
5,
|
||||
5 * 5,
|
||||
5 * 5 * 5,
|
||||
5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5,
|
||||
5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5
|
||||
};
|
||||
if (exp == 0) return *this;
|
||||
for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27
|
||||
for (; exp >= 13; exp -= 13) *this *= static_cast<uint32_t>(1220703125u); // 5^13
|
||||
if (exp > 0) *this *= kPow5[exp - 1];
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Compute absolute difference of this and rhs.
|
||||
// Assume this != rhs
|
||||
bool Difference(const BigInteger& rhs, BigInteger* out) const {
|
||||
int cmp = Compare(rhs);
|
||||
RAPIDJSON_ASSERT(cmp != 0);
|
||||
const BigInteger *a, *b; // Makes a > b
|
||||
bool ret;
|
||||
if (cmp < 0) { a = &rhs; b = this; ret = true; }
|
||||
else { a = this; b = &rhs; ret = false; }
|
||||
|
||||
Type borrow = 0;
|
||||
for (size_t i = 0; i < a->count_; i++) {
|
||||
Type d = a->digits_[i] - borrow;
|
||||
if (i < b->count_)
|
||||
d -= b->digits_[i];
|
||||
borrow = (d > a->digits_[i]) ? 1 : 0;
|
||||
out->digits_[i] = d;
|
||||
if (d != 0)
|
||||
out->count_ = i + 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Compare(const BigInteger& rhs) const {
|
||||
if (count_ != rhs.count_)
|
||||
return count_ < rhs.count_ ? -1 : 1;
|
||||
|
||||
for (size_t i = count_; i-- > 0;)
|
||||
if (digits_[i] != rhs.digits_[i])
|
||||
return digits_[i] < rhs.digits_[i] ? -1 : 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t GetCount() const { return count_; }
|
||||
Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; }
|
||||
bool IsZero() const { return count_ == 1 && digits_[0] == 0; }
|
||||
|
||||
private:
|
||||
template<typename Ch>
|
||||
void AppendDecimal64(const Ch* begin, const Ch* end) {
|
||||
uint64_t u = ParseUint64(begin, end);
|
||||
if (IsZero())
|
||||
*this = u;
|
||||
else {
|
||||
unsigned exp = static_cast<unsigned>(end - begin);
|
||||
(MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u
|
||||
}
|
||||
}
|
||||
|
||||
void PushBack(Type digit) {
|
||||
RAPIDJSON_ASSERT(count_ < kCapacity);
|
||||
digits_[count_++] = digit;
|
||||
}
|
||||
|
||||
template<typename Ch>
|
||||
static uint64_t ParseUint64(const Ch* begin, const Ch* end) {
|
||||
uint64_t r = 0;
|
||||
for (const Ch* p = begin; p != end; ++p) {
|
||||
RAPIDJSON_ASSERT(*p >= Ch('0') && *p <= Ch('9'));
|
||||
r = r * 10u + static_cast<unsigned>(*p - Ch('0'));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Assume a * b + k < 2^128
|
||||
static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) {
|
||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
||||
uint64_t low = _umul128(a, b, outHigh) + k;
|
||||
if (low < k)
|
||||
(*outHigh)++;
|
||||
return low;
|
||||
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__)
|
||||
__extension__ typedef unsigned __int128 uint128;
|
||||
uint128 p = static_cast<uint128>(a) * static_cast<uint128>(b);
|
||||
p += k;
|
||||
*outHigh = static_cast<uint64_t>(p >> 64);
|
||||
return static_cast<uint64_t>(p);
|
||||
#else
|
||||
const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32;
|
||||
uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1;
|
||||
x1 += (x0 >> 32); // can't give carry
|
||||
x1 += x2;
|
||||
if (x1 < x2)
|
||||
x3 += (static_cast<uint64_t>(1) << 32);
|
||||
uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF);
|
||||
uint64_t hi = x3 + (x1 >> 32);
|
||||
|
||||
lo += k;
|
||||
if (lo < k)
|
||||
hi++;
|
||||
*outHigh = hi;
|
||||
return lo;
|
||||
#endif
|
||||
}
|
||||
|
||||
static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000
|
||||
static const size_t kCapacity = kBitCount / sizeof(Type);
|
||||
static const size_t kTypeBit = sizeof(Type) * 8;
|
||||
|
||||
Type digits_[kCapacity];
|
||||
size_t count_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_BIGINTEGER_H_
|
||||
@@ -0,0 +1,71 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_CLZLL_H_
|
||||
#define RAPIDJSON_CLZLL_H_
|
||||
|
||||
#include "../rapidjson.h"
|
||||
|
||||
#if defined(_MSC_VER) && !defined(UNDER_CE)
|
||||
#include <intrin.h>
|
||||
#if defined(_WIN64)
|
||||
#pragma intrinsic(_BitScanReverse64)
|
||||
#else
|
||||
#pragma intrinsic(_BitScanReverse)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
namespace internal {
|
||||
|
||||
inline uint32_t clzll(uint64_t x) {
|
||||
// Passing 0 to __builtin_clzll is UB in GCC and results in an
|
||||
// infinite loop in the software implementation.
|
||||
RAPIDJSON_ASSERT(x != 0);
|
||||
|
||||
#if defined(_MSC_VER) && !defined(UNDER_CE)
|
||||
unsigned long r = 0;
|
||||
#if defined(_WIN64)
|
||||
_BitScanReverse64(&r, x);
|
||||
#else
|
||||
// Scan the high 32 bits.
|
||||
if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32)))
|
||||
return 63 - (r + 32);
|
||||
|
||||
// Scan the low 32 bits.
|
||||
_BitScanReverse(&r, static_cast<uint32_t>(x & 0xFFFFFFFF));
|
||||
#endif // _WIN64
|
||||
|
||||
return 63 - r;
|
||||
#elif (defined(__GNUC__) && __GNUC__ >= 4) || RAPIDJSON_HAS_BUILTIN(__builtin_clzll)
|
||||
// __builtin_clzll wrapper
|
||||
return static_cast<uint32_t>(__builtin_clzll(x));
|
||||
#else
|
||||
// naive version
|
||||
uint32_t r = 0;
|
||||
while (!(x & (static_cast<uint64_t>(1) << 63))) {
|
||||
x <<= 1;
|
||||
++r;
|
||||
}
|
||||
|
||||
return r;
|
||||
#endif // _MSC_VER
|
||||
}
|
||||
|
||||
#define RAPIDJSON_CLZLL RAPIDJSON_NAMESPACE::internal::clzll
|
||||
|
||||
} // namespace internal
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_CLZLL_H_
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
// This is a C++ header-only implementation of Grisu2 algorithm from the publication:
|
||||
// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with
|
||||
// integers." ACM Sigplan Notices 45.6 (2010): 233-243.
|
||||
|
||||
#ifndef RAPIDJSON_DIYFP_H_
|
||||
#define RAPIDJSON_DIYFP_H_
|
||||
|
||||
#include "../rapidjson.h"
|
||||
#include "clzll.h"
|
||||
#include <limits>
|
||||
|
||||
#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER)
|
||||
#include <intrin.h>
|
||||
#if !defined(_ARM64EC_)
|
||||
#pragma intrinsic(_umul128)
|
||||
#else
|
||||
#pragma comment(lib,"softintrin")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
namespace internal {
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++)
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(padded)
|
||||
#endif
|
||||
|
||||
struct DiyFp {
|
||||
DiyFp() : f(), e() {}
|
||||
|
||||
DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {}
|
||||
|
||||
explicit DiyFp(double d) {
|
||||
union {
|
||||
double d;
|
||||
uint64_t u64;
|
||||
} u = { d };
|
||||
|
||||
int biased_e = static_cast<int>((u.u64 & kDpExponentMask) >> kDpSignificandSize);
|
||||
uint64_t significand = (u.u64 & kDpSignificandMask);
|
||||
if (biased_e != 0) {
|
||||
f = significand + kDpHiddenBit;
|
||||
e = biased_e - kDpExponentBias;
|
||||
}
|
||||
else {
|
||||
f = significand;
|
||||
e = kDpMinExponent + 1;
|
||||
}
|
||||
}
|
||||
|
||||
DiyFp operator-(const DiyFp& rhs) const {
|
||||
return DiyFp(f - rhs.f, e);
|
||||
}
|
||||
|
||||
DiyFp operator*(const DiyFp& rhs) const {
|
||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
||||
uint64_t h;
|
||||
uint64_t l = _umul128(f, rhs.f, &h);
|
||||
if (l & (uint64_t(1) << 63)) // rounding
|
||||
h++;
|
||||
return DiyFp(h, e + rhs.e + 64);
|
||||
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__)
|
||||
__extension__ typedef unsigned __int128 uint128;
|
||||
uint128 p = static_cast<uint128>(f) * static_cast<uint128>(rhs.f);
|
||||
uint64_t h = static_cast<uint64_t>(p >> 64);
|
||||
uint64_t l = static_cast<uint64_t>(p);
|
||||
if (l & (uint64_t(1) << 63)) // rounding
|
||||
h++;
|
||||
return DiyFp(h, e + rhs.e + 64);
|
||||
#else
|
||||
const uint64_t M32 = 0xFFFFFFFF;
|
||||
const uint64_t a = f >> 32;
|
||||
const uint64_t b = f & M32;
|
||||
const uint64_t c = rhs.f >> 32;
|
||||
const uint64_t d = rhs.f & M32;
|
||||
const uint64_t ac = a * c;
|
||||
const uint64_t bc = b * c;
|
||||
const uint64_t ad = a * d;
|
||||
const uint64_t bd = b * d;
|
||||
uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32);
|
||||
tmp += 1U << 31; /// mult_round
|
||||
return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64);
|
||||
#endif
|
||||
}
|
||||
|
||||
DiyFp Normalize() const {
|
||||
int s = static_cast<int>(clzll(f));
|
||||
return DiyFp(f << s, e - s);
|
||||
}
|
||||
|
||||
DiyFp NormalizeBoundary() const {
|
||||
DiyFp res = *this;
|
||||
while (!(res.f & (kDpHiddenBit << 1))) {
|
||||
res.f <<= 1;
|
||||
res.e--;
|
||||
}
|
||||
res.f <<= (kDiySignificandSize - kDpSignificandSize - 2);
|
||||
res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2);
|
||||
return res;
|
||||
}
|
||||
|
||||
void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const {
|
||||
DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary();
|
||||
DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1);
|
||||
mi.f <<= mi.e - pl.e;
|
||||
mi.e = pl.e;
|
||||
*plus = pl;
|
||||
*minus = mi;
|
||||
}
|
||||
|
||||
double ToDouble() const {
|
||||
union {
|
||||
double d;
|
||||
uint64_t u64;
|
||||
}u;
|
||||
RAPIDJSON_ASSERT(f <= kDpHiddenBit + kDpSignificandMask);
|
||||
if (e < kDpDenormalExponent) {
|
||||
// Underflow.
|
||||
return 0.0;
|
||||
}
|
||||
if (e >= kDpMaxExponent) {
|
||||
// Overflow.
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 :
|
||||
static_cast<uint64_t>(e + kDpExponentBias);
|
||||
u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize);
|
||||
return u.d;
|
||||
}
|
||||
|
||||
static const int kDiySignificandSize = 64;
|
||||
static const int kDpSignificandSize = 52;
|
||||
static const int kDpExponentBias = 0x3FF + kDpSignificandSize;
|
||||
static const int kDpMaxExponent = 0x7FF - kDpExponentBias;
|
||||
static const int kDpMinExponent = -kDpExponentBias;
|
||||
static const int kDpDenormalExponent = -kDpExponentBias + 1;
|
||||
static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000);
|
||||
static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF);
|
||||
static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000);
|
||||
|
||||
uint64_t f;
|
||||
int e;
|
||||
};
|
||||
|
||||
inline DiyFp GetCachedPowerByIndex(size_t index) {
|
||||
// 10^-348, 10^-340, ..., 10^340
|
||||
static const uint64_t kCachedPowers_F[] = {
|
||||
RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76),
|
||||
RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea),
|
||||
RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df),
|
||||
RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f),
|
||||
RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c),
|
||||
RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5),
|
||||
RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d),
|
||||
RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637),
|
||||
RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7),
|
||||
RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5),
|
||||
RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b),
|
||||
RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996),
|
||||
RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6),
|
||||
RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8),
|
||||
RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053),
|
||||
RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd),
|
||||
RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94),
|
||||
RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b),
|
||||
RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac),
|
||||
RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3),
|
||||
RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb),
|
||||
RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c),
|
||||
RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000),
|
||||
RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984),
|
||||
RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70),
|
||||
RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245),
|
||||
RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8),
|
||||
RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a),
|
||||
RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea),
|
||||
RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85),
|
||||
RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2),
|
||||
RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3),
|
||||
RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25),
|
||||
RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece),
|
||||
RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5),
|
||||
RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a),
|
||||
RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c),
|
||||
RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a),
|
||||
RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129),
|
||||
RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429),
|
||||
RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d),
|
||||
RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841),
|
||||
RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9),
|
||||
RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b)
|
||||
};
|
||||
static const int16_t kCachedPowers_E[] = {
|
||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980,
|
||||
-954, -927, -901, -874, -847, -821, -794, -768, -741, -715,
|
||||
-688, -661, -635, -608, -582, -555, -529, -502, -475, -449,
|
||||
-422, -396, -369, -343, -316, -289, -263, -236, -210, -183,
|
||||
-157, -130, -103, -77, -50, -24, 3, 30, 56, 83,
|
||||
109, 136, 162, 189, 216, 242, 269, 295, 322, 348,
|
||||
375, 402, 428, 455, 481, 508, 534, 561, 588, 614,
|
||||
641, 667, 694, 720, 747, 774, 800, 827, 853, 880,
|
||||
907, 933, 960, 986, 1013, 1039, 1066
|
||||
};
|
||||
RAPIDJSON_ASSERT(index < 87);
|
||||
return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]);
|
||||
}
|
||||
|
||||
inline DiyFp GetCachedPower(int e, int* K) {
|
||||
|
||||
//int k = static_cast<int>(ceil((-61 - e) * 0.30102999566398114)) + 374;
|
||||
double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive
|
||||
int k = static_cast<int>(dk);
|
||||
if (dk - k > 0.0)
|
||||
k++;
|
||||
|
||||
unsigned index = static_cast<unsigned>((k >> 3) + 1);
|
||||
*K = -(-348 + static_cast<int>(index << 3)); // decimal exponent no need lookup table
|
||||
|
||||
return GetCachedPowerByIndex(index);
|
||||
}
|
||||
|
||||
inline DiyFp GetCachedPower10(int exp, int *outExp) {
|
||||
RAPIDJSON_ASSERT(exp >= -348);
|
||||
unsigned index = static_cast<unsigned>(exp + 348) / 8u;
|
||||
*outExp = -348 + static_cast<int>(index) * 8;
|
||||
return GetCachedPowerByIndex(index);
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
RAPIDJSON_DIAG_POP
|
||||
RAPIDJSON_DIAG_OFF(padded)
|
||||
#endif
|
||||
|
||||
} // namespace internal
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_DIYFP_H_
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
// This is a C++ header-only implementation of Grisu2 algorithm from the publication:
|
||||
// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with
|
||||
// integers." ACM Sigplan Notices 45.6 (2010): 233-243.
|
||||
|
||||
#ifndef RAPIDJSON_DTOA_
|
||||
#define RAPIDJSON_DTOA_
|
||||
|
||||
#include "itoa.h" // GetDigitsLut()
|
||||
#include "diyfp.h"
|
||||
#include "ieee754.h"
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
namespace internal {
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++)
|
||||
RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124
|
||||
#endif
|
||||
|
||||
inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) {
|
||||
while (rest < wp_w && delta - rest >= ten_kappa &&
|
||||
(rest + ten_kappa < wp_w || /// closer
|
||||
wp_w - rest > rest + ten_kappa - wp_w)) {
|
||||
buffer[len - 1]--;
|
||||
rest += ten_kappa;
|
||||
}
|
||||
}
|
||||
|
||||
inline int CountDecimalDigit32(uint32_t n) {
|
||||
// Simple pure C++ implementation was faster than __builtin_clz version in this situation.
|
||||
if (n < 10) return 1;
|
||||
if (n < 100) return 2;
|
||||
if (n < 1000) return 3;
|
||||
if (n < 10000) return 4;
|
||||
if (n < 100000) return 5;
|
||||
if (n < 1000000) return 6;
|
||||
if (n < 10000000) return 7;
|
||||
if (n < 100000000) return 8;
|
||||
// Will not reach 10 digits in DigitGen()
|
||||
//if (n < 1000000000) return 9;
|
||||
//return 10;
|
||||
return 9;
|
||||
}
|
||||
|
||||
inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) {
|
||||
static const uint64_t kPow10[] = { 1ULL, 10ULL, 100ULL, 1000ULL, 10000ULL, 100000ULL, 1000000ULL, 10000000ULL, 100000000ULL,
|
||||
1000000000ULL, 10000000000ULL, 100000000000ULL, 1000000000000ULL,
|
||||
10000000000000ULL, 100000000000000ULL, 1000000000000000ULL,
|
||||
10000000000000000ULL, 100000000000000000ULL, 1000000000000000000ULL,
|
||||
10000000000000000000ULL };
|
||||
const DiyFp one(uint64_t(1) << -Mp.e, Mp.e);
|
||||
const DiyFp wp_w = Mp - W;
|
||||
uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e);
|
||||
uint64_t p2 = Mp.f & (one.f - 1);
|
||||
int kappa = CountDecimalDigit32(p1); // kappa in [0, 9]
|
||||
*len = 0;
|
||||
|
||||
while (kappa > 0) {
|
||||
uint32_t d = 0;
|
||||
switch (kappa) {
|
||||
case 9: d = p1 / 100000000; p1 %= 100000000; break;
|
||||
case 8: d = p1 / 10000000; p1 %= 10000000; break;
|
||||
case 7: d = p1 / 1000000; p1 %= 1000000; break;
|
||||
case 6: d = p1 / 100000; p1 %= 100000; break;
|
||||
case 5: d = p1 / 10000; p1 %= 10000; break;
|
||||
case 4: d = p1 / 1000; p1 %= 1000; break;
|
||||
case 3: d = p1 / 100; p1 %= 100; break;
|
||||
case 2: d = p1 / 10; p1 %= 10; break;
|
||||
case 1: d = p1; p1 = 0; break;
|
||||
default:;
|
||||
}
|
||||
if (d || *len)
|
||||
buffer[(*len)++] = static_cast<char>('0' + static_cast<char>(d));
|
||||
kappa--;
|
||||
uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2;
|
||||
if (tmp <= delta) {
|
||||
*K += kappa;
|
||||
GrisuRound(buffer, *len, delta, tmp, kPow10[kappa] << -one.e, wp_w.f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// kappa = 0
|
||||
for (;;) {
|
||||
p2 *= 10;
|
||||
delta *= 10;
|
||||
char d = static_cast<char>(p2 >> -one.e);
|
||||
if (d || *len)
|
||||
buffer[(*len)++] = static_cast<char>('0' + d);
|
||||
p2 &= one.f - 1;
|
||||
kappa--;
|
||||
if (p2 < delta) {
|
||||
*K += kappa;
|
||||
int index = -kappa;
|
||||
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 20 ? kPow10[index] : 0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void Grisu2(double value, char* buffer, int* length, int* K) {
|
||||
const DiyFp v(value);
|
||||
DiyFp w_m, w_p;
|
||||
v.NormalizedBoundaries(&w_m, &w_p);
|
||||
|
||||
const DiyFp c_mk = GetCachedPower(w_p.e, K);
|
||||
const DiyFp W = v.Normalize() * c_mk;
|
||||
DiyFp Wp = w_p * c_mk;
|
||||
DiyFp Wm = w_m * c_mk;
|
||||
Wm.f++;
|
||||
Wp.f--;
|
||||
DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K);
|
||||
}
|
||||
|
||||
inline char* WriteExponent(int K, char* buffer) {
|
||||
if (K < 0) {
|
||||
*buffer++ = '-';
|
||||
K = -K;
|
||||
}
|
||||
|
||||
if (K >= 100) {
|
||||
*buffer++ = static_cast<char>('0' + static_cast<char>(K / 100));
|
||||
K %= 100;
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
else if (K >= 10) {
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
else
|
||||
*buffer++ = static_cast<char>('0' + static_cast<char>(K));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) {
|
||||
const int kk = length + k; // 10^(kk-1) <= v < 10^kk
|
||||
|
||||
if (0 <= k && kk <= 21) {
|
||||
// 1234e7 -> 12340000000
|
||||
for (int i = length; i < kk; i++)
|
||||
buffer[i] = '0';
|
||||
buffer[kk] = '.';
|
||||
buffer[kk + 1] = '0';
|
||||
return &buffer[kk + 2];
|
||||
}
|
||||
else if (0 < kk && kk <= 21) {
|
||||
// 1234e-2 -> 12.34
|
||||
std::memmove(&buffer[kk + 1], &buffer[kk], static_cast<size_t>(length - kk));
|
||||
buffer[kk] = '.';
|
||||
if (0 > k + maxDecimalPlaces) {
|
||||
// When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1
|
||||
// Remove extra trailing zeros (at least one) after truncation.
|
||||
for (int i = kk + maxDecimalPlaces; i > kk + 1; i--)
|
||||
if (buffer[i] != '0')
|
||||
return &buffer[i + 1];
|
||||
return &buffer[kk + 2]; // Reserve one zero
|
||||
}
|
||||
else
|
||||
return &buffer[length + 1];
|
||||
}
|
||||
else if (-6 < kk && kk <= 0) {
|
||||
// 1234e-6 -> 0.001234
|
||||
const int offset = 2 - kk;
|
||||
std::memmove(&buffer[offset], &buffer[0], static_cast<size_t>(length));
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
for (int i = 2; i < offset; i++)
|
||||
buffer[i] = '0';
|
||||
if (length - kk > maxDecimalPlaces) {
|
||||
// When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1
|
||||
// Remove extra trailing zeros (at least one) after truncation.
|
||||
for (int i = maxDecimalPlaces + 1; i > 2; i--)
|
||||
if (buffer[i] != '0')
|
||||
return &buffer[i + 1];
|
||||
return &buffer[3]; // Reserve one zero
|
||||
}
|
||||
else
|
||||
return &buffer[length + offset];
|
||||
}
|
||||
else if (kk < -maxDecimalPlaces) {
|
||||
// Truncate to zero
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
buffer[2] = '0';
|
||||
return &buffer[3];
|
||||
}
|
||||
else if (length == 1) {
|
||||
// 1e30
|
||||
buffer[1] = 'e';
|
||||
return WriteExponent(kk - 1, &buffer[2]);
|
||||
}
|
||||
else {
|
||||
// 1234e30 -> 1.234e33
|
||||
std::memmove(&buffer[2], &buffer[1], static_cast<size_t>(length - 1));
|
||||
buffer[1] = '.';
|
||||
buffer[length + 1] = 'e';
|
||||
return WriteExponent(kk - 1, &buffer[0 + length + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) {
|
||||
RAPIDJSON_ASSERT(maxDecimalPlaces >= 1);
|
||||
Double d(value);
|
||||
if (d.IsZero()) {
|
||||
if (d.Sign())
|
||||
*buffer++ = '-'; // -0.0, Issue #289
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
buffer[2] = '0';
|
||||
return &buffer[3];
|
||||
}
|
||||
else {
|
||||
if (value < 0) {
|
||||
*buffer++ = '-';
|
||||
value = -value;
|
||||
}
|
||||
int length, K;
|
||||
Grisu2(value, buffer, &length, &K);
|
||||
return Prettify(buffer, length, K, maxDecimalPlaces);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
RAPIDJSON_DIAG_POP
|
||||
#endif
|
||||
|
||||
} // namespace internal
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_DTOA_
|
||||
@@ -0,0 +1,78 @@
|
||||
// Tencent is pleased to support the open source community by making RapidJSON available.
|
||||
//
|
||||
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
|
||||
//
|
||||
// Licensed under the MIT License (the "License"); you may not use this file except
|
||||
// in compliance with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#ifndef RAPIDJSON_IEEE754_
|
||||
#define RAPIDJSON_IEEE754_
|
||||
|
||||
#include "../rapidjson.h"
|
||||
|
||||
RAPIDJSON_NAMESPACE_BEGIN
|
||||
namespace internal {
|
||||
|
||||
class Double {
|
||||
public:
|
||||
Double() {}
|
||||
Double(double d) : d_(d) {}
|
||||
Double(uint64_t u) : u_(u) {}
|
||||
|
||||
double Value() const { return d_; }
|
||||
uint64_t Uint64Value() const { return u_; }
|
||||
|
||||
double NextPositiveDouble() const {
|
||||
RAPIDJSON_ASSERT(!Sign());
|
||||
return Double(u_ + 1).Value();
|
||||
}
|
||||
|
||||
bool Sign() const { return (u_ & kSignMask) != 0; }
|
||||
uint64_t Significand() const { return u_ & kSignificandMask; }
|
||||
int Exponent() const { return static_cast<int>(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); }
|
||||
|
||||
bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; }
|
||||
bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; }
|
||||
bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; }
|
||||
bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; }
|
||||
bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; }
|
||||
|
||||
uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); }
|
||||
int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; }
|
||||
uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; }
|
||||
|
||||
static int EffectiveSignificandSize(int order) {
|
||||
if (order >= -1021)
|
||||
return 53;
|
||||
else if (order <= -1074)
|
||||
return 0;
|
||||
else
|
||||
return order + 1074;
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kSignificandSize = 52;
|
||||
static const int kExponentBias = 0x3FF;
|
||||
static const int kDenormalExponent = 1 - kExponentBias;
|
||||
static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000);
|
||||
static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000);
|
||||
static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF);
|
||||
static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000);
|
||||
|
||||
union {
|
||||
double d_;
|
||||
uint64_t u_;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
RAPIDJSON_NAMESPACE_END
|
||||
|
||||
#endif // RAPIDJSON_IEEE754_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user