add: implement StringUtils for string manipulation and refactor usages

Unified the scattered utility functions.
This commit is contained in:
kuwacom 2026-03-07 17:39:37 +09:00
parent 5f118ca6a4
commit fa09e28572
16 changed files with 191 additions and 158 deletions

View file

@ -101,6 +101,7 @@ list(APPEND MINECRAFT_SERVER_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandList.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandTp.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Console/commands/CliCommandGamemode.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/Common/StringUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Minecraft.Server/vendor/linenoise/linenoise.c"
)
@ -215,3 +216,4 @@ add_custom_command(TARGET MinecraftServer POST_BUILD
)
set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT MinecraftServer)

View file

@ -0,0 +1,111 @@
#include "stdafx.h"
#include "StringUtils.h"
#include <cctype>
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)
{
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 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;
}
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;
}
}
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <string>
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 TrimAscii(const std::string &value);
std::string ToLowerAscii(const std::string &value);
bool StartsWithIgnoreCase(const std::string &value, const std::string &prefix);
}
}

View file

@ -10,6 +10,7 @@
#include "commands\CliCommandList.h"
#include "commands\CliCommandStop.h"
#include "commands\CliCommandTp.h"
#include "..\Common\StringUtils.h"
#include "..\ServerLogger.h"
#include "..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\Minecraft.Client\PlayerList.h"
@ -17,7 +18,6 @@
#include "..\..\Minecraft.World\LevelSettings.h"
#include "..\..\Minecraft.World\StringHelpers.h"
#include <ctype.h>
#include <stdlib.h>
#include <unordered_set>
@ -68,36 +68,16 @@ namespace ServerRuntime
}
}
std::string ServerCliEngine::Normalize(const std::string &value)
{
std::string lowered = value;
for (size_t i = 0; i < lowered.size(); ++i)
{
lowered[i] = (char)tolower((unsigned char)lowered[i]);
}
return lowered;
}
std::wstring ServerCliEngine::ToWide(const std::string &value)
{
return convStringToWstring(value);
}
std::string ServerCliEngine::ToUtf8(const std::wstring &value)
{
return WideToUtf8(value);
}
bool ServerCliEngine::ExecuteCommandLine(const std::string &line)
{
// Normalize user input before parsing (trim + optional leading slash).
std::wstring wide = trimString(ToWide(line));
std::wstring wide = trimString(StringUtils::Utf8ToWide(line));
if (wide.empty())
{
return true;
}
std::string normalizedLine = ToUtf8(wide);
std::string normalizedLine = StringUtils::WideToUtf8(wide);
if (!normalizedLine.empty() && normalizedLine[0] == '/')
{
normalizedLine = normalizedLine.substr(1);
@ -221,7 +201,7 @@ namespace ServerRuntime
std::shared_ptr<ServerPlayer> player = players->players[i];
if (player != NULL)
{
result.push_back(ToUtf8(player->getName()));
result.push_back(StringUtils::WideToUtf8(player->getName()));
}
}
@ -242,7 +222,7 @@ namespace ServerRuntime
return nullptr;
}
std::wstring target = ToWide(name);
std::wstring target = StringUtils::Utf8ToWide(name);
for (size_t i = 0; i < players->players.size(); ++i)
{
std::shared_ptr<ServerPlayer> player = players->players[i];
@ -258,10 +238,10 @@ namespace ServerRuntime
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 = Normalize(prefix);
std::string loweredPrefix = StringUtils::ToLowerAscii(prefix);
for (size_t i = 0; i < players.size(); ++i)
{
std::string loweredName = Normalize(players[i]);
std::string loweredName = StringUtils::ToLowerAscii(players[i]);
if (loweredName.compare(0, loweredPrefix.size(), loweredPrefix) == 0)
{
out->push_back(linePrefix + players[i]);
@ -272,11 +252,11 @@ namespace ServerRuntime
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 = Normalize(prefix);
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 = Normalize(candidate);
std::string loweredCandidate = StringUtils::ToLowerAscii(candidate);
if (loweredCandidate.compare(0, loweredPrefix.size(), loweredPrefix) == 0)
{
out->push_back(linePrefix + candidate);
@ -286,7 +266,7 @@ namespace ServerRuntime
GameType *ServerCliEngine::ParseGamemode(const std::string &token) const
{
std::string lowered = Normalize(token);
std::string lowered = StringUtils::ToLowerAscii(token);
if (lowered == "survival" || lowered == "s" || lowered == "0")
{
return GameType::SURVIVAL;
@ -312,3 +292,4 @@ namespace ServerRuntime
return *m_registry;
}
}

View file

@ -102,9 +102,6 @@ namespace ServerRuntime
private:
void RegisterDefaultCommands();
static std::string Normalize(const std::string &value);
static std::wstring ToWide(const std::string &value);
static std::string ToUtf8(const std::wstring &value);
private:
mutable std::mutex m_queueMutex;

View file

@ -3,39 +3,10 @@
#include "ServerCliRegistry.h"
#include "commands\IServerCliCommand.h"
#include <ctype.h>
#include "..\Common\StringUtils.h"
namespace ServerRuntime
{
static 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)
{
char a = (char)tolower((unsigned char)value[i]);
char b = (char)tolower((unsigned char)prefix[i]);
if (a != b)
{
return false;
}
}
return true;
}
std::string ServerCliRegistry::Normalize(const std::string &value)
{
std::string normalized = value;
for (size_t i = 0; i < normalized.size(); ++i)
{
normalized[i] = (char)tolower((unsigned char)normalized[i]);
}
return normalized;
}
bool ServerCliRegistry::Register(std::unique_ptr<IServerCliCommand> command)
{
if (!command)
@ -44,7 +15,7 @@ namespace ServerRuntime
}
IServerCliCommand *raw = command.get();
std::string baseName = Normalize(raw->Name());
std::string baseName = StringUtils::ToLowerAscii(raw->Name());
// Reject empty/duplicate primary command names.
if (baseName.empty() || m_lookup.find(baseName) != m_lookup.end())
{
@ -55,7 +26,7 @@ namespace ServerRuntime
normalizedAliases.reserve(aliases.size());
for (size_t i = 0; i < aliases.size(); ++i)
{
std::string alias = Normalize(aliases[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())
{
@ -77,7 +48,7 @@ namespace ServerRuntime
const IServerCliCommand *ServerCliRegistry::Find(const std::string &name) const
{
std::string key = Normalize(name);
std::string key = StringUtils::ToLowerAscii(name);
auto it = m_lookup.find(key);
if (it == m_lookup.end())
{
@ -88,7 +59,7 @@ namespace ServerRuntime
IServerCliCommand *ServerCliRegistry::FindMutable(const std::string &name)
{
std::string key = Normalize(name);
std::string key = StringUtils::ToLowerAscii(name);
auto it = m_lookup.find(key);
if (it == m_lookup.end())
{
@ -103,7 +74,7 @@ namespace ServerRuntime
{
const IServerCliCommand *command = m_commands[i].get();
std::string name = command->Name();
if (StartsWithIgnoreCase(name, prefix))
if (StringUtils::StartsWithIgnoreCase(name, prefix))
{
out->push_back(linePrefix + name);
}
@ -112,7 +83,7 @@ namespace ServerRuntime
std::vector<std::string> aliases = command->Aliases();
for (size_t aliasIndex = 0; aliasIndex < aliases.size(); ++aliasIndex)
{
if (StartsWithIgnoreCase(aliases[aliasIndex], prefix))
if (StringUtils::StartsWithIgnoreCase(aliases[aliasIndex], prefix))
{
out->push_back(linePrefix + aliases[aliasIndex]);
}
@ -125,3 +96,4 @@ namespace ServerRuntime
return m_commands;
}
}

View file

@ -51,9 +51,6 @@ namespace ServerRuntime
*/
const std::vector<std::unique_ptr<IServerCliCommand>> &Commands() const;
private:
static std::string Normalize(const std::string &value);
private:
std::vector<std::unique_ptr<IServerCliCommand>> m_commands;
std::unordered_map<std::string, IServerCliCommand *> m_lookup;

View file

@ -4,7 +4,7 @@
#include "..\ServerCliEngine.h"
#include "..\ServerCliParser.h"
#include "..\..\ServerLogger.h"
#include "..\..\Common\StringUtils.h"
#include "..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\Minecraft.Client\PlayerList.h"
#include "..\..\..\Minecraft.Client\ServerPlayer.h"
@ -77,7 +77,9 @@ namespace ServerRuntime
target->setGameMode(mode);
target->fallDistance = 0.0f;
engine->LogInfo("Set " + WideToUtf8(target->getName()) + " gamemode to " + WideToUtf8(mode->getName()) + ".");
engine->LogInfo(
"Set " + StringUtils::WideToUtf8(target->getName()) + " gamemode to " +
StringUtils::WideToUtf8(mode->getName()) + ".");
return true;
}
@ -93,3 +95,4 @@ namespace ServerRuntime
}
}
}

View file

@ -3,7 +3,7 @@
#include "CliCommandList.h"
#include "..\ServerCliEngine.h"
#include "..\..\ServerLogger.h"
#include "..\..\Common\StringUtils.h"
#include "..\..\..\Minecraft.Client\MinecraftServer.h"
#include "..\..\..\Minecraft.Client\PlayerList.h"
@ -35,7 +35,7 @@ namespace ServerRuntime
}
PlayerList *players = server->getPlayers();
std::string names = WideToUtf8(players->getPlayerNames());
std::string names = StringUtils::WideToUtf8(players->getPlayerNames());
if (names.empty())
{
names = "(none)";
@ -45,3 +45,4 @@ namespace ServerRuntime
return true;
}
}

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?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">
@ -657,6 +657,7 @@
<ClCompile Include="Console\ServerCliEngine.cpp" />
<ClCompile Include="Console\ServerCliParser.cpp" />
<ClCompile Include="Console\ServerCliRegistry.cpp" />
<ClCompile Include="Common\StringUtils.cpp" />
<ClCompile Include="..\Minecraft.Client\glWrapper.cpp" />
<ClCompile Include="ServerLogger.cpp" />
<ClCompile Include="ServerProperties.cpp" />
@ -683,6 +684,7 @@
<ClInclude Include="Console\ServerCliEngine.h" />
<ClInclude Include="Console\ServerCliParser.h" />
<ClInclude Include="Console\ServerCliRegistry.h" />
<ClInclude Include="Common\StringUtils.h" />
<ClInclude Include="ServerLogger.h" />
<ClInclude Include="ServerProperties.h" />
<ClInclude Include="vendor\linenoise\linenoise.h" />
@ -705,3 +707,4 @@
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
</Project>

View file

@ -10,6 +10,9 @@
<Filter Include="Server\Console\Commands">
<UniqueIdentifier>{7C28D123-0DA3-4B17-84C0-E326F5A75740}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Common">
<UniqueIdentifier>{BC6FD58B-1A40-45FE-B8D9-1A087C25126D}</UniqueIdentifier>
</Filter>
<Filter Include="Server\Vendor">
<UniqueIdentifier>{3E4D5A41-CAB8-4A10-82B5-8B2AE2E25CB2}</UniqueIdentifier>
</Filter>
@ -36,6 +39,9 @@
<ClCompile Include="Console\ServerCliRegistry.cpp">
<Filter>Server\Console</Filter>
</ClCompile>
<ClCompile Include="Common\StringUtils.cpp">
<Filter>Server\Common</Filter>
</ClCompile>
<ClCompile Include="vendor\linenoise\linenoise.c">
<Filter>Server\Vendor</Filter>
</ClCompile>
@ -566,6 +572,9 @@
<ClInclude Include="Console\ServerCliRegistry.h">
<Filter>Server\Console</Filter>
</ClInclude>
<ClInclude Include="Common\StringUtils.h">
<Filter>Server\Common</Filter>
</ClInclude>
<ClInclude Include="ServerLogger.h">
<Filter>Server</Filter>
</ClInclude>
@ -604,4 +613,5 @@
<ItemGroup>
<MASM Include="..\Minecraft.Client\iob_shim.asm" />
</ItemGroup>
</Project>
</Project>

View file

@ -1,6 +1,7 @@
#include "stdafx.h"
#include "ServerLogger.h"
#include "Common\\StringUtils.h"
#include "vendor\\linenoise\\linenoise.h"
#include <stdio.h>
@ -186,53 +187,6 @@ EServerLogLevel GetServerLogLevel()
return (EServerLogLevel)g_minLogLevel;
}
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)
{
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;
}
void LogDebug(const char *category, const char *message)
{
WriteLogLine(eServerLogLevel_Debug, category, message);
@ -297,7 +251,8 @@ void LogWorldIO(const char *message)
void LogWorldName(const char *prefix, const std::wstring &name)
{
std::string utf8 = WideToUtf8(name);
std::string utf8 = StringUtils::WideToUtf8(name);
LogInfof("world-io", "%s: %s", (prefix != NULL) ? prefix : "name", utf8.c_str());
}
}

View file

@ -24,9 +24,6 @@ namespace ServerRuntime
void SetServerLogLevel(EServerLogLevel level);
EServerLogLevel GetServerLogLevel();
std::string WideToUtf8(const std::wstring &value);
std::wstring Utf8ToWide(const char *value);
void LogDebug(const char *category, const char *message);
void LogInfo(const char *category, const char *message);
void LogWarn(const char *category, const char *message);

View file

@ -3,6 +3,7 @@
#include "ServerProperties.h"
#include "ServerLogger.h"
#include "Common\\StringUtils.h"
#include <cctype>
#include <fstream>
@ -13,6 +14,11 @@
namespace ServerRuntime
{
using StringUtils::ToLowerAscii;
using StringUtils::TrimAscii;
using StringUtils::Utf8ToWide;
using StringUtils::WideToUtf8;
struct ServerPropertyDefault
{
const char *key;
@ -72,34 +78,6 @@ static const ServerPropertyDefault kServerPropertyDefaults[] =
{ "trust-players", "true" }
};
static 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);
}
static std::string ToLowerAscii(const std::string &value)
{
std::string lowered = value;
for (size_t i = 0; i < lowered.length(); ++i)
{
unsigned char ch = (unsigned char)lowered[i];
lowered[i] = (char)std::tolower(ch);
}
return lowered;
}
static std::string BoolToString(bool value)
{
return value ? "true" : "false";
@ -778,3 +756,4 @@ bool SaveServerPropertiesConfig(const ServerPropertiesConfig &config)
return WriteServerPropertiesFile(kServerPropertiesPath, merged);
}
}

View file

@ -5,6 +5,7 @@
#include "Input.h"
#include "Minecraft.h"
#include "MinecraftServer.h"
#include "..\Common\StringUtils.h"
#include "..\ServerLogger.h"
#include "..\ServerProperties.h"
#include "..\WorldManager.h"
@ -119,7 +120,7 @@ using ServerRuntime::SaveServerPropertiesConfig;
using ServerRuntime::SetServerLogLevel;
using ServerRuntime::ServerPropertiesConfig;
using ServerRuntime::TryParseServerLogLevel;
using ServerRuntime::WideToUtf8;
using ServerRuntime::StringUtils::WideToUtf8;
using ServerRuntime::BootstrapWorldForServer;
using ServerRuntime::eWorldBootstrap_CreatedNew;
using ServerRuntime::eWorldBootstrap_Failed;
@ -609,3 +610,4 @@ int main(int argc, char **argv)
return 0;
}

View file

@ -5,12 +5,16 @@
#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,
@ -525,3 +529,4 @@ bool WaitForWorldActionIdle(
return (app.GetXuiServerAction(actionPad) == eXuiServerAction_Idle);
}
}