aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/config/luaconfig.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-11-28 14:19:55 +0100
committerGitHub <[email protected]>2023-11-28 14:19:55 +0100
commit76e0fe5d3bdb06691ee534954acdbaf56da0577d (patch)
tree200d3dcdfe9b0f2dd14c2fb8c8650235fee890a0 /src/zenserver/config/luaconfig.cpp
parenttracing for gcv2 (#574) (diff)
downloadzen-76e0fe5d3bdb06691ee534954acdbaf56da0577d.tar.xz
zen-76e0fe5d3bdb06691ee534954acdbaf56da0577d.zip
moved LuaConfig code so it can be used outside of config.cpp (#575)
Diffstat (limited to 'src/zenserver/config/luaconfig.cpp')
-rw-r--r--src/zenserver/config/luaconfig.cpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/zenserver/config/luaconfig.cpp b/src/zenserver/config/luaconfig.cpp
new file mode 100644
index 000000000..cdc808cf6
--- /dev/null
+++ b/src/zenserver/config/luaconfig.cpp
@@ -0,0 +1,461 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "luaconfig.h"
+
+namespace zen::LuaConfig {
+
+std::string
+MakeSafePath(const std::string_view Path)
+{
+#if ZEN_PLATFORM_WINDOWS
+ if (Path.empty())
+ {
+ return std::string(Path);
+ }
+
+ std::string FixedPath(Path);
+ std::replace(FixedPath.begin(), FixedPath.end(), '/', '\\');
+ if (!FixedPath.starts_with("\\\\?\\"))
+ {
+ FixedPath.insert(0, "\\\\?\\");
+ }
+ return FixedPath;
+#else
+ return std::string(Path);
+#endif
+};
+
+void
+EscapeBackslash(std::string& InOutString)
+{
+ std::size_t BackslashPos = InOutString.find('\\');
+ if (BackslashPos != std::string::npos)
+ {
+ std::size_t Offset = 0;
+ zen::ExtendableStringBuilder<512> PathBuilder;
+ while (BackslashPos != std::string::npos)
+ {
+ PathBuilder.Append(InOutString.substr(Offset, BackslashPos + 1 - Offset));
+ PathBuilder.Append('\\');
+ Offset = BackslashPos + 1;
+ BackslashPos = InOutString.find('\\', Offset);
+ }
+ PathBuilder.Append(InOutString.substr(Offset, BackslashPos));
+ InOutString = PathBuilder.ToString();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+BoolOption::BoolOption(bool& Value) : Value(Value)
+{
+}
+
+void
+BoolOption::Print(std::string_view, zen::StringBuilderBase& StringBuilder)
+{
+ StringBuilder.Append(Value ? "true" : "false");
+}
+
+void
+BoolOption::Parse(sol::object Object)
+{
+ Value = Object.as<bool>();
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+StringOption::StringOption(std::string& Value) : Value(Value)
+{
+}
+
+void
+StringOption::Print(std::string_view, zen::StringBuilderBase& StringBuilder)
+{
+ StringBuilder.Append(fmt::format("\"{}\"", Value));
+}
+
+void
+StringOption::Parse(sol::object Object)
+{
+ Value = Object.as<std::string>();
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+FilePathOption::FilePathOption(std::filesystem::path& Value) : Value(Value)
+{
+}
+
+void
+FilePathOption::Print(std::string_view, zen::StringBuilderBase& StringBuilder)
+{
+ std::string Path = Value.string();
+ EscapeBackslash(Path);
+ StringBuilder.Append(fmt::format("\"{}\"", Path));
+}
+
+void
+FilePathOption::Parse(sol::object Object)
+{
+ std::string Str = Object.as<std::string>();
+ if (!Str.empty())
+ {
+ Value = MakeSafePath(Str);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+LuaContainerWriter::LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent)
+: StringBuilder(StringBuilder)
+, InitialIndent(Indent.length())
+, LocalIndent(Indent)
+{
+ StringBuilder.Append("{\n");
+ LocalIndent.push_back('\t');
+}
+
+LuaContainerWriter::~LuaContainerWriter()
+{
+ LocalIndent.pop_back();
+ StringBuilder.Append(LocalIndent);
+ StringBuilder.Append("}");
+}
+
+void
+LuaContainerWriter::BeginContainer(std::string_view Name)
+{
+ StringBuilder.Append(LocalIndent);
+ if (!Name.empty())
+ {
+ StringBuilder.Append(Name);
+ StringBuilder.Append(" = {\n");
+ }
+ else
+ {
+ StringBuilder.Append("{\n");
+ }
+ LocalIndent.push_back('\t');
+}
+
+void
+LuaContainerWriter::WriteValue(std::string_view Name, std::string_view Value)
+{
+ if (Name.empty())
+ {
+ StringBuilder.Append(fmt::format("{}\"{}\",\n", LocalIndent, Value));
+ }
+ else
+ {
+ StringBuilder.Append(fmt::format("{}{} = \"{}\",\n", LocalIndent, Name, Value));
+ }
+}
+
+void
+LuaContainerWriter::EndContainer()
+{
+ LocalIndent.pop_back();
+ StringBuilder.Append(LocalIndent);
+ StringBuilder.Append("}");
+ StringBuilder.Append(",\n");
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+StringArrayOption::StringArrayOption(std::vector<std::string>& Value) : Value(Value)
+{
+}
+
+void
+StringArrayOption::Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder)
+{
+ if (Value.empty())
+ {
+ StringBuilder.Append("{}");
+ }
+ if (Value.size() == 1)
+ {
+ StringBuilder.Append(fmt::format("\"{}\"", Value[0]));
+ }
+ else
+ {
+ LuaContainerWriter Writer(StringBuilder, Indent);
+ for (std::string String : Value)
+ {
+ Writer.WriteValue("", String);
+ }
+ }
+}
+
+void
+StringArrayOption::Parse(sol::object Object)
+{
+ if (Object.get_type() == sol::type::string)
+ {
+ Value.push_back(Object.as<std::string>());
+ }
+ else if (Object.get_type() == sol::type::table)
+ {
+ for (const auto& Kv : Object.as<sol::table>())
+ {
+ Value.push_back(Kv.second.as<std::string>());
+ }
+ }
+}
+
+std::shared_ptr<OptionValue>
+MakeOption(std::string& Value)
+{
+ return std::make_shared<StringOption>(Value);
+}
+
+std::shared_ptr<OptionValue>
+MakeOption(std::filesystem::path& Value)
+{
+ return std::make_shared<FilePathOption>(Value);
+}
+
+std::shared_ptr<OptionValue>
+MakeOption(bool& Value)
+{
+ return std::make_shared<BoolOption>(Value);
+}
+
+std::shared_ptr<OptionValue>
+MakeOption(std::vector<std::string>& Value)
+{
+ return std::make_shared<StringArrayOption>(Value);
+}
+
+void
+Options::Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult)
+{
+ zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path);
+
+ if (LuaScript)
+ {
+ sol::state lua;
+
+ lua.open_libraries(sol::lib::base);
+
+ lua.set_function("getenv", [&](const std::string env) -> sol::object {
+#if ZEN_PLATFORM_WINDOWS
+ std::wstring EnvVarValue;
+ size_t RequiredSize = 0;
+ std::wstring EnvWide = zen::Utf8ToWide(env);
+ _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str());
+
+ if (RequiredSize == 0)
+ return sol::make_object(lua, sol::lua_nil);
+
+ EnvVarValue.resize(RequiredSize);
+ _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str());
+ return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str()));
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ char* EnvVariable = getenv(env.c_str());
+ if (EnvVariable == nullptr)
+ {
+ return sol::make_object(lua, sol::lua_nil);
+ }
+ return sol::make_object(lua, EnvVariable);
+#else
+ ZEN_UNUSED(env);
+ return sol::make_object(lua, sol::lua_nil);
+#endif
+ });
+
+ try
+ {
+ sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg");
+
+ if (!config.valid())
+ {
+ sol::error err = config;
+
+ std::string ErrorString = sol::to_string(config.status());
+
+ throw std::runtime_error(fmt::format("{} error: {}", ErrorString, err.what()));
+ }
+
+ config();
+ }
+ catch (std::exception& e)
+ {
+ throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str());
+ }
+
+ Parse(lua, CmdLineResult);
+ }
+}
+
+void
+Options::Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult)
+{
+ for (auto It : LuaState)
+ {
+ sol::object Key = It.first;
+ sol::type KeyType = Key.get_type();
+ if (KeyType == sol::type::string)
+ {
+ sol::type ValueType = It.second.get_type();
+ switch (ValueType)
+ {
+ case sol::type::table:
+ {
+ std::string Name = Key.as<std::string>();
+ if (Name.starts_with("_"))
+ {
+ continue;
+ }
+ if (Name == "base")
+ {
+ continue;
+ }
+ Traverse(It.second.as<sol::table>(), Name, CmdLineResult);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void
+Options::Touch(std::string_view Key)
+{
+ UsedKeys.insert(std::string(Key));
+}
+
+void
+Options::Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult)
+{
+ for (auto It : OptionMap)
+ {
+ if (CmdLineResult.count(It.second.CommandLineOptionName) != 0)
+ {
+ UsedKeys.insert(It.first);
+ }
+ }
+
+ std::vector<std::string> SortedKeys(UsedKeys.begin(), UsedKeys.end());
+ std::sort(SortedKeys.begin(), SortedKeys.end());
+ auto GetTablePath = [](const std::string& Key) -> std::vector<std::string> {
+ std::vector<std::string> Path;
+ zen::ForEachStrTok(Key, '.', [&Path](std::string_view Part) {
+ Path.push_back(std::string(Part));
+ return true;
+ });
+ return Path;
+ };
+ std::vector<std::string> CurrentTablePath;
+ std::string Indent;
+ auto It = SortedKeys.begin();
+ for (const std::string& Key : SortedKeys)
+ {
+ std::vector<std::string> KeyPath = GetTablePath(Key);
+ std::string Name = KeyPath.back();
+ KeyPath.pop_back();
+ if (CurrentTablePath != KeyPath)
+ {
+ size_t EqualCount = 0;
+ while (EqualCount < CurrentTablePath.size() && EqualCount < KeyPath.size() &&
+ CurrentTablePath[EqualCount] == KeyPath[EqualCount])
+ {
+ EqualCount++;
+ }
+ while (CurrentTablePath.size() > EqualCount)
+ {
+ CurrentTablePath.pop_back();
+ Indent.pop_back();
+ SB.Append(Indent);
+ SB.Append("}");
+ if (CurrentTablePath.size() == EqualCount && !Indent.empty() && KeyPath.size() >= EqualCount)
+ {
+ SB.Append(",");
+ }
+ SB.Append("\n");
+ if (Indent.empty())
+ {
+ SB.Append("\n");
+ }
+ }
+ while (EqualCount < KeyPath.size())
+ {
+ SB.Append(Indent);
+ SB.Append(KeyPath[EqualCount]);
+ SB.Append(" = {\n");
+ Indent.push_back('\t');
+ CurrentTablePath.push_back(KeyPath[EqualCount]);
+ EqualCount++;
+ }
+ }
+
+ SB.Append(Indent);
+ SB.Append(Name);
+ SB.Append(" = ");
+ OptionMap[Key].Value->Print(Indent, SB);
+ SB.Append(",\n");
+ }
+ while (!CurrentTablePath.empty())
+ {
+ Indent.pop_back();
+ SB.Append(Indent);
+ SB.Append("}\n");
+ CurrentTablePath.pop_back();
+ }
+}
+
+void
+Options::Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult)
+{
+ for (auto It : Table)
+ {
+ sol::object Key = It.first;
+ sol::type KeyType = Key.get_type();
+ if (KeyType == sol::type::string || KeyType == sol::type::number)
+ {
+ sol::type ValueType = It.second.get_type();
+ switch (ValueType)
+ {
+ case sol::type::table:
+ case sol::type::string:
+ case sol::type::number:
+ case sol::type::boolean:
+ {
+ std::string Name = Key.as<std::string>();
+ if (Name.starts_with("_"))
+ {
+ continue;
+ }
+ Name = std::string(PathPrefix) + "." + Key.as<std::string>();
+ auto OptionIt = OptionMap.find(Name);
+ if (OptionIt != OptionMap.end())
+ {
+ UsedKeys.insert(Name);
+ if (CmdLineResult.count(OptionIt->second.CommandLineOptionName) != 0)
+ {
+ continue;
+ }
+ OptionIt->second.Value->Parse(It.second);
+ continue;
+ }
+ if (ValueType == sol::type::table)
+ {
+ if (Name == "base")
+ {
+ continue;
+ }
+ Traverse(It.second.as<sol::table>(), Name, CmdLineResult);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+} // namespace zen::LuaConfig