aboutsummaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/zenserver/config.cpp645
-rw-r--r--src/zenserver/config/luaconfig.cpp461
-rw-r--r--src/zenserver/config/luaconfig.h139
3 files changed, 705 insertions, 540 deletions
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp
index 92d6c7f7d..fe92613f4 100644
--- a/src/zenserver/config.cpp
+++ b/src/zenserver/config.cpp
@@ -2,6 +2,7 @@
#include "config.h"
+#include "config/luaconfig.h"
#include "diag/logging.h"
#include <zencore/crypto.h>
@@ -175,592 +176,156 @@ MakeSafePath(const std::string_view Path)
#endif
};
-namespace LuaConfig {
-
- 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();
+class CachePolicyOption : public LuaConfig::OptionValue
+{
+public:
+ CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {}
+ virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
+ {
+ switch (Value)
+ {
+ case UpstreamCachePolicy::Read:
+ StringBuilder.Append("readonly");
+ break;
+ case UpstreamCachePolicy::Write:
+ StringBuilder.Append("writeonly");
+ break;
+ case UpstreamCachePolicy::Disabled:
+ StringBuilder.Append("disabled");
+ break;
+ case UpstreamCachePolicy::ReadWrite:
+ StringBuilder.Append("readwrite");
+ break;
+ default:
+ ZEN_ASSERT(false);
}
}
-
- class OptionValue
- {
- public:
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0;
- virtual void Parse(sol::object Object) = 0;
-
- virtual ~OptionValue() {}
- };
-
- typedef std::shared_ptr<OptionValue> TOptionValue;
-
- class StringOption : public OptionValue
+ virtual void Parse(sol::object Object) override
{
- public:
- StringOption(std::string& Value) : Value(Value) {}
- virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
+ std::string PolicyString = Object.as<std::string>();
+ if (PolicyString == "readonly")
{
- StringBuilder.Append(fmt::format("\"{}\"", Value));
+ Value = UpstreamCachePolicy::Read;
}
- virtual void Parse(sol::object Object) override { Value = Object.as<std::string>(); }
- std::string& Value;
- };
-
- class FilePathOption : public OptionValue
- {
- public:
- FilePathOption(std::filesystem::path& Value) : Value(Value) {}
- virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
+ else if (PolicyString == "writeonly")
{
- std::string Path = Value.string();
- EscapeBackslash(Path);
- StringBuilder.Append(fmt::format("\"{}\"", Path));
+ Value = UpstreamCachePolicy::Write;
}
- virtual void Parse(sol::object Object) override
+ else if (PolicyString == "disabled")
{
- std::string Str = Object.as<std::string>();
- if (!Str.empty())
- {
- Value = MakeSafePath(Str);
- }
+ Value = UpstreamCachePolicy::Disabled;
}
- std::filesystem::path& Value;
- };
-
- class BoolOption : public OptionValue
- {
- public:
- BoolOption(bool& Value) : Value(Value) {}
- virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
+ else if (PolicyString == "readwrite")
{
- StringBuilder.Append(Value ? "true" : "false");
+ Value = UpstreamCachePolicy::ReadWrite;
}
- virtual void Parse(sol::object Object) override { Value = Object.as<bool>(); }
- bool& Value;
- };
-
- class CachePolicyOption : public OptionValue
- {
- public:
- CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {}
- virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
- {
- switch (Value)
- {
- case UpstreamCachePolicy::Read:
- StringBuilder.Append("readonly");
- break;
- case UpstreamCachePolicy::Write:
- StringBuilder.Append("writeonly");
- break;
- case UpstreamCachePolicy::Disabled:
- StringBuilder.Append("disabled");
- break;
- case UpstreamCachePolicy::ReadWrite:
- StringBuilder.Append("readwrite");
- break;
- default:
- ZEN_ASSERT(false);
- }
- }
- virtual void Parse(sol::object Object) override
- {
- std::string PolicyString = Object.as<std::string>();
- if (PolicyString == "readonly")
- {
- Value = UpstreamCachePolicy::Read;
- }
- else if (PolicyString == "writeonly")
- {
- Value = UpstreamCachePolicy::Write;
- }
- else if (PolicyString == "disabled")
- {
- Value = UpstreamCachePolicy::Disabled;
- }
- else if (PolicyString == "readwrite")
- {
- Value = UpstreamCachePolicy::ReadWrite;
- }
- }
- UpstreamCachePolicy& Value;
- };
-
- template<Integral T>
- class NumberOption : public OptionValue
- {
- public:
- NumberOption(T& Value) : Value(Value) {}
- virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
- {
- StringBuilder.Append(fmt::format("{}", Value));
- }
- virtual void Parse(sol::object Object) override { Value = Object.as<T>(); }
- T& Value;
- };
+ }
+ UpstreamCachePolicy& Value;
+};
- class LuaContainerWriter
+class ZenAuthConfigOption : public LuaConfig::OptionValue
+{
+public:
+ ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {}
+ virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
{
- public:
- LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent)
- : StringBuilder(StringBuilder)
- , InitialIndent(Indent.length())
- , LocalIndent(Indent)
- {
- StringBuilder.Append("{\n");
- LocalIndent.push_back('\t');
- }
- ~LuaContainerWriter()
+ if (Value.OpenIdProviders.empty())
{
- LocalIndent.pop_back();
- StringBuilder.Append(LocalIndent);
- StringBuilder.Append("}");
+ StringBuilder.Append("{}");
+ return;
}
-
- void BeginContainer(std::string_view Name)
+ LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent);
+ for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders)
{
- StringBuilder.Append(LocalIndent);
- if (!Name.empty())
- {
- StringBuilder.Append(Name);
- StringBuilder.Append(" = {\n");
- }
- else
+ Writer.BeginContainer("");
{
- StringBuilder.Append("{\n");
+ Writer.WriteValue("name", Config.Name);
+ Writer.WriteValue("url", Config.Url);
+ Writer.WriteValue("clientid", Config.ClientId);
}
- LocalIndent.push_back('\t');
+ Writer.EndContainer();
}
- void 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 EndContainer()
- {
- LocalIndent.pop_back();
- StringBuilder.Append(LocalIndent);
- StringBuilder.Append("}");
- StringBuilder.Append(",\n");
- }
-
- private:
- zen::StringBuilderBase& StringBuilder;
- const std::size_t InitialIndent;
- std::string LocalIndent;
- };
-
- class StringArrayOption : public OptionValue
+ }
+ virtual void Parse(sol::object Object) override
{
- public:
- StringArrayOption(std::vector<std::string>& Value) : Value(Value) {}
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
+ if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>())
{
- if (Value.empty())
- {
- StringBuilder.Append("{}");
- }
- if (Value.size() == 1)
- {
- StringBuilder.Append(fmt::format("\"{}\"", Value[0]));
- }
- else
+ for (const auto& Kv : OpenIdProviders.value())
{
- LuaContainerWriter Writer(StringBuilder, Indent);
- for (std::string String : Value)
+ if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>())
{
- Writer.WriteValue("", String);
- }
- }
- }
- virtual void Parse(sol::object Object) override
- {
- 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>());
- }
- }
- }
-
- private:
- std::vector<std::string>& Value;
- };
+ std::string Name = OpenIdProvider.value().get_or("name", std::string("Default"));
+ std::string Url = OpenIdProvider.value().get_or("url", std::string());
+ std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string());
- class ZenAuthConfigOption : public OptionValue
- {
- public:
- ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {}
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
- {
- if (Value.OpenIdProviders.empty())
- {
- StringBuilder.Append("{}");
- return;
- }
- LuaContainerWriter Writer(StringBuilder, Indent);
- for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders)
- {
- Writer.BeginContainer("");
- {
- Writer.WriteValue("name", Config.Name);
- Writer.WriteValue("url", Config.Url);
- Writer.WriteValue("clientid", Config.ClientId);
+ Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)});
}
- Writer.EndContainer();
}
}
- virtual void Parse(sol::object Object) override
- {
- if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>())
- {
- for (const auto& Kv : OpenIdProviders.value())
- {
- if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>())
- {
- std::string Name = OpenIdProvider.value().get_or("name", std::string("Default"));
- std::string Url = OpenIdProvider.value().get_or("url", std::string());
- std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string());
-
- Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)});
- }
- }
- }
- }
- ZenAuthConfig& Value;
- };
+ }
+ ZenAuthConfig& Value;
+};
- class ZenObjectStoreConfigOption : public OptionValue
+class ZenObjectStoreConfigOption : public LuaConfig::OptionValue
+{
+public:
+ ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {}
+ virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
{
- public:
- ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {}
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
+ if (Value.Buckets.empty())
{
- if (Value.Buckets.empty())
- {
- StringBuilder.Append("{}");
- return;
- }
- LuaContainerWriter Writer(StringBuilder, Indent);
- for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets)
- {
- Writer.BeginContainer("");
- {
- Writer.WriteValue("name", Config.Name);
- std::string Directory = Config.Directory.string();
- EscapeBackslash(Directory);
- Writer.WriteValue("directory", Directory);
- }
- Writer.EndContainer();
- }
+ StringBuilder.Append("{}");
+ return;
}
- virtual void Parse(sol::object Object) override
+ LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent);
+ for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets)
{
- if (sol::optional<sol::table> Buckets = Object.as<sol::table>())
+ Writer.BeginContainer("");
{
- for (const auto& Kv : Buckets.value())
- {
- if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>())
- {
- std::string Name = Bucket.value().get_or("name", std::string("Default"));
- std::string Directory = Bucket.value().get_or("directory", std::string());
-
- Value.Buckets.push_back({.Name = std::move(Name), .Directory = MakeSafePath(Directory)});
- }
- }
+ Writer.WriteValue("name", Config.Name);
+ std::string Directory = Config.Directory.string();
+ LuaConfig::EscapeBackslash(Directory);
+ Writer.WriteValue("directory", Directory);
}
+ Writer.EndContainer();
}
- ZenObjectStoreConfig& Value;
- };
-
- 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); };
-
- template<Integral T>
- std::shared_ptr<OptionValue> MakeOption(T& Value)
- {
- return std::make_shared<NumberOption<T>>(Value);
- };
-
- std::shared_ptr<OptionValue> MakeOption(bool& Value) { return std::make_shared<BoolOption>(Value); };
-
- std::shared_ptr<OptionValue> MakeOption(UpstreamCachePolicy& Value) { return std::make_shared<CachePolicyOption>(Value); };
-
- std::shared_ptr<OptionValue> MakeOption(std::vector<std::string>& Value) { return std::make_shared<StringArrayOption>(Value); };
-
- std::shared_ptr<OptionValue> MakeOption(ZenAuthConfig& Value) { return std::make_shared<ZenAuthConfigOption>(Value); };
-
- std::shared_ptr<OptionValue> MakeOption(ZenObjectStoreConfig& Value) { return std::make_shared<ZenObjectStoreConfigOption>(Value); };
-
- struct Option
- {
- std::string CommandLineOptionName;
- TOptionValue Value;
- };
-
- struct Options
+ }
+ virtual void Parse(sol::object Object) override
{
- public:
- template<typename T>
- void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "")
- {
- OptionMap.insert_or_assign(std::string(Key),
- Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)});
- };
-
- void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult)
+ if (sol::optional<sol::table> Buckets = Object.as<sol::table>())
{
- 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)
+ for (const auto& Kv : Buckets.value())
{
- 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
+ if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>())
{
- 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()));
- }
+ std::string Name = Bucket.value().get_or("name", std::string("Default"));
+ std::string Directory = Bucket.value().get_or("directory", std::string());
- config();
+ Value.Buckets.push_back({.Name = std::move(Name), .Directory = LuaConfig::MakeSafePath(Directory)});
}
- catch (std::exception& e)
- {
- throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str());
- }
-
- Parse(lua, CmdLineResult);
- }
- }
-
- void 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 Touch(std::string_view Key) { UsedKeys.insert(std::string(Key)); }
-
- void 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();
}
}
+ }
+ ZenObjectStoreConfig& Value;
+};
- private:
- void 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;
- }
- }
- }
- }
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(zen::UpstreamCachePolicy& Value)
+{
+ return std::make_shared<CachePolicyOption>(Value);
+};
- std::unordered_map<std::string, Option> OptionMap;
- std::unordered_set<std::string> UsedKeys;
- };
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(zen::ZenAuthConfig& Value)
+{
+ return std::make_shared<ZenAuthConfigOption>(Value);
+};
-} // namespace LuaConfig
+std::shared_ptr<LuaConfig::OptionValue>
+MakeOption(zen::ZenObjectStoreConfig& Value)
+{
+ return std::make_shared<ZenObjectStoreConfigOption>(Value);
+};
void
ParseConfigFile(const std::filesystem::path& Path,
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
diff --git a/src/zenserver/config/luaconfig.h b/src/zenserver/config/luaconfig.h
new file mode 100644
index 000000000..76b3088a3
--- /dev/null
+++ b/src/zenserver/config/luaconfig.h
@@ -0,0 +1,139 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zenbase/concepts.h>
+#include <zencore/fmtutils.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <fmt/format.h>
+#include <cxxopts.hpp>
+#include <sol/sol.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+#include <filesystem>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace zen::LuaConfig {
+
+std::string MakeSafePath(const std::string_view Path);
+void EscapeBackslash(std::string& InOutString);
+
+class OptionValue
+{
+public:
+ virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0;
+ virtual void Parse(sol::object Object) = 0;
+
+ virtual ~OptionValue() {}
+};
+
+class StringOption : public OptionValue
+{
+public:
+ explicit StringOption(std::string& Value);
+ virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override;
+ virtual void Parse(sol::object Object) override;
+ std::string& Value;
+};
+
+class FilePathOption : public OptionValue
+{
+public:
+ explicit FilePathOption(std::filesystem::path& Value);
+ virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override;
+ virtual void Parse(sol::object Object) override;
+ std::filesystem::path& Value;
+};
+
+class BoolOption : public OptionValue
+{
+public:
+ explicit BoolOption(bool& Value);
+ virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder);
+ virtual void Parse(sol::object Object);
+ bool& Value;
+};
+
+template<Integral T>
+class NumberOption : public OptionValue
+{
+public:
+ explicit NumberOption(T& Value) : Value(Value) {}
+ virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(fmt::format("{}", Value)); }
+ virtual void Parse(sol::object Object) override { Value = Object.as<T>(); }
+ T& Value;
+};
+
+class LuaContainerWriter
+{
+public:
+ LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent);
+ ~LuaContainerWriter();
+ void BeginContainer(std::string_view Name);
+ void WriteValue(std::string_view Name, std::string_view Value);
+ void EndContainer();
+
+private:
+ zen::StringBuilderBase& StringBuilder;
+ const std::size_t InitialIndent;
+ std::string LocalIndent;
+};
+
+class StringArrayOption : public OptionValue
+{
+public:
+ explicit StringArrayOption(std::vector<std::string>& Value);
+ virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override;
+ virtual void Parse(sol::object Object) override;
+
+private:
+ std::vector<std::string>& Value;
+};
+
+std::shared_ptr<OptionValue> MakeOption(std::string& Value);
+std::shared_ptr<OptionValue> MakeOption(std::filesystem::path& Value);
+
+template<Integral T>
+std::shared_ptr<OptionValue>
+MakeOption(T& Value)
+{
+ return std::make_shared<NumberOption<T>>(Value);
+};
+
+std::shared_ptr<OptionValue> MakeOption(bool& Value);
+std::shared_ptr<OptionValue> MakeOption(std::vector<std::string>& Value);
+
+struct Option
+{
+ std::string CommandLineOptionName;
+ std::shared_ptr<OptionValue> Value;
+};
+
+struct Options
+{
+public:
+ template<typename T>
+ void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "")
+ {
+ OptionMap.insert_or_assign(std::string(Key),
+ Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)});
+ };
+
+ void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult);
+ void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult);
+ void Touch(std::string_view Key);
+ void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult);
+
+private:
+ void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult);
+
+ std::unordered_map<std::string, Option> OptionMap;
+ std::unordered_set<std::string> UsedKeys;
+};
+
+} // namespace zen::LuaConfig