diff --git a/SrcMod/Shell/GlobalUsings.cs b/SrcMod/Shell/GlobalUsings.cs index a26ef43..8ae847e 100644 --- a/SrcMod/Shell/GlobalUsings.cs +++ b/SrcMod/Shell/GlobalUsings.cs @@ -1,12 +1,14 @@ global using Nerd_STF.Mathematics; +global using Newtonsoft.Json; global using SharpCompress.Archives.Rar; global using SharpCompress.Archives.SevenZip; global using SharpCompress.Readers; -global using SrcMod.Shell; global using SrcMod.Shell.Interop; global using SrcMod.Shell.Modules.ObjectModels; +global using SrcMod.Shell.ObjectModels; global using System; global using System.Collections.Generic; +global using System.ComponentModel; global using System.Diagnostics; global using System.Formats.Tar; global using System.IO; diff --git a/SrcMod/Shell/Modules/ConfigModule.cs b/SrcMod/Shell/Modules/ConfigModule.cs new file mode 100644 index 0000000..58e88a0 --- /dev/null +++ b/SrcMod/Shell/Modules/ConfigModule.cs @@ -0,0 +1,194 @@ +namespace SrcMod.Shell.Modules; + +[Module("config")] +public static class ConfigModule +{ + [Command("display")] + [Command("list")] + public static void DisplayConfig(ConfigDisplayMode mode = ConfigDisplayMode.All) + { + switch (mode) + { + case ConfigDisplayMode.Raw: + DisplayConfigRaw(); + break; + + case ConfigDisplayMode.All: + DisplayConfigAll(); + break; + + case ConfigDisplayMode.GameDirectories: + DisplayConfigGameDirectories(); + break; + + case ConfigDisplayMode.RunUnsafeCommands: + DisplayConfigUnsafeCommands(); + break; + } + } + + [Command("add")] + [Command("append")] + public static void AppendConfigVariable(string name, string value) + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + config.GameDirectories = config.GameDirectories.Append(value).ToArray(); + break; + + case "rununsafecommands": + throw new($"The config variable \"{name}\" is a single variable and cannot be appended to."); + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + [Command("delete")] + [Command("remove")] + public static void RemoveConfigVariable(string name, string value) + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + config.GameDirectories = config.GameDirectories + .Where(x => x.Trim().ToLower() != value.Trim().ToLower()) + .ToArray(); + break; + + case "rununsafecommands": + throw new($"The config variable \"{name}\" is a single variable and cannot be appended to."); + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + [Command("reset")] + public static void ResetConfig(string name = "all") + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + config.GameDirectories = Config.Defaults.GameDirectories; + break; + + case "rununsafecommands": + config.RunUnsafeCommands = Config.Defaults.RunUnsafeCommands; + break; + + case "all": + config = Config.Defaults; + break; + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + [Command("set")] + public static void SetConfigVariable(string name, string value) + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + throw new($"The config variable \"{name}\" is a list and must be added or removed to."); + + case "rununsafecommands": + if (int.TryParse(value, out int intRes)) + { + AskMode mode = (AskMode)intRes; + if (!Enum.IsDefined(mode)) throw new($"(AskMode){value} is not a valid AskMode."); + config.RunUnsafeCommands = mode; + } + else if (Enum.TryParse(value, true, out AskMode modeRes)) + { + if (!Enum.IsDefined(modeRes)) throw new($"\"{value}\" is not a valid AskMode."); + config.RunUnsafeCommands = modeRes; + } + else throw new($"\"{value}\" is not a valid AskMode."); + break; + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + private static void DisplayConfigAll() + { + DisplayConfigGameDirectories(); + DisplayConfigUnsafeCommands(); + } + private static void DisplayConfigRaw() + { + // This is definitely a bit inefficient, but shouldn't be too much of an issue. + + MemoryStream ms = new(); + StreamWriter writer = new(ms, leaveOpen: true); + JsonTextWriter jsonWriter = new(writer); + + Serializer.Serialize(jsonWriter, Config.LoadedConfig); + + jsonWriter.Close(); + writer.Close(); + ms.Position = 0; + + StreamReader reader = new(ms); + string msg = reader.ReadToEnd(); + + Write(msg); + + reader.Close(); + ms.Close(); + } + private static void DisplayConfigGameDirectories() + { + Write("Steam Game Directories: ", null, false); + if (Config.LoadedConfig.GameDirectories is null || Config.LoadedConfig.GameDirectories.Length <= 0) + Write("None", ConsoleColor.DarkGray); + else + { + Write("[", ConsoleColor.DarkGray); + for (int i = 0; i < Config.LoadedConfig.GameDirectories.Length; i++) + { + Write(" \"", ConsoleColor.DarkGray, false); + Write(Config.LoadedConfig.GameDirectories[i], ConsoleColor.White, false); + if (i < Config.LoadedConfig.GameDirectories.Length - 1) Write("\",", ConsoleColor.DarkGray); + else Write("\"", ConsoleColor.DarkGray); + } + Write("]", ConsoleColor.DarkGray); + } + } + private static void DisplayConfigUnsafeCommands() + { + Write("Run Unsafe Commands: ", null, false); + ConsoleColor color = Config.LoadedConfig.RunUnsafeCommands switch + { + AskMode.Never => ConsoleColor.Red, + AskMode.Always => ConsoleColor.Green, + AskMode.Ask or _ => ConsoleColor.DarkGray + }; + Write(Config.LoadedConfig.RunUnsafeCommands, color); + } + + public enum ConfigDisplayMode + { + Raw, + All, + GameDirectories, + RunUnsafeCommands + } +} diff --git a/SrcMod/Shell/ObjectModels/AskMode.cs b/SrcMod/Shell/ObjectModels/AskMode.cs new file mode 100644 index 0000000..e425960 --- /dev/null +++ b/SrcMod/Shell/ObjectModels/AskMode.cs @@ -0,0 +1,8 @@ +namespace SrcMod.Shell.ObjectModels; + +public enum AskMode : sbyte +{ + Never = -1, + Ask = 0, + Always = 1 +} diff --git a/SrcMod/Shell/ObjectModels/Config.cs b/SrcMod/Shell/ObjectModels/Config.cs new file mode 100644 index 0000000..a14e6df --- /dev/null +++ b/SrcMod/Shell/ObjectModels/Config.cs @@ -0,0 +1,86 @@ +namespace SrcMod.Shell.ObjectModels; + +public struct Config +{ + public const string FilePath = "config.json"; + + public static readonly Config Defaults; + + public static Config LoadedConfig + { + get => p_applied; + set + { + p_applied = value; + p_changes = p_applied.GetChanges(Defaults); + } + } + + private static Config p_applied; + private static ConfigChanges? p_changes; + + static Config() + { + Defaults = new() + { + GameDirectories = Array.Empty(), + RunUnsafeCommands = AskMode.Ask + }; + } + + public string[] GameDirectories; + public AskMode RunUnsafeCommands; + + public Config ApplyChanges(ConfigChanges changes) => this with + { + GameDirectories = GameDirectories.Union(changes.GameDirectories ?? Array.Empty()).ToArray(), + RunUnsafeCommands = changes.RunUnsafeCommands ?? RunUnsafeCommands + }; + public ConfigChanges GetChanges(Config? baseConfig = null) + { + Config reference = baseConfig ?? Defaults; + ConfigChanges changes = new() + { + GameDirectories = reference.GameDirectories == GameDirectories ? null : + GameDirectories.Where(x => !reference.GameDirectories.Contains(x)).ToArray(), + RunUnsafeCommands = reference.RunUnsafeCommands == RunUnsafeCommands ? null : RunUnsafeCommands + }; + + return changes; + } + + public static void LoadConfig(string basePath) + { + string fullPath = Path.Combine(basePath, FilePath); + + if (!File.Exists(fullPath)) + { + p_applied = Defaults; + p_changes = null; + return; + } + StreamReader reader = new(fullPath); + JsonTextReader jsonReader = new(reader); + p_changes = Serializer.Deserialize(jsonReader); + jsonReader.Close(); + reader.Close(); + + p_applied = p_changes is null ? Defaults : Defaults.ApplyChanges(p_changes.Value); + } + public static void SaveConfig(string basePath) + { + string fullPath = Path.Combine(basePath, FilePath); + + if (p_changes is null || !p_changes.Value.HasChange) + { + if (File.Exists(fullPath)) File.Delete(fullPath); + return; + } + + StreamWriter writer = new(fullPath); + JsonTextWriter jsonWriter = new(writer); + Serializer.Serialize(jsonWriter, p_changes); + jsonWriter.Close(); + writer.Close(); + } +} diff --git a/SrcMod/Shell/ObjectModels/ConfigChanges.cs b/SrcMod/Shell/ObjectModels/ConfigChanges.cs new file mode 100644 index 0000000..8328796 --- /dev/null +++ b/SrcMod/Shell/ObjectModels/ConfigChanges.cs @@ -0,0 +1,10 @@ +namespace SrcMod.Shell.ObjectModels; + +public record struct ConfigChanges +{ + [JsonIgnore] + public bool HasChange => GameDirectories is not null || RunUnsafeCommands is not null; + + public string[]? GameDirectories; + public AskMode? RunUnsafeCommands; +} diff --git a/SrcMod/Shell/Shell.cs b/SrcMod/Shell/Shell.cs index 161cfa0..372d084 100644 --- a/SrcMod/Shell/Shell.cs +++ b/SrcMod/Shell/Shell.cs @@ -1,12 +1,10 @@ -using System.ComponentModel; - -namespace SrcMod.Shell; +namespace SrcMod.Shell; public class Shell { public const string Author = "That_One_Nerd"; public const string Name = "SrcMod"; - public const string Version = "Alpha 0.3.2"; + public const string Version = "Alpha 0.3.3"; public readonly string? ShellDirectory; @@ -50,6 +48,10 @@ public class Shell WorkingDirectory = Directory.GetCurrentDirectory(); + // Load config. + if (ShellDirectory is null) Write("[WARNING] Could not load config from shell location. Defaults will be used."); + else Config.LoadConfig(ShellDirectory); + // Load modules and commands. List possibleAsms = new() { @@ -274,6 +276,9 @@ public class Shell activeCommand = null; } + if (ShellDirectory is null) Write("[WARNING] Could not save config to shell location. Any changes will be ignored."); + else Config.SaveConfig(ShellDirectory); + ReloadDirectoryInfo(); return; } diff --git a/SrcMod/Shell/Shell.csproj b/SrcMod/Shell/Shell.csproj index d662f45..253835f 100644 --- a/SrcMod/Shell/Shell.csproj +++ b/SrcMod/Shell/Shell.csproj @@ -13,6 +13,7 @@ false Logo.ico true + true @@ -31,6 +32,7 @@ + diff --git a/SrcMod/Shell/Tools.cs b/SrcMod/Shell/Tools.cs index 34c63b5..bd1c219 100644 --- a/SrcMod/Shell/Tools.cs +++ b/SrcMod/Shell/Tools.cs @@ -2,6 +2,17 @@ public static class Tools { + public static JsonSerializer Serializer { get; private set; } + + static Tools() + { + Serializer = JsonSerializer.Create(new() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }); + } + public static void DisplayWithPages(IEnumerable lines, ConsoleColor? color = null) { int written = 0; @@ -82,15 +93,36 @@ public static class Tools public static bool ValidateUnsafe() { - Write("You are about to execute an unsafe command.\nProceed? > ", ConsoleColor.DarkYellow, false); + switch (Config.LoadedConfig.RunUnsafeCommands) + { + case AskMode.Always: + Write("[INFO] The shell has been configured to always run unsafe commands. " + + "This can be changed in the config.", ConsoleColor.DarkGray); + return true; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.CursorVisible = true; - string result = Console.ReadLine()!.Trim().ToLower(); - Console.CursorVisible = false; - Console.ResetColor(); + case AskMode.Never: + Write("[ERROR] The shell has been configured to never run unsafe commands. " + + "This can be changed in the config.", ConsoleColor.Red); + return false; - return result == "y" || result == "yes" || result == "t" || - result == "true" || result == "p" || result == "proceed"; + case AskMode.Ask or _: + Write("You are about to execute an unsafe command.\nProceed? > ", ConsoleColor.DarkYellow, false); + Int2 start = (Console.CursorLeft, Console.CursorTop); + Write("\nTip: You can disable this dialog in the config.", ConsoleColor.DarkGray); + int finish = Console.CursorTop; + + Console.SetCursorPosition(start.x, start.y); + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.CursorVisible = true; + string result = Console.ReadLine()!.Trim().ToLower(); + Console.CursorVisible = false; + Console.ResetColor(); + + Console.SetCursorPosition(0, finish); + + return result == "y" || result == "yes" || result == "t" || + result == "true" || result == "p" || result == "proceed"; + } } }