From 8f674d2b6201c9c081099b45648c58e302c075b8 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 8 Jun 2019 17:03:37 -0400 Subject: [PATCH 1/7] Added config hierarchy checking - Added config hierarchy checking to prevent infinite loops - Updated GitIgnore --- .gitignore | 202 +++++++++++++++++++++----- MultiAdmin/Config/MultiAdminConfig.cs | 19 +++ MultiAdmin/Server.cs | 9 ++ 3 files changed, 197 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index edbae2c..1a269a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ + +# Created by https://www.gitignore.io/api/git,rider,linux,macos,csharp,windows,monodevelop +# Edit at https://www.gitignore.io/?templates=git,rider,linux,macos,csharp,windows,monodevelop + +### Csharp ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## @@ -13,6 +18,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Mono auto generated files +mono_crash.* + # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -206,6 +214,8 @@ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx +*.appxbundle +*.appxupload # Visual Studio cache files # files ending in .cache can be ignored @@ -231,8 +241,6 @@ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ -# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true -**/wwwroot/lib/ # RIA/Silverlight projects Generated_Code/ @@ -257,6 +265,7 @@ ServiceFabricBackup/ *.bim.layout *.bim_*.settings *.rptproj.rsuser +*- Backup*.rdl # Microsoft Fakes FakesAssemblies/ @@ -292,10 +301,6 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - # CodeRush personal settings .cr/personal @@ -340,37 +345,168 @@ ASALocalRun/ # BeatPulse healthcheck temp database healthchecksdb -# Common IntelliJ Platform excludes +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory -# User specific -**/.idea/**/workspace.xml -**/.idea/**/tasks.xml -**/.idea/shelf/* -**/.idea/dictionaries +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### MonoDevelop ### +#User Specific +*.usertasks + +#Mono Project Files +*.resources +test-results/ + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml # Sensitive or high-churn files -**/.idea/**/dataSources/ -**/.idea/**/dataSources.ids -**/.idea/**/dataSources.xml -**/.idea/**/dataSources.local.xml -**/.idea/**/sqlDataSources.xml -**/.idea/**/dynamic.xml - -# Rider -# Rider auto-generates .iml files, and contentModel.xml -**/.idea/**/*.iml -**/.idea/**/contentModel.xml -**/.idea/**/modules.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml -*.suo -*.user -.vs/ -[Bb]in/ -[Oo]bj/ -_UpgradeReport_Files/ -[Pp]ackages/ +# Gradle +.idea/**/gradle.xml +.idea/**/libraries +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# JetBrains templates +**___jb_tmp___ + +### Windows ### +# Windows thumbnail cache files Thumbs.db -Desktop.ini -.DS_Store +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk +# End of https://www.gitignore.io/api/git,rider,linux,macos,csharp,windows,monodevelop diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index e098377..a84c725 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -185,5 +185,24 @@ public bool ConfigOrGlobalConfigContains(string key) { return ConfigContains(key) || GlobalConfig.ConfigContains(key); } + + public bool ConfigHierarchyContainsPath(string path) + { + string fullPath = Utils.GetFullPathSafe(path); + + if (!string.IsNullOrEmpty(fullPath)) + { + MultiAdminConfig config = this; + while (config != null) + { + if (config.Config?.ConfigPath == path) + return true; + + config = config.ParentConfig; + } + } + + return false; + } } } diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index 032dde5..b5f8b07 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -50,14 +50,23 @@ public Server(string serverId = null, string configLocation = null, uint? port = // Load config serverConfig = MultiAdminConfig.GlobalConfig; + // Load config hierarchy string serverConfigLocation = this.configLocation; while (!string.IsNullOrEmpty(serverConfigLocation)) { + // Update the Server object's config location with the valid config location this.configLocation = serverConfigLocation; + // Load the child MultiAdminConfig serverConfig = new MultiAdminConfig(serverConfigLocation + Path.DirectorySeparatorChar + MultiAdminConfig.ConfigFileName, serverConfig); + // Set the server config location to the value from the config, this should be empty or null if there is no valid value serverConfigLocation = Utils.GetFullPathSafe(serverConfig.ConfigLocation.Value); + + // If the config hierarchy already contains the MultiAdmin config from the target path, stop looping + // Without this, a user could unintentionally cause a lockup when their server starts up due to infinite looping + if (serverConfig.ConfigHierarchyContainsPath(serverConfigLocation)) + break; } // Set port From e1bfee8472b35dfc692bebfe5748db04350669f4 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 8 Jun 2019 17:17:40 -0400 Subject: [PATCH 2/7] Remove unnecessary array from scpslArgs --- MultiAdmin/Server.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index b5f8b07..63ecd6c 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -288,7 +288,7 @@ public void StartServer(bool restartOnCrash = true) Write($"Executing \"{scpslExe}\"...", ConsoleColor.DarkGreen); - List scpslArgs = new List(new string[] + List scpslArgs = new List { "-batchmode", "-nographics", @@ -297,7 +297,7 @@ public void StartServer(bool restartOnCrash = true) $"-key{SessionId}", $"-id{Process.GetCurrentProcess().Id}", $"-port{port ?? ServerConfig.Port.Value}" - }); + }; if (string.IsNullOrEmpty(ScpLogFile) || ServerConfig.NoLog.Value) { From b4db8516351dbc8f8c076f2e64d80d2cb9cfabde Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 8 Jun 2019 19:08:35 -0400 Subject: [PATCH 3/7] Fixed MultiAdminConfig inheritance - Added config inheritance value to ConfigEntry, allowing for inheritable choice for each ConfigEntry - Added MultiAdminConfig hierarchy methods - Added MultiAdminConfig parent printing to Server --- MultiAdmin/Config/Config.cs | 35 ++-- .../Config/ConfigHandler/ConfigEntry.cs | 52 +++-- MultiAdmin/Config/MultiAdminConfig.cs | 187 +++++++++++++----- MultiAdmin/Server.cs | 10 +- 4 files changed, 209 insertions(+), 75 deletions(-) diff --git a/MultiAdmin/Config/Config.cs b/MultiAdmin/Config/Config.cs index 8363098..2c3cf8a 100644 --- a/MultiAdmin/Config/Config.cs +++ b/MultiAdmin/Config/Config.cs @@ -15,21 +15,30 @@ public Config(string path) ReadConfigFile(path); } - public string ConfigPath { get; private set; } + private string internalConfigPath; + + public string ConfigPath + { + get => internalConfigPath; + private set + { + try + { + internalConfigPath = Utils.GetFullPathSafe(value); + } + catch (Exception e) + { + internalConfigPath = value; + Program.LogDebugException(nameof(ConfigPath), e); + } + } + } public void ReadConfigFile(string configPath) { if (string.IsNullOrEmpty(configPath)) return; ConfigPath = configPath; - try - { - ConfigPath = Utils.GetFullPathSafe(ConfigPath); - } - catch (Exception e) - { - Program.LogDebugException(nameof(ReadConfigFile), e); - } try { @@ -37,11 +46,9 @@ public void ReadConfigFile(string configPath) } catch (Exception e) { - new ColoredMessage[] - { - new ColoredMessage($"Error while reading config (Path = {ConfigPath ?? "Null"}):", ConsoleColor.Red), - new ColoredMessage(e.ToString(), ConsoleColor.Red) - }.WriteLines(); + Program.LogDebugException(nameof(ReadConfigFile), e); + + new ColoredMessage[] {new ColoredMessage($"Error while reading config (Path = {ConfigPath ?? "Null"}):", ConsoleColor.Red), new ColoredMessage(e.ToString(), ConsoleColor.Red)}.WriteLines(); } } diff --git a/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs b/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs index bc11202..1268005 100644 --- a/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs +++ b/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs @@ -12,16 +12,6 @@ public abstract class ConfigEntry /// public string Key { get; } - /// - /// The name of the . - /// - public string Name { get; } - - /// - /// The description of the . - /// - public string Description { get; } - /// /// The type of the value of the . /// @@ -38,15 +28,39 @@ public abstract class ConfigEntry public abstract object ObjectDefault { get; set; } /// - /// Creates a basic with no values. + /// Whether to inherit this config value from the 's parent s if they support value inheritance. + /// + public bool Inherit { get; } + + /// + /// The name of the . + /// + public string Name { get; } + + /// + /// The description of the . /// - public ConfigEntry(string key, string name = null, string description = null) + public string Description { get; } + + /// + /// Creates a basic with no values and indication for whether to inherit the value. + /// + public ConfigEntry(string key, bool inherit = true, string name = null, string description = null) { Key = key; - Name = name; + Inherit = inherit; + + Name = name; Description = description; } + + /// + /// Creates a basic with no values. + /// + public ConfigEntry(string key, string name = null, string description = null) : this(key, true, name, description) + { + } } /// @@ -81,11 +95,19 @@ public override object ObjectDefault /// /// - /// Creates a with the provided type and provided default value. + /// Creates a with the provided type, default value, and indication for whether to inherit the value. /// - public ConfigEntry(string key, T defaultValue = default, string name = null, string description = null) : base(key, name, description) + public ConfigEntry(string key, T defaultValue = default, bool inherit = true, string name = null, string description = null) : base(key, inherit, name, description) { Default = defaultValue; } + + /// + /// + /// Creates a with the provided type and default value. + /// + public ConfigEntry(string key, T defaultValue = default, string name = null, string description = null) : this(key, defaultValue, true, name, description) + { + } } } diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index a84c725..49364a3 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using MultiAdmin.Config.ConfigHandler; using MultiAdmin.ConsoleTools; @@ -10,37 +12,129 @@ public class MultiAdminConfig : ConfigRegister { #region Config Keys and Values - public ConfigEntry ConfigLocation { get; } = new ConfigEntry("config_location", "", "Config Location", "The default location for the game to use for storing configuration files (a directory)"); - public ConfigEntry DisableConfigValidation { get; } = new ConfigEntry("disable_config_validation", false, "Disable Config Validation", "Disable the config validator"); - public ConfigEntry ShareNonConfigs { get; } = new ConfigEntry("share_non_configs", true, "Share Non-Configs", "Makes all files other than the config files store in AppData"); - public ConfigEntry NoLog { get; } = new ConfigEntry("multiadmin_nolog", false, "MultiAdmin No-Logging", "Disable logging to file"); - public ConfigEntry DebugLog { get; } = new ConfigEntry("multiadmin_debug_log", true, "MultiAdmin Debug Logging", "Enables MultiAdmin debug logging, this logs to a separate file than any other logs"); - public ConfigEntry DebugLogBlacklist { get; } = new ConfigEntry("multiadmin_debug_log_blacklist", new string[] {"ProcessFile"}, "MultiAdmin Debug Logging Blacklist", "Which tags to block for MultiAdmin debug logging"); - public ConfigEntry DebugLogWhitelist { get; } = new ConfigEntry("multiadmin_debug_log_whitelist", new string[0], "MultiAdmin Debug Logging Whitelist", "Which tags to log for MultiAdmin debug logging (Defaults to logging all if none are provided)"); - public ConfigEntry UseNewInputSystem { get; } = new ConfigEntry("use_new_input_system", true, "Use New Input System", "Whether to use the new input system, if false, the original input system will be used"); - public ConfigEntry Port { get; } = new ConfigEntry("port", 7777, "Game Port", "The port for the server to use"); - public ConfigEntry CopyFromFolderOnReload { get; } = new ConfigEntry("copy_from_folder_on_reload", "", "Copy from Folder on Reload", "The location of a folder to copy files from into the folder defined by \"config_location\" whenever the configuration file is reloaded"); - public ConfigEntry FolderCopyWhitelist { get; } = new ConfigEntry("folder_copy_whitelist", new string[0], "Folder Copy Whitelist", "The list of file names to copy from the folder defined by \"copy_from_folder_on_reload\" (accepts \"*\" wildcards)"); - public ConfigEntry FolderCopyBlacklist { get; } = new ConfigEntry("folder_copy_blacklist", new string[0], "Folder Copy Blacklist", "The list of file names to not copy from the folder defined by \"copy_from_folder_on_reload\" (accepts \"*\" wildcards)"); - public ConfigEntry FolderCopyRoundQueue { get; } = new ConfigEntry("folder_copy_round_queue", new string[0], "Folder Copy Round Queue", "The location of a folder to copy files from into the folder defined by \"config_location\" after each round, looping through the locations"); - public ConfigEntry FolderCopyRoundQueueWhitelist { get; } = new ConfigEntry("folder_copy_round_queue_whitelist", new string[0], "Folder Copy Round Queue Whitelist", "The list of file names to copy from the folders defined by \"folder_copy_round_queue\" (accepts \"*\" wildcards)"); - public ConfigEntry FolderCopyRoundQueueBlacklist { get; } = new ConfigEntry("folder_copy_round_queue_blacklist", new string[0], "Folder Copy Round Queue Blacklist", "The list of file names to not copy from the folders defined by \"folder_copy_round_queue\" (accepts \"*\" wildcards)"); - public ConfigEntry RandomizeFolderCopyRoundQueue { get; } = new ConfigEntry("randomize_folder_copy_round_queue", false, "Randomize Folder Copy Round Queue", "Whether to randomize the order of entries in \"folder_copy_round_queue\""); - public ConfigEntry LogModActionsToOwnFile { get; } = new ConfigEntry("log_mod_actions_to_own_file", false, "Log Mod Actions to Own File", "Logs admin messages to separate file"); - public ConfigEntry ManualStart { get; } = new ConfigEntry("manual_start", false, "Manual Start", "Whether or not to start the server automatically when launching MultiAdmin"); - public ConfigEntry MaxMemory { get; } = new ConfigEntry("max_memory", 2048, "Max Memory", "The amount of memory in megabytes for MultiAdmin to check against"); - public ConfigEntry RestartLowMemory { get; } = new ConfigEntry("restart_low_memory", 400, "Restart Low Memory", "Restart if the game's remaining memory falls below this value in megabytes"); - public ConfigEntry RestartLowMemoryRoundEnd { get; } = new ConfigEntry("restart_low_memory_roundend", 450, "Restart Low Memory Round-End", "Restart at the end of the round if the game's remaining memory falls below this value in megabytes"); - public ConfigEntry MaxPlayers { get; } = new ConfigEntry("max_players", 20, "Max Players", "The number of players to display as the maximum for the server (within MultiAdmin, not in-game)"); - public ConfigEntry RandomInputColors { get; } = new ConfigEntry("random_input_colors", false, "Random Input Colors", "Randomize the new input system's colors every time a message is input"); - public ConfigEntry RestartEveryNumRounds { get; } = new ConfigEntry("restart_every_num_rounds", -1, "Restart Every Number of Rounds", "Restart the server every number of rounds"); - public ConfigEntry SafeServerShutdown { get; } = new ConfigEntry("safe_server_shutdown", true, "Safe Server Shutdown", "When MultiAdmin closes, if this is true, MultiAdmin will attempt to safely shutdown all the servers"); - public ConfigEntry ServerRestartTimeout { get; } = new ConfigEntry("server_restart_timeout", 10, "Server Restart Timeout", "The time in seconds before MultiAdmin forces a server restart if it doesn't respond to the regular restart command"); - public ConfigEntry ServerStopTimeout { get; } = new ConfigEntry("server_stop_timeout", 10, "Server Stop Timeout", "The time in seconds before MultiAdmin forces a server shutdown if it doesn't respond to the regular shutdown command"); - public ConfigEntry ServersFolder { get; } = new ConfigEntry("servers_folder", "servers", "Servers Folder", "The location of the \"servers\" folder for MultiAdmin to load multiple server configurations from"); - public ConfigEntry SetTitleBar { get; } = new ConfigEntry("set_title_bar", true, "Set Title Bar", "Whether to set the console window's titlebar, if false, this feature won't be used"); - public ConfigEntry ShutdownWhenEmptyFor { get; } = new ConfigEntry("shutdown_when_empty_for", -1, "Shutdown When Empty For", "Shutdown the server once a round hasn't started in a number of seconds"); - public ConfigEntry StartConfigOnFull { get; } = new ConfigEntry("start_config_on_full", "", "Start Config on Full", "Start server with this config folder once the server becomes full [Requires ServerMod]"); + public ConfigEntry ConfigLocation { get; } = + new ConfigEntry("config_location", "", false, + "Config Location", "The default location for the game to use for storing configuration files (a directory)"); + + public ConfigEntry DisableConfigValidation { get; } = + new ConfigEntry("disable_config_validation", false, + "Disable Config Validation", "Disable the config validator"); + + public ConfigEntry ShareNonConfigs { get; } = + new ConfigEntry("share_non_configs", true, + "Share Non-Configs", "Makes all files other than the config files store in AppData"); + + public ConfigEntry NoLog { get; } = + new ConfigEntry("multiadmin_nolog", false, + "MultiAdmin No-Logging", "Disable logging to file"); + + public ConfigEntry DebugLog { get; } = + new ConfigEntry("multiadmin_debug_log", true, + "MultiAdmin Debug Logging", "Enables MultiAdmin debug logging, this logs to a separate file than any other logs"); + + public ConfigEntry DebugLogBlacklist { get; } = + new ConfigEntry("multiadmin_debug_log_blacklist", new string[] {"ProcessFile"}, + "MultiAdmin Debug Logging Blacklist", "Which tags to block for MultiAdmin debug logging"); + + public ConfigEntry DebugLogWhitelist { get; } = + new ConfigEntry("multiadmin_debug_log_whitelist", new string[0], + "MultiAdmin Debug Logging Whitelist", "Which tags to log for MultiAdmin debug logging (Defaults to logging all if none are provided)"); + + public ConfigEntry UseNewInputSystem { get; } = + new ConfigEntry("use_new_input_system", true, + "Use New Input System", "Whether to use the new input system, if false, the original input system will be used"); + + public ConfigEntry Port { get; } = + new ConfigEntry("port", 7777, + "Game Port", "The port for the server to use"); + + public ConfigEntry CopyFromFolderOnReload { get; } = + new ConfigEntry("copy_from_folder_on_reload", "", + "Copy from Folder on Reload", "The location of a folder to copy files from into the folder defined by \"config_location\" whenever the configuration file is reloaded"); + + public ConfigEntry FolderCopyWhitelist { get; } = + new ConfigEntry("folder_copy_whitelist", new string[0], + "Folder Copy Whitelist", "The list of file names to copy from the folder defined by \"copy_from_folder_on_reload\" (accepts \"*\" wildcards)"); + + public ConfigEntry FolderCopyBlacklist { get; } = + new ConfigEntry("folder_copy_blacklist", new string[0], + "Folder Copy Blacklist", "The list of file names to not copy from the folder defined by \"copy_from_folder_on_reload\" (accepts \"*\" wildcards)"); + + public ConfigEntry FolderCopyRoundQueue { get; } = + new ConfigEntry("folder_copy_round_queue", new string[0], + "Folder Copy Round Queue", "The location of a folder to copy files from into the folder defined by \"config_location\" after each round, looping through the locations"); + + public ConfigEntry FolderCopyRoundQueueWhitelist { get; } = + new ConfigEntry("folder_copy_round_queue_whitelist", new string[0], + "Folder Copy Round Queue Whitelist", "The list of file names to copy from the folders defined by \"folder_copy_round_queue\" (accepts \"*\" wildcards)"); + + public ConfigEntry FolderCopyRoundQueueBlacklist { get; } = + new ConfigEntry("folder_copy_round_queue_blacklist", new string[0], + "Folder Copy Round Queue Blacklist", "The list of file names to not copy from the folders defined by \"folder_copy_round_queue\" (accepts \"*\" wildcards)"); + + public ConfigEntry RandomizeFolderCopyRoundQueue { get; } = + new ConfigEntry("randomize_folder_copy_round_queue", false, + "Randomize Folder Copy Round Queue", "Whether to randomize the order of entries in \"folder_copy_round_queue\""); + + public ConfigEntry LogModActionsToOwnFile { get; } = + new ConfigEntry("log_mod_actions_to_own_file", false, + "Log Mod Actions to Own File", "Logs admin messages to separate file"); + + public ConfigEntry ManualStart { get; } = + new ConfigEntry("manual_start", false, + "Manual Start", "Whether or not to start the server automatically when launching MultiAdmin"); + + public ConfigEntry MaxMemory { get; } = + new ConfigEntry("max_memory", 2048, + "Max Memory", "The amount of memory in megabytes for MultiAdmin to check against"); + + public ConfigEntry RestartLowMemory { get; } = + new ConfigEntry("restart_low_memory", 400, + "Restart Low Memory", "Restart if the game's remaining memory falls below this value in megabytes"); + + public ConfigEntry RestartLowMemoryRoundEnd { get; } = + new ConfigEntry("restart_low_memory_roundend", 450, + "Restart Low Memory Round-End", "Restart at the end of the round if the game's remaining memory falls below this value in megabytes"); + + public ConfigEntry MaxPlayers { get; } = + new ConfigEntry("max_players", 20, + "Max Players", "The number of players to display as the maximum for the server (within MultiAdmin, not in-game)"); + + public ConfigEntry RandomInputColors { get; } = + new ConfigEntry("random_input_colors", false, + "Random Input Colors", "Randomize the new input system's colors every time a message is input"); + + public ConfigEntry RestartEveryNumRounds { get; } = + new ConfigEntry("restart_every_num_rounds", -1, + "Restart Every Number of Rounds", "Restart the server every number of rounds"); + + public ConfigEntry SafeServerShutdown { get; } = + new ConfigEntry("safe_server_shutdown", true, + "Safe Server Shutdown", "When MultiAdmin closes, if this is true, MultiAdmin will attempt to safely shutdown all the servers"); + + public ConfigEntry ServerRestartTimeout { get; } = + new ConfigEntry("server_restart_timeout", 10, + "Server Restart Timeout", "The time in seconds before MultiAdmin forces a server restart if it doesn't respond to the regular restart command"); + + public ConfigEntry ServerStopTimeout { get; } = + new ConfigEntry("server_stop_timeout", 10, + "Server Stop Timeout", "The time in seconds before MultiAdmin forces a server shutdown if it doesn't respond to the regular shutdown command"); + + public ConfigEntry ServersFolder { get; } = + new ConfigEntry("servers_folder", "servers", + "Servers Folder", "The location of the \"servers\" folder for MultiAdmin to load multiple server configurations from"); + + public ConfigEntry SetTitleBar { get; } = + new ConfigEntry("set_title_bar", true, + "Set Title Bar", "Whether to set the console window's titlebar, if false, this feature won't be used"); + + public ConfigEntry ShutdownWhenEmptyFor { get; } = + new ConfigEntry("shutdown_when_empty_for", -1, + "Shutdown When Empty For", "Shutdown the server once a round hasn't started in a number of seconds"); + + public ConfigEntry StartConfigOnFull { get; } = + new ConfigEntry("start_config_on_full", "", + "Start Config on Full", "Start server with this config folder once the server becomes full [Requires ServerMod]"); #endregion @@ -104,7 +198,7 @@ public override void UpdateConfigValue(ConfigEntry configEntry) if (configEntry == null) throw new NullReferenceException("Config type unsupported (Config: Null)."); - if (!ShouldGetFromConfig(configEntry.Key)) + if (configEntry.Inherit && !ShouldGetFromConfig(configEntry.Key)) { ParentConfig.UpdateConfigValue(configEntry); return; @@ -186,23 +280,28 @@ public bool ConfigOrGlobalConfigContains(string key) return ConfigContains(key) || GlobalConfig.ConfigContains(key); } - public bool ConfigHierarchyContainsPath(string path) + public MultiAdminConfig[] GetConfigHierarchy(bool highestToLowest = true) { - string fullPath = Utils.GetFullPathSafe(path); + List configHierarchy = new List(); - if (!string.IsNullOrEmpty(fullPath)) + MultiAdminConfig config = this; + while (config != null && !configHierarchy.Contains(config)) { - MultiAdminConfig config = this; - while (config != null) - { - if (config.Config?.ConfigPath == path) - return true; - - config = config.ParentConfig; - } + configHierarchy.Add(config); + config = config.ParentConfig; } - return false; + if (highestToLowest) + configHierarchy.Reverse(); + + return configHierarchy.ToArray(); + } + + public bool ConfigHierarchyContainsPath(string path) + { + string fullPath = Utils.GetFullPathSafe(path); + + return !string.IsNullOrEmpty(fullPath) && GetConfigHierarchy().Any(config => config.Config?.ConfigPath == path); } } } diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index 63ecd6c..b8205d4 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -260,8 +260,14 @@ public void StartServer(bool restartOnCrash = true) if (!string.IsNullOrEmpty(MultiAdminConfig.GlobalConfigFilePath)) Write($"Using global config \"{MultiAdminConfig.GlobalConfigFilePath}\"..."); - if (!string.IsNullOrEmpty(configLocation) && !string.IsNullOrEmpty(ServerConfig?.Config?.ConfigPath)) - Write($"Using server config \"{ServerConfig.Config.ConfigPath}\"..."); + if (ServerConfig != null) + { + foreach (MultiAdminConfig config in ServerConfig.GetConfigHierarchy()) + { + if (!string.IsNullOrEmpty(config?.Config?.ConfigPath) && MultiAdminConfig.GlobalConfigFilePath != config.Config.ConfigPath) + Write($"Using server config \"{config.Config.ConfigPath}\"..."); + } + } #endregion From d3dcbd317692cf51d7c5dbc2c614e5ae9f4fde11 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 8 Jun 2019 20:17:54 -0400 Subject: [PATCH 4/7] Added and implemented InheritableConfigRegister - Added InheritableConfigRegister, this is a built in class to ConfigHandler for handling config value inheritance - Implemented InheritableConfigRegister into MultiAdminConfig to handle config value inheritance - Bumped version to 3.2.1 --- .../InheritableConfigRegister.cs | 73 +++++++++++++++++++ MultiAdmin/Config/MultiAdminConfig.cs | 39 +++++----- MultiAdmin/MultiAdmin.csproj | 1 + MultiAdmin/Program.cs | 2 +- 4 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs diff --git a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs new file mode 100644 index 0000000..05fc0fb --- /dev/null +++ b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; + +namespace MultiAdmin.Config.ConfigHandler +{ + /// + /// A register. This abstract class provides a base for a config handler implementation and inheritance. + /// + public abstract class InheritableConfigRegister : ConfigRegister + { + /// + /// Creates an with the parent to inherit unset config values from. + /// + /// The to inherit unset config values from. + protected InheritableConfigRegister(InheritableConfigRegister parentConfigRegister = null) + { + ParentConfigRegister = parentConfigRegister; + } + + /// + /// The parent to inherit from. + /// + public InheritableConfigRegister ParentConfigRegister { get; protected set; } + + /// + /// Returns whether should be inherited from the parent . + /// + /// The to decide whether to inherit. + public abstract bool ShouldInheritConfigEntry(ConfigEntry configEntry); + + /// + /// Updates the value of which could be provided by another . + /// + /// The to be assigned a value. + public abstract void UpdateConfigValueInheritable(ConfigEntry configEntry); + + /// + /// Updates the value of from this if the is null or if returns true. + /// + /// The to be assigned a value. + public override void UpdateConfigValue(ConfigEntry configEntry) + { + if (configEntry != null && configEntry.Inherit && ParentConfigRegister != null && ShouldInheritConfigEntry(configEntry)) + { + ParentConfigRegister.UpdateConfigValue(configEntry); + } + else + { + UpdateConfigValueInheritable(configEntry); + } + } + + /// + /// Returns an array of the hierarchy of s. + /// + /// Whether to order the returned array from highest in the hierarchy to the lowest. + public InheritableConfigRegister[] GetConfigRegisterHierarchy(bool highestToLowest = true) + { + List configRegisterHierarchy = new List(); + + InheritableConfigRegister configRegister = this; + while (configRegister != null && !configRegisterHierarchy.Contains(configRegister)) + { + configRegisterHierarchy.Add(configRegister); + configRegister = configRegister.ParentConfigRegister; + } + + if (highestToLowest) + configRegisterHierarchy.Reverse(); + + return configRegisterHierarchy.ToArray(); + } + } +} diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index 49364a3..900410a 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -8,7 +8,7 @@ namespace MultiAdmin.Config { - public class MultiAdminConfig : ConfigRegister + public class MultiAdminConfig : InheritableConfigRegister { #region Config Keys and Values @@ -143,7 +143,12 @@ public class MultiAdminConfig : ConfigRegister public static readonly MultiAdminConfig GlobalConfig = new MultiAdminConfig(GlobalConfigFilePath, null); - public MultiAdminConfig ParentConfig { get; } + public MultiAdminConfig ParentConfig + { + get => ParentConfigRegister as MultiAdminConfig; + protected set => ParentConfigRegister = value; + } + public Config Config { get; } public MultiAdminConfig(Config config, MultiAdminConfig parentConfig, bool createConfig = true) @@ -193,17 +198,11 @@ public MultiAdminConfig(string path, bool createConfig = true) : this(path, Glob #region Config Registration - public override void UpdateConfigValue(ConfigEntry configEntry) + public override void UpdateConfigValueInheritable(ConfigEntry configEntry) { if (configEntry == null) throw new NullReferenceException("Config type unsupported (Config: Null)."); - if (configEntry.Inherit && !ShouldGetFromConfig(configEntry.Key)) - { - ParentConfig.UpdateConfigValue(configEntry); - return; - } - if (Config == null) { configEntry.ObjectValue = configEntry.ObjectDefault; @@ -250,11 +249,16 @@ public override void UpdateConfigValue(ConfigEntry configEntry) default: { - throw new Exception($"Config type unsupported (Config: Key = \"{configEntry.Key ?? "Null"}\" Type = \"{configEntry.ValueType.FullName ?? "Null"}\" Name = \"{configEntry.Name ?? "Null"}\" Description = \"{configEntry.Description ?? "Null"}\")."); + throw new ArgumentException($"Config type unsupported (Config: Key = \"{configEntry.Key ?? "Null"}\" Type = \"{configEntry.ValueType.FullName ?? "Null"}\" Name = \"{configEntry.Name ?? "Null"}\" Description = \"{configEntry.Description ?? "Null"}\").", nameof(configEntry)); } } } + public override bool ShouldInheritConfigEntry(ConfigEntry configEntry) + { + return !ConfigContains(configEntry.Key); + } + #endregion public void ReloadConfig() @@ -265,11 +269,6 @@ public void ReloadConfig() UpdateRegisteredConfigValues(); } - private bool ShouldGetFromConfig(string key) - { - return ParentConfig == null || ConfigContains(key); - } - public bool ConfigContains(string key) { return Config != null && Config.Contains(key); @@ -284,16 +283,12 @@ public MultiAdminConfig[] GetConfigHierarchy(bool highestToLowest = true) { List configHierarchy = new List(); - MultiAdminConfig config = this; - while (config != null && !configHierarchy.Contains(config)) + foreach (InheritableConfigRegister configRegister in GetConfigRegisterHierarchy(highestToLowest)) { - configHierarchy.Add(config); - config = config.ParentConfig; + if (configRegister is MultiAdminConfig config) + configHierarchy.Add(config); } - if (highestToLowest) - configHierarchy.Reverse(); - return configHierarchy.ToArray(); } diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj index c2cc610..a0b2c45 100644 --- a/MultiAdmin/MultiAdmin.csproj +++ b/MultiAdmin/MultiAdmin.csproj @@ -75,6 +75,7 @@ + diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs index 288850f..008ddb5 100644 --- a/MultiAdmin/Program.cs +++ b/MultiAdmin/Program.cs @@ -14,7 +14,7 @@ namespace MultiAdmin { public static class Program { - public const string MaVersion = "3.2.0"; + public const string MaVersion = "3.2.1"; private static readonly List InstantiatedServers = new List(); From 4aa7e801422c3cd4bffce7f4376db4550258d423 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 8 Jun 2019 20:32:22 -0400 Subject: [PATCH 5/7] Changed the type of ParentConfigRegister to ConfigRegister --- .../InheritableConfigRegister.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs index 05fc0fb..0b0f202 100644 --- a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs +++ b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs @@ -10,25 +10,25 @@ public abstract class InheritableConfigRegister : ConfigRegister /// /// Creates an with the parent to inherit unset config values from. /// - /// The to inherit unset config values from. - protected InheritableConfigRegister(InheritableConfigRegister parentConfigRegister = null) + /// The to inherit unset config values from. + protected InheritableConfigRegister(ConfigRegister parentConfigRegister = null) { ParentConfigRegister = parentConfigRegister; } /// - /// The parent to inherit from. + /// The parent to inherit from. /// - public InheritableConfigRegister ParentConfigRegister { get; protected set; } + public ConfigRegister ParentConfigRegister { get; protected set; } /// - /// Returns whether should be inherited from the parent . + /// Returns whether should be inherited from the parent . /// /// The to decide whether to inherit. public abstract bool ShouldInheritConfigEntry(ConfigEntry configEntry); /// - /// Updates the value of which could be provided by another . + /// Updates the value of . /// /// The to be assigned a value. public abstract void UpdateConfigValueInheritable(ConfigEntry configEntry); @@ -50,18 +50,27 @@ public override void UpdateConfigValue(ConfigEntry configEntry) } /// - /// Returns an array of the hierarchy of s. + /// Returns an array of the hierarchy of s. /// - /// Whether to order the returned array from highest in the hierarchy to the lowest. - public InheritableConfigRegister[] GetConfigRegisterHierarchy(bool highestToLowest = true) + /// Whether to order the returned array from highest in the hierarchy to the lowest. + public ConfigRegister[] GetConfigRegisterHierarchy(bool highestToLowest = true) { - List configRegisterHierarchy = new List(); + List configRegisterHierarchy = new List(); - InheritableConfigRegister configRegister = this; + ConfigRegister configRegister = this; while (configRegister != null && !configRegisterHierarchy.Contains(configRegister)) { configRegisterHierarchy.Add(configRegister); - configRegister = configRegister.ParentConfigRegister; + + // If there's another InheritableConfigRegister as a parent, then get the parent of that, otherwise, break the loop as there are no more parents + if (configRegister is InheritableConfigRegister inheritableConfigRegister) + { + configRegister = inheritableConfigRegister.ParentConfigRegister; + } + else + { + break; + } } if (highestToLowest) From 3c5424cd0934cd6ba0632d81837244a481783a79 Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Sat, 8 Jun 2019 21:32:19 -0400 Subject: [PATCH 6/7] Added automated config documentation generation - Added automated config documentation generation - Updated config documentation (as a result of automated generation) --- MultiAdmin/Config/MultiAdminConfig.cs | 16 ++--- MultiAdmin/Features/GithubGenerator.cs | 81 ++++++++++++++++++++++---- README.md | 19 +++--- 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs index 900410a..d8caf49 100644 --- a/MultiAdmin/Config/MultiAdminConfig.cs +++ b/MultiAdmin/Config/MultiAdminConfig.cs @@ -50,31 +50,31 @@ public class MultiAdminConfig : InheritableConfigRegister public ConfigEntry CopyFromFolderOnReload { get; } = new ConfigEntry("copy_from_folder_on_reload", "", - "Copy from Folder on Reload", "The location of a folder to copy files from into the folder defined by \"config_location\" whenever the configuration file is reloaded"); + "Copy from Folder on Reload", "The location of a folder to copy files from into the folder defined by `config_location` whenever the configuration file is reloaded"); public ConfigEntry FolderCopyWhitelist { get; } = new ConfigEntry("folder_copy_whitelist", new string[0], - "Folder Copy Whitelist", "The list of file names to copy from the folder defined by \"copy_from_folder_on_reload\" (accepts \"*\" wildcards)"); + "Folder Copy Whitelist", "The list of file names to copy from the folder defined by `copy_from_folder_on_reload` (accepts `*` wildcards)"); public ConfigEntry FolderCopyBlacklist { get; } = new ConfigEntry("folder_copy_blacklist", new string[0], - "Folder Copy Blacklist", "The list of file names to not copy from the folder defined by \"copy_from_folder_on_reload\" (accepts \"*\" wildcards)"); + "Folder Copy Blacklist", "The list of file names to not copy from the folder defined by `copy_from_folder_on_reload` (accepts `*` wildcards)"); public ConfigEntry FolderCopyRoundQueue { get; } = new ConfigEntry("folder_copy_round_queue", new string[0], - "Folder Copy Round Queue", "The location of a folder to copy files from into the folder defined by \"config_location\" after each round, looping through the locations"); + "Folder Copy Round Queue", "The location of a folder to copy files from into the folder defined by `config_location` after each round, looping through the locations"); public ConfigEntry FolderCopyRoundQueueWhitelist { get; } = new ConfigEntry("folder_copy_round_queue_whitelist", new string[0], - "Folder Copy Round Queue Whitelist", "The list of file names to copy from the folders defined by \"folder_copy_round_queue\" (accepts \"*\" wildcards)"); + "Folder Copy Round Queue Whitelist", "The list of file names to copy from the folders defined by `folder_copy_round_queue` (accepts `*` wildcards)"); public ConfigEntry FolderCopyRoundQueueBlacklist { get; } = new ConfigEntry("folder_copy_round_queue_blacklist", new string[0], - "Folder Copy Round Queue Blacklist", "The list of file names to not copy from the folders defined by \"folder_copy_round_queue\" (accepts \"*\" wildcards)"); + "Folder Copy Round Queue Blacklist", "The list of file names to not copy from the folders defined by `folder_copy_round_queue` (accepts `*` wildcards)"); public ConfigEntry RandomizeFolderCopyRoundQueue { get; } = new ConfigEntry("randomize_folder_copy_round_queue", false, - "Randomize Folder Copy Round Queue", "Whether to randomize the order of entries in \"folder_copy_round_queue\""); + "Randomize Folder Copy Round Queue", "Whether to randomize the order of entries in `folder_copy_round_queue`"); public ConfigEntry LogModActionsToOwnFile { get; } = new ConfigEntry("log_mod_actions_to_own_file", false, @@ -122,7 +122,7 @@ public class MultiAdminConfig : InheritableConfigRegister public ConfigEntry ServersFolder { get; } = new ConfigEntry("servers_folder", "servers", - "Servers Folder", "The location of the \"servers\" folder for MultiAdmin to load multiple server configurations from"); + "Servers Folder", "The location of the `servers` folder for MultiAdmin to load multiple server configurations from"); public ConfigEntry SetTitleBar { get; } = new ConfigEntry("set_title_bar", true, diff --git a/MultiAdmin/Features/GithubGenerator.cs b/MultiAdmin/Features/GithubGenerator.cs index f608c54..40bf1fe 100644 --- a/MultiAdmin/Features/GithubGenerator.cs +++ b/MultiAdmin/Features/GithubGenerator.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using MultiAdmin.Config; +using MultiAdmin.Config.ConfigHandler; using MultiAdmin.Features.Attributes; namespace MultiAdmin.Features @@ -8,6 +11,9 @@ namespace MultiAdmin.Features [Feature] internal class GithubGenerator : Feature, ICommand { + public const string EmptyIndicator = "**Empty**"; + public const string ColumnSeparator = " | "; + public GithubGenerator(Server server) : base(server) { } @@ -37,28 +43,81 @@ public void OnCall(string[] args) string dir = string.Join(" ", args); - List lines = new List - { - "# MultiAdmin", - string.Empty, - "## Features" - }; + List lines = new List {"# MultiAdmin", string.Empty, "## Features"}; foreach (Feature feature in Server.features) { if (feature.Equals(this)) continue; - lines.Add("- " + feature.GetFeatureName() + ": " + feature.GetFeatureDescription()); + + lines.Add($"- {feature.GetFeatureName()}: {feature.GetFeatureDescription()}"); } lines.Add(string.Empty); lines.Add("## MultiAdmin Commands"); - lines.Add( - "This does not include ServerMod or in-game commands, for a full list type `HELP` in MultiAdmin which will produce all commands."); lines.Add(string.Empty); foreach (ICommand comm in Server.commands.Values) { - string commandString = (comm.GetCommand() + " " + comm.GetUsage()).Trim(); - lines.Add("- " + commandString + ": " + comm.GetCommandDescription()); + lines.Add($"- {(comm.GetCommand() + " " + comm.GetUsage()).Trim()}: {comm.GetCommandDescription()}"); + } + + lines.Add(string.Empty); + lines.Add("## Config Settings"); + lines.Add(string.Empty); + lines.Add($"Config Option{ColumnSeparator}Value Type{ColumnSeparator}Default Value{ColumnSeparator}Description"); + lines.Add($"---{ColumnSeparator}:---:{ColumnSeparator}:---:{ColumnSeparator}:------:"); + + foreach (ConfigEntry configEntry in MultiAdminConfig.GlobalConfig.GetRegisteredConfigs()) + { + StringBuilder stringBuilder = new StringBuilder($"{configEntry.Key ?? EmptyIndicator}{ColumnSeparator}"); + + switch (configEntry) + { + case ConfigEntry config: + { + stringBuilder.Append($"String{ColumnSeparator}{(string.IsNullOrEmpty(config.Default) ? EmptyIndicator : config.Default)}"); + break; + } + + case ConfigEntry config: + { + stringBuilder.Append($"String List{ColumnSeparator}{(!config.Default?.Any() ?? true ? EmptyIndicator : string.Join(", ", config.Default))}"); + break; + } + + case ConfigEntry config: + { + stringBuilder.Append($"Integer{ColumnSeparator}{config.Default}"); + break; + } + + case ConfigEntry config: + { + stringBuilder.Append($"Unsigned Integer{ColumnSeparator}{config.Default}"); + break; + } + + case ConfigEntry config: + { + stringBuilder.Append($"Float{ColumnSeparator}{config.Default}"); + break; + } + + case ConfigEntry config: + { + stringBuilder.Append($"Boolean{ColumnSeparator}{config.Default}"); + break; + } + + default: + { + stringBuilder.Append($"{configEntry.ValueType?.Name ?? EmptyIndicator}{ColumnSeparator}{configEntry.ObjectDefault ?? EmptyIndicator}"); + break; + } + } + + stringBuilder.Append($"{ColumnSeparator}{configEntry.Description ?? EmptyIndicator}"); + + lines.Add(stringBuilder.ToString()); } File.WriteAllLines(dir, lines); diff --git a/README.md b/README.md index 468675c..eedc8ee 100644 --- a/README.md +++ b/README.md @@ -82,19 +82,20 @@ folder_copy_round_queue | String List | **Empty** | The location of a folder to folder_copy_round_queue_whitelist | String List | **Empty** | The list of file names to copy from the folders defined by `folder_copy_round_queue` (accepts `*` wildcards) folder_copy_round_queue_blacklist | String List | **Empty** | The list of file names to not copy from the folders defined by `folder_copy_round_queue` (accepts `*` wildcards) randomize_folder_copy_round_queue | Boolean | False | Whether to randomize the order of entries in `folder_copy_round_queue` -log_mod_actions_to_own_file | Boolean | False | Logs admin messages to seperate file +log_mod_actions_to_own_file | Boolean | False | Logs admin messages to separate file manual_start | Boolean | False | Whether or not to start the server automatically when launching MultiAdmin -max_memory | Float | 2048.0 | The amount of memory in megabytes for MultiAdmin to check against -restart_low_memory | Float | 400.0 | Restart if the games memory falls below this value in megabytes -restart_low_memory_roundend | Float | 450.0 | Restart at the end of the round if the game's memory falls below this value in megabytes +max_memory | Float | 2048 | The amount of memory in megabytes for MultiAdmin to check against +restart_low_memory | Float | 400 | Restart if the game's remaining memory falls below this value in megabytes +restart_low_memory_roundend | Float | 450 | Restart at the end of the round if the game's remaining memory falls below this value in megabytes max_players | Integer | 20 | The number of players to display as the maximum for the server (within MultiAdmin, not in-game) -restart_every_num_rounds | Integer | -1 | Restart the server every number rounds +random_input_colors | Boolean | False | Randomize the new input system's colors every time a message is input +restart_every_num_rounds | Integer | -1 | Restart the server every number of rounds safe_server_shutdown | Boolean | True | When MultiAdmin closes, if this is true, MultiAdmin will attempt to safely shutdown all the servers -server_restart_timeout | Float | 10.0 | The time in seconds before MultiAdmin forces a server restart if it doesn't respond to the regular restart command -server_stop_timeout | Float | 10.0 | The time in seconds before MultiAdmin forces a server shutdown if it doesn't respond to the regular shutdown command -servers_folder | String | servers | The location of the "servers" folder for MultiAdmin to load multiple server configurations from +server_restart_timeout | Float | 10 | The time in seconds before MultiAdmin forces a server restart if it doesn't respond to the regular restart command +server_stop_timeout | Float | 10 | The time in seconds before MultiAdmin forces a server shutdown if it doesn't respond to the regular shutdown command +servers_folder | String | servers | The location of the `servers` folder for MultiAdmin to load multiple server configurations from set_title_bar | Boolean | True | Whether to set the console window's titlebar, if false, this feature won't be used -shutdown_when_empty_for | Seconds | -1 | Shutdown the server once a round hasn't started in a number of seconds +shutdown_when_empty_for | Integer | -1 | Shutdown the server once a round hasn't started in a number of seconds start_config_on_full | String | **Empty** | Start server with this config folder once the server becomes full [Requires ServerMod] ## Upcoming Features From c9a474806bd96a2f2de5f3d6e2f7fbf6f62eefaf Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Wed, 12 Jun 2019 17:52:05 -0400 Subject: [PATCH 7/7] Added Mono version checker - Added Mono version checker - Put "Server#DeleteSession()" into a try catch statement - Fixed bug with session staying after an error in execution, MultiAdmin should now attempt to delete it when it's closed --- MultiAdmin/Program.cs | 81 +++++++++++++++++++++++++++++++++++++++++-- MultiAdmin/Server.cs | 50 +++++++++++++++----------- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs index 008ddb5..5c83b81 100644 --- a/MultiAdmin/Program.cs +++ b/MultiAdmin/Program.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading; using MultiAdmin.Config; using MultiAdmin.ConsoleTools; @@ -15,6 +16,7 @@ namespace MultiAdmin public static class Program { public const string MaVersion = "3.2.1"; + public const string RecommendedMonoVersion = "5.18.0"; private static readonly List InstantiatedServers = new List(); @@ -142,7 +144,8 @@ private static void OnExit(object sender, EventArgs e) } } - // For some reason Mono hangs on this, but it works perfectly without it + // For some reason Mono hangs on this, but it works perfectly without it, + // but on Windows it doesn't close immediately unless this is here if (Utils.IsWindows) Environment.Exit(0); } @@ -165,6 +168,8 @@ public static void Main() Headless = GetFlagFromArgs("headless", "h"); + CheckMonoVersion(); + string serverIdArg = GetParamFromArgs("server-id", "id"); string configArg = GetParamFromArgs("config", "c"); portArg = uint.TryParse(GetParamFromArgs("port", "p"), out uint port) ? (uint?)port : null; @@ -326,7 +331,12 @@ public static bool GetFlagFromArgs(string key = null, string alias = null) public static Process StartServer(Server server) { - string assemblyLocation = Assembly.GetEntryAssembly().Location; + string assemblyLocation = Assembly.GetEntryAssembly()?.Location; + + if (string.IsNullOrEmpty(assemblyLocation)) + { + Write("Error while starting new server: Could not find the executable location!", ConsoleColor.Red); + } List args = new List(); @@ -353,5 +363,72 @@ public static Process StartServer(Server server) return serverProcess; } + + private static bool IsVersionFormat(string input) + { + foreach (char character in input) + { + if (!char.IsNumber(character) && character != '.') + return false; + } + + return true; + } + + private static int CompareVersionStrings(string firstVersion, string secondVersion) + { + if (firstVersion == null || secondVersion == null) + return -1; + + string[] firstVersionNums = firstVersion.Split('.'); + string[] secondVersionNums = secondVersion.Split('.'); + + int returnValue = 0; + + for (int i = 0; i < Math.Min(firstVersionNums.Length, secondVersionNums.Length); i++) + { + if (!int.TryParse(firstVersionNums[i], out int current) || !int.TryParse(secondVersionNums[i], out int recommended)) + continue; + + if (current > recommended) + { + returnValue = 1; + } + else if (current < recommended) + { + return -1; + } + } + + return returnValue; + } + + public static void CheckMonoVersion() + { + try + { + if (!RuntimeInformation.FrameworkDescription.StartsWith("Mono")) + { + return; + } + + string monoVersion = RuntimeInformation.FrameworkDescription?.Split(' ').FirstOrDefault(IsVersionFormat); + + if (string.IsNullOrEmpty(monoVersion)) + return; + + int versionDifference = CompareVersionStrings(monoVersion, RecommendedMonoVersion); + + if (versionDifference >= 0 && (versionDifference != 0 || monoVersion.Length >= RecommendedMonoVersion.Length)) + return; + + Write($"Warning: Your Mono version ({monoVersion}) is below the recommended version ({RecommendedMonoVersion})", ConsoleColor.Red); + Write("Please update your Mono installation: https://www.mono-project.com/download/stable/", ConsoleColor.Red); + } + catch (Exception e) + { + LogDebugException(nameof(CheckMonoVersion), e); + } + } } } diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index b8205d4..7a7ec4c 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -406,9 +406,12 @@ public void StartServer(bool restartOnCrash = true) { Write("Failed - Executable file not found or config issue!", ConsoleColor.Red); Write(e.Message, ConsoleColor.Red); - Write("Press any key to close...", ConsoleColor.DarkGray); - Console.ReadKey(true); - Process.GetCurrentProcess().Kill(); + + shouldRestart = false; + } + finally + { + DeleteSession(); } } while (shouldRestart); } @@ -558,28 +561,35 @@ public void CleanSession() public void DeleteSession() { - CleanSession(); + try + { + CleanSession(); - if (!Directory.Exists(SessionDirectory)) return; + if (!Directory.Exists(SessionDirectory)) return; - for (int i = 0; i < 20; i++) - { - try - { - Directory.Delete(SessionDirectory); - break; - } - catch (UnauthorizedAccessException e) - { - Program.LogDebugException(nameof(DeleteSession), e); - Thread.Sleep(5); - } - catch (Exception e) + for (int i = 0; i < 20; i++) { - Program.LogDebugException(nameof(DeleteSession), e); - Thread.Sleep(2); + try + { + Directory.Delete(SessionDirectory); + break; + } + catch (UnauthorizedAccessException e) + { + Program.LogDebugException(nameof(DeleteSession), e); + Thread.Sleep(5); + } + catch (Exception e) + { + Program.LogDebugException(nameof(DeleteSession), e); + Thread.Sleep(2); + } } } + catch (Exception e) + { + Program.LogDebugException(nameof(DeleteSession), e); + } } #endregion