From ff4eac7d3ffa089b6f7f97c4fc0377dc993fd644 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 03:39:50 -0500
Subject: [PATCH 01/16] Update for .NET 7

---
 .github/workflows/main.yml               |  2 +-
 MultiAdmin.Tests/MultiAdmin.Tests.csproj | 12 ++++++------
 MultiAdmin/MultiAdmin.csproj             | 12 ++++++------
 3 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 61d4882..7feaaa7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -9,7 +9,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-18.04, windows-latest]
-        framework: ['6.0']
+        framework: ['7.0']
         include:
         - os: ubuntu-18.04
           target: linux-x64
diff --git a/MultiAdmin.Tests/MultiAdmin.Tests.csproj b/MultiAdmin.Tests/MultiAdmin.Tests.csproj
index e87b34b..445d9ec 100644
--- a/MultiAdmin.Tests/MultiAdmin.Tests.csproj
+++ b/MultiAdmin.Tests/MultiAdmin.Tests.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
-  	<LangVersion>8</LangVersion>
+    <TargetFramework>net7.0</TargetFramework>
+  	<LangVersion>11</LangVersion>
     <IsPackable>false</IsPackable>
     <RootNamespace>MultiAdmin.Tests</RootNamespace>
     
@@ -15,16 +15,16 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
 
-    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit" Version="2.4.2" />
 
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
 
-    <PackageReference Include="coverlet.collector" Version="3.1.0">
+    <PackageReference Include="coverlet.collector" Version="3.2.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj
index 3aded0a..d313540 100644
--- a/MultiAdmin/MultiAdmin.csproj
+++ b/MultiAdmin/MultiAdmin.csproj
@@ -1,8 +1,8 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
-  	<LangVersion>8</LangVersion>
+    <TargetFramework>net7.0</TargetFramework>
+  	<LangVersion>11</LangVersion>
     <RootNamespace>MultiAdmin</RootNamespace>
     <ApplicationIcon>Icon.ico</ApplicationIcon>
 
@@ -19,6 +19,10 @@
 
     <Optimize>true</Optimize>
 
+    <!-- AOT Compilation -->
+    <PublishAot>true</PublishAot>
+    <StripSymbols>true</StripSymbols>
+
     <!-- Workaround to ILCompiler error code 139 -->
     <DebugSymbols>false</DebugSymbols>
     <DebugType>none</DebugType>
@@ -31,8 +35,4 @@
   <ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
     <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
   </ItemGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" />
-  </ItemGroup>
 </Project>

From 1d21e774f5f7e3db8ffd65e3f909dbdb6901bfe5 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 03:42:42 -0500
Subject: [PATCH 02/16] Reformat all and simplify `new`

---
 .../ServerIO/ShiftingListTests.cs             |  8 +-
 .../ServerIO/StringSectionsTests.cs           |  4 +-
 MultiAdmin/Config/Config.cs                   |  2 +-
 .../Config/ConfigHandler/ConfigRegister.cs    |  2 +-
 .../InheritableConfigRegister.cs              |  4 +-
 MultiAdmin/Config/MultiAdminConfig.cs         | 90 +++++++++----------
 MultiAdmin/ConsoleTools/ColoredConsole.cs     |  6 +-
 MultiAdmin/ConsoleTools/ConsolePositioning.cs | 14 +--
 MultiAdmin/Features/ConfigGenerator.cs        | 18 ++--
 MultiAdmin/Features/FolderCopyRoundQueue.cs   |  2 +-
 MultiAdmin/Features/GithubGenerator.cs        | 90 +++++++++----------
 MultiAdmin/Features/HelpCommand.cs            |  4 +-
 MultiAdmin/Features/TitleBar.cs               |  2 +-
 MultiAdmin/Program.cs                         | 22 ++---
 MultiAdmin/Server.cs                          | 67 +++++++-------
 MultiAdmin/ServerIO/InputHandler.cs           | 21 +++--
 MultiAdmin/ServerIO/OutputHandler.cs          |  6 +-
 MultiAdmin/ServerIO/ServerSocket.cs           |  4 +-
 MultiAdmin/ServerIO/StringSections.cs         |  6 +-
 MultiAdmin/Utility/CommandUtils.cs            |  4 +-
 .../Utility/StringEnumerableExtensions.cs     |  2 +-
 MultiAdmin/Utility/Utils.cs                   |  4 +-
 22 files changed, 191 insertions(+), 191 deletions(-)

diff --git a/MultiAdmin.Tests/ServerIO/ShiftingListTests.cs b/MultiAdmin.Tests/ServerIO/ShiftingListTests.cs
index de287cb..8b00360 100644
--- a/MultiAdmin.Tests/ServerIO/ShiftingListTests.cs
+++ b/MultiAdmin.Tests/ServerIO/ShiftingListTests.cs
@@ -10,7 +10,7 @@ public class ShiftingListTests
 		public void ShiftingListTest()
 		{
 			const int maxCount = 2;
-			ShiftingList shiftingList = new ShiftingList(maxCount);
+			ShiftingList shiftingList = new(maxCount);
 
 			Assert.Equal(maxCount, shiftingList.MaxCount);
 		}
