// Copyright Epic Games, Inc. All Rights Reserved. #include "config.h" #include "storage/storageconfig.h" #include "config/luaconfig.h" #include "diag/logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include #include ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # include #else # include #endif namespace zen { std::filesystem::path PickDefaultStateDirectory(std::filesystem::path SystemRoot) { if (SystemRoot.empty()) return SystemRoot; return SystemRoot / "Data"; } void EmitCentralManifest(const std::filesystem::path& SystemRoot, Oid Identifier, CbObject Manifest, std::filesystem::path ManifestPath) { CbObjectWriter Cbo; Cbo << "path" << ManifestPath.generic_wstring(); Cbo << "manifest" << Manifest; const std::filesystem::path StatesPath = SystemRoot / "States"; CreateDirectories(StatesPath); WriteFile(StatesPath / fmt::format("{}", Identifier), Cbo.Save().GetBuffer().AsIoBuffer()); } std::vector ReadAllCentralManifests(const std::filesystem::path& SystemRoot) { std::vector Manifests; DirectoryContent Content; GetDirectoryContent(SystemRoot / "States", DirectoryContentFlags::IncludeFiles, Content); for (std::filesystem::path& File : Content.Files) { try { FileContents FileData = ReadFile(File); CbValidateError ValidateError; if (CbObject Manifest = ValidateAndReadCompactBinaryObject(FileData.Flatten(), ValidateError); ValidateError == CbValidateError::None) { Manifests.emplace_back(std::move(Manifest)); } else { ZEN_WARN("failed to load manifest '{}': {}", File, ToString(ValidateError)); } } catch (const std::exception& Ex) { ZEN_WARN("failed to load manifest '{}': {}", File, Ex.what()); } } return Manifests; } void ZenServerConfiguratorBase::ParseEnvVariables(const cxxopts::ParseResult& CmdLineResult) { using namespace std::literals; EnvironmentOptions Options; ZenServerConfig& ServerOptions = m_ServerOptions; bool EnvEnableSentry = !ServerOptions.SentryConfig.Disable; // clang-format off Options.AddOption("UE_ZEN_SENTRY_ALLOWPERSONALINFO"sv, ServerOptions.SentryConfig.AllowPII, "sentry-allow-personal-info"sv); Options.AddOption("UE_ZEN_SENTRY_DSN"sv, ServerOptions.SentryConfig.Dsn, "sentry-dsn"sv); Options.AddOption("UE_ZEN_SENTRY_ENVIRONMENT"sv, ServerOptions.SentryConfig.Environment, "sentry-environment"sv); Options.AddOption("UE_ZEN_SENTRY_ENABLED"sv, EnvEnableSentry, "no-sentry"sv); Options.AddOption("UE_ZEN_SENTRY_DEBUG"sv, ServerOptions.SentryConfig.Debug, "sentry-debug"sv); // clang-format on Options.Parse(CmdLineResult); if (EnvEnableSentry != !ServerOptions.SentryConfig.Disable) { ServerOptions.SentryConfig.Disable = !EnvEnableSentry; } } void ZenServerConfiguratorBase::AddCommonConfigOptions(LuaConfig::Options& LuaOptions) { using namespace std::literals; ZenServerConfig& ServerOptions = m_ServerOptions; // logging LuaOptions.AddOption("server.logid"sv, ServerOptions.LoggingConfig.LogId, "log-id"sv); LuaOptions.AddOption("server.abslog"sv, ServerOptions.LoggingConfig.AbsLogFile, "abslog"sv); LuaOptions.AddOption("server.otlpendpoint"sv, ServerOptions.LoggingConfig.OtelEndpointUri, "otlp-endpoint"sv); LuaOptions.AddOption("server.quiet"sv, ServerOptions.LoggingConfig.QuietConsole, "quiet"sv); LuaOptions.AddOption("server.noconsole"sv, ServerOptions.LoggingConfig.NoConsoleOutput, "noconsole"sv); // server LuaOptions.AddOption("server.dedicated"sv, ServerOptions.IsDedicated, "dedicated"sv); LuaOptions.AddOption("server.sentry.disable"sv, ServerOptions.SentryConfig.Disable, "no-sentry"sv); LuaOptions.AddOption("server.sentry.allowpersonalinfo"sv, ServerOptions.SentryConfig.AllowPII, "sentry-allow-personal-info"sv); LuaOptions.AddOption("server.sentry.dsn"sv, ServerOptions.SentryConfig.Dsn, "sentry-dsn"sv); LuaOptions.AddOption("server.sentry.environment"sv, ServerOptions.SentryConfig.Environment, "sentry-environment"sv); LuaOptions.AddOption("server.sentry.debug"sv, ServerOptions.SentryConfig.Debug, "sentry-debug"sv); LuaOptions.AddOption("server.systemrootdir"sv, ServerOptions.SystemRootDir, "system-dir"sv); LuaOptions.AddOption("server.datadir"sv, ServerOptions.DataDir, "data-dir"sv); LuaOptions.AddOption("server.contentdir"sv, ServerOptions.ContentDir, "content-dir"sv); LuaOptions.AddOption("server.debug"sv, ServerOptions.IsDebug, "debug"sv); LuaOptions.AddOption("server.clean"sv, ServerOptions.IsCleanStart, "clean"sv); LuaOptions.AddOption("server.security.configpath"sv, ServerOptions.SecurityConfigPath, "security-config-path"sv); ////// network LuaOptions.AddOption("network.httpclientbackend"sv, ServerOptions.HttpClient.Backend, "httpclient"sv); LuaOptions.AddOption("network.httpserverclass"sv, ServerOptions.HttpConfig.ServerClass, "http"sv); LuaOptions.AddOption("network.httpserverthreads"sv, ServerOptions.HttpConfig.ThreadCount, "http-threads"sv); LuaOptions.AddOption("network.port"sv, ServerOptions.BasePort, "port"sv); LuaOptions.AddOption("network.forceloopback"sv, ServerOptions.HttpConfig.ForceLoopback, "http-forceloopback"sv); LuaOptions.AddOption("network.unixsocket"sv, ServerOptions.HttpConfig.UnixSocketPath, "unix-socket"sv); LuaOptions.AddOption("network.nonetwork"sv, ServerOptions.HttpConfig.NoNetwork, "no-network"sv); LuaOptions.AddOption("network.https.port"sv, ServerOptions.HttpConfig.HttpsPort, "https-port"sv); LuaOptions.AddOption("network.https.certfile"sv, ServerOptions.HttpConfig.CertFile, "cert-file"sv); LuaOptions.AddOption("network.https.keyfile"sv, ServerOptions.HttpConfig.KeyFile, "key-file"sv); #if ZEN_WITH_HTTPSYS LuaOptions.AddOption("network.httpsys.async.workthreads"sv, ServerOptions.HttpConfig.HttpSys.AsyncWorkThreadCount, "httpsys-async-work-threads"sv); LuaOptions.AddOption("network.httpsys.async.response"sv, ServerOptions.HttpConfig.HttpSys.IsAsyncResponseEnabled, "httpsys-enable-async-response"sv); LuaOptions.AddOption("network.httpsys.requestlogging"sv, ServerOptions.HttpConfig.HttpSys.IsRequestLoggingEnabled, "httpsys-enable-request-logging"sv); LuaOptions.AddOption("network.httpsys.httpsport"sv, ServerOptions.HttpConfig.HttpSys.HttpsPort, "httpsys-https-port"sv); LuaOptions.AddOption("network.httpsys.certthumbprint"sv, ServerOptions.HttpConfig.HttpSys.CertThumbprint, "httpsys-cert-thumbprint"sv); LuaOptions.AddOption("network.httpsys.certstorename"sv, ServerOptions.HttpConfig.HttpSys.CertStoreName, "httpsys-cert-store"sv); LuaOptions.AddOption("network.httpsys.httpsonly"sv, ServerOptions.HttpConfig.HttpSys.HttpsOnly, "httpsys-https-only"sv); #endif #if ZEN_WITH_TRACE ////// trace LuaOptions.AddOption("trace.channels"sv, ServerOptions.TraceCmdLineOptions.Channels, "trace"sv); LuaOptions.AddOption("trace.host"sv, ServerOptions.TraceCmdLineOptions.Host, "tracehost"sv); LuaOptions.AddOption("trace.file"sv, ServerOptions.TraceCmdLineOptions.File, "tracefile"sv); #endif ////// stats LuaOptions.AddOption("stats.enable"sv, ServerOptions.StatsConfig.Enabled, "statsd"sv); LuaOptions.AddOption("stats.host"sv, ServerOptions.StatsConfig.StatsdHost); LuaOptions.AddOption("stats.port"sv, ServerOptions.StatsConfig.StatsdPort); } struct ZenServerCmdLineOptions { // Note to those adding future options; std::filesystem::path-type options // must be read into a std::string first. As of cxxopts-3.0.0 it uses a >> // stream operator to convert argv value into the options type. std::fs::path // expects paths in streams to be quoted but argv paths are unquoted. By // going into a std::string first, paths with whitespace parse correctly. std::string ConfigFile; std::string OutputConfigFile; std::string SystemRootDir; std::string ContentDir; std::string DataDir; std::string BaseSnapshotDir; std::string SecurityConfigPath; std::string UnixSocketPath; std::string PortStr; ZenLoggingCmdLineOptions LoggingOptions; void AddCliOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions); void ApplyOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions); }; void ZenServerCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions) { const char* DefaultHttp = "asio"; #if ZEN_WITH_HTTPSYS if (!windows::IsRunningOnWine()) { DefaultHttp = "httpsys"; } #endif options.add_options()("dedicated", "Enable dedicated server mode", cxxopts::value(ServerOptions.IsDedicated)->default_value("false")); options.add_options()("d, debug", "Enable debugging", cxxopts::value(ServerOptions.IsDebug)->default_value("false")); options.add_options()("clean", "Clean out all state at startup", cxxopts::value(ServerOptions.IsCleanStart)->default_value("false")); options.add_options()("help", "Show command line help"); options.add_options()("t, test", "Enable test mode", cxxopts::value(ServerOptions.IsTest)->default_value("false")); options.add_options()("data-dir", "Specify persistence root", cxxopts::value(DataDir)); options.add_options()("system-dir", "Specify system root", cxxopts::value(SystemRootDir)); options.add_options()("content-dir", "Frontend content directory", cxxopts::value(ContentDir)); options.add_options()("config", "Path to Lua config file", cxxopts::value(ConfigFile)); options.add_options()("write-config", "Path to output Lua config file", cxxopts::value(OutputConfigFile)); options.add_options()("snapshot-dir", "Specify a snapshot of server state to mirror into the persistence root at startup", cxxopts::value(BaseSnapshotDir)); options.add_options()("no-sentry", "Disable Sentry crash handler", cxxopts::value(ServerOptions.SentryConfig.Disable)->default_value("false")); options.add_options()("sentry-allow-personal-info", "Allow personally identifiable information in sentry crash reports", cxxopts::value(ServerOptions.SentryConfig.AllowPII)->default_value("false")); options.add_options()("sentry-dsn", "Sentry DSN to send events to", cxxopts::value(ServerOptions.SentryConfig.Dsn)); options.add_options()("sentry-environment", "Sentry environment", cxxopts::value(ServerOptions.SentryConfig.Environment)); options.add_options()("sentry-debug", "Enable debug mode for Sentry", cxxopts::value(ServerOptions.SentryConfig.Debug)->default_value("false")); options.add_options()("detach", "Indicate whether zenserver should detach from parent process group", cxxopts::value(ServerOptions.Detach)->default_value("true")); options.add_options()("malloc", "Configure memory allocator subsystem", cxxopts::value(ServerOptions.MemoryOptions)->default_value("mimalloc")); options.add_options()("corelimit", "Limit concurrency", cxxopts::value(ServerOptions.CoreLimit)); options.add_options()("powercycle", "Exit immediately after initialization is complete", cxxopts::value(ServerOptions.IsPowerCycle)); options.add_option("diagnostics", "", "crash", "Simulate a crash", cxxopts::value(ServerOptions.ShouldCrash)->default_value("false"), ""); LoggingOptions.AddCliOptions(options, ServerOptions.LoggingConfig); options .add_option("lifetime", "", "owner-pid", "Specify owning process id", cxxopts::value(ServerOptions.OwnerPid), ""); options.add_option("lifetime", "", "child-id", "Specify id which can be used to signal parent", cxxopts::value(ServerOptions.ChildId), ""); #if ZEN_PLATFORM_WINDOWS options.add_option("lifetime", "", "install", "Install zenserver as a Windows service", cxxopts::value(ServerOptions.InstallService), ""); options.add_option("lifetime", "", "uninstall", "Uninstall zenserver as a Windows service", cxxopts::value(ServerOptions.UninstallService), ""); #endif options.add_option("network", "", "http-threads", "Number of http server connection threads", cxxopts::value(ServerOptions.HttpConfig.ThreadCount), ""); options.add_option("network", "p", "port", "Select HTTP port", cxxopts::value(PortStr)->default_value("auto"), ""); options.add_option("network", "", "http-forceloopback", "Force using local loopback interface", cxxopts::value(ServerOptions.HttpConfig.ForceLoopback)->default_value("false"), ""); options.add_option("network", "", "unix-socket", "Unix domain socket path to listen on (in addition to TCP)", cxxopts::value(UnixSocketPath), ""); options.add_option("network", "", "no-network", "Disable TCP/HTTPS listeners; only accept connections via --unix-socket", cxxopts::value(ServerOptions.HttpConfig.NoNetwork)->default_value("false"), ""); options.add_option("network", "", "https-port", "HTTPS listen port (0 = disabled)", cxxopts::value(ServerOptions.HttpConfig.HttpsPort)->default_value("0"), ""); options.add_option("network", "", "cert-file", "Path to PEM certificate chain file for HTTPS", cxxopts::value(ServerOptions.HttpConfig.CertFile), ""); options.add_option("network", "", "key-file", "Path to PEM private key file for HTTPS", cxxopts::value(ServerOptions.HttpConfig.KeyFile), ""); options.add_option("network", "", "security-config-path", "Path to http security configuration file", cxxopts::value(SecurityConfigPath), ""); #if ZEN_WITH_HTTPSYS options.add_option("httpsys", "", "httpsys-async-work-threads", "Number of HttpSys async worker threads", cxxopts::value(ServerOptions.HttpConfig.HttpSys.AsyncWorkThreadCount), ""); options.add_option("httpsys", "", "httpsys-enable-async-response", "Enables Httpsys async response", cxxopts::value(ServerOptions.HttpConfig.HttpSys.IsAsyncResponseEnabled)->default_value("true"), ""); options.add_option("httpsys", "", "httpsys-enable-request-logging", "Enables Httpsys request logging", cxxopts::value(ServerOptions.HttpConfig.HttpSys.IsRequestLoggingEnabled), ""); options.add_option("httpsys", "", "httpsys-https-port", "HTTPS listen port for http.sys (0 = disabled)", cxxopts::value(ServerOptions.HttpConfig.HttpSys.HttpsPort)->default_value("0"), ""); options.add_option("httpsys", "", "httpsys-cert-thumbprint", "SHA-1 certificate thumbprint for auto SSL binding", cxxopts::value(ServerOptions.HttpConfig.HttpSys.CertThumbprint), ""); options.add_option("httpsys", "", "httpsys-cert-store", "Windows certificate store name for SSL binding", cxxopts::value(ServerOptions.HttpConfig.HttpSys.CertStoreName)->default_value("MY"), ""); options.add_option("httpsys", "", "httpsys-https-only", "Disable HTTP listener when HTTPS is active", cxxopts::value(ServerOptions.HttpConfig.HttpSys.HttpsOnly)->default_value("false"), ""); #endif options.add_option("network", "", "httpclient", "Select HTTP client implementation (e.g. 'curl', 'cpr')", cxxopts::value(ServerOptions.HttpClient.Backend)->default_value("curl"), ""); options.add_option("network", "", "http", "Select HTTP server implementation (asio|" #if ZEN_WITH_HTTPSYS "httpsys|" #endif "null)", cxxopts::value(ServerOptions.HttpConfig.ServerClass)->default_value(DefaultHttp), ""); #if ZEN_WITH_TRACE // We only have this in options for command line help purposes - we parse these argument separately earlier using // GetTraceOptionsFromCommandline() options.add_option("ue-trace", "", "trace", "Specify which trace channels should be enabled", cxxopts::value(ServerOptions.TraceCmdLineOptions.Channels)->default_value(""), ""); options.add_option("ue-trace", "", "tracehost", "Hostname to send the trace to", cxxopts::value(ServerOptions.TraceCmdLineOptions.Host)->default_value(""), ""); options.add_option("ue-trace", "", "tracefile", "Path to write a trace to", cxxopts::value(ServerOptions.TraceCmdLineOptions.File)->default_value(""), ""); #endif // ZEN_WITH_TRACE options.add_option("stats", "", "statsd", "", cxxopts::value(ServerOptions.StatsConfig.Enabled)->default_value("false"), "Enable statsd reporter (localhost:8125)"); } void ZenServerCmdLineOptions::ApplyOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions) { if (!BaseSnapshotDir.empty()) { if (ServerOptions.DataDir.empty()) throw OptionParseException("'--snapshot-dir' requires '--data-dir'", options.help()); if (!IsDir(ServerOptions.BaseSnapshotDir)) throw std::runtime_error(fmt::format("'--snapshot-dir' ('{}') must be a directory", ServerOptions.BaseSnapshotDir)); } ServerOptions.SystemRootDir = MakeSafeAbsolutePath(SystemRootDir); ServerOptions.DataDir = MakeSafeAbsolutePath(DataDir); ServerOptions.ContentDir = MakeSafeAbsolutePath(ContentDir); ServerOptions.ConfigFile = MakeSafeAbsolutePath(ConfigFile); ServerOptions.BaseSnapshotDir = MakeSafeAbsolutePath(BaseSnapshotDir); ServerOptions.SecurityConfigPath = MakeSafeAbsolutePath(SecurityConfigPath); if (!UnixSocketPath.empty()) { ServerOptions.HttpConfig.UnixSocketPath = MakeSafeAbsolutePath(UnixSocketPath); } if (PortStr != "auto") { int Port = 0; auto [Ptr, Ec] = std::from_chars(PortStr.data(), PortStr.data() + PortStr.size(), Port); if (Ec != std::errc{} || Ptr != PortStr.data() + PortStr.size() || Port <= 0 || Port > 65535) { throw OptionParseException(fmt::format("invalid port '{}': expected 'auto' or a number between 1 and 65535", PortStr), options.help()); } ServerOptions.BasePort = Port; } LoggingOptions.ApplyOptions(ServerOptions.LoggingConfig); #if ZEN_WITH_HTTPSYS // Validate HTTPS options const auto& HttpSys = ServerOptions.HttpConfig.HttpSys; if (HttpSys.HttpsOnly && HttpSys.HttpsPort == 0) { throw OptionParseException("'--httpsys-https-only' requires '--httpsys-https-port' to be set", options.help()); } if (!HttpSys.CertThumbprint.empty() && HttpSys.CertThumbprint.size() != 40) { throw OptionParseException("'--httpsys-cert-thumbprint' must be exactly 40 hex characters (SHA-1)", options.help()); } if (!HttpSys.CertThumbprint.empty()) { for (char Ch : HttpSys.CertThumbprint) { if (!((Ch >= '0' && Ch <= '9') || (Ch >= 'a' && Ch <= 'f') || (Ch >= 'A' && Ch <= 'F'))) { throw OptionParseException("'--httpsys-cert-thumbprint' contains non-hex characters", options.help()); } } } if (HttpSys.HttpsPort > 0 && HttpSys.HttpsPort == ServerOptions.BasePort && !HttpSys.HttpsOnly) { throw OptionParseException("'--httpsys-https-port' must differ from '--port' when both HTTP and HTTPS are active", options.help()); } #endif // Validate --no-network if (ServerOptions.HttpConfig.NoNetwork) { if (ServerOptions.HttpConfig.UnixSocketPath.empty()) { throw OptionParseException("'--no-network' requires '--unix-socket' to be set", options.help()); } #if ZEN_WITH_HTTPSYS if (ServerOptions.HttpConfig.ServerClass == "httpsys") { throw OptionParseException("'--no-network' is not compatible with '--http=httpsys'", options.help()); } #endif if (ServerOptions.HttpConfig.ServerClass.empty()) { ServerOptions.HttpConfig.ServerClass = "asio"; } } // Validate generic HTTPS options (used by ASIO backend) if (ServerOptions.HttpConfig.HttpsPort > 0) { if (ServerOptions.HttpConfig.CertFile.empty() || ServerOptions.HttpConfig.KeyFile.empty()) { throw OptionParseException("'--https-port' requires both '--cert-file' and '--key-file' to be set", options.help()); } if (!std::filesystem::exists(ServerOptions.HttpConfig.CertFile)) { throw OptionParseException(fmt::format("'--cert-file' path '{}' does not exist", ServerOptions.HttpConfig.CertFile), options.help()); } if (!std::filesystem::exists(ServerOptions.HttpConfig.KeyFile)) { throw OptionParseException(fmt::format("'--key-file' path '{}' does not exist", ServerOptions.HttpConfig.KeyFile), options.help()); } if (ServerOptions.HttpConfig.HttpsPort == ServerOptions.BasePort) { throw OptionParseException("'--https-port' must differ from '--port'", options.help()); } } } ////////////////////////////////////////////////////////////////////////// ZenServerConfiguratorBase::ZenServerConfiguratorBase(ZenServerConfig& BaseServerOptions) : m_ServerOptions(BaseServerOptions) { } ZenServerConfiguratorBase::~ZenServerConfiguratorBase() { } void ZenServerConfiguratorBase::Configure(int argc, char* argv[]) { for (int i = 0; i < argc; ++i) { if (i) { m_ServerOptions.CommandLine.push_back(' '); } m_ServerOptions.CommandLine += argv[i]; } cxxopts::Options options("zenserver", "Zen Storage Server"); options.set_width(TuiConsoleColumns(80)); ZenServerCmdLineOptions BaseOptions; BaseOptions.AddCliOptions(options, m_ServerOptions); // Add subclass' command line options AddCliOptions(options); try { cxxopts::ParseResult Result; try { Result = options.parse(argc, argv); } catch (const std::exception& Ex) { throw OptionParseException(Ex.what(), options.help()); } if (Result.count("help")) { ZEN_CONSOLE("{}", options.help()); #if ZEN_PLATFORM_WINDOWS ZEN_CONSOLE("Press any key to exit!"); _getch(); #else // Assume the user's in a terminal on all other platforms and that // they'll use less/more/etc. if need be. #endif exit(0); } #if ZEN_WITH_TRACE if (!m_ServerOptions.HasTraceCommandlineOptions) { // Apply any Lua settings if we don't have them set from the command line TraceConfigure(m_ServerOptions.TraceCmdLineOptions); } #endif ApplyLoggingOptions(options, m_ServerOptions.LoggingConfig); BaseOptions.ApplyOptions(options, m_ServerOptions); ApplyOptions(options); // Handle any environment variables ParseEnvVariables(Result); ZEN_TRACE_CPU("ConfigParse"); if (!m_ServerOptions.ConfigFile.empty()) { ParseConfigFile(m_ServerOptions.ConfigFile, Result, BaseOptions.OutputConfigFile); } else { ParseConfigFile(m_ServerOptions.DataDir / "zen_cfg.lua", Result, BaseOptions.OutputConfigFile); } ValidateOptions(); // subclass validation // Resolve auto port: subclass ValidateOptions may have set a // mode-specific default; if BasePort is still 0, fall back to the // global default. if (m_ServerOptions.BasePort == 0) { m_ServerOptions.BasePort = ZenServerConfig::kDefaultBasePort; } } catch (const OptionParseException& e) { ZEN_CONSOLE("{}\n", options.help()); ZEN_CONSOLE_ERROR("Invalid zenserver arguments: {}", e.what()); throw; } if (m_ServerOptions.SystemRootDir.empty()) { m_ServerOptions.SystemRootDir = PickDefaultSystemRootDirectory(); } if (m_ServerOptions.DataDir.empty()) { m_ServerOptions.DataDir = PickDefaultStateDirectory(m_ServerOptions.SystemRootDir); } if (m_ServerOptions.LoggingConfig.AbsLogFile.empty()) { m_ServerOptions.LoggingConfig.AbsLogFile = m_ServerOptions.DataDir / "logs" / "zenserver.log"; } m_ServerOptions.HttpConfig.IsDedicatedServer = m_ServerOptions.IsDedicated; } void ZenServerConfiguratorBase::ParseConfigFile(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult, std::string_view OutputConfigFile) { ZEN_TRACE_CPU("ParseConfigFile"); LuaConfig::Options LuaOptions; AddCommonConfigOptions(LuaOptions); AddConfigOptions(LuaOptions); // subclass options LuaOptions.Parse(Path, CmdLineResult); OnConfigFileParsed(LuaOptions); // subclass hook if (!OutputConfigFile.empty()) { ExtendableStringBuilder<512> ConfigStringBuilder; LuaOptions.Print(ConfigStringBuilder, CmdLineResult); BasicFile Output; std::filesystem::path WritePath(MakeSafeAbsolutePath(OutputConfigFile)); Output.Open(WritePath, BasicFile::Mode::kTruncate); Output.Write(ConfigStringBuilder.Data(), ConfigStringBuilder.Size(), 0); } } } // namespace zen