@@ -20,7 +20,7 @@ public void AddTest()
 		{
 			const int maxCount = 2;
 			const int entriesToAdd = 6;
-			ShiftingList shiftingList = new ShiftingList(maxCount);
+			ShiftingList shiftingList = new(maxCount);
 
 			for (int i = 0; i < entriesToAdd; i++)
 			{
@@ -40,7 +40,7 @@ public void RemoveFromEndTest()
 		{
 			const int maxCount = 6;
 			const int entriesToRemove = 2;
-			ShiftingList shiftingList = new ShiftingList(maxCount);
+			ShiftingList shiftingList = new(maxCount);
 
 			for (int i = 0; i < maxCount; i++)
 			{
@@ -65,7 +65,7 @@ public void ReplaceTest()
 		{
 			const int maxCount = 6;
 			const int indexToReplace = 2;
-			ShiftingList shiftingList = new ShiftingList(maxCount);
+			ShiftingList shiftingList = new(maxCount);
 
 			for (int i = 0; i < maxCount; i++)
 			{
diff --git a/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs b/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs
index dee6d8c..c435aaa 100644
--- a/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs
+++ b/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs
@@ -16,8 +16,8 @@ public StringSectionsTests(ITestOutputHelper output)
 		}
 
 		[Theory]
-		[InlineData("test string", new[] {"te", "st", " s", "tr", "in", "g"}, 2)]
-		[InlineData("test string", new[] {"tes..", ".t ..", ".st..", ".ring"}, 5, ".", "..")]
+		[InlineData("test string", new[] { "te", "st", " s", "tr", "in", "g" }, 2)]
+		[InlineData("test string", new[] { "tes..", ".t ..", ".st..", ".ring" }, 5, ".", "..")]
 		public void FromStringTest(string testString, string[] expectedSections, int sectionLength,
 			string leftIndictator = null, string rightIndictator = null)
 		{
diff --git a/MultiAdmin/Config/Config.cs b/MultiAdmin/Config/Config.cs
index 133de2e..6bab93e 100644
--- a/MultiAdmin/Config/Config.cs
+++ b/MultiAdmin/Config/Config.cs
@@ -67,7 +67,7 @@ public void ReadConfigFile()
 		public bool Contains(string key)
 		{
 			return rawData != null &&
-			       rawData.Any(entry => entry.StartsWith($"{key}:", StringComparison.CurrentCultureIgnoreCase));
+				   rawData.Any(entry => entry.StartsWith($"{key}:", StringComparison.CurrentCultureIgnoreCase));
 		}
 
 		private static string CleanValue(string value, bool removeQuotes = true)
diff --git a/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs b/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs
index 37cfc70..eb12762 100644
--- a/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs
+++ b/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs
@@ -10,7 +10,7 @@ public abstract class ConfigRegister
 		/// <summary>
 		/// A list of registered <see cref="ConfigEntry"/>s.
 		/// </summary>
-		protected readonly List<ConfigEntry> registeredConfigs = new List<ConfigEntry>();
+		protected readonly List<ConfigEntry> registeredConfigs = new();
 
 		/// <summary>
 		/// Returns an array of registered <see cref="ConfigEntry"/>s.
diff --git a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs
index ad6d52e..99e143e 100644
--- a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs
+++ b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs
@@ -40,7 +40,7 @@ protected InheritableConfigRegister(ConfigRegister parentConfigRegister = null)
 		public override void UpdateConfigValue(ConfigEntry configEntry)
 		{
 			if (configEntry != null && configEntry.Inherit && ParentConfigRegister != null &&
-			    ShouldInheritConfigEntry(configEntry))
+				ShouldInheritConfigEntry(configEntry))
 			{
 				ParentConfigRegister.UpdateConfigValue(configEntry);
 			}
@@ -56,7 +56,7 @@ public override void UpdateConfigValue(ConfigEntry configEntry)
 		/// <param name="highestToLowest">Whether to order the returned array from highest <see cref="ConfigRegister"/> in the hierarchy to the lowest.</param>
 		public ConfigRegister[] GetConfigRegisterHierarchy(bool highestToLowest = true)
 		{
-			List<ConfigRegister> configRegisterHierarchy = new List<ConfigRegister>();
+			List<ConfigRegister> configRegisterHierarchy = new();
 
 			ConfigRegister configRegister = this;
 			while (configRegister != null && !configRegisterHierarchy.Contains(configRegister))
diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs
index 7911b7e..d9c1d7b 100644
--- a/MultiAdmin/Config/MultiAdminConfig.cs
+++ b/MultiAdmin/Config/MultiAdminConfig.cs
@@ -43,7 +43,7 @@ public class MultiAdminConfig : InheritableConfigRegister
 				"MultiAdmin Debug Logging", "Enables MultiAdmin debug logging, this logs to a separate file than any other logs");
 
 		public ConfigEntry<string[]> DebugLogBlacklist { get; } =
-			new ConfigEntry<string[]>("multiadmin_debug_log_blacklist", new string[] {nameof(OutputHandler.HandleMessage), nameof(Utils.StringMatches), nameof(ServerSocket.MessageListener) },
+			new ConfigEntry<string[]>("multiadmin_debug_log_blacklist", new string[] { nameof(OutputHandler.HandleMessage), nameof(Utils.StringMatches), nameof(ServerSocket.MessageListener) },
 				"MultiAdmin Debug Logging Blacklist", "Which tags to block for MultiAdmin debug logging");
 
 		public ConfigEntry<string[]> DebugLogWhitelist { get; } =
@@ -199,7 +199,7 @@ public InputHandler.ConsoleInputSystem ActualConsoleInputSystem
 		public const string ConfigFileName = "scp_multiadmin.cfg";
 		public static readonly string GlobalConfigFilePath = Utils.GetFullPathSafe(ConfigFileName);
 
-		public static readonly MultiAdminConfig GlobalConfig = new MultiAdminConfig(GlobalConfigFilePath, null);
+		public static readonly MultiAdminConfig GlobalConfig = new(GlobalConfigFilePath, null);
 
 		public MultiAdminConfig ParentConfig
 		{
@@ -276,65 +276,65 @@ public override void UpdateConfigValueInheritable(ConfigEntry configEntry)
 			switch (configEntry)
 			{
 				case ConfigEntry<string> config:
-				{
-					config.Value = Config.GetString(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetString(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<string[]> config:
-				{
-					config.Value = Config.GetStringArray(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetStringArray(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<int> config:
-				{
-					config.Value = Config.GetInt(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetInt(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<uint> config:
-				{
-					config.Value = Config.GetUInt(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetUInt(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<float> config:
-				{
-					config.Value = Config.GetFloat(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetFloat(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<double> config:
-				{
-					config.Value = Config.GetDouble(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetDouble(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<decimal> config:
-				{
-					config.Value = Config.GetDecimal(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetDecimal(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<bool> config:
-				{
-					config.Value = Config.GetBool(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetBool(config.Key, config.Default);
+						break;
+					}
 
 				case ConfigEntry<InputHandler.ConsoleInputSystem> config:
-				{
-					config.Value = Config.GetConsoleInputSystem(config.Key, config.Default);
-					break;
-				}
+					{
+						config.Value = Config.GetConsoleInputSystem(config.Key, config.Default);
+						break;
+					}
 
 				default:
-				{
-					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));
-				}
+					{
+						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));
+					}
 			}
 		}
 
@@ -365,7 +365,7 @@ public bool ConfigOrGlobalConfigContains(string key)
 
 		public MultiAdminConfig[] GetConfigHierarchy(bool highestToLowest = true)
 		{
-			List<MultiAdminConfig> configHierarchy = new List<MultiAdminConfig>();
+			List<MultiAdminConfig> configHierarchy = new();
 
 			foreach (ConfigRegister configRegister in GetConfigRegisterHierarchy(highestToLowest))
 			{
@@ -381,7 +381,7 @@ public bool ConfigHierarchyContainsPath(string path)
 			string fullPath = Utils.GetFullPathSafe(path);
 
 			return !string.IsNullOrEmpty(fullPath) &&
-			       GetConfigHierarchy().Any(config => config.Config?.ConfigPath == path);
+				   GetConfigHierarchy().Any(config => config.Config?.ConfigPath == path);
 		}
 	}
 }
diff --git a/MultiAdmin/ConsoleTools/ColoredConsole.cs b/MultiAdmin/ConsoleTools/ColoredConsole.cs
index a54a886..50f85bc 100644
--- a/MultiAdmin/ConsoleTools/ColoredConsole.cs
+++ b/MultiAdmin/ConsoleTools/ColoredConsole.cs
@@ -5,7 +5,7 @@ namespace MultiAdmin.ConsoleTools
 {
 	public static class ColoredConsole
 	{
-		public static readonly object WriteLock = new object();
+		public static readonly object WriteLock = new();
 
 		public static void Write(string text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
 		{
@@ -95,7 +95,7 @@ public ColoredMessage(string text, ConsoleColor? textColor = null, ConsoleColor?
 		public bool Equals(ColoredMessage other)
 		{
 			return string.Equals(text, other.text) && textColor == other.textColor &&
-			       backgroundColor == other.backgroundColor;
+				   backgroundColor == other.backgroundColor;
 		}
 
 		public override bool Equals(object obj)
@@ -181,7 +181,7 @@ public static class ColoredMessageArrayExtensions
 	{
 		private static string JoinTextIgnoreNull(ColoredMessage[] coloredMessages)
 		{
-			StringBuilder builder = new StringBuilder("");
+			StringBuilder builder = new("");
 
 			foreach (ColoredMessage coloredMessage in coloredMessages)
 			{
diff --git a/MultiAdmin/ConsoleTools/ConsolePositioning.cs b/MultiAdmin/ConsoleTools/ConsolePositioning.cs
index dcea3a9..028bac7 100644
--- a/MultiAdmin/ConsoleTools/ConsolePositioning.cs
+++ b/MultiAdmin/ConsoleTools/ConsolePositioning.cs
@@ -8,7 +8,7 @@ public static class ConsolePositioning
 
 		public static BufferPoint BufferCursor
 		{
-			get => new BufferPoint(Console.CursorLeft, Console.CursorTop);
+			get => new(Console.CursorLeft, Console.CursorTop);
 			set => Console.SetCursorPosition(value.x, value.y);
 		}
 
@@ -20,22 +20,22 @@ public static ConsolePoint ConsoleCursor
 
 		public static BufferPoint BufferLeft
 		{
-			get => new BufferPoint(0, 0);
+			get => new(0, 0);
 		}
 
 		public static BufferPoint BufferRight
 		{
-			get => new BufferPoint(Console.BufferWidth - 1, 0);
+			get => new(Console.BufferWidth - 1, 0);
 		}
 
 		public static BufferPoint BufferTop
 		{
-			get => new BufferPoint(0, 0);
+			get => new(0, 0);
 		}
 
 		public static BufferPoint BufferBottom
 		{
-			get => new BufferPoint(0, Console.BufferHeight - 1);
+			get => new(0, Console.BufferHeight - 1);
 		}
 
 		#endregion
@@ -45,7 +45,7 @@ public struct ConsolePoint
 	{
 		public readonly int x, y;
 
-		public BufferPoint BufferPoint => new BufferPoint(this);
+		public BufferPoint BufferPoint => new(this);
 
 		public ConsolePoint(int x, int y)
 		{
@@ -78,7 +78,7 @@ public struct BufferPoint
 	{
 		public readonly int x, y;
 
-		public ConsolePoint ConsolePoint => new ConsolePoint(this);
+		public ConsolePoint ConsolePoint => new(this);
 
 		public BufferPoint(int x, int y)
 		{
diff --git a/MultiAdmin/Features/ConfigGenerator.cs b/MultiAdmin/Features/ConfigGenerator.cs
index 0c54baa..4e5ee74 100644
--- a/MultiAdmin/Features/ConfigGenerator.cs
+++ b/MultiAdmin/Features/ConfigGenerator.cs
@@ -72,22 +72,22 @@ public void OnCall(string[] args)
 
 			ConfigEntry[] registeredConfigs = MultiAdminConfig.GlobalConfig.GetRegisteredConfigs();
 
-			List<string> lines = new List<string>(registeredConfigs.Length);
+			List<string> lines = new(registeredConfigs.Length);
 			foreach (ConfigEntry configEntry in registeredConfigs)
 			{
 				switch (configEntry)
 				{
 					case ConfigEntry<string[]> config:
-					{
-						lines.Add($"{config.Key}: {(config.Default == null ? "" : string.Join(", ", config.Default))}");
-						break;
-					}
+						{
+							lines.Add($"{config.Key}: {(config.Default == null ? "" : string.Join(", ", config.Default))}");
+							break;
+						}
 
 					default:
-					{
-						lines.Add($"{configEntry.Key}: {configEntry.ObjectDefault ?? ""}");
-						break;
-					}
+						{
+							lines.Add($"{configEntry.Key}: {configEntry.ObjectDefault ?? ""}");
+							break;
+						}
 				}
 			}
 
diff --git a/MultiAdmin/Features/FolderCopyRoundQueue.cs b/MultiAdmin/Features/FolderCopyRoundQueue.cs
index 6f91266..bbe9fb1 100644
--- a/MultiAdmin/Features/FolderCopyRoundQueue.cs
+++ b/MultiAdmin/Features/FolderCopyRoundQueue.cs
@@ -60,7 +60,7 @@ private int GetNextRandomIndex()
 		{
 			if (!HasValidQueue) return 0;
 
-			Random random = new Random();
+			Random random = new();
 
 			int index;
 			do
diff --git a/MultiAdmin/Features/GithubGenerator.cs b/MultiAdmin/Features/GithubGenerator.cs
index f779cec..5c01a75 100644
--- a/MultiAdmin/Features/GithubGenerator.cs
+++ b/MultiAdmin/Features/GithubGenerator.cs
@@ -74,7 +74,7 @@ public void OnCall(string[] args)
 				// Ignore, any proper exceptions will be presented when the file is written
 			}
 
-			List<string> lines = new List<string> {"# MultiAdmin", "", "## Features", ""};
+			List<string> lines = new() { "# MultiAdmin", "", "## Features", "" };
 
 			foreach (Feature feature in Server.features)
 			{
@@ -99,72 +99,72 @@ public void OnCall(string[] args)
 			foreach (ConfigEntry configEntry in MultiAdminConfig.GlobalConfig.GetRegisteredConfigs())
 			{
 				StringBuilder stringBuilder =
-					new StringBuilder($"{configEntry.Key ?? EmptyIndicator}{ColumnSeparator}");
+					new($"{configEntry.Key ?? EmptyIndicator}{ColumnSeparator}");
 
 				switch (configEntry)
 				{
 					case ConfigEntry<string> config:
-					{
-						stringBuilder.Append(
-							$"String{ColumnSeparator}{(string.IsNullOrEmpty(config.Default) ? EmptyIndicator : config.Default)}");
-						break;
-					}
+						{
+							stringBuilder.Append(
+								$"String{ColumnSeparator}{(string.IsNullOrEmpty(config.Default) ? EmptyIndicator : config.Default)}");
+							break;
+						}
 
 					case ConfigEntry<string[]> config:
-					{
-						stringBuilder.Append(
-							$"String List{ColumnSeparator}{(config.Default?.IsEmpty() ?? true ? EmptyIndicator : string.Join(", ", config.Default))}");
-						break;
-					}
+						{
+							stringBuilder.Append(
+								$"String List{ColumnSeparator}{(config.Default?.IsEmpty() ?? true ? EmptyIndicator : string.Join(", ", config.Default))}");
+							break;
+						}
 
 					case ConfigEntry<int> config:
-					{
-						stringBuilder.Append($"Integer{ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"Integer{ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					case ConfigEntry<uint> config:
-					{
-						stringBuilder.Append($"Unsigned Integer{ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"Unsigned Integer{ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					case ConfigEntry<float> config:
-					{
-						stringBuilder.Append($"Float{ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"Float{ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					case ConfigEntry<double> config:
-					{
-						stringBuilder.Append($"Double{ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"Double{ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					case ConfigEntry<decimal> config:
-					{
-						stringBuilder.Append($"Decimal{ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"Decimal{ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					case ConfigEntry<bool> config:
-					{
-						stringBuilder.Append($"Boolean{ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"Boolean{ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					case ConfigEntry<InputHandler.ConsoleInputSystem> config:
-					{
-						stringBuilder.Append($"[ConsoleInputSystem](#ConsoleInputSystem){ColumnSeparator}{config.Default}");
-						break;
-					}
+						{
+							stringBuilder.Append($"[ConsoleInputSystem](#ConsoleInputSystem){ColumnSeparator}{config.Default}");
+							break;
+						}
 
 					default:
-					{
-						stringBuilder.Append(
-							$"{configEntry.ValueType?.Name ?? EmptyIndicator}{ColumnSeparator}{configEntry.ObjectDefault ?? EmptyIndicator}");
-						break;
-					}
+						{
+							stringBuilder.Append(
+								$"{configEntry.ValueType?.Name ?? EmptyIndicator}{ColumnSeparator}{configEntry.ObjectDefault ?? EmptyIndicator}");
+							break;
+						}
 				}
 
 				stringBuilder.Append($"{ColumnSeparator}{configEntry.Description ?? EmptyIndicator}");
diff --git a/MultiAdmin/Features/HelpCommand.cs b/MultiAdmin/Features/HelpCommand.cs
index f2e42f1..aec5c09 100644
--- a/MultiAdmin/Features/HelpCommand.cs
+++ b/MultiAdmin/Features/HelpCommand.cs
@@ -9,7 +9,7 @@ namespace MultiAdmin.Features
 	[Feature]
 	public class HelpCommand : Feature, ICommand
 	{
-		private static readonly ColoredMessage helpPrefix = new ColoredMessage("Commands from MultiAdmin:\n", ConsoleColor.Yellow);
+		private static readonly ColoredMessage helpPrefix = new("Commands from MultiAdmin:\n", ConsoleColor.Yellow);
 
 		public HelpCommand(Server server) : base(server)
 		{
@@ -31,7 +31,7 @@ public void OnCall(string[] args)
 
 			message[0] = helpPrefix;
 
-			List<string> helpOutput = new List<string>();
+			List<string> helpOutput = new();
 			foreach (KeyValuePair<string, ICommand> command in Server.commands)
 			{
 				string usage = command.Value.GetUsage();
diff --git a/MultiAdmin/Features/TitleBar.cs b/MultiAdmin/Features/TitleBar.cs
index 8bedad4..3b04790 100644
--- a/MultiAdmin/Features/TitleBar.cs
+++ b/MultiAdmin/Features/TitleBar.cs
@@ -53,7 +53,7 @@ private void UpdateTitlebar()
 		{
 			if (Program.Headless || !Server.ServerConfig.SetTitleBar.Value) return;
 
-			List<string> titleBar = new List<string> {$"MultiAdmin {Program.MaVersion}"};
+			List<string> titleBar = new() { $"MultiAdmin {Program.MaVersion}" };
 
 			if (!string.IsNullOrEmpty(Server.serverId))
 			{
diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs
index 38d170d..624ca83 100644
--- a/MultiAdmin/Program.cs
+++ b/MultiAdmin/Program.cs
@@ -17,7 +17,7 @@ public static class Program
 	{
 		public const string MaVersion = "3.4.1.0";
 
-		private static readonly List<Server> InstantiatedServers = new List<Server>();
+		private static readonly List<Server> InstantiatedServers = new();
 
 		private static readonly string MaDebugLogDir =
 			Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.LogLocation.Value);
@@ -34,7 +34,7 @@ public static class Program
 		private static IExitSignal exitSignalListener;
 
 		private static bool exited = false;
-		private static readonly object ExitLock = new object();
+		private static readonly object ExitLock = new();
 
 		#region Server Properties
 
@@ -86,8 +86,8 @@ public static void Write(string message, ConsoleColor color = ConsoleColor.DarkY
 		private static bool IsDebugLogTagAllowed(string tag)
 		{
 			return (!MultiAdminConfig.GlobalConfig?.DebugLogBlacklist?.Value?.Contains(tag) ?? true) &&
-			       ((MultiAdminConfig.GlobalConfig?.DebugLogWhitelist?.Value?.IsEmpty() ?? true) ||
-			        MultiAdminConfig.GlobalConfig.DebugLogWhitelist.Value.Contains(tag));
+				   ((MultiAdminConfig.GlobalConfig?.DebugLogWhitelist?.Value?.IsEmpty() ?? true) ||
+					MultiAdminConfig.GlobalConfig.DebugLogWhitelist.Value.Contains(tag));
 		}
 
 		public static void LogDebugException(string tag, Exception exception)
@@ -107,7 +107,7 @@ public static void LogDebug(string tag, string message)
 				try
 				{
 					if ((!MultiAdminConfig.GlobalConfig?.DebugLog?.Value ?? true) ||
-					    string.IsNullOrEmpty(MaDebugLogFile) || tag == null || !IsDebugLogTagAllowed(tag)) return;
+						string.IsNullOrEmpty(MaDebugLogFile) || tag == null || !IsDebugLogTagAllowed(tag)) return;
 
 					// Assign debug log stream as needed
 					if (debugLogStream == null)
@@ -221,7 +221,7 @@ public static void Main()
 
 			Headless = GetFlagFromArgs(Args, "headless", "h");
 
-            string serverIdArg = GetParamFromArgs(Args, "server-id", "id");
+			string serverIdArg = GetParamFromArgs(Args, "server-id", "id");
 			string configArg = GetParamFromArgs(Args, "config", "c");
 			portArg = uint.TryParse(GetParamFromArgs(Args, "port", "p"), out uint port) ? (uint?)port : null;
 
@@ -383,17 +383,17 @@ public static bool GetFlagFromArgs(string[] args, string[] keys = null, string[]
 
 		public static string GetParamFromArgs(string[] args, string key = null, string alias = null)
 		{
-			return GetParamFromArgs(args, new string[] {key}, new string[] {alias});
+			return GetParamFromArgs(args, new string[] { key }, new string[] { alias });
 		}
 
 		public static bool ArgsContainsParam(string[] args, string key = null, string alias = null)
 		{
-			return ArgsContainsParam(args, new string[] {key}, new string[] {alias});
+			return ArgsContainsParam(args, new string[] { key }, new string[] { alias });
 		}
 
 		public static bool GetFlagFromArgs(string[] args, string key = null, string alias = null)
 		{
-			return GetFlagFromArgs(args, new string[] {key}, new string[] {alias});
+			return GetFlagFromArgs(args, new string[] { key }, new string[] { alias });
 		}
 
 		public static Process StartServer(Server server)
@@ -405,7 +405,7 @@ public static Process StartServer(Server server)
 				Write("Error while starting new server: Could not find the executable location!", ConsoleColor.Red);
 			}
 
-			List<string> args = new List<string>(server.args);
+			List<string> args = new(server.args);
 
 			if (!string.IsNullOrEmpty(server.serverId))
 			{
@@ -422,7 +422,7 @@ public static Process StartServer(Server server)
 			if (Headless)
 				args.Add("-h");
 
-			ProcessStartInfo startInfo = new ProcessStartInfo(assemblyLocation, args.JoinArgs());
+			ProcessStartInfo startInfo = new(assemblyLocation, args.JoinArgs());
 
 			Write($"Launching \"{startInfo.FileName}\" with arguments \"{startInfo.Arguments}\"...");
 
diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs
index daa4439..b0dddec 100644
--- a/MultiAdmin/Server.cs
+++ b/MultiAdmin/Server.cs
@@ -16,12 +16,12 @@ namespace MultiAdmin
 {
 	public class Server
 	{
-		public readonly Dictionary<string, ICommand> commands = new Dictionary<string, ICommand>();
+		public readonly Dictionary<string, ICommand> commands = new();
 
-		public readonly List<Feature> features = new List<Feature>();
+		public readonly List<Feature> features = new();
 
 		// We want a tick only list since its the only event that happens constantly, all the rest can be in a single list
-		private readonly List<IEventTick> tick = new List<IEventTick>();
+		private readonly List<IEventTick> tick = new();
 
 		private readonly MultiAdminConfig serverConfig;
 		public MultiAdminConfig ServerConfig => serverConfig ?? MultiAdminConfig.GlobalConfig;
@@ -48,8 +48,8 @@ public Server(string serverId = null, string configLocation = null, uint? port =
 				: Utils.GetFullPathSafe(Path.Combine(MultiAdminConfig.GlobalConfig.ServersFolder.Value, this.serverId));
 
 			this.configLocation = Utils.GetFullPathSafe(configLocation) ??
-			                      Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.ConfigLocation.Value) ??
-			                      Utils.GetFullPathSafe(serverDir);
+								  Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.ConfigLocation.Value) ??
+								  Utils.GetFullPathSafe(serverDir);
 
 			// Load config
 			serverConfig = MultiAdminConfig.GlobalConfig;
@@ -104,7 +104,7 @@ private set
 		}
 
 		public bool IsStopped => Status == ServerStatus.NotStarted || Status == ServerStatus.Stopped ||
-		                         Status == ServerStatus.StoppedUnexpectedly;
+								 Status == ServerStatus.StoppedUnexpectedly;
 
 		public bool IsRunning => !IsStopped;
 		public bool IsStarted => !IsStopped && !IsStarting;
@@ -112,7 +112,7 @@ private set
 		public bool IsStarting => Status == ServerStatus.Starting;
 
 		public bool IsStopping => Status == ServerStatus.Stopping || Status == ServerStatus.ForceStopping ||
-		                          Status == ServerStatus.Restarting;
+								  Status == ServerStatus.Restarting;
 
 		public bool IsLoading { get; set; }
 
@@ -191,7 +191,7 @@ public bool IsGameProcessRunning
 		private void MainLoop()
 		{
 			// Creates and starts a timer
-			Stopwatch timer = new Stopwatch();
+			Stopwatch timer = new();
 			timer.Restart();
 
 			while (IsGameProcessRunning)
@@ -255,7 +255,7 @@ public void WriteConfigInformation()
 				foreach (MultiAdminConfig config in ServerConfig.GetConfigHierarchy())
 				{
 					if (!string.IsNullOrEmpty(config?.Config?.ConfigPath) &&
-					    MultiAdminConfig.GlobalConfigFilePath != config.Config.ConfigPath)
+						MultiAdminConfig.GlobalConfigFilePath != config.Config.ConfigPath)
 						Write($"Using server config \"{config.Config.ConfigPath}\"...");
 				}
 			}
@@ -316,13 +316,13 @@ public void StartServer(bool restartOnCrash = true)
 					Write($"Executing \"{scpslExe}\"...", ConsoleColor.DarkGreen);
 
 					// Start the console socket connection to the game server
-					ServerSocket consoleSocket = new ServerSocket();
+					ServerSocket consoleSocket = new();
 					// Start the connection before the game to find an open port for communication
 					consoleSocket.Connect();
 
 					SessionSocket = consoleSocket;
 
-					List<string> scpslArgs = new List<string>
+					List<string> scpslArgs = new()
 					{
 						$"-multiadmin:{Program.MaVersion}:{(int)ModFeatures.All}",
 						"-batchmode",
@@ -381,9 +381,10 @@ public void StartServer(bool restartOnCrash = true)
 					// Add custom arguments
 					scpslArgs.AddRange(args);
 
-					ProcessStartInfo startInfo = new ProcessStartInfo(scpslExe, scpslArgs.JoinArgs())
+					ProcessStartInfo startInfo = new(scpslExe, scpslArgs.JoinArgs())
 					{
-						CreateNoWindow = true, UseShellExecute = false
+						CreateNoWindow = true,
+						UseShellExecute = false
 					};
 
 					Write($"Starting server with the following parameters:\n{scpslExe} {startInfo.Arguments}");
@@ -397,7 +398,7 @@ public void StartServer(bool restartOnCrash = true)
 					ForEachHandler<IEventServerPreStart>(eventPreStart => eventPreStart.OnServerPreStart());
 
 					// Start the input reader
-					CancellationTokenSource inputHandlerCancellation = new CancellationTokenSource();
+					CancellationTokenSource inputHandlerCancellation = new();
 					Task inputHandler = null;
 
 					if (!Program.Headless)
@@ -406,7 +407,7 @@ public void StartServer(bool restartOnCrash = true)
 					}
 
 					// Start the output reader
-					OutputHandler outputHandler = new OutputHandler(this);
+					OutputHandler outputHandler = new(this);
 					// Assign the socket events to the OutputHandler
 					consoleSocket.OnReceiveMessage += outputHandler.HandleMessage;
 					consoleSocket.OnReceiveAction += outputHandler.HandleAction;
@@ -568,25 +569,25 @@ private void RegisterFeature(Feature feature)
 					break;
 
 				case ICommand command:
-				{
-					string commandKey = command.GetCommand().ToLower().Trim();
-
-					// If the command was already registered
-					if (commands.ContainsKey(commandKey))
 					{
-						string message =
-							$"Warning, {nameof(MultiAdmin)} tried to register duplicate command \"{commandKey}\"";
+						string commandKey = command.GetCommand().ToLower().Trim();
 
-						Program.LogDebug(nameof(RegisterFeature), message);
-						Write(message);
-					}
-					else
-					{
-						commands.Add(commandKey, command);
-					}
+						// If the command was already registered
+						if (commands.ContainsKey(commandKey))
+						{
+							string message =
+								$"Warning, {nameof(MultiAdmin)} tried to register duplicate command \"{commandKey}\"";
 
-					break;
-				}
+							Program.LogDebug(nameof(RegisterFeature), message);
+							Write(message);
+						}
+						else
+						{
+							commands.Add(commandKey, command);
+						}
+
+						break;
+					}
 			}
 
 			features.Add(feature);
@@ -664,7 +665,7 @@ public void Write(ColoredMessage message, ConsoleColor? timeStampColor = null)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
-				Write(new ColoredMessage[] {message}, timeStampColor ?? message.textColor);
+				Write(new ColoredMessage[] { message }, timeStampColor ?? message.textColor);
 			}
 		}
 
@@ -712,7 +713,7 @@ public void ReloadConfig(bool copyFiles = true, bool runEvent = true)
 			// Handle directory copying
 			string copyFromDir;
 			if (copyFiles && !string.IsNullOrEmpty(configLocation) &&
-			    !string.IsNullOrEmpty(copyFromDir = ServerConfig.CopyFromFolderOnReload.Value))
+				!string.IsNullOrEmpty(copyFromDir = ServerConfig.CopyFromFolderOnReload.Value))
 			{
 				CopyFromDir(copyFromDir, ServerConfig.FolderCopyWhitelist.Value,
 					ServerConfig.FolderCopyBlacklist.Value);
diff --git a/MultiAdmin/ServerIO/InputHandler.cs b/MultiAdmin/ServerIO/InputHandler.cs
index 32520e2..d90d0ed 100644
--- a/MultiAdmin/ServerIO/InputHandler.cs
+++ b/MultiAdmin/ServerIO/InputHandler.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
@@ -11,13 +10,13 @@ namespace MultiAdmin.ServerIO
 {
 	public static class InputHandler
 	{
-		private static readonly char[] Separator = {' '};
+		private static readonly char[] Separator = { ' ' };
 
-		public static readonly ColoredMessage BaseSection = new ColoredMessage(null, ConsoleColor.White);
+		public static readonly ColoredMessage BaseSection = new(null, ConsoleColor.White);
 
-		public static readonly ColoredMessage InputPrefix = new ColoredMessage("> ", ConsoleColor.Yellow);
-		public static readonly ColoredMessage LeftSideIndicator = new ColoredMessage("...", ConsoleColor.Yellow);
-		public static readonly ColoredMessage RightSideIndicator = new ColoredMessage("...", ConsoleColor.Yellow);
+		public static readonly ColoredMessage InputPrefix = new("> ", ConsoleColor.Yellow);
+		public static readonly ColoredMessage LeftSideIndicator = new("...", ConsoleColor.Yellow);
+		public static readonly ColoredMessage RightSideIndicator = new("...", ConsoleColor.Yellow);
 
 		public static int InputPrefixLength => InputPrefix?.Length ?? 0;
 
@@ -43,14 +42,14 @@ public static int SectionBufferWidth
 		}
 
 		public static string CurrentMessage { get; private set; }
-		public static ColoredMessage[] CurrentInput { get; private set; } = {InputPrefix};
+		public static ColoredMessage[] CurrentInput { get; private set; } = { InputPrefix };
 		public static int CurrentCursor { get; private set; }
 
 		public static async void Write(Server server, CancellationToken cancellationToken)
 		{
 			try
 			{
-				ShiftingList prevMessages = new ShiftingList(25);
+				ShiftingList prevMessages = new(25);
 
 				while (server.IsRunning && !server.IsStopping)
 				{
@@ -124,7 +123,7 @@ public static async Task WaitForKey(CancellationToken cancellationToken)
 
 		public static async Task<string> GetInputLineOld(Server server, CancellationToken cancellationToken)
 		{
-			StringBuilder message = new StringBuilder();
+			StringBuilder message = new();
 			while (true)
 			{
 				await WaitForKey(cancellationToken);
@@ -356,7 +355,7 @@ public static void ResetInputParams()
 
 		public static void SetCurrentInput(params ColoredMessage[] coloredMessages)
 		{
-			List<ColoredMessage> message = new List<ColoredMessage> {InputPrefix};
+			List<ColoredMessage> message = new() { InputPrefix };
 
 			if (coloredMessages != null)
 				message.AddRange(coloredMessages);
@@ -444,7 +443,7 @@ public static void RandomizeInputColors()
 		{
 			try
 			{
-				Random random = new Random();
+				Random random = new();
 				Array colors = Enum.GetValues(typeof(ConsoleColor));
 
 				ConsoleColor random1 = (ConsoleColor)colors.GetValue(random.Next(colors.Length));
diff --git a/MultiAdmin/ServerIO/OutputHandler.cs b/MultiAdmin/ServerIO/OutputHandler.cs
index e8b9883..b689632 100644
--- a/MultiAdmin/ServerIO/OutputHandler.cs
+++ b/MultiAdmin/ServerIO/OutputHandler.cs
@@ -8,9 +8,9 @@ namespace MultiAdmin.ServerIO
 	public class OutputHandler
 	{
 		public static readonly Regex SmodRegex =
-			new Regex(@"\[(DEBUG|INFO|WARN|ERROR)\] (\[.*?\]) (.*)", RegexOptions.Compiled | RegexOptions.Singleline);
+			new(@"\[(DEBUG|INFO|WARN|ERROR)\] (\[.*?\]) (.*)", RegexOptions.Compiled | RegexOptions.Singleline);
 		public static readonly char[] TrimChars = { '.', ' ', '\t', '!', '?', ',' };
-		public static readonly char[] EventSplitChars = new char[] {':'};
+		public static readonly char[] EventSplitChars = new char[] { ':' };
 
 		private readonly Server server;
 
@@ -41,7 +41,7 @@ public void HandleMessage(object source, ServerSocket.MessageEventArgs message)
 			if (message.message == null)
 				return;
 
-			ColoredMessage coloredMessage = new ColoredMessage(message.message, ConsoleColor.White);
+			ColoredMessage coloredMessage = new(message.message, ConsoleColor.White);
 
 			if (!coloredMessage.text.IsEmpty())
 			{
diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs
index 6a176cd..81c2d36 100644
--- a/MultiAdmin/ServerIO/ServerSocket.cs
+++ b/MultiAdmin/ServerIO/ServerSocket.cs
@@ -10,9 +10,9 @@ namespace MultiAdmin.ServerIO
 	public class ServerSocket : IDisposable
 	{
 		private const int IntBytes = sizeof(int);
-		public static readonly UTF8Encoding Encoding = new UTF8Encoding(false, true);
+		public static readonly UTF8Encoding Encoding = new(false, true);
 
-		private readonly CancellationTokenSource disposeCancellationSource = new CancellationTokenSource();
+		private readonly CancellationTokenSource disposeCancellationSource = new();
 		private bool disposed = false;
 
 		private readonly TcpListener listener;
diff --git a/MultiAdmin/ServerIO/StringSections.cs b/MultiAdmin/ServerIO/StringSections.cs
index be27d92..f60d969 100644
--- a/MultiAdmin/ServerIO/StringSections.cs
+++ b/MultiAdmin/ServerIO/StringSections.cs
@@ -56,7 +56,7 @@ public static StringSections FromString(string fullString, int sectionLength,
 					$"{nameof(sectionLength)} must be greater than the total length of {nameof(leftIndicator)} and {nameof(rightIndicator)}",
 					nameof(sectionLength));
 
-			List<StringSection> sections = new List<StringSection>();
+			List<StringSection> sections = new();
 
 			if (string.IsNullOrEmpty(fullString))
 				return new StringSections(sections.ToArray());
@@ -69,7 +69,7 @@ public static StringSections FromString(string fullString, int sectionLength,
 			int sectionStartIndex = 0;
 
 			// The text of the current section being created
-			StringBuilder curSecBuilder = new StringBuilder();
+			StringBuilder curSecBuilder = new();
 
 			for (int i = 0; i < fullString.Length; i++)
 			{
@@ -128,7 +128,7 @@ public struct StringSection
 		public ColoredMessage LeftIndicator { get; }
 		public ColoredMessage RightIndicator { get; }
 
-		public ColoredMessage[] Section => new ColoredMessage[] {LeftIndicator, Text, RightIndicator};
+		public ColoredMessage[] Section => new ColoredMessage[] { LeftIndicator, Text, RightIndicator };
 
 		public int MinIndex { get; }
 		public int MaxIndex { get; }
diff --git a/MultiAdmin/Utility/CommandUtils.cs b/MultiAdmin/Utility/CommandUtils.cs
index be0380a..238e111 100644
--- a/MultiAdmin/Utility/CommandUtils.cs
+++ b/MultiAdmin/Utility/CommandUtils.cs
@@ -80,8 +80,8 @@ public static string[] StringToArgs(string inString, int startIndex, int count,
 			if (inString.IsEmpty())
 				return Array.Empty<string>();
 
-			List<string> args = new List<string>();
-			StringBuilder strBuilder = new StringBuilder();
+			List<string> args = new();
+			StringBuilder strBuilder = new();
 			bool inQuotes = false;
 			bool escaped = false;
 
diff --git a/MultiAdmin/Utility/StringEnumerableExtensions.cs b/MultiAdmin/Utility/StringEnumerableExtensions.cs
index 1d9aadb..e509b34 100644
--- a/MultiAdmin/Utility/StringEnumerableExtensions.cs
+++ b/MultiAdmin/Utility/StringEnumerableExtensions.cs
@@ -8,7 +8,7 @@ public static class StringEnumerableExtensions
 	{
 		public static string JoinArgs(this IEnumerable<string> args)
 		{
-			StringBuilder argsStringBuilder = new StringBuilder();
+			StringBuilder argsStringBuilder = new();
 			foreach (string arg in args)
 			{
 				if (arg.IsNullOrEmpty())
diff --git a/MultiAdmin/Utility/Utils.cs b/MultiAdmin/Utility/Utils.cs
index 137b54e..83cad0c 100644
--- a/MultiAdmin/Utility/Utils.cs
+++ b/MultiAdmin/Utility/Utils.cs
@@ -48,7 +48,7 @@ public static ColoredMessage[] TimeStampMessage(ColoredMessage[] message, Consol
 		public static ColoredMessage[] TimeStampMessage(ColoredMessage message, ConsoleColor? color = null,
 			bool cloneMessages = false)
 		{
-			return TimeStampMessage(new ColoredMessage[] {message}, color, cloneMessages);
+			return TimeStampMessage(new ColoredMessage[] { message }, color, cloneMessages);
 		}
 
 		public static string GetFullPathSafe(string path)
@@ -144,7 +144,7 @@ private static bool PassesWhitelistAndBlacklist(string toCheck, string[] whiteli
 			string[] blacklist = null)
 		{
 			return (whitelist.IsNullOrEmpty() || InputMatchesAnyPattern(toCheck, whitelist)) &&
-			       (blacklist.IsNullOrEmpty() || !InputMatchesAnyPattern(toCheck, blacklist));
+				   (blacklist.IsNullOrEmpty() || !InputMatchesAnyPattern(toCheck, blacklist));
 		}
 
 		public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string[] fileWhitelist = null,

From a140f9e3b3c2ceabbabe76a3e6d0a9d14e5a0c9a Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 04:02:45 -0500
Subject: [PATCH 03/16] Update GitHub Actions workflows

---
 .github/workflows/codeql-analysis.yml | 43 +++++++++++++++------------
 .github/workflows/main.yml            |  8 ++---
 2 files changed, 28 insertions(+), 23 deletions(-)

diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 870f972..c2dc1ce 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -13,12 +13,12 @@ name: "CodeQL"
 
 on:
   push:
-    branches: [ master ]
+    branches: [ "main" ]
   pull_request:
     # The branches below must be a subset of the branches above
-    branches: [ master ]
+    branches: [ "main" ]
   schedule:
-    - cron: '44 20 * * 2'
+    - cron: '40 7 * * 5'
 
 jobs:
   analyze:
@@ -33,39 +33,44 @@ jobs:
       fail-fast: false
       matrix:
         language: [ 'csharp' ]
-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
-        # Learn more:
-        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+        # Use only 'java' to analyze code written in Java, Kotlin or both
+        # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
+        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
         # By default, queries listed here will override any specified in a config file.
         # Prefix the list here with "+" to use these queries and those in the config file.
-        # queries: ./path/to/local/query, your-org/your-repo/queries@main
 
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+        # queries: security-extended,security-and-quality
+
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, Go, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 https://git.io/JvXDl
+    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
 
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
+    #   If the Autobuild fails above, remove it and uncomment the following three lines.
+    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
 
-    #- run: |
-    #   make bootstrap
-    #   make release
+    # - run: |
+    #   echo "Run, Build Application using script"
+    #   ./location_of_script_within_repo/buildscript.sh
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
+      uses: github/codeql-action/analyze@v2
+      with:
+        category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7feaaa7..c57a079 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -18,7 +18,7 @@ jobs:
     timeout-minutes: 30
 
     steps:
-    - uses: actions/checkout@v2.3.4
+    - uses: actions/checkout@v3
 
     - if: matrix.os == 'ubuntu-18.04'
       name: Install Linux packages
@@ -27,7 +27,7 @@ jobs:
         sudo apt install -y clang zlib1g-dev libkrb5-dev libtinfo5
 
     - name: Setup .NET
-      uses: actions/setup-dotnet@v1.7.2
+      uses: actions/setup-dotnet@v3
       with:
         dotnet-version: ${{matrix.framework}}
 
@@ -41,13 +41,13 @@ jobs:
       run: dotnet test
 
     - name: Upload ${{matrix.target}} build
-      uses: actions/upload-artifact@v2.2.2
+      uses: actions/upload-artifact@v3
       with:
         name: MultiAdmin-${{matrix.target}}-${{matrix.framework}}
         path: ${{github.workspace}}/Builds/${{matrix.framework}}/${{matrix.target}}
 
     - name: Upload ${{matrix.target}} build to bundle
-      uses: actions/upload-artifact@v2.2.2
+      uses: actions/upload-artifact@v3
       with:
         name: MultiAdmin-all-${{matrix.framework}}
         path: ${{github.workspace}}/Builds/${{matrix.framework}}

From fbd8e70de8b618a6a67a373a0df290c18d006e04 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 04:09:26 -0500
Subject: [PATCH 04/16] Fix potential error with AOT for Enum.GetValues

---
 MultiAdmin/ServerIO/InputHandler.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/MultiAdmin/ServerIO/InputHandler.cs b/MultiAdmin/ServerIO/InputHandler.cs
index d90d0ed..745c82e 100644
--- a/MultiAdmin/ServerIO/InputHandler.cs
+++ b/MultiAdmin/ServerIO/InputHandler.cs
@@ -444,7 +444,7 @@ public static void RandomizeInputColors()
 			try
 			{
 				Random random = new();
-				Array colors = Enum.GetValues(typeof(ConsoleColor));
+				Array colors = Enum.GetValues<ConsoleColor>();
 
 				ConsoleColor random1 = (ConsoleColor)colors.GetValue(random.Next(colors.Length));
 				ConsoleColor random2 = (ConsoleColor)colors.GetValue(random.Next(colors.Length));

From d9962e3fcacb5db8a23b27489170e70e24f42f4c Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 04:11:16 -0500
Subject: [PATCH 05/16] Minor cleanup for newer .NET

---
 MultiAdmin/Server.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs
index b0dddec..f9ace0f 100644
--- a/MultiAdmin/Server.cs
+++ b/MultiAdmin/Server.cs
@@ -261,7 +261,7 @@ public void WriteConfigInformation()
 			}
 		}
 
-		public string GetExecutablePath()
+		public static string GetExecutablePath()
 		{
 			string scpslExe;
 
@@ -329,7 +329,7 @@ public void StartServer(bool restartOnCrash = true)
 						"-nographics",
 						"-silent-crashes",
 						"-nodedicateddelete",
-						$"-id{Process.GetCurrentProcess().Id}",
+						$"-id{Environment.ProcessId}",
 						$"-console{consoleSocket.Port}",
 						$"-port{Port}"
 					};

From 1f802cfaee89930f1edf3524a83f57d4fc93e44c Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 07:36:21 -0500
Subject: [PATCH 06/16] Enable nullable on the project

---
 MultiAdmin.Tests/MultiAdmin.Tests.csproj      |  1 +
 .../ServerIO/StringSectionsTests.cs           |  6 +-
 .../Utility/StringExtensionsTests.cs          |  6 +-
 MultiAdmin/Config/Config.cs                   | 35 ++++----
 .../Config/ConfigHandler/ConfigEntry.cs       | 27 ++++---
 .../Config/ConfigHandler/ConfigRegister.cs    | 22 ++----
 .../InheritableConfigRegister.cs              | 12 +--
 MultiAdmin/Config/MultiAdminConfig.cs         | 36 ++++-----
 MultiAdmin/ConsoleTools/ColoredConsole.cs     | 46 +++++------
 MultiAdmin/ConsoleTools/ConsolePositioning.cs |  4 +-
 MultiAdmin/ConsoleTools/ConsoleUtils.cs       |  8 +-
 MultiAdmin/Features/FolderCopyRoundQueue.cs   | 12 +--
 MultiAdmin/Features/NewCommand.cs             |  4 +-
 MultiAdmin/MultiAdmin.csproj                  |  1 +
 MultiAdmin/NativeExitSignal/IExitSignal.cs    |  2 +-
 MultiAdmin/NativeExitSignal/UnixExitSignal.cs |  2 +-
 MultiAdmin/NativeExitSignal/WinExitSignal.cs  |  2 +-
 MultiAdmin/Program.cs                         | 79 ++++++++++---------
 MultiAdmin/Server.cs                          | 62 +++++++--------
 MultiAdmin/ServerIO/InputHandler.cs           | 31 ++++----
 MultiAdmin/ServerIO/OutputHandler.cs          | 14 ++--
 MultiAdmin/ServerIO/ServerSocket.cs           | 26 +++---
 MultiAdmin/ServerIO/StringSections.cs         | 20 ++---
 MultiAdmin/Utility/CommandUtils.cs            |  5 --
 MultiAdmin/Utility/EmptyExtensions.cs         | 20 ++---
 .../Utility/StringEnumerableExtensions.cs     |  6 +-
 MultiAdmin/Utility/StringExtensions.cs        |  6 +-
 MultiAdmin/Utility/Utils.cs                   | 34 ++++----
 28 files changed, 257 insertions(+), 272 deletions(-)

diff --git a/MultiAdmin.Tests/MultiAdmin.Tests.csproj b/MultiAdmin.Tests/MultiAdmin.Tests.csproj
index 445d9ec..c48508d 100644
--- a/MultiAdmin.Tests/MultiAdmin.Tests.csproj
+++ b/MultiAdmin.Tests/MultiAdmin.Tests.csproj
@@ -4,6 +4,7 @@
   	<LangVersion>11</LangVersion>
     <IsPackable>false</IsPackable>
     <RootNamespace>MultiAdmin.Tests</RootNamespace>
+    <Nullable>enable</Nullable>
     
     <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
     <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
diff --git a/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs b/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs
index c435aaa..ab4e08c 100644
--- a/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs
+++ b/MultiAdmin.Tests/ServerIO/StringSectionsTests.cs
@@ -19,7 +19,7 @@ public StringSectionsTests(ITestOutputHelper output)
 		[InlineData("test string", new[] { "te", "st", " s", "tr", "in", "g" }, 2)]
 		[InlineData("test string", new[] { "tes..", ".t ..", ".st..", ".ring" }, 5, ".", "..")]
 		public void FromStringTest(string testString, string[] expectedSections, int sectionLength,
-			string leftIndictator = null, string rightIndictator = null)
+			string? leftIndictator = null, string? rightIndictator = null)
 		{
 			StringSections sections = StringSections.FromString(testString, sectionLength,
 				leftIndictator != null ? new ColoredMessage(leftIndictator) : null,
@@ -43,8 +43,8 @@ public void FromStringTest(string testString, string[] expectedSections, int sec
 		[Theory]
 		// No further characters can be output because of the prefix and suffix
 		[InlineData("test string", 2, ".", ".")]
-		public void FromStringThrowsTest(string testString, int sectionLength, string leftIndictator = null,
-			string rightIndictator = null)
+		public void FromStringThrowsTest(string testString, int sectionLength, string? leftIndictator = null,
+			string? rightIndictator = null)
 		{
 			Assert.Throws<ArgumentException>(() =>
 			{
diff --git a/MultiAdmin.Tests/Utility/StringExtensionsTests.cs b/MultiAdmin.Tests/Utility/StringExtensionsTests.cs
index 52b622e..1a68c29 100644
--- a/MultiAdmin.Tests/Utility/StringExtensionsTests.cs
+++ b/MultiAdmin.Tests/Utility/StringExtensionsTests.cs
@@ -14,7 +14,7 @@ public class StringExtensionsTests
 		[InlineData("test", "es", 1, 2)]
 		[InlineData(null, null, 0)]
 		[InlineData(null, null, 0, 1)]
-		public void EqualsTest(string main, string section, int startIndex, int count = -1)
+		public void EqualsTest(string? main, string? section, int startIndex, int count = -1)
 		{
 			Assert.True(count < 0 ? main.Equals(section, startIndex) : main.Equals(section, startIndex, count));
 		}
@@ -27,7 +27,7 @@ public void EqualsTest(string main, string section, int startIndex, int count =
 		[InlineData(null, "test", 0)]
 		[InlineData("test", null, 0, 1)]
 		[InlineData(null, "test", 0, 1)]
-		public void NotEqualsTest(string main, string section, int startIndex, int count = -1)
+		public void NotEqualsTest(string? main, string? section, int startIndex, int count = -1)
 		{
 			Assert.False(count < 0 ? main.Equals(section, startIndex) : main.Equals(section, startIndex, count));
 		}
@@ -37,7 +37,7 @@ public void NotEqualsTest(string main, string section, int startIndex, int count
 		[InlineData(typeof(ArgumentOutOfRangeException), "test", "st", 3)]
 		[InlineData(typeof(ArgumentOutOfRangeException), "test", "te", -1)]
 		[InlineData(typeof(ArgumentOutOfRangeException), "test", "es", 4)]
-		public void EqualsThrowsTest(Type expected, string main, string section, int startIndex, int count = -1)
+		public void EqualsThrowsTest(Type expected, string? main, string? section, int startIndex, int count = -1)
 		{
 			Assert.Throws(expected, () =>
 			{
diff --git a/MultiAdmin/Config/Config.cs b/MultiAdmin/Config/Config.cs
index 6bab93e..c118dd9 100644
--- a/MultiAdmin/Config/Config.cs
+++ b/MultiAdmin/Config/Config.cs
@@ -10,10 +10,11 @@ namespace MultiAdmin.Config
 {
 	public class Config
 	{
-		public string[] rawData = { };
+		public string[] rawData = Array.Empty<string>();
 
 		public Config(string path)
 		{
+			internalConfigPath = path;
 			ReadConfigFile(path);
 		}
 
@@ -26,7 +27,7 @@ private set
 			{
 				try
 				{
-					internalConfigPath = Utils.GetFullPathSafe(value);
+					internalConfigPath = Utils.GetFullPathSafe(value) ?? value;
 				}
 				catch (Exception e)
 				{
@@ -38,13 +39,11 @@ private set
 
 		public void ReadConfigFile(string configPath)
 		{
-			if (string.IsNullOrEmpty(configPath)) return;
-
 			ConfigPath = configPath;
 
 			try
 			{
-				rawData = File.Exists(ConfigPath) ? File.ReadAllLines(ConfigPath, Encoding.UTF8) : new string[] { };
+				rawData = File.Exists(ConfigPath) ? File.ReadAllLines(ConfigPath, Encoding.UTF8) : Array.Empty<string>();
 			}
 			catch (Exception e)
 			{
@@ -52,7 +51,7 @@ public void ReadConfigFile(string configPath)
 
 				new ColoredMessage[]
 				{
-					new ColoredMessage($"Error while reading config (Path = {ConfigPath ?? "Null"}):",
+					new ColoredMessage($"Error while reading config (Path = {ConfigPath}):",
 						ConsoleColor.Red),
 					new ColoredMessage(e.ToString(), ConsoleColor.Red)
 				}.WriteLines();
@@ -79,7 +78,7 @@ private static string CleanValue(string value, bool removeQuotes = true)
 			try
 			{
 				if (removeQuotes && newValue.StartsWith("\"") && newValue.EndsWith("\""))
-					return newValue.Substring(1, newValue.Length - 2);
+					return newValue[1..^1];
 			}
 			catch (Exception e)
 			{
@@ -89,7 +88,7 @@ private static string CleanValue(string value, bool removeQuotes = true)
 			return newValue;
 		}
 
-		public string GetString(string key, string def = null, bool removeQuotes = true)
+		public string? GetString(string key, string? def = null, bool removeQuotes = true)
 		{
 			try
 			{
@@ -99,7 +98,7 @@ public string GetString(string key, string def = null, bool removeQuotes = true)
 
 					try
 					{
-						return CleanValue(line.Substring(key.Length + 1), removeQuotes);
+						return CleanValue(line[(key.Length + 1)..], removeQuotes);
 					}
 					catch (Exception e)
 					{
@@ -115,11 +114,11 @@ public string GetString(string key, string def = null, bool removeQuotes = true)
 			return def;
 		}
 
-		public string[] GetStringArray(string key, string[] def = null)
+		public string[]? GetStringArray(string key, string[]? def = null)
 		{
 			try
 			{
-				string value = GetString(key, removeQuotes: false);
+				string? value = GetString(key, removeQuotes: false);
 
 				if (!string.IsNullOrEmpty(value))
 				{
@@ -145,7 +144,7 @@ public int GetInt(string key, int def = 0)
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && int.TryParse(value, out int parseValue))
 					return parseValue;
@@ -162,7 +161,7 @@ public uint GetUInt(string key, uint def = 0)
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && uint.TryParse(value, out uint parseValue))
 					return parseValue;
@@ -179,7 +178,7 @@ public float GetFloat(string key, float def = 0)
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && float.TryParse(value, out float parsedValue))
 					return parsedValue;
@@ -196,7 +195,7 @@ public double GetDouble(string key, double def = 0)
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && double.TryParse(value, out double parsedValue))
 					return parsedValue;
@@ -213,7 +212,7 @@ public decimal GetDecimal(string key, decimal def = 0)
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && decimal.TryParse(value, out decimal parsedValue))
 					return parsedValue;
@@ -230,7 +229,7 @@ public bool GetBool(string key, bool def = false)
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && bool.TryParse(value, out bool parsedValue))
 					return parsedValue;
@@ -247,7 +246,7 @@ public InputHandler.ConsoleInputSystem GetConsoleInputSystem(string key, InputHa
 		{
 			try
 			{
-				string value = GetString(key);
+				string? value = GetString(key);
 
 				if (!string.IsNullOrEmpty(value) && Enum.TryParse<InputHandler.ConsoleInputSystem>(value, out var consoleInputSystem))
 					return consoleInputSystem;
diff --git a/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs b/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs
index 70e170a..6411a6b 100644
--- a/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs
+++ b/MultiAdmin/Config/ConfigHandler/ConfigEntry.cs
@@ -20,12 +20,12 @@ public abstract class ConfigEntry
 		/// <summary>
 		/// The value of the <see cref="ConfigEntry"/>.
 		/// </summary>
-		public abstract object ObjectValue { get; set; }
+		public abstract object? ObjectValue { get; set; }
 
 		/// <summary>
 		/// The default value of the <see cref="ConfigEntry"/>.
 		/// </summary>
-		public abstract object ObjectDefault { get; set; }
+		public abstract object? ObjectDefault { get; set; }
 
 		/// <summary>
 		/// Whether to inherit this config value from the <see cref="ConfigRegister"/>'s parent <see cref="ConfigRegister"/>s if they support value inheritance.
@@ -35,17 +35,17 @@ public abstract class ConfigEntry
 		/// <summary>
 		/// The name of the <see cref="ConfigEntry"/>.
 		/// </summary>
-		public string Name { get; }
+		public string? Name { get; }
 
 		/// <summary>
 		/// The description of the <see cref="ConfigEntry"/>.
 		/// </summary>
-		public string Description { get; }
+		public string? Description { get; }
 
 		/// <summary>
 		/// Creates a basic <see cref="ConfigEntry"/> with no values and indication for whether to inherit the value.
 		/// </summary>
-		public ConfigEntry(string key, bool inherit = true, string name = null, string description = null)
+		public ConfigEntry(string key, bool inherit = true, string? name = null, string? description = null)
 		{
 			Key = key;
 
@@ -58,7 +58,7 @@ public ConfigEntry(string key, bool inherit = true, string name = null, string d
 		/// <summary>
 		/// Creates a basic <see cref="ConfigEntry"/> with no values.
 		/// </summary>
-		public ConfigEntry(string key, string name = null, string description = null) : this(key, true, name,
+		public ConfigEntry(string key, string? name = null, string? description = null) : this(key, true, name,
 			description)
 		{
 		}
@@ -82,25 +82,26 @@ public class ConfigEntry<T> : ConfigEntry
 		/// </summary>
 		public T Default { get; set; }
 
-		public override object ObjectValue
+		public override object? ObjectValue
 		{
 			get => Value;
-			set => Value = (T)value;
+			set => Value = (T)value!;
 		}
 
-		public override object ObjectDefault
+		public override object? ObjectDefault
 		{
 			get => Default;
-			set => Default = (T)value;
+			set => Default = (T)value!;
 		}
 
 		/// <inheritdoc />
 		/// <summary>
 		/// Creates a <see cref="ConfigEntry{T}" /> with the provided type, default value, and indication for whether to inherit the value.
 		/// </summary>
-		public ConfigEntry(string key, T defaultValue = default, bool inherit = true, string name = null,
-			string description = null) : base(key, inherit, name, description)
+		public ConfigEntry(string key, T defaultValue, bool inherit = true, string? name = null,
+			string? description = null) : base(key, inherit, name, description)
 		{
+			Value = defaultValue;
 			Default = defaultValue;
 		}
 
@@ -108,7 +109,7 @@ public ConfigEntry(string key, T defaultValue = default, bool inherit = true, st
 		/// <summary>
 		/// Creates a <see cref="ConfigEntry{T}" /> with the provided type and default value.
 		/// </summary>
-		public ConfigEntry(string key, T defaultValue = default, string name = null, string description = null) : this(
+		public ConfigEntry(string key, T defaultValue, string? name = null, string? description = null) : this(
 			key, defaultValue, true, name, description)
 		{
 		}
diff --git a/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs b/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs
index eb12762..81311c1 100644
--- a/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs
+++ b/MultiAdmin/Config/ConfigHandler/ConfigRegister.cs
@@ -24,7 +24,7 @@ public ConfigEntry[] GetRegisteredConfigs()
 		/// Returns the first <see cref="ConfigEntry"/> with a key matching <paramref name="key"/>.
 		/// </summary>
 		/// <param name="key">The key of the <see cref="ConfigEntry"/> to retrieve.</param>
-		public ConfigEntry GetRegisteredConfig(string key)
+		public ConfigEntry? GetRegisteredConfig(string key)
 		{
 			if (string.IsNullOrEmpty(key))
 				return null;
@@ -47,7 +47,7 @@ public ConfigEntry GetRegisteredConfig(string key)
 		/// <param name="updateValue">Whether to update the value of the config after registration.</param>
 		public void RegisterConfig(ConfigEntry configEntry, bool updateValue = true)
 		{
-			if (configEntry == null || string.IsNullOrEmpty(configEntry.Key))
+			if (string.IsNullOrEmpty(configEntry.Key))
 				return;
 
 			registeredConfigs.Add(configEntry);
@@ -63,9 +63,6 @@ public void RegisterConfig(ConfigEntry configEntry, bool updateValue = true)
 		/// <param name="updateValue">Whether to update the value of the config after registration.</param>
 		public void RegisterConfigs(ConfigEntry[] configEntries, bool updateValue = true)
 		{
-			if (configEntries == null)
-				return;
-
 			foreach (ConfigEntry configEntry in configEntries)
 			{
 				RegisterConfig(configEntry, updateValue);
@@ -78,7 +75,7 @@ public void RegisterConfigs(ConfigEntry[] configEntries, bool updateValue = true
 		/// <param name="configEntry">The <see cref="ConfigEntry"/> to be un-registered.</param>
 		public void UnRegisterConfig(ConfigEntry configEntry)
 		{
-			if (configEntry == null || string.IsNullOrEmpty(configEntry.Key))
+			if (string.IsNullOrEmpty(configEntry.Key))
 				return;
 
 			registeredConfigs.Remove(configEntry);
@@ -90,7 +87,9 @@ public void UnRegisterConfig(ConfigEntry configEntry)
 		/// <param name="key">The key of the <see cref="ConfigEntry"/> to be un-registered.</param>
 		public void UnRegisterConfig(string key)
 		{
-			UnRegisterConfig(GetRegisteredConfig(key));
+			ConfigEntry? entry = GetRegisteredConfig(key);
+			if (entry != null)
+				UnRegisterConfig(entry);
 		}
 
 		/// <summary>
@@ -99,9 +98,6 @@ public void UnRegisterConfig(string key)
 		/// <param name="configEntries">The <see cref="ConfigEntry"/>s to be un-registered.</param>
 		public void UnRegisterConfigs(params ConfigEntry[] configEntries)
 		{
-			if (configEntries == null)
-				return;
-
 			foreach (ConfigEntry configEntry in configEntries)
 			{
 				UnRegisterConfig(configEntry);
@@ -114,9 +110,6 @@ public void UnRegisterConfigs(params ConfigEntry[] configEntries)
 		/// <param name="keys">The keys of the <see cref="ConfigEntry"/>s to be un-registered.</param>
 		public void UnRegisterConfigs(params string[] keys)
 		{
-			if (keys == null)
-				return;
-
 			foreach (string key in keys)
 			{
 				UnRegisterConfig(key);
@@ -146,9 +139,6 @@ public void UnRegisterConfigs()
 		/// <param name="configEntries">The <see cref="ConfigEntry"/>s to be assigned values.</param>
 		public void UpdateConfigValues(params ConfigEntry[] configEntries)
 		{
-			if (configEntries == null)
-				return;
-
 			foreach (ConfigEntry configEntry in configEntries)
 			{
 				UpdateConfigValue(configEntry);
diff --git a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs
index 99e143e..e08ecbd 100644
--- a/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs
+++ b/MultiAdmin/Config/ConfigHandler/InheritableConfigRegister.cs
@@ -11,7 +11,7 @@ public abstract class InheritableConfigRegister : ConfigRegister
 		/// Creates an <see cref="InheritableConfigRegister"/> with the parent <paramref name="parentConfigRegister"/> to inherit unset config values from.
 		/// </summary>
 		/// <param name="parentConfigRegister">The <see cref="ConfigRegister"/> to inherit unset config values from.</param>
-		protected InheritableConfigRegister(ConfigRegister parentConfigRegister = null)
+		protected InheritableConfigRegister(ConfigRegister? parentConfigRegister = null)
 		{
 			ParentConfigRegister = parentConfigRegister;
 		}
@@ -19,7 +19,7 @@ protected InheritableConfigRegister(ConfigRegister parentConfigRegister = null)
 		/// <summary>
 		/// The parent <see cref="ConfigRegister"/> to inherit from.
 		/// </summary>
-		public ConfigRegister ParentConfigRegister { get; protected set; }
+		public ConfigRegister? ParentConfigRegister { get; protected set; }
 
 		/// <summary>
 		/// Returns whether <paramref name="configEntry"/> should be inherited from the parent <see cref="ConfigRegister"/>.
@@ -34,7 +34,7 @@ protected InheritableConfigRegister(ConfigRegister parentConfigRegister = null)
 		public abstract void UpdateConfigValueInheritable(ConfigEntry configEntry);
 
 		/// <summary>
-		/// Updates the value of <paramref name="configEntry"/> from this <see cref="InheritableConfigRegister"/> if the <see cref="ParentConfigRegister"/> is null or if <seealso cref="ShouldInheritConfigEntry"/> returns true.
+		/// Updates the value of <paramref name="configEntry"/> from this <see cref="InheritableConfigRegister"/> if the <see cref="ParentConfigRegister"/> is null or if <seealso cref="ShouldInheritConfigEntry"/> returns false.
 		/// </summary>
 		/// <param name="configEntry">The <see cref="ConfigEntry"/> to be assigned a value.</param>
 		public override void UpdateConfigValue(ConfigEntry configEntry)
@@ -44,7 +44,7 @@ public override void UpdateConfigValue(ConfigEntry configEntry)
 			{
 				ParentConfigRegister.UpdateConfigValue(configEntry);
 			}
-			else
+			else if (configEntry != null)
 			{
 				UpdateConfigValueInheritable(configEntry);
 			}
@@ -59,12 +59,12 @@ public ConfigRegister[] GetConfigRegisterHierarchy(bool highestToLowest = true)
 			List<ConfigRegister> configRegisterHierarchy = new();
 
 			ConfigRegister configRegister = this;
-			while (configRegister != null && !configRegisterHierarchy.Contains(configRegister))
+			while (!configRegisterHierarchy.Contains(configRegister))
 			{
 				configRegisterHierarchy.Add(configRegister);
 
 				// 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)
+				if (configRegister is InheritableConfigRegister inheritableConfigRegister && inheritableConfigRegister.ParentConfigRegister != null)
 				{
 					configRegister = inheritableConfigRegister.ParentConfigRegister;
 				}
diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs
index d9c1d7b..edf9a7b 100644
--- a/MultiAdmin/Config/MultiAdminConfig.cs
+++ b/MultiAdmin/Config/MultiAdminConfig.cs
@@ -47,7 +47,7 @@ public class MultiAdminConfig : InheritableConfigRegister
 				"MultiAdmin Debug Logging Blacklist", "Which tags to block for MultiAdmin debug logging");
 
 		public ConfigEntry<string[]> DebugLogWhitelist { get; } =
-			new ConfigEntry<string[]>("multiadmin_debug_log_whitelist", new string[0],
+			new ConfigEntry<string[]>("multiadmin_debug_log_whitelist", Array.Empty<string>(),
 				"MultiAdmin Debug Logging Whitelist", "Which tags to log for MultiAdmin debug logging (Defaults to logging all if none are provided)");
 
 		public ConfigEntry<bool> UseNewInputSystem { get; } =
@@ -71,23 +71,23 @@ public class MultiAdminConfig : InheritableConfigRegister
 				"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<string[]> FolderCopyWhitelist { get; } =
-			new ConfigEntry<string[]>("folder_copy_whitelist", new string[0],
+			new ConfigEntry<string[]>("folder_copy_whitelist", Array.Empty<string>(),
 				"Folder Copy Whitelist", "The list of file names to copy from the folder defined by `copy_from_folder_on_reload` (accepts `*` wildcards)");
 
 		public ConfigEntry<string[]> FolderCopyBlacklist { get; } =
-			new ConfigEntry<string[]>("folder_copy_blacklist", new string[0],
+			new ConfigEntry<string[]>("folder_copy_blacklist", Array.Empty<string>(),
 				"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<string[]> FolderCopyRoundQueue { get; } =
-			new ConfigEntry<string[]>("folder_copy_round_queue", new string[0],
+			new ConfigEntry<string[]>("folder_copy_round_queue", Array.Empty<string>(),
 				"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<string[]> FolderCopyRoundQueueWhitelist { get; } =
-			new ConfigEntry<string[]>("folder_copy_round_queue_whitelist", new string[0],
+			new ConfigEntry<string[]>("folder_copy_round_queue_whitelist", Array.Empty<string>(),
 				"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<string[]> FolderCopyRoundQueueBlacklist { get; } =
-			new ConfigEntry<string[]>("folder_copy_round_queue_blacklist", new string[0],
+			new ConfigEntry<string[]>("folder_copy_round_queue_blacklist", Array.Empty<string>(),
 				"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<bool> RandomizeFolderCopyRoundQueue { get; } =
@@ -197,19 +197,19 @@ public InputHandler.ConsoleInputSystem ActualConsoleInputSystem
 		}
 
 		public const string ConfigFileName = "scp_multiadmin.cfg";
-		public static readonly string GlobalConfigFilePath = Utils.GetFullPathSafe(ConfigFileName);
+		public static readonly string GlobalConfigFilePath = Utils.GetFullPathSafe(ConfigFileName) ?? throw new FileNotFoundException($"Config file \"{nameof(GlobalConfigFilePath)}\" was not set", ConfigFileName);
 
 		public static readonly MultiAdminConfig GlobalConfig = new(GlobalConfigFilePath, null);
 
-		public MultiAdminConfig ParentConfig
+		public MultiAdminConfig? ParentConfig
 		{
 			get => ParentConfigRegister as MultiAdminConfig;
 			protected set => ParentConfigRegister = value;
 		}
 
-		public Config Config { get; }
+		public Config? Config { get; }
 
-		public MultiAdminConfig(Config config, MultiAdminConfig parentConfig, bool createConfig = true)
+		public MultiAdminConfig(Config? config, MultiAdminConfig? parentConfig, bool createConfig = true)
 		{
 			Config = config;
 			ParentConfig = parentConfig;
@@ -247,16 +247,16 @@ public MultiAdminConfig(Config config, MultiAdminConfig parentConfig, bool creat
 			ReloadConfig();
 		}
 
-		public MultiAdminConfig(Config config, bool createConfig = true) : this(config, GlobalConfig, createConfig)
+		public MultiAdminConfig(Config? config, bool createConfig = true) : this(config, GlobalConfig, createConfig)
 		{
 		}
 
-		public MultiAdminConfig(string path, MultiAdminConfig parentConfig, bool createConfig = true) : this(
-			new Config(path), parentConfig, createConfig)
+		public MultiAdminConfig(string? path, MultiAdminConfig? parentConfig, bool createConfig = true) : this(
+			path != null ? new Config(path) : null, parentConfig, createConfig)
 		{
 		}
 
-		public MultiAdminConfig(string path, bool createConfig = true) : this(path, GlobalConfig, createConfig)
+		public MultiAdminConfig(string? path, bool createConfig = true) : this(path, GlobalConfig, createConfig)
 		{
 		}
 
@@ -277,13 +277,13 @@ public override void UpdateConfigValueInheritable(ConfigEntry configEntry)
 			{
 				case ConfigEntry<string> config:
 					{
-						config.Value = Config.GetString(config.Key, config.Default);
+						config.Value = Config.GetString(config.Key, config.Default) ?? config.Default;
 						break;
 					}
 
 				case ConfigEntry<string[]> config:
 					{
-						config.Value = Config.GetStringArray(config.Key, config.Default);
+						config.Value = Config.GetStringArray(config.Key, config.Default) ?? config.Default;
 						break;
 					}
 
@@ -376,9 +376,9 @@ public MultiAdminConfig[] GetConfigHierarchy(bool highestToLowest = true)
 			return configHierarchy.ToArray();
 		}
 
-		public bool ConfigHierarchyContainsPath(string path)
+		public bool ConfigHierarchyContainsPath(string? path)
 		{
-			string fullPath = Utils.GetFullPathSafe(path);
+			string? fullPath = Utils.GetFullPathSafe(path) ?? path;
 
 			return !string.IsNullOrEmpty(fullPath) &&
 				   GetConfigHierarchy().Any(config => config.Config?.ConfigPath == path);
diff --git a/MultiAdmin/ConsoleTools/ColoredConsole.cs b/MultiAdmin/ConsoleTools/ColoredConsole.cs
index 50f85bc..0706753 100644
--- a/MultiAdmin/ConsoleTools/ColoredConsole.cs
+++ b/MultiAdmin/ConsoleTools/ColoredConsole.cs
@@ -7,7 +7,7 @@ public static class ColoredConsole
 	{
 		public static readonly object WriteLock = new();
 
-		public static void Write(string text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
+		public static void Write(string? text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
 		{
 			lock (WriteLock)
 			{
@@ -36,7 +36,7 @@ public static void Write(string text, ConsoleColor? textColor = null, ConsoleCol
 			}
 		}
 
-		public static void WriteLine(string text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
+		public static void WriteLine(string? text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
 		{
 			lock (WriteLock)
 			{
@@ -46,11 +46,11 @@ public static void WriteLine(string text, ConsoleColor? textColor = null, Consol
 			}
 		}
 
-		public static void Write(params ColoredMessage[] message)
+		public static void Write(params ColoredMessage?[] message)
 		{
 			lock (WriteLock)
 			{
-				foreach (ColoredMessage coloredMessage in message)
+				foreach (ColoredMessage? coloredMessage in message)
 				{
 					if (coloredMessage != null)
 						Write(coloredMessage.text, coloredMessage.textColor, coloredMessage.backgroundColor);
@@ -58,7 +58,7 @@ public static void Write(params ColoredMessage[] message)
 			}
 		}
 
-		public static void WriteLine(params ColoredMessage[] message)
+		public static void WriteLine(params ColoredMessage?[] message)
 		{
 			lock (WriteLock)
 			{
@@ -68,24 +68,24 @@ public static void WriteLine(params ColoredMessage[] message)
 			}
 		}
 
-		public static void WriteLines(params ColoredMessage[] message)
+		public static void WriteLines(params ColoredMessage?[] message)
 		{
 			lock (WriteLock)
 			{
-				foreach (ColoredMessage coloredMessage in message) WriteLine(coloredMessage);
+				foreach (ColoredMessage? coloredMessage in message) WriteLine(coloredMessage);
 			}
 		}
 	}
 
 	public class ColoredMessage : ICloneable
 	{
-		public string text;
+		public string? text;
 		public ConsoleColor? textColor;
 		public ConsoleColor? backgroundColor;
 
 		public int Length => text?.Length ?? 0;
 
-		public ColoredMessage(string text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
+		public ColoredMessage(string? text, ConsoleColor? textColor = null, ConsoleColor? backgroundColor = null)
 		{
 			this.text = text;
 			this.textColor = textColor;
@@ -98,7 +98,7 @@ public bool Equals(ColoredMessage other)
 				   backgroundColor == other.backgroundColor;
 		}
 
-		public override bool Equals(object obj)
+		public override bool Equals(object? obj)
 		{
 			if (ReferenceEquals(null, obj))
 			{
@@ -120,16 +120,10 @@ public override bool Equals(object obj)
 
 		public override int GetHashCode()
 		{
-			unchecked
-			{
-				int hashCode = text != null ? text.GetHashCode() : 0;
-				hashCode = (hashCode * 397) ^ textColor.GetHashCode();
-				hashCode = (hashCode * 397) ^ backgroundColor.GetHashCode();
-				return hashCode;
-			}
+			return HashCode.Combine(text, textColor, backgroundColor);
 		}
 
-		public static bool operator ==(ColoredMessage firstMessage, ColoredMessage secondMessage)
+		public static bool operator ==(ColoredMessage? firstMessage, ColoredMessage? secondMessage)
 		{
 			if (ReferenceEquals(firstMessage, secondMessage))
 				return true;
@@ -140,12 +134,12 @@ public override int GetHashCode()
 			return firstMessage.Equals(secondMessage);
 		}
 
-		public static bool operator !=(ColoredMessage firstMessage, ColoredMessage secondMessage)
+		public static bool operator !=(ColoredMessage? firstMessage, ColoredMessage? secondMessage)
 		{
 			return !(firstMessage == secondMessage);
 		}
 
-		public override string ToString()
+		public override string? ToString()
 		{
 			return text;
 		}
@@ -179,11 +173,11 @@ public void WriteLine(bool clearConsoleLine = false)
 
 	public static class ColoredMessageArrayExtensions
 	{
-		private static string JoinTextIgnoreNull(ColoredMessage[] coloredMessages)
+		private static string JoinTextIgnoreNull(ColoredMessage?[] coloredMessages)
 		{
 			StringBuilder builder = new("");
 
-			foreach (ColoredMessage coloredMessage in coloredMessages)
+			foreach (ColoredMessage? coloredMessage in coloredMessages)
 			{
 				if (coloredMessage != null)
 					builder.Append(coloredMessage);
@@ -192,12 +186,12 @@ private static string JoinTextIgnoreNull(ColoredMessage[] coloredMessages)
 			return builder.ToString();
 		}
 
-		public static string GetText(this ColoredMessage[] message)
+		public static string GetText(this ColoredMessage?[] message)
 		{
 			return JoinTextIgnoreNull(message);
 		}
 
-		public static void Write(this ColoredMessage[] message, bool clearConsoleLine = false)
+		public static void Write(this ColoredMessage?[] message, bool clearConsoleLine = false)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
@@ -205,7 +199,7 @@ public static void Write(this ColoredMessage[] message, bool clearConsoleLine =
 			}
 		}
 
-		public static void WriteLine(this ColoredMessage[] message, bool clearConsoleLine = false)
+		public static void WriteLine(this ColoredMessage?[] message, bool clearConsoleLine = false)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
@@ -213,7 +207,7 @@ public static void WriteLine(this ColoredMessage[] message, bool clearConsoleLin
 			}
 		}
 
-		public static void WriteLines(this ColoredMessage[] message, bool clearConsoleLine = false)
+		public static void WriteLines(this ColoredMessage?[] message, bool clearConsoleLine = false)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
diff --git a/MultiAdmin/ConsoleTools/ConsolePositioning.cs b/MultiAdmin/ConsoleTools/ConsolePositioning.cs
index 028bac7..3767cd1 100644
--- a/MultiAdmin/ConsoleTools/ConsolePositioning.cs
+++ b/MultiAdmin/ConsoleTools/ConsolePositioning.cs
@@ -41,7 +41,7 @@ public static BufferPoint BufferBottom
 		#endregion
 	}
 
-	public struct ConsolePoint
+	public readonly struct ConsolePoint
 	{
 		public readonly int x, y;
 
@@ -74,7 +74,7 @@ public void SetAsCursorY()
 		}
 	}
 
-	public struct BufferPoint
+	public readonly struct BufferPoint
 	{
 		public readonly int x, y;
 
diff --git a/MultiAdmin/ConsoleTools/ConsoleUtils.cs b/MultiAdmin/ConsoleTools/ConsoleUtils.cs
index 28d94ef..c05ff16 100644
--- a/MultiAdmin/ConsoleTools/ConsoleUtils.cs
+++ b/MultiAdmin/ConsoleTools/ConsoleUtils.cs
@@ -45,7 +45,7 @@ public static void ClearConsoleLine(int index, bool returnCursorPos = false)
 			}
 		}
 
-		public static string ClearConsoleLine(string message)
+		public static string? ClearConsoleLine(string? message)
 		{
 			if (!string.IsNullOrEmpty(message))
 				ClearConsoleLine(message.Contains(Environment.NewLine) ? 0 : message.Length);
@@ -55,16 +55,16 @@ public static string ClearConsoleLine(string message)
 			return message;
 		}
 
-		public static ColoredMessage ClearConsoleLine(ColoredMessage message)
+		public static ColoredMessage? ClearConsoleLine(ColoredMessage? message)
 		{
 			ClearConsoleLine(message?.text);
 			return message;
 		}
 
-		public static ColoredMessage[] ClearConsoleLine(ColoredMessage[] message)
+		public static ColoredMessage?[] ClearConsoleLine(ColoredMessage?[] message)
 		{
 			ClearConsoleLine(message?.GetText());
-			return message;
+			return message!;
 		}
 
 		#endregion
diff --git a/MultiAdmin/Features/FolderCopyRoundQueue.cs b/MultiAdmin/Features/FolderCopyRoundQueue.cs
index bbe9fb1..f0eb36a 100644
--- a/MultiAdmin/Features/FolderCopyRoundQueue.cs
+++ b/MultiAdmin/Features/FolderCopyRoundQueue.cs
@@ -7,9 +7,9 @@ namespace MultiAdmin.Features
 	[Feature]
 	internal class FileCopyRoundQueue : Feature, IEventRoundEnd
 	{
-		private string[] queue;
-		private string[] whitelist;
-		private string[] blacklist;
+		private string[] queue = Array.Empty<string>();
+		private string[] whitelist = Array.Empty<string>();
+		private string[] blacklist = Array.Empty<string>();
 		private bool randomizeQueue;
 		private int queueIndex;
 
@@ -80,9 +80,9 @@ public override void Init()
 
 		public override void OnConfigReload()
 		{
-			queue = Server.ServerConfig.FolderCopyRoundQueue.Value;
-			whitelist = Server.ServerConfig.FolderCopyRoundQueueWhitelist.Value;
-			blacklist = Server.ServerConfig.FolderCopyRoundQueueBlacklist.Value;
+			queue = Server.ServerConfig.FolderCopyRoundQueue.Value ?? Array.Empty<string>();
+			whitelist = Server.ServerConfig.FolderCopyRoundQueueWhitelist.Value ?? Array.Empty<string>();
+			blacklist = Server.ServerConfig.FolderCopyRoundQueueBlacklist.Value ?? Array.Empty<string>();
 			randomizeQueue = Server.ServerConfig.RandomizeFolderCopyRoundQueue.Value;
 		}
 
diff --git a/MultiAdmin/Features/NewCommand.cs b/MultiAdmin/Features/NewCommand.cs
index 2b0d355..c660ae2 100644
--- a/MultiAdmin/Features/NewCommand.cs
+++ b/MultiAdmin/Features/NewCommand.cs
@@ -7,8 +7,8 @@ namespace MultiAdmin.Features
 	[Feature]
 	internal class NewCommand : Feature, ICommand, IEventServerFull
 	{
-		private string onFullServerId;
-		private Process onFullServerInstance;
+		private string? onFullServerId;
+		private Process? onFullServerInstance;
 
 		public NewCommand(Server server) : base(server)
 		{
diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj
index d313540..ed63baf 100644
--- a/MultiAdmin/MultiAdmin.csproj
+++ b/MultiAdmin/MultiAdmin.csproj
@@ -5,6 +5,7 @@
   	<LangVersion>11</LangVersion>
     <RootNamespace>MultiAdmin</RootNamespace>
     <ApplicationIcon>Icon.ico</ApplicationIcon>
+    <Nullable>enable</Nullable>
 
     <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
     <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
diff --git a/MultiAdmin/NativeExitSignal/IExitSignal.cs b/MultiAdmin/NativeExitSignal/IExitSignal.cs
index 00b353f..d3acd1f 100644
--- a/MultiAdmin/NativeExitSignal/IExitSignal.cs
+++ b/MultiAdmin/NativeExitSignal/IExitSignal.cs
@@ -4,6 +4,6 @@ namespace MultiAdmin.NativeExitSignal
 {
 	public interface IExitSignal
 	{
-		event EventHandler Exit;
+		event EventHandler? Exit;
 	}
 }
diff --git a/MultiAdmin/NativeExitSignal/UnixExitSignal.cs b/MultiAdmin/NativeExitSignal/UnixExitSignal.cs
index 715e9e5..ac5cbaa 100644
--- a/MultiAdmin/NativeExitSignal/UnixExitSignal.cs
+++ b/MultiAdmin/NativeExitSignal/UnixExitSignal.cs
@@ -8,7 +8,7 @@ namespace MultiAdmin.NativeExitSignal
 {
 	public class UnixExitSignal : IExitSignal
 	{
-		public event EventHandler Exit;
+		public event EventHandler? Exit;
 
 		private static readonly UnixSignal[] Signals = {
 			new UnixSignal(Signum.SIGINT),  // CTRL + C pressed
diff --git a/MultiAdmin/NativeExitSignal/WinExitSignal.cs b/MultiAdmin/NativeExitSignal/WinExitSignal.cs
index a8ac2c8..8a1c7f7 100644
--- a/MultiAdmin/NativeExitSignal/WinExitSignal.cs
+++ b/MultiAdmin/NativeExitSignal/WinExitSignal.cs
@@ -5,7 +5,7 @@ namespace MultiAdmin.NativeExitSignal
 {
 	public class WinExitSignal : IExitSignal
 	{
-		public event EventHandler Exit;
+		public event EventHandler? Exit;
 
 		[DllImport("Kernel32")]
 		public static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);
diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs
index 624ca83..08500cf 100644
--- a/MultiAdmin/Program.cs
+++ b/MultiAdmin/Program.cs
@@ -3,7 +3,6 @@
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Reflection;
 using System.Threading;
 using MultiAdmin.Config;
 using MultiAdmin.ConsoleTools;
@@ -15,23 +14,23 @@ namespace MultiAdmin
 {
 	public static class Program
 	{
-		public const string MaVersion = "3.4.1.0";
+		public const string MaVersion = "3.5.0.0";
 
 		private static readonly List<Server> InstantiatedServers = new();
 
 		private static readonly string MaDebugLogDir =
-			Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.LogLocation.Value);
+			Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.LogLocation.Value) ?? throw new FileNotFoundException($"Log file \"{nameof(MaDebugLogDir)}\" was not set", MultiAdminConfig.GlobalConfig.LogLocation.Value);
 
-		private static readonly string MaDebugLogFile = !string.IsNullOrEmpty(MaDebugLogDir)
+		private static readonly string? MaDebugLogFile = !string.IsNullOrEmpty(MaDebugLogDir)
 			? Utils.GetFullPathSafe(Path.Combine(MaDebugLogDir, $"{Utils.DateTime}_MA_{MaVersion}_debug_log.txt"))
 			: null;
 
-		private static StreamWriter debugLogStream = null;
+		private static StreamWriter? debugLogStream = null;
 
 		private static uint? portArg;
-		public static readonly string[] Args = Environment.GetCommandLineArgs();
+		public static readonly string?[] Args = Environment.GetCommandLineArgs();
 
-		private static IExitSignal exitSignalListener;
+		private static IExitSignal? exitSignalListener;
 
 		private static bool exited = false;
 		private static readonly object ExitLock = new();
@@ -45,14 +44,14 @@ public static string[] ServerDirectories
 		{
 			get
 			{
-				string globalServersFolder = MultiAdminConfig.GlobalConfig.ServersFolder.Value;
+				string globalServersFolder = MultiAdminConfig.GlobalConfig.ServersFolder.Value!;
 				return !Directory.Exists(globalServersFolder)
-					? new string[] { }
+					? Array.Empty<string>()
 					: Directory.GetDirectories(globalServersFolder);
 			}
 		}
 
-		public static string[] ServerIds => Servers.Select(server => server.serverId).ToArray();
+		public static string[] ServerIds => Servers.Select(server => server.serverId).OfType<string>().ToArray();
 
 		#endregion
 
@@ -62,10 +61,10 @@ public static string[] ServerDirectories
 			Servers.Where(server => !server.ServerConfig.ManualStart.Value).ToArray();
 
 		public static string[] AutoStartServerDirectories =>
-			AutoStartServers.Select(autoStartServer => autoStartServer.serverDir).ToArray();
+			AutoStartServers.Select(autoStartServer => autoStartServer.serverDir).OfType<string>().ToArray();
 
 		public static string[] AutoStartServerIds =>
-			AutoStartServers.Select(autoStartServer => autoStartServer.serverId).ToArray();
+			AutoStartServers.Select(autoStartServer => autoStartServer.serverId).OfType<string>().ToArray();
 
 		#endregion
 
@@ -92,6 +91,7 @@ private static bool IsDebugLogTagAllowed(string tag)
 
 		public static void LogDebugException(string tag, Exception exception)
 		{
+			if (MaDebugLogFile == null) return;
 			lock (MaDebugLogFile)
 			{
 				if (tag == null || !IsDebugLogTagAllowed(tag)) return;
@@ -102,6 +102,7 @@ public static void LogDebugException(string tag, Exception exception)
 
 		public static void LogDebug(string tag, string message)
 		{
+			if (MaDebugLogFile == null) return;
 			lock (MaDebugLogFile)
 			{
 				try
@@ -135,7 +136,7 @@ public static void LogDebug(string tag, string message)
 
 		#endregion
 
-		private static void OnExit(object sender, EventArgs e)
+		private static void OnExit(object? sender, EventArgs e)
 		{
 			lock (ExitLock)
 			{
@@ -221,11 +222,11 @@ public static void Main()
 
 			Headless = GetFlagFromArgs(Args, "headless", "h");
 
-			string serverIdArg = GetParamFromArgs(Args, "server-id", "id");
-			string configArg = GetParamFromArgs(Args, "config", "c");
-			portArg = uint.TryParse(GetParamFromArgs(Args, "port", "p"), out uint port) ? (uint?)port : null;
+			string? serverIdArg = GetParamFromArgs(Args, "server-id", "id");
+			string? configArg = GetParamFromArgs(Args, "config", "c");
+			portArg = uint.TryParse(GetParamFromArgs(Args, "port", "p"), out uint port) ? port : null;
 
-			Server server = null;
+			Server? server = null;
 
 			if (!string.IsNullOrEmpty(serverIdArg) || !string.IsNullOrEmpty(configArg))
 			{
@@ -294,7 +295,7 @@ public static void Main()
 			}
 		}
 
-		public static string GetParamFromArgs(string[] args, string[] keys = null, string[] aliases = null)
+		public static string? GetParamFromArgs(string?[] args, string[]? keys = null, string[]? aliases = null)
 		{
 			bool hasKeys = !keys.IsNullOrEmpty();
 			bool hasAliases = !aliases.IsNullOrEmpty();
@@ -303,15 +304,15 @@ public static string GetParamFromArgs(string[] args, string[] keys = null, strin
 
 			for (int i = 0; i < args.Length - 1; i++)
 			{
-				string lowArg = args[i]?.ToLower();
+				string? lowArg = args[i]?.ToLower();
 
 				if (string.IsNullOrEmpty(lowArg)) continue;
 
 				if (hasKeys)
 				{
-					if (keys.Any(key => lowArg == $"--{key?.ToLower()}"))
+					if (keys?.Any(key => lowArg == $"--{key?.ToLower()}") == true)
 					{
-						string value = args[i + 1];
+						string? value = args[i + 1];
 
 						args[i] = null;
 						args[i + 1] = null;
@@ -322,9 +323,9 @@ public static string GetParamFromArgs(string[] args, string[] keys = null, strin
 
 				if (hasAliases)
 				{
-					if (aliases.Any(alias => lowArg == $"-{alias?.ToLower()}"))
+					if (aliases?.Any(alias => lowArg == $"-{alias?.ToLower()}") == true)
 					{
-						string value = args[i + 1];
+						string? value = args[i + 1];
 
 						args[i] = null;
 						args[i + 1] = null;
@@ -337,7 +338,7 @@ public static string GetParamFromArgs(string[] args, string[] keys = null, strin
 			return null;
 		}
 
-		public static bool ArgsContainsParam(string[] args, string[] keys = null, string[] aliases = null)
+		public static bool ArgsContainsParam(string?[] args, string[]? keys = null, string[]? aliases = null)
 		{
 			bool hasKeys = !keys.IsNullOrEmpty();
 			bool hasAliases = !aliases.IsNullOrEmpty();
@@ -346,13 +347,13 @@ public static bool ArgsContainsParam(string[] args, string[] keys = null, string
 
 			for (int i = 0; i < args.Length; i++)
 			{
-				string lowArg = args[i]?.ToLower();
+				string? lowArg = args[i]?.ToLower();
 
 				if (string.IsNullOrEmpty(lowArg)) continue;
 
 				if (hasKeys)
 				{
-					if (keys.Any(key => lowArg == $"--{key?.ToLower()}"))
+					if (keys?.Any(key => lowArg == $"--{key?.ToLower()}") == true)
 					{
 						args[i] = null;
 						return true;
@@ -361,7 +362,7 @@ public static bool ArgsContainsParam(string[] args, string[] keys = null, string
 
 				if (hasAliases)
 				{
-					if (aliases.Any(alias => lowArg == $"-{alias?.ToLower()}"))
+					if (aliases?.Any(alias => lowArg == $"-{alias?.ToLower()}") == true)
 					{
 						args[i] = null;
 						return true;
@@ -372,7 +373,7 @@ public static bool ArgsContainsParam(string[] args, string[] keys = null, string
 			return false;
 		}
 
-		public static bool GetFlagFromArgs(string[] args, string[] keys = null, string[] aliases = null)
+		public static bool GetFlagFromArgs(string?[] args, string[]? keys = null, string[]? aliases = null)
 		{
 			if (keys.IsNullOrEmpty() && aliases.IsNullOrEmpty()) return false;
 
@@ -381,31 +382,31 @@ public static bool GetFlagFromArgs(string[] args, string[] keys = null, string[]
 				: ArgsContainsParam(args, keys, aliases);
 		}
 
-		public static string GetParamFromArgs(string[] args, string key = null, string alias = null)
+		public static string? GetParamFromArgs(string?[] args, string? key = null, string? alias = null)
 		{
-			return GetParamFromArgs(args, new string[] { key }, new string[] { alias });
+			return GetParamFromArgs(args, key != null ? new string[] { key } : null, alias != null ? new string[] { alias } : null);
 		}
 
-		public static bool ArgsContainsParam(string[] args, string key = null, string alias = null)
+		public static bool ArgsContainsParam(string?[] args, string? key = null, string? alias = null)
 		{
-			return ArgsContainsParam(args, new string[] { key }, new string[] { alias });
+			return ArgsContainsParam(args, key != null ? new string[] { key } : null, alias != null ? new string[] { alias } : null);
 		}
 
-		public static bool GetFlagFromArgs(string[] args, string key = null, string alias = null)
+		public static bool GetFlagFromArgs(string?[] args, string? key = null, string? alias = null)
 		{
-			return GetFlagFromArgs(args, new string[] { key }, new string[] { alias });
+			return GetFlagFromArgs(args, key != null ? new string[] { key } : null, alias != null ? new string[] { alias } : null);
 		}
 
-		public static Process StartServer(Server server)
+		public static Process? StartServer(Server server)
 		{
-			string assemblyLocation = Assembly.GetEntryAssembly()?.Location;
+			string assemblyLocation = AppContext.BaseDirectory;
 
 			if (string.IsNullOrEmpty(assemblyLocation))
 			{
 				Write("Error while starting new server: Could not find the executable location!", ConsoleColor.Red);
 			}
 
-			List<string> args = new(server.args);
+			List<string?> args = server.args != null ? new(server.args) : new();
 
 			if (!string.IsNullOrEmpty(server.serverId))
 			{
@@ -426,9 +427,9 @@ public static Process StartServer(Server server)
 
 			Write($"Launching \"{startInfo.FileName}\" with arguments \"{startInfo.Arguments}\"...");
 
-			Process serverProcess = Process.Start(startInfo);
+			Process? serverProcess = Process.Start(startInfo);
 
-			InstantiatedServers.Add(server);
+			if (serverProcess != null) InstantiatedServers.Add(server);
 
 			return serverProcess;
 		}
diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs
index f9ace0f..83624df 100644
--- a/MultiAdmin/Server.cs
+++ b/MultiAdmin/Server.cs
@@ -26,11 +26,11 @@ public class Server
 		private readonly MultiAdminConfig serverConfig;
 		public MultiAdminConfig ServerConfig => serverConfig ?? MultiAdminConfig.GlobalConfig;
 
-		public readonly string serverId;
-		public readonly string configLocation;
+		public readonly string? serverId;
+		public readonly string? configLocation;
 		private readonly uint? port;
-		public readonly string[] args;
-		public readonly string serverDir;
+		public readonly string?[]? args;
+		public readonly string? serverDir;
 		public readonly string logDir;
 
 		public uint Port => port ?? ServerConfig.Port.Value;
@@ -40,12 +40,12 @@ public class Server
 
 		public ModFeatures supportedModFeatures = ModFeatures.None;
 
-		public Server(string serverId = null, string configLocation = null, uint? port = null, string[] args = null)
+		public Server(string? serverId = null, string? configLocation = null, uint? port = null, string?[]? args = null)
 		{
 			this.serverId = serverId;
-			serverDir = string.IsNullOrEmpty(this.serverId)
+			serverDir = string.IsNullOrEmpty(serverId)
 				? null
-				: Utils.GetFullPathSafe(Path.Combine(MultiAdminConfig.GlobalConfig.ServersFolder.Value, this.serverId));
+				: Utils.GetFullPathSafe(Path.Combine(MultiAdminConfig.GlobalConfig.ServersFolder.Value, serverId));
 
 			this.configLocation = Utils.GetFullPathSafe(configLocation) ??
 								  Utils.GetFullPathSafe(MultiAdminConfig.GlobalConfig.ConfigLocation.Value) ??
@@ -55,7 +55,7 @@ public Server(string serverId = null, string configLocation = null, uint? port =
 			serverConfig = MultiAdminConfig.GlobalConfig;
 
 			// Load config hierarchy
-			string serverConfigLocation = this.configLocation;
+			string? serverConfigLocation = this.configLocation;
 			while (!string.IsNullOrEmpty(serverConfigLocation))
 			{
 				// Update the Server object's config location with the valid config location
@@ -81,7 +81,7 @@ public Server(string serverId = null, string configLocation = null, uint? port =
 			this.args = args;
 
 			logDir = Utils.GetFullPathSafe(Path.Combine(string.IsNullOrEmpty(serverDir) ? "" : serverDir,
-				serverConfig.LogLocation.Value));
+				serverConfig.LogLocation.Value)) ?? throw new FileNotFoundException($"Log file \"{nameof(logDir)}\" was not set");
 
 			// Register all features
 			RegisterFeatures();
@@ -131,9 +131,9 @@ public bool SetServerRequestedStatus(ServerStatus status)
 
 		#endregion
 
-		private string startDateTime;
+		private string? startDateTime;
 
-		public string StartDateTime
+		public string? StartDateTime
 		{
 			get => startDateTime;
 
@@ -160,13 +160,13 @@ private set
 		public bool CheckRestartTimeout =>
 			(DateTime.Now - initRestartTimeoutTime).Seconds > ServerConfig.ServerRestartTimeout.Value;
 
-		public string LogDirFile { get; private set; }
-		public string MaLogFile { get; private set; }
-		public string ScpLogFile { get; private set; }
+		public string? LogDirFile { get; private set; }
+		public string? MaLogFile { get; private set; }
+		public string? ScpLogFile { get; private set; }
 
-		private StreamWriter maLogStream;
+		private StreamWriter? maLogStream;
 
-		public Process GameProcess { get; private set; }
+		public Process? GameProcess { get; private set; }
 
 		public bool IsGameProcessRunning
 		{
@@ -182,9 +182,9 @@ public bool IsGameProcessRunning
 		}
 
 
-		public static readonly string DedicatedDir = Utils.GetFullPathSafe(Path.Combine("SCPSL_Data", "Dedicated"));
+		public static readonly string? DedicatedDir = Utils.GetFullPathSafe(Path.Combine("SCPSL_Data", "Dedicated"));
 
-		public ServerSocket SessionSocket { get; private set; }
+		public ServerSocket? SessionSocket { get; private set; }
 
 		#region Server Core
 
@@ -297,7 +297,7 @@ public void StartServer(bool restartOnCrash = true)
 					// Set up logging
 					maLogStream?.Close();
 					Directory.CreateDirectory(logDir);
-					maLogStream = File.AppendText(MaLogFile);
+					maLogStream = File.AppendText(MaLogFile ?? throw new FileNotFoundException($"Log file \"{nameof(MaLogFile)}\" was not set"));
 
 					#region Startup Info Printing & Logging
 
@@ -322,7 +322,7 @@ public void StartServer(bool restartOnCrash = true)
 
 					SessionSocket = consoleSocket;
 
-					List<string> scpslArgs = new()
+					List<string?> scpslArgs = new()
 					{
 						$"-multiadmin:{Program.MaVersion}:{(int)ModFeatures.All}",
 						"-batchmode",
@@ -371,7 +371,7 @@ public void StartServer(bool restartOnCrash = true)
 						scpslArgs.Add(configLocation);
 					}
 
-					string appDataPath = Utils.GetFullPathSafe(ServerConfig.AppDataLocation.Value);
+					string? appDataPath = Utils.GetFullPathSafe(ServerConfig.AppDataLocation.Value);
 					if (!string.IsNullOrEmpty(appDataPath))
 					{
 						scpslArgs.Add("-appdatapath");
@@ -379,7 +379,7 @@ public void StartServer(bool restartOnCrash = true)
 					}
 
 					// Add custom arguments
-					scpslArgs.AddRange(args);
+					if (args != null) scpslArgs.AddRange(args);
 
 					ProcessStartInfo startInfo = new(scpslExe, scpslArgs.JoinArgs())
 					{
@@ -399,7 +399,7 @@ public void StartServer(bool restartOnCrash = true)
 
 					// Start the input reader
 					CancellationTokenSource inputHandlerCancellation = new();
-					Task inputHandler = null;
+					Task? inputHandler = null;
 
 					if (!Program.Headless)
 					{
@@ -448,7 +448,7 @@ public void StartServer(bool restartOnCrash = true)
 						}
 
 						// Cleanup after exit from MainLoop
-						GameProcess.Dispose();
+						GameProcess?.Dispose();
 						GameProcess = null;
 
 						// Stop the input handler if it's running
@@ -535,7 +535,7 @@ public void StopServer(bool killGame = false)
 			SetStopStatus(killGame);
 
 			if ((killGame || !SendMessage("QUIT")) && IsGameProcessRunning)
-				GameProcess.Kill();
+				GameProcess?.Kill();
 		}
 
 		public void SetRestartStatus()
@@ -553,7 +553,7 @@ public void RestartServer(bool killGame = false)
 			SetRestartStatus();
 
 			if ((killGame || !SendMessage("SOFTRESTART")) && IsGameProcessRunning)
-				GameProcess.Kill();
+				GameProcess?.Kill();
 		}
 
 		#endregion
@@ -612,7 +612,7 @@ private void RegisterFeatures()
 			{
 				try
 				{
-					object featureInstance = Activator.CreateInstance(type, this);
+					object? featureInstance = Activator.CreateInstance(type, this);
 					if (featureInstance is Feature feature) RegisterFeature(feature);
 				}
 				catch (Exception e)
@@ -642,7 +642,7 @@ public void ForEachHandler<T>(Action<T> action) where T : IMAEvent
 
 		#region Console Output and Logging
 
-		public void Write(ColoredMessage[] messages, ConsoleColor? timeStampColor = null)
+		public void Write(ColoredMessage?[] messages, ConsoleColor? timeStampColor = null)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
@@ -652,7 +652,7 @@ public void Write(ColoredMessage[] messages, ConsoleColor? timeStampColor = null
 
 				if (Program.Headless) return;
 
-				ColoredMessage[] timeStampedMessage = Utils.TimeStampMessage(messages, timeStampColor);
+				ColoredMessage?[] timeStampedMessage = Utils.TimeStampMessage(messages, timeStampColor);
 
 				timeStampedMessage.WriteLine(ServerConfig.ActualConsoleInputSystem == InputHandler.ConsoleInputSystem.New);
 
@@ -682,7 +682,7 @@ public void Log(string message)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
-				if (message == null || string.IsNullOrEmpty(MaLogFile) || ServerConfig.NoLog.Value) return;
+				if (maLogStream == null || string.IsNullOrEmpty(MaLogFile) || ServerConfig.NoLog.Value) return;
 
 				try
 				{
@@ -725,7 +725,7 @@ public void ReloadConfig(bool copyFiles = true, bool runEvent = true)
 					feature.OnConfigReload();
 		}
 
-		public bool CopyFromDir(string sourceDir, string[] fileWhitelist = null, string[] fileBlacklist = null)
+		public bool CopyFromDir(string? sourceDir, string[]? fileWhitelist = null, string[]? fileBlacklist = null)
 		{
 			if (string.IsNullOrEmpty(configLocation) || string.IsNullOrEmpty(sourceDir)) return false;
 
diff --git a/MultiAdmin/ServerIO/InputHandler.cs b/MultiAdmin/ServerIO/InputHandler.cs
index 745c82e..6993f23 100644
--- a/MultiAdmin/ServerIO/InputHandler.cs
+++ b/MultiAdmin/ServerIO/InputHandler.cs
@@ -41,8 +41,8 @@ public static int SectionBufferWidth
 			}
 		}
 
-		public static string CurrentMessage { get; private set; }
-		public static ColoredMessage[] CurrentInput { get; private set; } = { InputPrefix };
+		public static string? CurrentMessage { get; private set; }
+		public static ColoredMessage?[]? CurrentInput { get; private set; } = { InputPrefix };
 		public static int CurrentCursor { get; private set; }
 
 		public static async void Write(Server server, CancellationToken cancellationToken)
@@ -58,10 +58,10 @@ public static async void Write(Server server, CancellationToken cancellationToke
 						break;
 					}
 
-					string message;
+					string? message;
 					if (server.ServerConfig.ActualConsoleInputSystem == ConsoleInputSystem.New && SectionBufferWidth - TotalIndicatorLength > 0)
 					{
-						message = await GetInputLineNew(server, cancellationToken, prevMessages);
+						message = await GetInputLineNew(server, prevMessages, cancellationToken);
 					}
 					else if (server.ServerConfig.ActualConsoleInputSystem == ConsoleInputSystem.Old)
 					{
@@ -77,12 +77,11 @@ public static async void Write(Server server, CancellationToken cancellationToke
 					server.Write($">>> {message}", ConsoleColor.DarkMagenta);
 
 					int separatorIndex = message.IndexOfAny(Separator);
-					string commandName = (separatorIndex < 0 ? message : message.Substring(0, separatorIndex)).ToLower().Trim();
+					string commandName = (separatorIndex < 0 ? message : message[..separatorIndex]).ToLower().Trim();
 					if (commandName.IsNullOrEmpty()) continue;
 
 					bool callServer = true;
-					server.commands.TryGetValue(commandName, out ICommand command);
-					if (command != null)
+					if (server.commands.TryGetValue(commandName, out ICommand? command))
 					{
 						try
 						{
@@ -147,7 +146,7 @@ public static async Task<string> GetInputLineOld(Server server, CancellationToke
 			}
 		}
 
-		public static async Task<string> GetInputLineNew(Server server, CancellationToken cancellationToken, ShiftingList prevMessages)
+		public static async Task<string> GetInputLineNew(Server server, ShiftingList prevMessages, CancellationToken cancellationToken)
 		{
 			if (server.ServerConfig.RandomInputColors.Value)
 				RandomizeInputColors();
@@ -156,7 +155,7 @@ public static async Task<string> GetInputLineNew(Server server, CancellationToke
 			string message = "";
 			int messageCursor = 0;
 			int prevMessageCursor = -1;
-			StringSections curSections = null;
+			StringSections? curSections = null;
 			int lastSectionIndex = -1;
 			bool exitLoop = false;
 			while (!exitLoop)
@@ -353,9 +352,9 @@ public static void ResetInputParams()
 			CurrentCursor = 0;
 		}
 
-		public static void SetCurrentInput(params ColoredMessage[] coloredMessages)
+		public static void SetCurrentInput(params ColoredMessage?[]? coloredMessages)
 		{
-			List<ColoredMessage> message = new() { InputPrefix };
+			List<ColoredMessage?> message = new() { InputPrefix };
 
 			if (coloredMessages != null)
 				message.AddRange(coloredMessages);
@@ -365,7 +364,7 @@ public static void SetCurrentInput(params ColoredMessage[] coloredMessages)
 
 		public static void SetCurrentInput(string message)
 		{
-			ColoredMessage baseSection = BaseSection?.Clone();
+			ColoredMessage? baseSection = BaseSection?.Clone();
 
 			if (baseSection == null)
 				baseSection = new ColoredMessage(message);
@@ -411,7 +410,7 @@ public static void SetCursor()
 			SetCursor(CurrentCursor);
 		}
 
-		public static void WriteInput(ColoredMessage[] message, bool clearConsoleLine = false)
+		public static void WriteInput(ColoredMessage?[]? message, bool clearConsoleLine = false)
 		{
 			lock (ColoredConsole.WriteLock)
 			{
@@ -444,10 +443,10 @@ public static void RandomizeInputColors()
 			try
 			{
 				Random random = new();
-				Array colors = Enum.GetValues<ConsoleColor>();
+				ConsoleColor[] colors = Enum.GetValues<ConsoleColor>();
 
-				ConsoleColor random1 = (ConsoleColor)colors.GetValue(random.Next(colors.Length));
-				ConsoleColor random2 = (ConsoleColor)colors.GetValue(random.Next(colors.Length));
+				ConsoleColor random1 = colors[random.Next(colors.Length)];
+				ConsoleColor random2 = colors[random.Next(colors.Length)];
 
 				BaseSection.textColor = random1;
 
diff --git a/MultiAdmin/ServerIO/OutputHandler.cs b/MultiAdmin/ServerIO/OutputHandler.cs
index b689632..168c251 100644
--- a/MultiAdmin/ServerIO/OutputHandler.cs
+++ b/MultiAdmin/ServerIO/OutputHandler.cs
@@ -36,20 +36,20 @@ public OutputHandler(Server server)
 			this.server = server;
 		}
 
-		public void HandleMessage(object source, ServerSocket.MessageEventArgs message)
+		public void HandleMessage(object? source, ServerSocket.MessageEventArgs message)
 		{
 			if (message.message == null)
 				return;
 
 			ColoredMessage coloredMessage = new(message.message, ConsoleColor.White);
 
-			if (!coloredMessage.text.IsEmpty())
+			if (!coloredMessage.text.IsNullOrEmpty())
 			{
 				// Parse the color byte
 				coloredMessage.textColor = (ConsoleColor)message.color;
 
 				// Smod2 loggers pretty printing
-				Match match = SmodRegex.Match(coloredMessage.text);
+				Match match = SmodRegex.Match(coloredMessage.text!);
 				if (match.Success)
 				{
 					if (match.Groups.Count >= 3)
@@ -93,7 +93,7 @@ public void HandleMessage(object source, ServerSocket.MessageEventArgs message)
 					}
 				}
 
-				string lowerMessage = coloredMessage.text.ToLower();
+				string lowerMessage = coloredMessage.text!.ToLower();
 				if (!server.supportedModFeatures.HasFlag(ModFeatures.CustomEvents))
 				{
 					switch (lowerMessage.Trim(TrimChars))
@@ -127,13 +127,13 @@ public void HandleMessage(object source, ServerSocket.MessageEventArgs message)
 				if (lowerMessage.StartsWith("multiadmin:"))
 				{
 					// 11 chars in "multiadmin:"
-					string eventMessage = coloredMessage.text.Substring(11);
+					string eventMessage = coloredMessage.text[11..];
 
 					// Split event and event data
 					string[] eventSplit = eventMessage.Split(EventSplitChars, 2);
 
 					string @event = eventSplit[0].ToLower();
-					string eventData = eventSplit.Length > 1 ? eventSplit[1] : null; // Handle events with no data
+					string? eventData = eventSplit.Length > 1 ? eventSplit[1] : null; // Handle events with no data
 
 					switch (@event)
 					{
@@ -177,7 +177,7 @@ public void HandleMessage(object source, ServerSocket.MessageEventArgs message)
 			server.Write(coloredMessage);
 		}
 
-		public void HandleAction(object source, byte action)
+		public void HandleAction(object? source, byte action)
 		{
 			switch ((OutputCodes)action)
 			{
diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs
index 81c2d36..25e25a5 100644
--- a/MultiAdmin/ServerIO/ServerSocket.cs
+++ b/MultiAdmin/ServerIO/ServerSocket.cs
@@ -17,23 +17,23 @@ public class ServerSocket : IDisposable
 
 		private readonly TcpListener listener;
 
-		private TcpClient client;
-		private NetworkStream networkStream;
+		private TcpClient? client;
+		private NetworkStream? networkStream;
 
-		public struct MessageEventArgs
+		public readonly struct MessageEventArgs
 		{
-			public MessageEventArgs(string message, byte color)
+			public MessageEventArgs(string? message, byte color)
 			{
 				this.message = message;
 				this.color = color;
 			}
 
-			public readonly string message;
+			public readonly string? message;
 			public readonly byte color;
 		}
 
-		public event EventHandler<MessageEventArgs> OnReceiveMessage;
-		public event EventHandler<byte> OnReceiveAction;
+		public event EventHandler<MessageEventArgs>? OnReceiveMessage;
+		public event EventHandler<byte>? OnReceiveAction;
 
 		public int Port => ((IPEndPoint)listener.LocalEndpoint).Port;
 
@@ -74,13 +74,17 @@ public void Connect()
 		public async void MessageListener()
 		{
 			byte[] typeBuffer = new byte[1];
+			Memory<byte> typeBufferMemory = new(typeBuffer);
+
 			byte[] intBuffer = new byte[IntBytes];
+			Memory<byte> intBufferMemory = new(intBuffer);
+
 			while (!disposed && networkStream != null)
 			{
 				try
 				{
 					int messageTypeBytesRead =
-						await networkStream.ReadAsync(typeBuffer, 0, 1, disposeCancellationSource.Token);
+						await networkStream.ReadAsync(typeBufferMemory, disposeCancellationSource.Token);
 
 					// Socket has been disconnected
 					if (messageTypeBytesRead <= 0)
@@ -99,7 +103,7 @@ public async void MessageListener()
 					}
 
 					int lengthBytesRead =
-						await networkStream.ReadAsync(intBuffer, 0, IntBytes, disposeCancellationSource.Token);
+						await networkStream.ReadAsync(intBufferMemory, disposeCancellationSource.Token);
 
 					// Socket has been disconnected or integer read is invalid
 					if (lengthBytesRead != IntBytes)
@@ -123,7 +127,7 @@ public async void MessageListener()
 
 					byte[] messageBuffer = new byte[length];
 					int messageBytesRead =
-						await networkStream.ReadAsync(messageBuffer, 0, length, disposeCancellationSource.Token);
+						await networkStream.ReadAsync(messageBuffer.AsMemory(0, length), disposeCancellationSource.Token);
 
 					// Socket has been disconnected
 					if (messageBytesRead <= 0)
@@ -176,6 +180,8 @@ public void Dispose()
 			if (disposed)
 				return;
 
+			GC.SuppressFinalize(this);
+
 			disposed = true;
 			disposeCancellationSource.Cancel();
 			disposeCancellationSource.Dispose();
diff --git a/MultiAdmin/ServerIO/StringSections.cs b/MultiAdmin/ServerIO/StringSections.cs
index f60d969..49d2aa6 100644
--- a/MultiAdmin/ServerIO/StringSections.cs
+++ b/MultiAdmin/ServerIO/StringSections.cs
@@ -45,8 +45,8 @@ public StringSections(StringSection[] sections)
 		}
 
 		public static StringSections FromString(string fullString, int sectionLength,
-			ColoredMessage leftIndicator = null, ColoredMessage rightIndicator = null,
-			ColoredMessage sectionBase = null)
+			ColoredMessage? leftIndicator = null, ColoredMessage? rightIndicator = null,
+			ColoredMessage? sectionBase = null)
 		{
 			int rightIndicatorLength = rightIndicator?.Length ?? 0;
 			int totalIndicatorLength = (leftIndicator?.Length ?? 0) + rightIndicatorLength;
@@ -79,9 +79,9 @@ public static StringSections FromString(string fullString, int sectionLength,
 				if (curSecBuilder.Length < sectionLength - totalIndicatorLength) continue;
 
 				// Decide what the left indicator text should be accounting for the leftmost section
-				ColoredMessage leftIndicatorSection = sections.Count > 0 ? leftIndicator : null;
+				ColoredMessage? leftIndicatorSection = sections.Count > 0 ? leftIndicator : null;
 				// Decide what the right indicator text should be accounting for the rightmost section
-				ColoredMessage rightIndicatorSection =
+				ColoredMessage? rightIndicatorSection =
 					i < fullString.Length - (1 + rightIndicatorLength) ? rightIndicator : null;
 
 				// Check the section length against the final section length
@@ -106,7 +106,7 @@ public static StringSections FromString(string fullString, int sectionLength,
 			if (!curSecBuilder.IsEmpty())
 			{
 				// Only decide for the left indicator, as this last section will always be the rightmost section
-				ColoredMessage leftIndicatorSection = sections.Count > 0 ? leftIndicator : null;
+				ColoredMessage? leftIndicatorSection = sections.Count > 0 ? leftIndicator : null;
 
 				// Copy the section base message and replace the text
 				ColoredMessage section = sectionBase.Clone();
@@ -121,19 +121,19 @@ public static StringSections FromString(string fullString, int sectionLength,
 		}
 	}
 
-	public struct StringSection
+	public readonly struct StringSection
 	{
 		public ColoredMessage Text { get; }
 
-		public ColoredMessage LeftIndicator { get; }
-		public ColoredMessage RightIndicator { get; }
+		public ColoredMessage? LeftIndicator { get; }
+		public ColoredMessage? RightIndicator { get; }
 
-		public ColoredMessage[] Section => new ColoredMessage[] { LeftIndicator, Text, RightIndicator };
+		public ColoredMessage?[] Section => new ColoredMessage?[] { LeftIndicator, Text, RightIndicator };
 
 		public int MinIndex { get; }
 		public int MaxIndex { get; }
 
-		public StringSection(ColoredMessage text, ColoredMessage leftIndicator, ColoredMessage rightIndicator,
+		public StringSection(ColoredMessage text, ColoredMessage? leftIndicator, ColoredMessage? rightIndicator,
 			int minIndex, int maxIndex)
 		{
 			Text = text;
diff --git a/MultiAdmin/Utility/CommandUtils.cs b/MultiAdmin/Utility/CommandUtils.cs
index 238e111..8dd2f80 100644
--- a/MultiAdmin/Utility/CommandUtils.cs
+++ b/MultiAdmin/Utility/CommandUtils.cs
@@ -62,11 +62,6 @@ public static int IndexOfNonEscaped(string inString, char inChar, char escapeCha
 
 		public static string[] StringToArgs(string inString, int startIndex, int count, char separator = ' ', char escapeChar = '\\', char quoteChar = '\"', bool keepQuotes = false)
 		{
-			if (inString == null)
-			{
-				return null;
-			}
-
 			if (startIndex < 0 || startIndex >= inString.Length)
 			{
 				throw new ArgumentOutOfRangeException(nameof(startIndex));
diff --git a/MultiAdmin/Utility/EmptyExtensions.cs b/MultiAdmin/Utility/EmptyExtensions.cs
index be39677..f7cf5d3 100644
--- a/MultiAdmin/Utility/EmptyExtensions.cs
+++ b/MultiAdmin/Utility/EmptyExtensions.cs
@@ -12,7 +12,7 @@ public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
 			return !enumerable.Any();
 		}
 
-		public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
+		public static bool IsNullOrEmpty<T>(this IEnumerable<T?>? enumerable)
 		{
 			return enumerable?.IsEmpty() ?? true;
 		}
@@ -22,17 +22,17 @@ public static bool IsEmpty(this Array array)
 			return array.Length <= 0;
 		}
 
-		public static bool IsNullOrEmpty(this Array array)
+		public static bool IsNullOrEmpty(this Array? array)
 		{
 			return array?.IsEmpty() ?? true;
 		}
 
-		public static bool IsEmpty<T>(this T[] array)
+		public static bool IsEmpty<T>(this T?[] array)
 		{
 			return array.Length <= 0;
 		}
 
-		public static bool IsNullOrEmpty<T>(this T[] array)
+		public static bool IsNullOrEmpty<T>(this T?[]? array)
 		{
 			return array?.IsEmpty() ?? true;
 		}
@@ -42,7 +42,7 @@ public static bool IsEmpty<T>(this ICollection<T> collection)
 			return collection.Count <= 0;
 		}
 
-		public static bool IsNullOrEmpty<T>(this ICollection<T> collection)
+		public static bool IsNullOrEmpty<T>(this ICollection<T?>? collection)
 		{
 			return collection?.IsEmpty() ?? true;
 		}
@@ -52,17 +52,17 @@ public static bool IsEmpty<T>(this List<T> list)
 			return list.Count <= 0;
 		}
 
-		public static bool IsNullOrEmpty<T>(this List<T> list)
+		public static bool IsNullOrEmpty<T>(this List<T?>? list)
 		{
 			return list?.IsEmpty() ?? true;
 		}
 
-		public static bool IsEmpty<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
+		public static bool IsEmpty<TKey, TValue>(this Dictionary<TKey, TValue> dictionary) where TKey : notnull
 		{
 			return dictionary.Count <= 0;
 		}
 
-		public static bool IsNullOrEmpty<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
+		public static bool IsNullOrEmpty<TKey, TValue>(this Dictionary<TKey, TValue?>? dictionary) where TKey : notnull
 		{
 			return dictionary?.IsEmpty() ?? true;
 		}
@@ -72,7 +72,7 @@ public static bool IsEmpty(this StringBuilder stringBuilder)
 			return stringBuilder.Length <= 0;
 		}
 
-		public static bool IsNullOrEmpty(this StringBuilder stringBuilder)
+		public static bool IsNullOrEmpty(this StringBuilder? stringBuilder)
 		{
 			return stringBuilder?.IsEmpty() ?? true;
 		}
@@ -82,7 +82,7 @@ public static bool IsEmpty(this string @string)
 			return @string.Length <= 0;
 		}
 
-		public static bool IsNullOrEmpty(this string @string)
+		public static bool IsNullOrEmpty(this string? @string)
 		{
 			return @string?.IsEmpty() ?? true;
 		}
diff --git a/MultiAdmin/Utility/StringEnumerableExtensions.cs b/MultiAdmin/Utility/StringEnumerableExtensions.cs
index e509b34..e4fc972 100644
--- a/MultiAdmin/Utility/StringEnumerableExtensions.cs
+++ b/MultiAdmin/Utility/StringEnumerableExtensions.cs
@@ -6,12 +6,12 @@ namespace MultiAdmin.Utility
 {
 	public static class StringEnumerableExtensions
 	{
-		public static string JoinArgs(this IEnumerable<string> args)
+		public static string JoinArgs(this IEnumerable<string?> args)
 		{
 			StringBuilder argsStringBuilder = new();
-			foreach (string arg in args)
+			foreach (string? arg in args)
 			{
-				if (arg.IsNullOrEmpty())
+				if (string.IsNullOrEmpty(arg))
 					continue;
 
 				// Escape escape characters (if not on Windows) and quotation marks
diff --git a/MultiAdmin/Utility/StringExtensions.cs b/MultiAdmin/Utility/StringExtensions.cs
index eadb1f0..66007bd 100644
--- a/MultiAdmin/Utility/StringExtensions.cs
+++ b/MultiAdmin/Utility/StringExtensions.cs
@@ -4,7 +4,7 @@ namespace MultiAdmin.Utility
 {
 	public static class StringExtensions
 	{
-		public static bool Equals(this string input, string value, int startIndex, int count)
+		public static bool Equals(this string? input, string? value, int startIndex, int count)
 		{
 			if (input == null && value == null)
 				return true;
@@ -25,7 +25,7 @@ public static bool Equals(this string input, string value, int startIndex, int c
 			return true;
 		}
 
-		public static bool Equals(this string input, string value, int startIndex)
+		public static bool Equals(this string? input, string? value, int startIndex)
 		{
 			if (input == null && value == null)
 				return true;
@@ -47,7 +47,7 @@ public static bool Equals(this string input, string value, int startIndex)
 		/// <returns>A <see cref="string"/> escaped for use with <see cref="string.Format"/></returns>
 		public static string EscapeFormat(this string input)
 		{
-			return input?.Replace("{", "{{").Replace("}", "}}");
+			return input.Replace("{", "{{").Replace("}", "}}");
 		}
 	}
 }
diff --git a/MultiAdmin/Utility/Utils.cs b/MultiAdmin/Utility/Utils.cs
index 83cad0c..606b425 100644
--- a/MultiAdmin/Utility/Utils.cs
+++ b/MultiAdmin/Utility/Utils.cs
@@ -23,12 +23,10 @@ public static string TimeStampMessage(string message)
 			return string.IsNullOrEmpty(message) ? message : $"{TimeStamp} {message}";
 		}
 
-		public static ColoredMessage[] TimeStampMessage(ColoredMessage[] message, ConsoleColor? color = null,
+		public static ColoredMessage?[] TimeStampMessage(ColoredMessage?[] message, ConsoleColor? color = null,
 			bool cloneMessages = false)
 		{
-			if (message == null) return null;
-
-			ColoredMessage[] newMessage = new ColoredMessage[message.Length + 1];
+			ColoredMessage?[] newMessage = new ColoredMessage?[message.Length + 1];
 			newMessage[0] = new ColoredMessage($"{TimeStamp} ", color);
 
 			if (cloneMessages)
@@ -45,13 +43,13 @@ public static ColoredMessage[] TimeStampMessage(ColoredMessage[] message, Consol
 			return newMessage;
 		}
 
-		public static ColoredMessage[] TimeStampMessage(ColoredMessage message, ConsoleColor? color = null,
+		public static ColoredMessage?[] TimeStampMessage(ColoredMessage? message, ConsoleColor? color = null,
 			bool cloneMessages = false)
 		{
-			return TimeStampMessage(new ColoredMessage[] { message }, color, cloneMessages);
+			return TimeStampMessage(new ColoredMessage?[] { message }, color, cloneMessages);
 		}
 
-		public static string GetFullPathSafe(string path)
+		public static string? GetFullPathSafe(string? path)
 		{
 			return string.IsNullOrWhiteSpace(path) ? null : Path.GetFullPath(path);
 		}
@@ -93,7 +91,7 @@ public static bool StringMatches(string input, string pattern, char wildCard = W
 					return false;
 
 				Program.LogDebug(nameof(StringMatches),
-					$"Matching \"{wildCardSection}\" with \"{input.Substring(matchIndex)}\"...");
+					$"Matching \"{wildCardSection}\" with \"{input[matchIndex..]}\"...");
 
 				if (matchIndex <= 0 && pattern[0] != wildCard)
 				{
@@ -130,25 +128,25 @@ public static bool StringMatches(string input, string pattern, char wildCard = W
 			}
 
 			Program.LogDebug(nameof(StringMatches),
-				$"Done matching. Matches = {matchIndex == input.Length || wildCardSections[wildCardSections.Length - 1].IsEmpty()}.");
+				$"Done matching. Matches = {matchIndex == input.Length || wildCardSections[^1].IsEmpty()}.");
 
-			return matchIndex == input.Length || wildCardSections[wildCardSections.Length - 1].IsEmpty();
+			return matchIndex == input.Length || wildCardSections[^1].IsEmpty();
 		}
 
-		public static bool InputMatchesAnyPattern(string input, params string[] namePatterns)
+		public static bool InputMatchesAnyPattern(string input, params string[]? namePatterns)
 		{
-			return !namePatterns.IsNullOrEmpty() && namePatterns.Any(namePattern => StringMatches(input, namePattern));
+			return namePatterns != null && namePatterns.Length > 0 && namePatterns.Any(namePattern => StringMatches(input, namePattern));
 		}
 
-		private static bool PassesWhitelistAndBlacklist(string toCheck, string[] whitelist = null,
-			string[] blacklist = null)
+		private static bool PassesWhitelistAndBlacklist(string toCheck, string[]? whitelist = null,
+			string[]? blacklist = null)
 		{
 			return (whitelist.IsNullOrEmpty() || InputMatchesAnyPattern(toCheck, whitelist)) &&
 				   (blacklist.IsNullOrEmpty() || !InputMatchesAnyPattern(toCheck, blacklist));
 		}
 
-		public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string[] fileWhitelist = null,
-			string[] fileBlacklist = null)
+		public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string[]? fileWhitelist = null,
+			string[]? fileBlacklist = null)
 		{
 			// If the target directory is the same as the source directory 
 			if (source.FullName == target.FullName)
@@ -176,8 +174,8 @@ public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string[]
 			}
 		}
 
-		public static void CopyAll(string source, string target, string[] fileWhitelist = null,
-			string[] fileBlacklist = null)
+		public static void CopyAll(string source, string target, string[]? fileWhitelist = null,
+			string[]? fileBlacklist = null)
 		{
 			CopyAll(new DirectoryInfo(source), new DirectoryInfo(target), fileWhitelist, fileBlacklist);
 		}

From 9b35bb29c95a030f72a97adc9cef633f7f665826 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 08:13:09 -0500
Subject: [PATCH 07/16] Manually register all features to support trimming

---
 .../Features/Attributes/FeatureAttribute.cs   | 10 -----
 MultiAdmin/Features/ConfigGenerator.cs        |  2 -
 MultiAdmin/Features/ConfigReload.cs           |  2 -
 MultiAdmin/Features/ExitCommand.cs            |  3 --
 ...opyRoundQueue.cs => FileCopyRoundQueue.cs} |  4 +-
 MultiAdmin/Features/GithubGenerator.cs        |  2 -
 MultiAdmin/Features/HelpCommand.cs            |  2 -
 MultiAdmin/Features/MemoryChecker.cs          |  2 -
 MultiAdmin/Features/MultiAdminInfo.cs         |  2 -
 MultiAdmin/Features/NewCommand.cs             |  2 -
 MultiAdmin/Features/Restart.cs                |  3 --
 MultiAdmin/Features/RestartRoundCounter.cs    |  3 --
 MultiAdmin/Features/TitleBar.cs               |  2 -
 MultiAdmin/Server.cs                          | 41 ++++++-------------
 14 files changed, 14 insertions(+), 66 deletions(-)
 delete mode 100644 MultiAdmin/Features/Attributes/FeatureAttribute.cs
 rename MultiAdmin/Features/{FolderCopyRoundQueue.cs => FileCopyRoundQueue.cs} (95%)

diff --git a/MultiAdmin/Features/Attributes/FeatureAttribute.cs b/MultiAdmin/Features/Attributes/FeatureAttribute.cs
deleted file mode 100644
index ce37dff..0000000
--- a/MultiAdmin/Features/Attributes/FeatureAttribute.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-
-namespace MultiAdmin.Features.Attributes
-{
-	[AttributeUsage(AttributeTargets.Class)
-	]
-	public class FeatureAttribute : Attribute
-	{
-	}
-}
diff --git a/MultiAdmin/Features/ConfigGenerator.cs b/MultiAdmin/Features/ConfigGenerator.cs
index 4e5ee74..c612d54 100644
--- a/MultiAdmin/Features/ConfigGenerator.cs
+++ b/MultiAdmin/Features/ConfigGenerator.cs
@@ -3,12 +3,10 @@
 using System.IO;
 using MultiAdmin.Config;
 using MultiAdmin.Config.ConfigHandler;
-using MultiAdmin.Features.Attributes;
 using MultiAdmin.Utility;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class ConfigGenerator : Feature, ICommand
 	{
 
diff --git a/MultiAdmin/Features/ConfigReload.cs b/MultiAdmin/Features/ConfigReload.cs
index 76c5747..d053d79 100644
--- a/MultiAdmin/Features/ConfigReload.cs
+++ b/MultiAdmin/Features/ConfigReload.cs
@@ -1,9 +1,7 @@
-using MultiAdmin.Features.Attributes;
 using MultiAdmin.Utility;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class ConfigReload : Feature, ICommand
 	{
 		public ConfigReload(Server server) : base(server)
diff --git a/MultiAdmin/Features/ExitCommand.cs b/MultiAdmin/Features/ExitCommand.cs
index e59aa12..b6eda58 100644
--- a/MultiAdmin/Features/ExitCommand.cs
+++ b/MultiAdmin/Features/ExitCommand.cs
@@ -1,8 +1,5 @@
-using MultiAdmin.Features.Attributes;
-
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class ExitCommand : Feature, ICommand
 	{
 		public ExitCommand(Server server) : base(server)
diff --git a/MultiAdmin/Features/FolderCopyRoundQueue.cs b/MultiAdmin/Features/FileCopyRoundQueue.cs
similarity index 95%
rename from MultiAdmin/Features/FolderCopyRoundQueue.cs
rename to MultiAdmin/Features/FileCopyRoundQueue.cs
index f0eb36a..b78bc2e 100644
--- a/MultiAdmin/Features/FolderCopyRoundQueue.cs
+++ b/MultiAdmin/Features/FileCopyRoundQueue.cs
@@ -1,10 +1,8 @@
 using System;
-using MultiAdmin.Features.Attributes;
 using MultiAdmin.Utility;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class FileCopyRoundQueue : Feature, IEventRoundEnd
 	{
 		private string[] queue = Array.Empty<string>();
@@ -93,7 +91,7 @@ public override string GetFeatureDescription()
 
 		public override string GetFeatureName()
 		{
-			return "Folder Copy Round Queue";
+			return "File Copy Round Queue";
 		}
 	}
 }
diff --git a/MultiAdmin/Features/GithubGenerator.cs b/MultiAdmin/Features/GithubGenerator.cs
index 5c01a75..1291858 100644
--- a/MultiAdmin/Features/GithubGenerator.cs
+++ b/MultiAdmin/Features/GithubGenerator.cs
@@ -4,13 +4,11 @@
 using System.Text;
 using MultiAdmin.Config;
 using MultiAdmin.Config.ConfigHandler;
-using MultiAdmin.Features.Attributes;
 using MultiAdmin.ServerIO;
 using MultiAdmin.Utility;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class GithubGenerator : Feature, ICommand
 	{
 		public const string EmptyIndicator = "**Empty**";
diff --git a/MultiAdmin/Features/HelpCommand.cs b/MultiAdmin/Features/HelpCommand.cs
index aec5c09..f2200aa 100644
--- a/MultiAdmin/Features/HelpCommand.cs
+++ b/MultiAdmin/Features/HelpCommand.cs
@@ -1,12 +1,10 @@
 using System;
 using System.Collections.Generic;
 using MultiAdmin.ConsoleTools;
-using MultiAdmin.Features.Attributes;
 using MultiAdmin.Utility;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	public class HelpCommand : Feature, ICommand
 	{
 		private static readonly ColoredMessage helpPrefix = new("Commands from MultiAdmin:\n", ConsoleColor.Yellow);
diff --git a/MultiAdmin/Features/MemoryChecker.cs b/MultiAdmin/Features/MemoryChecker.cs
index 2280929..4ba2380 100644
--- a/MultiAdmin/Features/MemoryChecker.cs
+++ b/MultiAdmin/Features/MemoryChecker.cs
@@ -1,9 +1,7 @@
 using System;
-using MultiAdmin.Features.Attributes;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class MemoryChecker : Feature, IEventTick, IEventRoundEnd
 	{
 		private const decimal BytesInMegabyte = 1048576;
diff --git a/MultiAdmin/Features/MultiAdminInfo.cs b/MultiAdmin/Features/MultiAdminInfo.cs
index e07effb..f9bcdb9 100644
--- a/MultiAdmin/Features/MultiAdminInfo.cs
+++ b/MultiAdmin/Features/MultiAdminInfo.cs
@@ -1,9 +1,7 @@
 using System;
-using MultiAdmin.Features.Attributes;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class MultiAdminInfo : Feature, IEventServerPreStart, ICommand
 	{
 		public MultiAdminInfo(Server server) : base(server)
diff --git a/MultiAdmin/Features/NewCommand.cs b/MultiAdmin/Features/NewCommand.cs
index c660ae2..87bfe7b 100644
--- a/MultiAdmin/Features/NewCommand.cs
+++ b/MultiAdmin/Features/NewCommand.cs
@@ -1,10 +1,8 @@
 using System.Diagnostics;
-using MultiAdmin.Features.Attributes;
 using MultiAdmin.Utility;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class NewCommand : Feature, ICommand, IEventServerFull
 	{
 		private string? onFullServerId;
diff --git a/MultiAdmin/Features/Restart.cs b/MultiAdmin/Features/Restart.cs
index 67fad4c..cb36724 100644
--- a/MultiAdmin/Features/Restart.cs
+++ b/MultiAdmin/Features/Restart.cs
@@ -1,8 +1,5 @@
-using MultiAdmin.Features.Attributes;
-
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class Restart : Feature, ICommand
 	{
 		public Restart(Server server) : base(server)
diff --git a/MultiAdmin/Features/RestartRoundCounter.cs b/MultiAdmin/Features/RestartRoundCounter.cs
index a9517e9..f972cfa 100644
--- a/MultiAdmin/Features/RestartRoundCounter.cs
+++ b/MultiAdmin/Features/RestartRoundCounter.cs
@@ -1,8 +1,5 @@
-using MultiAdmin.Features.Attributes;
-
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class RestartRoundCounter : Feature, IEventRoundEnd
 	{
 		private int count;
diff --git a/MultiAdmin/Features/TitleBar.cs b/MultiAdmin/Features/TitleBar.cs
index 3b04790..dfe8528 100644
--- a/MultiAdmin/Features/TitleBar.cs
+++ b/MultiAdmin/Features/TitleBar.cs
@@ -1,10 +1,8 @@
 using System;
 using System.Collections.Generic;
-using MultiAdmin.Features.Attributes;
 
 namespace MultiAdmin.Features
 {
-	[Feature]
 	internal class Titlebar : Feature, IEventServerStart
 	{
 		private int ServerProcessId
diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs
index 83624df..34d0a2b 100644
--- a/MultiAdmin/Server.cs
+++ b/MultiAdmin/Server.cs
@@ -2,13 +2,11 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-using System.Linq;
-using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
 using MultiAdmin.Config;
 using MultiAdmin.ConsoleTools;
-using MultiAdmin.Features.Attributes;
+using MultiAdmin.Features;
 using MultiAdmin.ServerIO;
 using MultiAdmin.Utility;
 
@@ -593,33 +591,20 @@ private void RegisterFeature(Feature feature)
 			features.Add(feature);
 		}
 
-		private static IEnumerable<Type> GetTypesWithAttribute(Type attribute)
-		{
-			foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
-			{
-				foreach (Type type in assembly.GetTypes())
-				{
-					object[] attributes = type.GetCustomAttributes(attribute, true);
-					if (!attributes.IsEmpty()) yield return type;
-				}
-			}
-		}
-
 		private void RegisterFeatures()
 		{
-			Type[] assembly = GetTypesWithAttribute(typeof(FeatureAttribute)).ToArray();
-			foreach (Type type in assembly)
-			{
-				try
-				{
-					object? featureInstance = Activator.CreateInstance(type, this);
-					if (featureInstance is Feature feature) RegisterFeature(feature);
-				}
-				catch (Exception e)
-				{
-					Program.LogDebugException(nameof(RegisterFeatures), e);
-				}
-			}
+			RegisterFeature(new ConfigGenerator(this));
+			RegisterFeature(new ConfigReload(this));
+			RegisterFeature(new ExitCommand(this));
+			RegisterFeature(new FileCopyRoundQueue(this));
+			RegisterFeature(new GithubGenerator(this));
+			RegisterFeature(new HelpCommand(this));
+			RegisterFeature(new MemoryChecker(this));
+			RegisterFeature(new MultiAdminInfo(this));
+			RegisterFeature(new NewCommand(this));
+			RegisterFeature(new Restart(this));
+			RegisterFeature(new RestartRoundCounter(this));
+			RegisterFeature(new Titlebar(this));
 		}
 
 		private void InitFeatures()

From 4c7ca1bc0f4a31b5588602a38b7310cac326e699 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Sun, 1 Jan 2023 08:25:31 -0500
Subject: [PATCH 08/16] Manually register all configs to support trimming

---
 MultiAdmin/Config/MultiAdminConfig.cs | 48 ++++++++++++++++++++++-----
 README.md                             |  2 +-
 2 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs
index edf9a7b..df74f7e 100644
--- a/MultiAdmin/Config/MultiAdminConfig.cs
+++ b/MultiAdmin/Config/MultiAdminConfig.cs
@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using System.Reflection;
 using MultiAdmin.Config.ConfigHandler;
 using MultiAdmin.ConsoleTools;
 using MultiAdmin.ServerIO;
@@ -234,13 +233,46 @@ public MultiAdminConfig(Config? config, MultiAdminConfig? parentConfig, bool cre
 
 			#region MultiAdmin Config Register
 
-			foreach (PropertyInfo property in GetType().GetProperties())
-			{
-				if (property.GetValue(this) is ConfigEntry entry)
-				{
-					RegisterConfig(entry);
-				}
-			}
+			RegisterConfig(ConfigLocation);
+			RegisterConfig(AppDataLocation);
+			RegisterConfig(DisableConfigValidation);
+			RegisterConfig(ShareNonConfigs);
+			RegisterConfig(LogLocation);
+			RegisterConfig(NoLog);
+			RegisterConfig(DebugLog);
+			RegisterConfig(DebugLogBlacklist);
+			RegisterConfig(DebugLogWhitelist);
+			RegisterConfig(UseNewInputSystem);
+			RegisterConfig(ConsoleInputSystem);
+			RegisterConfig(HideInput);
+			RegisterConfig(Port);
+			RegisterConfig(CopyFromFolderOnReload);
+			RegisterConfig(FolderCopyWhitelist);
+			RegisterConfig(FolderCopyBlacklist);
+			RegisterConfig(FolderCopyRoundQueue);
+			RegisterConfig(FolderCopyRoundQueueWhitelist);
+			RegisterConfig(FolderCopyRoundQueueBlacklist);
+			RegisterConfig(RandomizeFolderCopyRoundQueue);
+			RegisterConfig(ManualStart);
+			RegisterConfig(MaxMemory);
+			RegisterConfig(RestartLowMemory);
+			RegisterConfig(RestartLowMemoryTicks);
+			RegisterConfig(RestartLowMemoryRoundEnd);
+			RegisterConfig(RestartLowMemoryRoundEndTicks);
+			RegisterConfig(RandomInputColors);
+			RegisterConfig(RestartEveryNumRounds);
+			RegisterConfig(RestartEveryNumRoundsCounting);
+			RegisterConfig(SafeServerShutdown);
+			RegisterConfig(SafeShutdownCheckDelay);
+			RegisterConfig(SafeShutdownTimeout);
+			RegisterConfig(ServerRestartTimeout);
+			RegisterConfig(ServerStopTimeout);
+			RegisterConfig(ServerStartRetry);
+			RegisterConfig(ServerStartRetryDelay);
+			RegisterConfig(MultiAdminTickDelay);
+			RegisterConfig(ServersFolder);
+			RegisterConfig(SetTitleBar);
+			RegisterConfig(StartConfigOnFull);
 
 			#endregion
 
diff --git a/README.md b/README.md
index 3378da4..660463f 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Make sure that you are running Mono 5.18.0 or higher, otherwise you might have i
 - Config Generator: Generates a full default MultiAdmin config file
 - Config Reload: Reloads the MultiAdmin configuration file
 - Exit Command: Adds a graceful exit command
-- Folder Copy Round Queue: Copies files from folders in a queue
+- File Copy Round Queue: Copies files from folders in a queue
 - GitHub Generator: Generates a GitHub README file outlining all the features/commands
 - Help: Display a full list of MultiAdmin commands and in game commands
 - Restart On Low Memory: Restarts the server if the working memory becomes too low

From 7cc7721974f71e082184f2ccdff4c5e22def4e6f Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Mon, 2 Jan 2023 01:39:10 -0500
Subject: [PATCH 09/16] Fix project file formatting & update gitignores

---
 .gitignore                               | 67 ++++++++++++++++++------
 MultiAdmin.Tests/.gitignore              | 67 ++++++++++++++++++------
 MultiAdmin.Tests/MultiAdmin.Tests.csproj |  6 +--
 MultiAdmin.sln                           |  5 +-
 MultiAdmin/.gitignore                    | 67 ++++++++++++++++++------
 MultiAdmin/MultiAdmin.csproj             |  4 +-
 MultiAdmin/nuget.config                  |  9 ----
 7 files changed, 160 insertions(+), 65 deletions(-)
 delete mode 100644 MultiAdmin/nuget.config

diff --git a/.gitignore b/.gitignore
index 920d1cd..154e127 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
 ## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.
 ##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
 
 # User-specific files
 *.rsuser
@@ -23,6 +23,7 @@ mono_crash.*
 [Rr]eleases/
 x64/
 x86/
+[Ww][Ii][Nn]32/
 [Aa][Rr][Mm]/
 [Aa][Rr][Mm]64/
 bld/
@@ -56,11 +57,17 @@ dlldata.c
 # Benchmark Results
 BenchmarkDotNet.Artifacts/
 
-# .NET Core
+# .NET
 project.lock.json
 project.fragment.lock.json
 artifacts/
 
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
 # StyleCop
 StyleCopReport.xml
 
@@ -86,6 +93,7 @@ StyleCopReport.xml
 *.tmp_proj
 *_wpftmp.csproj
 *.log
+*.tlog
 *.vspscc
 *.vssscc
 .builds
@@ -138,7 +146,9 @@ _TeamCity*
 !.axoCover/settings.json
 
 # Coverlet is a free, cross platform Code Coverage Tool
-coverage*[.json, .xml, .info]
+coverage*.json
+coverage*.xml
+coverage*.info
 
 # Visual Studio code coverage results
 *.coverage
@@ -287,6 +297,17 @@ node_modules/
 # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
 *.vbw
 
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
 # Visual Studio LightSwitch build output
 **/*.HTMLClient/GeneratedArtifacts
 **/*.DesktopClient/GeneratedArtifacts
@@ -343,6 +364,9 @@ ASALocalRun/
 # Local History for Visual Studio
 .localhistory/
 
+# Visual Studio History (VSHistory) files
+.vshistory/
+
 # BeatPulse healthcheck temp database
 healthchecksdb
 
@@ -352,6 +376,30 @@ MigrationBackup/
 # Ionide (cross platform F# VS Code tools) working folder
 .ionide/
 
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
 ##
 ## Visual studio for Mac
 ##
@@ -427,16 +475,3 @@ $RECYCLE.BIN/
 
 # Windows shortcuts
 *.lnk
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-##
-## Visual Studio Code
-##
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
diff --git a/MultiAdmin.Tests/.gitignore b/MultiAdmin.Tests/.gitignore
index 920d1cd..154e127 100644
--- a/MultiAdmin.Tests/.gitignore
+++ b/MultiAdmin.Tests/.gitignore
@@ -1,7 +1,7 @@
 ## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.
 ##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
 
 # User-specific files
 *.rsuser
@@ -23,6 +23,7 @@ mono_crash.*
 [Rr]eleases/
 x64/
 x86/
+[Ww][Ii][Nn]32/
 [Aa][Rr][Mm]/
 [Aa][Rr][Mm]64/
 bld/
@@ -56,11 +57,17 @@ dlldata.c
 # Benchmark Results
 BenchmarkDotNet.Artifacts/
 
-# .NET Core
+# .NET
 project.lock.json
 project.fragment.lock.json
 artifacts/
 
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
 # StyleCop
 StyleCopReport.xml
 
@@ -86,6 +93,7 @@ StyleCopReport.xml
 *.tmp_proj
 *_wpftmp.csproj
 *.log
+*.tlog
 *.vspscc
 *.vssscc
 .builds
@@ -138,7 +146,9 @@ _TeamCity*
 !.axoCover/settings.json
 
 # Coverlet is a free, cross platform Code Coverage Tool
-coverage*[.json, .xml, .info]
+coverage*.json
+coverage*.xml
+coverage*.info
 
 # Visual Studio code coverage results
 *.coverage
@@ -287,6 +297,17 @@ node_modules/
 # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
 *.vbw
 
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
 # Visual Studio LightSwitch build output
 **/*.HTMLClient/GeneratedArtifacts
 **/*.DesktopClient/GeneratedArtifacts
@@ -343,6 +364,9 @@ ASALocalRun/
 # Local History for Visual Studio
 .localhistory/
 
+# Visual Studio History (VSHistory) files
+.vshistory/
+
 # BeatPulse healthcheck temp database
 healthchecksdb
 
@@ -352,6 +376,30 @@ MigrationBackup/
 # Ionide (cross platform F# VS Code tools) working folder
 .ionide/
 
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
 ##
 ## Visual studio for Mac
 ##
@@ -427,16 +475,3 @@ $RECYCLE.BIN/
 
 # Windows shortcuts
 *.lnk
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-##
-## Visual Studio Code
-##
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
diff --git a/MultiAdmin.Tests/MultiAdmin.Tests.csproj b/MultiAdmin.Tests/MultiAdmin.Tests.csproj
index c48508d..f5f3663 100644
--- a/MultiAdmin.Tests/MultiAdmin.Tests.csproj
+++ b/MultiAdmin.Tests/MultiAdmin.Tests.csproj
@@ -1,11 +1,11 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>net7.0</TargetFramework>
-  	<LangVersion>11</LangVersion>
+    <LangVersion>11</LangVersion>
     <IsPackable>false</IsPackable>
     <RootNamespace>MultiAdmin.Tests</RootNamespace>
     <Nullable>enable</Nullable>
-    
+
     <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
     <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
     <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
@@ -30,4 +30,4 @@
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
   </ItemGroup>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/MultiAdmin.sln b/MultiAdmin.sln
index 692e8c1..294569f 100644
--- a/MultiAdmin.sln
+++ b/MultiAdmin.sln
@@ -1,5 +1,4 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 VisualStudioVersion = 15.0.26124.0
 MinimumVisualStudioVersion = 15.0.26124.0
@@ -45,4 +44,4 @@ Global
 		{314971BB-616B-4FAE-B375-5A4A670D8626}.Release|x86.ActiveCfg = Release|Any CPU
 		{314971BB-616B-4FAE-B375-5A4A670D8626}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
-EndGlobal
+EndGlobal
\ No newline at end of file
diff --git a/MultiAdmin/.gitignore b/MultiAdmin/.gitignore
index 920d1cd..154e127 100644
--- a/MultiAdmin/.gitignore
+++ b/MultiAdmin/.gitignore
@@ -1,7 +1,7 @@
 ## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.
 ##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
 
 # User-specific files
 *.rsuser
@@ -23,6 +23,7 @@ mono_crash.*
 [Rr]eleases/
 x64/
 x86/
+[Ww][Ii][Nn]32/
 [Aa][Rr][Mm]/
 [Aa][Rr][Mm]64/
 bld/
@@ -56,11 +57,17 @@ dlldata.c
 # Benchmark Results
 BenchmarkDotNet.Artifacts/
 
-# .NET Core
+# .NET
 project.lock.json
 project.fragment.lock.json
 artifacts/
 
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
 # StyleCop
 StyleCopReport.xml
 
@@ -86,6 +93,7 @@ StyleCopReport.xml
 *.tmp_proj
 *_wpftmp.csproj
 *.log
+*.tlog
 *.vspscc
 *.vssscc
 .builds
@@ -138,7 +146,9 @@ _TeamCity*
 !.axoCover/settings.json
 
 # Coverlet is a free, cross platform Code Coverage Tool
-coverage*[.json, .xml, .info]
+coverage*.json
+coverage*.xml
+coverage*.info
 
 # Visual Studio code coverage results
 *.coverage
@@ -287,6 +297,17 @@ node_modules/
 # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
 *.vbw
 
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
 # Visual Studio LightSwitch build output
 **/*.HTMLClient/GeneratedArtifacts
 **/*.DesktopClient/GeneratedArtifacts
@@ -343,6 +364,9 @@ ASALocalRun/
 # Local History for Visual Studio
 .localhistory/
 
+# Visual Studio History (VSHistory) files
+.vshistory/
+
 # BeatPulse healthcheck temp database
 healthchecksdb
 
@@ -352,6 +376,30 @@ MigrationBackup/
 # Ionide (cross platform F# VS Code tools) working folder
 .ionide/
 
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
 ##
 ## Visual studio for Mac
 ##
@@ -427,16 +475,3 @@ $RECYCLE.BIN/
 
 # Windows shortcuts
 *.lnk
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-##
-## Visual Studio Code
-##
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
diff --git a/MultiAdmin/MultiAdmin.csproj b/MultiAdmin/MultiAdmin.csproj
index ed63baf..abafc12 100644
--- a/MultiAdmin/MultiAdmin.csproj
+++ b/MultiAdmin/MultiAdmin.csproj
@@ -2,7 +2,7 @@
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TargetFramework>net7.0</TargetFramework>
-  	<LangVersion>11</LangVersion>
+    <LangVersion>11</LangVersion>
     <RootNamespace>MultiAdmin</RootNamespace>
     <ApplicationIcon>Icon.ico</ApplicationIcon>
     <Nullable>enable</Nullable>
@@ -36,4 +36,4 @@
   <ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
     <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
   </ItemGroup>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/MultiAdmin/nuget.config b/MultiAdmin/nuget.config
deleted file mode 100644
index 7c26348..0000000
--- a/MultiAdmin/nuget.config
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <packageSources>
-    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
-    <clear />
-    <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
-    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
-  </packageSources>
-</configuration>
\ No newline at end of file

From 84ff6a1629bfa42ece60c4d3170650f4eecf4936 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Mon, 16 Jan 2023 23:07:09 -0500
Subject: [PATCH 10/16] Update workflow to ubuntu-20.04

https://github.com/actions/runner-images/issues/6002
---
 .github/workflows/main.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c57a079..cf8128e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -8,10 +8,10 @@ jobs:
     runs-on: ${{matrix.os}}
     strategy:
       matrix:
-        os: [ubuntu-18.04, windows-latest]
+        os: [ubuntu-20.04, windows-latest]
         framework: ['7.0']
         include:
-        - os: ubuntu-18.04
+        - os: ubuntu-20.04
           target: linux-x64
         - os: windows-latest
           target: win-x64
@@ -20,7 +20,7 @@ jobs:
     steps:
     - uses: actions/checkout@v3
 
-    - if: matrix.os == 'ubuntu-18.04'
+    - if: matrix.os == 'ubuntu-20.04'
       name: Install Linux packages
       run: |
         sudo apt update

From 1b26b378fa46cf30ffda896f033a6ed11ecf28c4 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Mon, 16 Jan 2023 23:18:10 -0500
Subject: [PATCH 11/16] Fix ConsoleInputSystem link

---
 MultiAdmin/Features/GithubGenerator.cs | 2 +-
 README.md                              | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/MultiAdmin/Features/GithubGenerator.cs b/MultiAdmin/Features/GithubGenerator.cs
index 1291858..17d58d7 100644
--- a/MultiAdmin/Features/GithubGenerator.cs
+++ b/MultiAdmin/Features/GithubGenerator.cs
@@ -153,7 +153,7 @@ public void OnCall(string[] args)
 
 					case ConfigEntry<InputHandler.ConsoleInputSystem> config:
 						{
-							stringBuilder.Append($"[ConsoleInputSystem](#ConsoleInputSystem){ColumnSeparator}{config.Default}");
+							stringBuilder.Append($"[ConsoleInputSystem](#consoleinputsystem){ColumnSeparator}{config.Default}");
 							break;
 						}
 
diff --git a/README.md b/README.md
index 660463f..9c40690 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ multiadmin_debug_log | Boolean | True | Enables MultiAdmin debug logging, this l
 multiadmin_debug_log_blacklist | String List | HandleMessage, StringMatches, MessageListener | Which tags to block for MultiAdmin debug logging
 multiadmin_debug_log_whitelist | String List | **Empty** | Which tags to log for MultiAdmin debug logging (Defaults to logging all if none are provided)
 use_new_input_system | Boolean | True | **OBSOLETE: Use `console_input_system` instead, this config option may be removed in a future version of MultiAdmin.** Whether to use the new input system, if false, the original input system will be used
-console_input_system | [ConsoleInputSystem](#ConsoleInputSystem) | New | Which console input system to use
+console_input_system | [ConsoleInputSystem](#consoleinputsystem) | New | Which console input system to use
 hide_input | Boolean | False | Whether to hide console input, if true, typed input will not be printed
 port | Unsigned Integer | 7777 | The port for the server to use
 copy_from_folder_on_reload | String | **Empty** | The location of a folder to copy files from into the folder defined by `config_location` whenever the configuration file is reloaded

From f9dff6cbf90e8dd4a0618e9dc7c1e31f0eb33308 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Mon, 16 Jan 2023 23:39:45 -0500
Subject: [PATCH 12/16] Add `input-system` execution argument

---
 MultiAdmin/Config/MultiAdminConfig.cs |  5 +++++
 MultiAdmin/Program.cs                 | 10 ++++++----
 README.md                             |  1 +
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/MultiAdmin/Config/MultiAdminConfig.cs b/MultiAdmin/Config/MultiAdminConfig.cs
index df74f7e..f3cba36 100644
--- a/MultiAdmin/Config/MultiAdminConfig.cs
+++ b/MultiAdmin/Config/MultiAdminConfig.cs
@@ -179,6 +179,11 @@ public InputHandler.ConsoleInputSystem ActualConsoleInputSystem
 		{
 			get
 			{
+				// If defined through execution arguments, use that as an override
+				var programInputSystem = Program.ConsoleInputSystem;
+				if (programInputSystem != null)
+					return (InputHandler.ConsoleInputSystem)programInputSystem;
+
 				if (UseNewInputSystem.Value)
 				{
 					switch (ConsoleInputSystem.Value)
diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs
index 08500cf..ff4d99c 100644
--- a/MultiAdmin/Program.cs
+++ b/MultiAdmin/Program.cs
@@ -69,6 +69,7 @@ public static string[] ServerDirectories
 		#endregion
 
 		public static bool Headless { get; private set; }
+		public static InputHandler.ConsoleInputSystem? ConsoleInputSystem { get; private set; } = null;
 
 		#region Output Printing & Logging
 
@@ -78,14 +79,14 @@ public static void Write(string message, ConsoleColor color = ConsoleColor.DarkY
 			{
 				if (Headless) return;
 
-				new ColoredMessage(Utils.TimeStampMessage(message), color).WriteLine(MultiAdminConfig.GlobalConfig?.ActualConsoleInputSystem == InputHandler.ConsoleInputSystem.New);
+				new ColoredMessage(Utils.TimeStampMessage(message), color).WriteLine(MultiAdminConfig.GlobalConfig.ActualConsoleInputSystem == InputHandler.ConsoleInputSystem.New);
 			}
 		}
 
 		private static bool IsDebugLogTagAllowed(string tag)
 		{
-			return (!MultiAdminConfig.GlobalConfig?.DebugLogBlacklist?.Value?.Contains(tag) ?? true) &&
-				   ((MultiAdminConfig.GlobalConfig?.DebugLogWhitelist?.Value?.IsEmpty() ?? true) ||
+			return (!MultiAdminConfig.GlobalConfig.DebugLogBlacklist.Value.Contains(tag)) &&
+				   (MultiAdminConfig.GlobalConfig.DebugLogWhitelist.Value.IsEmpty() ||
 					MultiAdminConfig.GlobalConfig.DebugLogWhitelist.Value.Contains(tag));
 		}
 
@@ -107,7 +108,7 @@ public static void LogDebug(string tag, string message)
 			{
 				try
 				{
-					if ((!MultiAdminConfig.GlobalConfig?.DebugLog?.Value ?? true) ||
+					if ((!MultiAdminConfig.GlobalConfig.DebugLog?.Value ?? true) ||
 						string.IsNullOrEmpty(MaDebugLogFile) || tag == null || !IsDebugLogTagAllowed(tag)) return;
 
 					// Assign debug log stream as needed
@@ -221,6 +222,7 @@ public static void Main()
 				Args[0] = null;
 
 			Headless = GetFlagFromArgs(Args, "headless", "h");
+			ConsoleInputSystem = Enum.TryParse(GetParamFromArgs(Args, "input-system", "is"), out InputHandler.ConsoleInputSystem inputSystem) ? inputSystem : null;
 
 			string? serverIdArg = GetParamFromArgs(Args, "server-id", "id");
 			string? configArg = GetParamFromArgs(Args, "config", "c");
diff --git a/README.md b/README.md
index 9c40690..3c1de2b 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ This does not include ingame commands, for a full list type `HELP` in MultiAdmin
 The arguments available for running MultiAdmin with
 
 - `--headless` or `-h`: Runs MultiAdmin in headless mode, this makes MultiAdmin not accept any input at all and only output to log files, not in console (Note: This argument is inherited by processes started by this MultiAdmin process)
+- `--input-system <ConsoleInputSystem>` or `-is <ConsoleInputSystem>`: The [ConsoleInputSystem](#consoleinputsystem) to use for this MultiAdmin instance (Note: This is used over the config option `console_input_system` and is inherited by processes started by this MultiAdmin process)
 - `--server-id <Server ID>` or `-id <Server ID>`: The Server ID to run this MultiAdmin instance with a config location (`--config` or `-c`) so that it reads the configs from the location, but stores the logs in the Server ID's folder
 - `--config <Config Location>` or `-c <Config Location>`: The config location to use for this MultiAdmin instance (Note: This is used over the config option `config_location`)
 - `--port <Server Port>` or `-p <Server Port>`: The port to use for this MultiAdmin instance (Note: This is used over the config option `port` and is inherited by processes started by this MultiAdmin process)

From 804de92177790eeb901b955c8be799114b0e210d Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Mon, 16 Jan 2023 23:43:09 -0500
Subject: [PATCH 13/16] Only run workflow on pushes to main

---
 .github/workflows/main.yml | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index cf8128e..43bfc18 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,6 +1,12 @@
 name: MultiAdmin Build
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+  workflow_dispatch:
+  create:
 
 jobs:
   build:

From 48e6dfd1efb37b4e43d60b6d5b3b2b95f17d617c Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Mon, 16 Jan 2023 23:45:56 -0500
Subject: [PATCH 14/16] Use `cache-apt-pkgs-action`

---
 .github/workflows/main.yml | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 43bfc18..5199b39 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -27,10 +27,16 @@ jobs:
     - uses: actions/checkout@v3
 
     - if: matrix.os == 'ubuntu-20.04'
-      name: Install Linux packages
-      run: |
-        sudo apt update
-        sudo apt install -y clang zlib1g-dev libkrb5-dev libtinfo5
+      name: Set up Linux dependencies
+      uses: awalsh128/cache-apt-pkgs-action@latest
+      with:
+        packages: clang zlib1g-dev libkrb5-dev libtinfo5
+        # Increment to invalidate the cache
+        version: 1.0
+        # Enables a workaround to attempt to run pre and post install scripts
+        execute_install_scripts: true
+        # Disables uploading logs as a build artifact
+        debug: false
 
     - name: Setup .NET
       uses: actions/setup-dotnet@v3

From 6c0a64004e925d516c93b071bfb9bd7d6d41cad0 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Tue, 17 Jan 2023 00:44:02 -0500
Subject: [PATCH 15/16] Also build on develop branch

---
 .github/workflows/main.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5199b39..3e07afb 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - main
+      - develop
   pull_request:
   workflow_dispatch:
   create:

From d9802ddb0fe5ed40778f2ba52150d2296ccaa913 Mon Sep 17 00:00:00 2001
From: Butterscotch! <bscotchvanilla@gmail.com>
Date: Tue, 17 Jan 2023 00:46:59 -0500
Subject: [PATCH 16/16] Test exit output

---
 MultiAdmin/Program.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/MultiAdmin/Program.cs b/MultiAdmin/Program.cs
index ff4d99c..72eabcc 100644
--- a/MultiAdmin/Program.cs
+++ b/MultiAdmin/Program.cs
@@ -295,6 +295,8 @@ public static void Main()
 
 				server.StartServer();
 			}
+
+			LogDebug(nameof(Main), "Exiting MultiAdmin...");
 		}
 
 		public static string? GetParamFromArgs(string?[] args, string[]? keys = null, string[]? aliases = null)