diff --git a/ShinRyuModManager-CE/ModLoadOrder/Dependency/CPKGen/CPK.cs b/ShinRyuModManager-CE/ModLoadOrder/Dependency/CPKGen/CPK.cs index b6e8787..c51af5b 100644 --- a/ShinRyuModManager-CE/ModLoadOrder/Dependency/CPKGen/CPK.cs +++ b/ShinRyuModManager-CE/ModLoadOrder/Dependency/CPKGen/CPK.cs @@ -1,4 +1,5 @@ using System.Text; +using Serilog; namespace CriPakTools { public class CPK(Tools tool) { @@ -61,7 +62,7 @@ public bool ReadCPK(string sPath) { } } catch (Exception ex) { //MessageBox.Show(ex.ToString()); - Console.WriteLine(ex.ToString()); + Log.Error(ex, ""); } TocOffset = (ulong)GetColumnsData2(Utf, 0, "TocOffset", 3); @@ -660,8 +661,7 @@ public static object GetColumnData(UTF utf, int row, string name) { break; } } catch (Exception ex) { - //MessageBox.Show(ex.ToString()); - Console.WriteLine(ex.ToString()); + Log.Error(ex, ""); return null; } @@ -682,8 +682,7 @@ public static long GetColumnPosition(UTF utf, int row, string name) { break; } } catch (Exception ex) { - //MessageBox.Show(ex.ToString()); - Console.WriteLine(ex.ToString()); + Log.Error(ex, ""); return -1; } @@ -704,8 +703,7 @@ public static Type GetColumnType(UTF utf, int row, string name) { break; } } catch (Exception ex) { - //MessageBox.Show(ex.ToString()); - Console.WriteLine(ex.ToString()); + Log.Error(ex, ""); return null; } diff --git a/ShinRyuModManager-CE/ModLoadOrder/Generator.cs b/ShinRyuModManager-CE/ModLoadOrder/Generator.cs index 114d264..60c9670 100644 --- a/ShinRyuModManager-CE/ModLoadOrder/Generator.cs +++ b/ShinRyuModManager-CE/ModLoadOrder/Generator.cs @@ -1,4 +1,5 @@ using Serilog; +using Serilog.Events; using ShinRyuModManager.ModLoadOrder.Mods; using Utils; @@ -92,19 +93,19 @@ public static async Task GenerateModeLoadOrder(List mods, bool loos modsObjects[i] = mod; } - Log.Information("Added {ModCount} mod(s) and {FilesCount} file(s)!\n", mods.Count, files.Count); + Log.Information("Added {ModCount} mod(s) and {FilesCount} file(s)!", mods.Count, files.Count); // Reverse the list because the last mod in the list should have the highest priority mods.Reverse(); - Console.Write($"Generating {Constants.MLO} file..."); + Log.Information($"Generating {Constants.MLO} file..."); // Generate MLO var mlo = new MLO(modIndices, mods, files, loose.ParlessFolders, cpkDictionary); mlo.WriteMLO(Path.Combine(GamePath.FullGamePath, Constants.MLO)); - Console.WriteLine(" DONE!\n"); + Log.Information("Finished generating MLO."); // Check if a mod has a par that will override the repacked par, and skip repacking it in that case foreach (var key in parDictionary.Keys.ToList()) { @@ -140,21 +141,21 @@ public static async Task GenerateModeLoadOrder(List mods, bool loos if (cpkRepackingEnabled) { await CpkPatcher.RepackDictionary(cpkRepackDict); } - - if (ConsoleOutput.ShowWarnings) { - foreach (var key in modsWithFoldersNotFound.Keys.ToList()) { - Console.WriteLine($"Warning: Some folders in the root of \"{key}\" do not exist in the game's data. Check if the mod was extracted correctly."); - - if (ConsoleOutput.Verbose) { - foreach (var folder in modsWithFoldersNotFound[key]) { - Console.WriteLine($"Folder not found: {folder}"); - } - } - - Console.WriteLine(); + + if (Program.LogLevel > LogEventLevel.Warning) + return mlo; + + foreach (var key in modsWithFoldersNotFound.Keys.ToList()) { + Log.Warning("Warning: Some folders in the root of \"{Key}\" do not exist in the game's data. Check if the mod was extracted correctly.", key); + + if (Program.LogLevel != LogEventLevel.Verbose) + continue; + + foreach (var folder in modsWithFoldersNotFound[key]) { + Log.Warning("Folder not found: {Folder}", folder); } } - + return mlo; } } diff --git a/ShinRyuModManager-CE/ModLoadOrder/Mods/Mod.cs b/ShinRyuModManager-CE/ModLoadOrder/Mods/Mod.cs index 96cb864..654b9c7 100644 --- a/ShinRyuModManager-CE/ModLoadOrder/Mods/Mod.cs +++ b/ShinRyuModManager-CE/ModLoadOrder/Mods/Mod.cs @@ -1,4 +1,5 @@ using System.Text; +using Serilog; using Utils; namespace ShinRyuModManager.ModLoadOrder.Mods; @@ -26,45 +27,38 @@ public class Mod { /// public List RepackCpKs { get; } - protected readonly ConsoleOutput ConsoleOutput; - - public Mod(string name, int indent = 2) { + public Mod(string name) { Name = name; Files = []; ParFolders = []; CpkFolders = []; RepackCpKs = []; - ConsoleOutput = new ConsoleOutput(indent); - ConsoleOutput.WriteLine($"Reading directory: {name} ..."); + Log.Information("Reading directory: {Name} ...", name); } public void PrintInfo() { - ConsoleOutput.WriteLineIfVerbose(); - if (Files.Count > 0 || ParFolders.Count > 0) { if (Files.Count > 0) { - ConsoleOutput.WriteLine($"Added {Files.Count} file(s)"); + Log.Information("Added {FilesCount} file(s)", Files.Count); } if (ParFolders.Count > 0) { - ConsoleOutput.WriteLine($"Added {ParFolders.Count} folder(s) to be repacked"); + Log.Information("Added {ParFoldersCount} folder(s) to be repacked", ParFolders.Count); } if (CpkFolders.Count > 0) { - ConsoleOutput.WriteLine($"Added {CpkFolders.Count} CPK folder(s) to be bound"); + Log.Information("Added {CpkFoldersCount} CPK folder(s) to be bound", CpkFolders.Count); } } else { - ConsoleOutput.WriteLine($"Nothing found for {Name}, skipping"); + Log.Information("Nothing found for {Name}, skipping", Name); } - - ConsoleOutput.Flush(); } public void AddFiles(string path, string check) { var needsRepack = false; var basename = GamePath.GetBasename(path); - var parentDir = new DirectoryInfo(path).Parent.Name; + var parentDir = new DirectoryInfo(path).Parent!.Name; // Check if this path does not need repacking if (Name != "Parless") { @@ -144,7 +138,7 @@ public void AddFiles(string path, string check) { if (GamePath.CurrentGame == Game.Yakuza5) { CpkFolders.Add(cpkDataPath + ".cpk"); - ConsoleOutput.WriteLineIfVerbose($"Adding CPK folder: {cpkDataPath}"); + Log.Verbose("Adding CPK folder: {CpkDataPath}", cpkDataPath); } else { if (GamePath.CurrentGame <= Game.YakuzaKiwami) { RepackCpKs.Add(cpkDataPath + ".cpk"); @@ -163,7 +157,7 @@ public void AddFiles(string path, string check) { if (GamePath.CurrentGame is Game.Judgment or Game.LostJudgment) { CpkFolders.Add(cpkDataPath + ".par"); - ConsoleOutput.WriteLineIfVerbose($"Adding CPK folder: {cpkDataPath}"); + Log.Verbose("Adding CPK folder: {CpkDataPath}", cpkDataPath); } break; @@ -171,7 +165,7 @@ public void AddFiles(string path, string check) { cpkDataPath = GamePath.RemoveModPath(path); CpkFolders.Add($"{cpkDataPath}.cpk"); - ConsoleOutput.WriteLineIfVerbose($"Adding CPK folder: {cpkDataPath}"); + Log.Verbose("Adding CPK folder: {CpkDataPath}", cpkDataPath); break; } @@ -241,14 +235,14 @@ public void AddFiles(string path, string check) { // Add this folder to the list of folders to be repacked and stop recursing ParFolders.Add(dataPath); - ConsoleOutput.WriteLineIfVerbose($"Adding repackable folder: {dataPath}"); + Log.Verbose("Adding repackable folder: {DataPath}", dataPath); } else { // Add files in current directory var files = Directory.GetFiles(path).Where(f => !f.EndsWith(Constants.VORTEX_MANAGED_FILE)).Select(GamePath.GetDataPathFrom); foreach (var p in files) { Files.Add(p); - ConsoleOutput.WriteLineIfVerbose($"Adding file: {p}"); + Log.Verbose("Adding file: {file}", p); } var isParlessMod = GetType() == typeof(ParlessMod); diff --git a/ShinRyuModManager-CE/ModLoadOrder/Mods/ParlessMod.cs b/ShinRyuModManager-CE/ModLoadOrder/Mods/ParlessMod.cs index 817557b..18494e3 100644 --- a/ShinRyuModManager-CE/ModLoadOrder/Mods/ParlessMod.cs +++ b/ShinRyuModManager-CE/ModLoadOrder/Mods/ParlessMod.cs @@ -1,15 +1,14 @@ +using Serilog; using Utils; namespace ShinRyuModManager.ModLoadOrder.Mods; -public class ParlessMod() : Mod(Constants.PARLESS_NAME, 0) { +public class ParlessMod() : Mod(Constants.PARLESS_NAME) { public List ParlessFolders { get; } = []; public new void PrintInfo() { - ConsoleOutput.WriteLineIfVerbose(); - if (ParlessFolders.Count > 0) { - ConsoleOutput.WriteLine($"Added {ParlessFolders.Count} .parless path(s)"); + Log.Information("Added {ParlessFoldersCount} .parless path(s)", ParlessFolders.Count); } base.PrintInfo(); @@ -35,7 +34,7 @@ public class ParlessMod() : Mod(Constants.PARLESS_NAME, 0) { ParlessFolders.Add(folder); - ConsoleOutput.WriteLineIfVerbose($"Adding .parless path: {loosePath}"); + Log.Verbose("Adding .parless path: {LoosePath}", loosePath); } else { // Continue recursing until we find the next ".parless" foreach (var folder in Directory.GetDirectories(path)) { diff --git a/ShinRyuModManager-CE/ParRepacker.cs b/ShinRyuModManager-CE/ParRepacker.cs index 90072a1..f63e31a 100644 --- a/ShinRyuModManager-CE/ParRepacker.cs +++ b/ShinRyuModManager-CE/ParRepacker.cs @@ -31,19 +31,19 @@ public static void RemoveOldRepackedPars() { if (!Directory.Exists(pathToParlessMods)) return; - Console.Write("Removing old pars..."); + Log.Information("Removing old pars..."); try { DeleteDirectory(pathToParlessMods); } catch { - Console.WriteLine($" FAIL! {pathToParlessMods}\n"); + Log.Warning("Failed to remove old pars! {PathToParlessMods}", pathToParlessMods); } - Console.WriteLine(" DONE!\n"); + Log.Information("Removed old pars."); } public static async Task RepackDictionary(Dictionary> parDictionary) { - var parTasks = new List>(); + var parTasks = new List(); if (parDictionary.Count == 0) { Log.Information("No pars to repack."); @@ -54,31 +54,19 @@ public static async Task RepackDictionary(Dictionary> parDi Log.Information("Repacking pars..."); foreach (var parModPair in parDictionary) { - var consoleOutput = new ConsoleOutput(2); - - parTasks.Add(Task.Run(() => RepackPar(parModPair.Key, parModPair.Value, consoleOutput))); + parTasks.Add(Task.Run(() => RepackPar(parModPair.Key, parModPair.Value))); } - while (parTasks.Count > 0) { - var console = await Task.WhenAny(parTasks); - - console.Result?.Flush(); - - parTasks.Remove(console); - } + await Task.WhenAll(parTasks); /*foreach (var parModPair in parDictionary) { - var consoleOutput = new ConsoleOutput(2); - - RepackPar(parModPair.Key, parModPair.Value, consoleOutput); - - consoleOutput.Flush(); + RepackPar(parModPair.Key, parModPair.Value); }*/ Log.Information("Repacked {ParDictionaryCount} par(s)!", parDictionary.Count); } - private static ConsoleOutput RepackPar(string parPath, List mods, ConsoleOutput console) { + private static void RepackPar(string parPath, List mods) { parPath = parPath.TrimStart(Path.DirectorySeparatorChar); var parPathReal = GamePath.GetRootParPath(parPath + ".par"); @@ -104,7 +92,7 @@ private static ConsoleOutput RepackPar(string parPath, List mods, Consol // Populate fileDict with the files inside each mod foreach (var mod in mods) { - foreach (var modFile in GetModFiles(parPath, mod, console)) { + foreach (var modFile in GetModFiles(parPath, mod)) { fileDict.TryAdd(modFile, mod); } } @@ -195,39 +183,36 @@ private static ConsoleOutput RepackPar(string parPath, List mods, Consol // Remove the .partemp directory DeleteDirectory(pathToTempPar); - console.WriteLineIfVerbose(); - console.WriteLine($"Repacked {fileDict.Count} file(s) in {parPath + ".par"}!"); - - return console; + Log.Information("Repacked {FileDictCount} file(s) in {ParPath}!", fileDict.Count, parPath + ".par"); } - private static List GetModFiles(string par, string mod, ConsoleOutput console) { + private static List GetModFiles(string par, string mod) { List result; if (mod.StartsWith(Constants.PARLESS_NAME)) { // Get index of ".parless" in par path // 15 = ParlessMod.NAME.Length + 1 - result = GetModFiles(Path.Combine(GamePath.DataPath, par.Insert(int.Parse(mod[15..]) - 1, ".parless")), console); + result = GetModFiles(Path.Combine(GamePath.DataPath, par.Insert(int.Parse(mod[15..]) - 1, ".parless"))); } else { - result = GetModFiles(GamePath.GetModPathFromDataPath(mod, par), console); + result = GetModFiles(GamePath.GetModPathFromDataPath(mod, par)); } // Get file path relative to par return result.Select(f => f.Replace(".parless", "")[(f.Replace(".parless", "").IndexOf(par, StringComparison.Ordinal) + par.Length + 1)..]).ToList(); } - private static List GetModFiles(string path, ConsoleOutput console) { + private static List GetModFiles(string path) { List files = []; // Add files in current directory foreach (var p in Directory.GetFiles(path).Where(f => !f.EndsWith(Constants.VORTEX_MANAGED_FILE)).Select(GamePath.GetDataPathFrom)) { files.Add(p); - console.WriteLineIfVerbose($"Adding file: {p}"); + Log.Verbose("Adding file: {file}", p); } // Get files for all subdirectories foreach (var folder in Directory.GetDirectories(path)) { - files.AddRange(GetModFiles(folder, console)); + files.AddRange(GetModFiles(folder)); } return files; diff --git a/ShinRyuModManager-CE/Program.cs b/ShinRyuModManager-CE/Program.cs index 0bfa513..c1b2400 100644 --- a/ShinRyuModManager-CE/Program.cs +++ b/ShinRyuModManager-CE/Program.cs @@ -7,6 +7,7 @@ using IniParser.Model; using Serilog; using Serilog.Core; +using Serilog.Events; using Serilog.Exceptions; using Serilog.Formatting.Json; using ShinRyuModManager.Helpers; @@ -21,35 +22,54 @@ namespace ShinRyuModManager; public static class Program { private static bool _externalModsOnly = true; - private static bool _looseFilesEnabled = false; + private static bool _looseFilesEnabled; private static bool _cpkRepackingEnabled = true; private static bool _checkForUpdates = true; - private static bool _isSilent = false; - private static bool _migrated = false; + private static bool _isSilent; + private static bool _migrated; + private static IniData _iniData; - public static bool RebuildMlo = true; - public static bool IsRebuildMloSupported = true; - - public static List LibraryMetaCache = new List(); + private static readonly FileIniDataParser IniParser; + + public static bool RebuildMlo { get; private set; } = true; + public static bool IsRebuildMloSupported { get; private set; } = true; + public static LogEventLevel LogLevel { get; private set; } = LogEventLevel.Information; + public static List LibraryMetaCache { get; set; } = []; + + static Program() { + IniParser = new FileIniDataParser { + Parser = { + Configuration = { + AssigmentSpacer = string.Empty + } + } + }; + } [STAThread] private static void Main(string[] args) { + Directory.CreateDirectory(Settings.LOGS_BASE_PATH); + + var defaultLogsPath = Path.Combine(Settings.LOGS_BASE_PATH, "srmm_logs.log"); + var errorLogsPath = Path.Combine(Settings.LOGS_BASE_PATH, "srmm_errors.log"); + + LoadConfig(); + // Create global logger Log.Logger = new LoggerConfiguration() - .MinimumLevel.ControlledBy(new LoggingLevelSwitch()) + .MinimumLevel.ControlledBy(new LoggingLevelSwitch(LogLevel)) .Enrich.WithExceptionDetails() // Log to SRMM logs file .WriteTo.Logger(l => l .Filter.ByIncludingOnly(e => e.Exception == null) - .WriteTo.Async(a => a.File("srmm_logs.txt", shared: false))) + .WriteTo.Async(a => a.File(defaultLogsPath, rollingInterval: RollingInterval.Day)) + .WriteTo.Async(a => a.Console())) // Logs exceptions separately .WriteTo.Logger(l => l .Filter.ByIncludingOnly(e => e.Exception != null) - .WriteTo.Async(a => a.File(new JsonFormatter(renderMessage: true), "srmm_errors.txt"))) + .WriteTo.Async(a => a.File(new JsonFormatter(renderMessage: true), errorLogsPath, rollingInterval: RollingInterval.Day))) .CreateLogger(); - Log.Information("Shin Ryu Mod Manager Start"); - // TODO: Temporary, YakuzaParless.asi currently only supports the Windows binary. Currently disabling RebuildMLO on Linux if (!OperatingSystem.IsWindows()) { IsRebuildMloSupported = false; @@ -59,7 +79,7 @@ private static void Main(string[] args) { // Unfortunately, no one way to detect left Ctrl while being cross-platform if (args.Length == 0) { if (_checkForUpdates) { - // TODO: Implemente updates + // TODO: Implement updates } Log.Information("Shin Ryu Mod Manager GUI Application Start"); @@ -75,6 +95,7 @@ private static void Main(string[] args) { private static AppBuilder BuildAvaloniaApp() { GC.KeepAlive(typeof(SvgImageExtension).Assembly); GC.KeepAlive(typeof(Svg.Skia.SKSvg).Assembly); + return AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() @@ -82,19 +103,19 @@ private static AppBuilder BuildAvaloniaApp() { } private static async Task MainCLI(string[] args) { - Console.WriteLine($"Shin Ryu Mod Manager-CE v{AssemblyVersion.GetVersion()}"); - Console.WriteLine("By TheTrueColonel (a port of SRMM Studio's work)\n"); + Log.Information("Shin Ryu Mod Manager-CE v{Version}", AssemblyVersion.GetVersion()); + Log.Information("By TheTrueColonel (a port of SRMM Studio's work)"); // Parse arguments var list = new List(args); if (list.Contains("-h") || list.Contains("--help")) { - Console.WriteLine("Usage: run without arguments to generate mod load order."); + Log.Information("Usage: run without arguments to generate mod load order."); - Console.WriteLine(" run with \"-s\" or \"--silent\" flag to prevent checking for updates and remove prompts."); + Log.Information(" run with \"-s\" or \"--silent\" flag to prevent checking for updates and remove prompts."); - //Console.WriteLine(" run with \"-r\" or \"--run\" flag to run the game after the program finishes."); - Console.WriteLine(" run with \"-h\" or \"--help\" flag to show this message and exit."); + //Log.Information(" run with \"-r\" or \"--run\" flag to run the game after the program finishes."); + Log.Information(" run with \"-h\" or \"--help\" flag to show this message and exit."); return; } @@ -119,73 +140,69 @@ private static async Task MainCLI(string[] args) { }*/ } - internal static List PreRun() { - var iniParser = new FileIniDataParser(); - iniParser.Parser.Configuration.AssigmentSpacer = string.Empty; - - IniData ini; - + private static void LoadConfig() { if (File.Exists(Constants.INI)) { - ini = iniParser.ReadFile(Constants.INI); + _iniData = IniParser.ReadFile(Constants.INI); - if (ini.TryGetKey("Overrides.LooseFilesEnabled", out var looseFiles)) { + if (_iniData.TryGetKey("Overrides.LooseFilesEnabled", out var looseFiles)) { _looseFilesEnabled = int.Parse(looseFiles) == 1; } - if (ini.TryGetKey("RyuModManager.Verbose", out var verbose)) { - ConsoleOutput.Verbose = int.Parse(verbose) == 1; + if (_iniData.TryGetKey("RyuModManager.Verbose", out var verbose)) { + if (int.Parse(verbose) == 1) { + LogLevel = LogEventLevel.Verbose; + } } - if (ini.TryGetKey("RyuModManager.CheckForUpdates", out var check)) { + if (_iniData.TryGetKey("RyuModManager.CheckForUpdates", out var check)) { _checkForUpdates = int.Parse(check) == 1; } - if (ini.TryGetKey("RyuModManager.ShowWarnings", out var showWarnings)) { - ConsoleOutput.ShowWarnings = int.Parse(showWarnings) == 1; + if (_iniData.TryGetKey("RyuModManager.ShowWarnings", out var showWarnings)) { + //ConsoleOutput.ShowWarnings = int.Parse(showWarnings) == 1; } - if (ini.TryGetKey("RyuModManager.LoadExternalModsOnly", out var extMods)) { + if (_iniData.TryGetKey("RyuModManager.LoadExternalModsOnly", out var extMods)) { _externalModsOnly = int.Parse(extMods) == 1; } - if (ini.TryGetKey("Overrides.RebuildMLO", out var rebuildMlo)) { + if (_iniData.TryGetKey("Overrides.RebuildMLO", out var rebuildMlo)) { RebuildMlo = int.Parse(rebuildMlo) == 1; } - if (!ini.TryGetKey("Parless.IniVersion", out var iniVersion) || + if (!_iniData.TryGetKey("Parless.IniVersion", out var iniVersion) || int.Parse(iniVersion) < ParlessIni.CURRENT_VERSION) { // Update if ini version is old (or does not exist) - Console.Write(Constants.INI + " is outdated. Updating ini to the latest version... "); + Log.Information($"{Constants.INI} is outdated. Updating ini to the latest version... "); if (int.Parse(iniVersion) <= 3) { // Force enable RebuildMLO option - ini.Sections["Overrides"]["RebuildMLO"] = "1"; + _iniData.Sections["Overrides"]["RebuildMLO"] = "1"; RebuildMlo = true; } - iniParser.WriteFile(Constants.INI, IniTemplate.UpdateIni(ini)); - Console.WriteLine("DONE!\n"); + IniParser.WriteFile(Constants.INI, IniTemplate.UpdateIni(_iniData)); + Log.Information($"Updated {Constants.INI}"); } } else { // Create ini if it does not exist - Console.Write(Constants.INI + " was not found. Creating default ini... "); - iniParser.WriteFile(Constants.INI, IniTemplate.NewIni()); - Console.WriteLine("DONE!\n"); + Log.Information($"{Constants.INI} was not found. Creating default ini..."); + IniParser.WriteFile(Constants.INI, IniTemplate.NewIni()); } - + } + + internal static List PreRun() { if (GamePath.CurrentGame != Game.Unsupported && !Directory.Exists(GamePath.MODS)) { if (!Directory.Exists(GamePath.MODS)) { // Create mods folder if it does not exist - Console.Write($"\"{GamePath.MODS}\" folder was not found. Creating empty folder... "); + Log.Information($"\"{GamePath.MODS}\" folder was not found. Creating empty folder... "); Directory.CreateDirectory(GamePath.MODS); - Console.WriteLine("DONE!\n"); } if (!Directory.Exists(GamePath.LIBRARIES)) { // Create libraries folder if it does not exist - Console.Write($"\"{GamePath.LIBRARIES}\" folder was not found. Creating empty folder... "); + Log.Information($"\"{GamePath.LIBRARIES}\" folder was not found. Creating empty folder... "); Directory.CreateDirectory(GamePath.LIBRARIES); - Console.WriteLine("DONE!\n"); } } @@ -193,59 +210,47 @@ internal static List PreRun() { // Virtua Fighter eSports crashes when used with dinput8.dll as the ASI loader if (GamePath.CurrentGame == Game.Eve && File.Exists(Constants.DINPUT8DLL)) { if (File.Exists(Constants.VERSIONDLL)) { - Console.Write($"Game specific patch: Deleting {Constants.DINPUT8DLL} because {Constants.VERSIONDLL} exists..."); + Log.Warning($"Game specific patch: Deleting {Constants.DINPUT8DLL} because {Constants.VERSIONDLL} exists..."); // Remove dinput8.dll File.Delete(Constants.DINPUT8DLL); } else { - Console.Write($"Game specific patch: Renaming {Constants.DINPUT8DLL} to {Constants.VERSIONDLL}..."); + Log.Warning($"Game specific patch: Renaming {Constants.DINPUT8DLL} to {Constants.VERSIONDLL}..."); // Rename dinput8.dll to version.dll to prevent the game from crashing File.Move(Constants.DINPUT8DLL, Constants.VERSIONDLL); } - - Console.WriteLine(" DONE!\n"); } else if (GamePath.CurrentGame is Game.Judgment or Game.LostJudgment) { // Lost Judgment (and Judgment post update 1) does not like Ultimate ASI Loader, so instead we use a custom build of DllSpoofer (https://github.com/Kazurin-775/DllSpoofer) if (File.Exists(Constants.DINPUT8DLL)) { - Console.Write($"Game specific patch: Deleting {Constants.DINPUT8DLL} because it causes crashes with Judgment games..."); + Log.Warning($"Game specific patch: Deleting {Constants.DINPUT8DLL} because it causes crashes with Judgment games..."); // Remove dinput8.dll File.Delete(Constants.DINPUT8DLL); - - Console.WriteLine(" DONE!\n"); } if (!File.Exists(Constants.WINMMDLL)) { if (File.Exists(Constants.WINMMLJ)) { - Console.Write($"Game specific patch: Enabling {Constants.WINMMDLL} by renaming {Constants.WINMMLJ} to fix Judgment games crashes..."); + Log.Warning($"Game specific patch: Enabling {Constants.WINMMDLL} by renaming {Constants.WINMMLJ} to fix Judgment games crashes..."); // Rename dinput8.dll to version.dll to prevent the game from crashing File.Move(Constants.WINMMLJ, Constants.WINMMDLL); - - Console.WriteLine(" DONE!\n"); } else { - Console.ForegroundColor = ConsoleColor.Red; - - Console.WriteLine($"WARNING: {Constants.WINMMLJ} was not found. Judgment games will NOT load mods without this file. Please redownload Shin Ryu Mod Manager.\n"); - - Console.ResetColor(); + Log.Error($"WARNING: {Constants.WINMMLJ} was not found. Judgment games will NOT load mods without this file. Please redownload Shin Ryu Mod Manager."); } } } // Read ini (again) to check if we should try importing the old load order file - ini = iniParser.ReadFile(Constants.INI); + _iniData = IniParser.ReadFile(Constants.INI); if (GamePath.CurrentGame is Game.Judgment or Game.LostJudgment or Game.LikeADragonPirates) { // Disable RebuildMLO when using an external mod manager - if (ini.TryGetKey("Overrides.RebuildMLO", out _)) { - Console.Write("Game specific patch: Disabling RebuildMLO for some games when using an external mod manager..."); - - ini.Sections["Overrides"]["RebuildMLO"] = "0"; - iniParser.WriteFile(Constants.INI, ini); + if (_iniData.TryGetKey("Overrides.RebuildMLO", out _)) { + Log.Warning("Game specific patch: Disabling RebuildMLO for some games when using an external mod manager..."); - Console.WriteLine(" DONE!\n"); + _iniData.Sections["Overrides"]["RebuildMLO"] = "0"; + IniParser.WriteFile(Constants.INI, _iniData); } } @@ -257,7 +262,7 @@ internal static List PreRun() { } else { var defaultEnabled = true; - if (File.Exists(Constants.TXT_OLD) && ini.GetKey("SavedSettings.ModListImported") == null) { + if (File.Exists(Constants.TXT_OLD) && _iniData.GetKey("SavedSettings.ModListImported") == null) { // Scanned mods should be disabled, because that's how they were with the old txt format defaultEnabled = false; @@ -265,53 +270,47 @@ internal static List PreRun() { _migrated = true; // Migrate old format to new - Console.Write("Old format load order file (" + Constants.TXT_OLD + ") was found. Importing to the new format..."); + Log.Information("Old format load order file (" + Constants.TXT_OLD + ") was found. Importing to the new format..."); mods.AddRange(ConvertOldToNewModList(ReadModLoadOrderTxt(Constants.TXT_OLD)) .Where(n => !mods.Any(m => EqualModNames(m.Name, n.Name)))); - - Console.WriteLine(" DONE!\n"); } else if (File.Exists(Constants.TXT)) { mods.AddRange(ReadModListTxt(Constants.TXT).Where(n => !mods.Any(m => EqualModNames(m.Name, n.Name)))); } else { - Console.WriteLine(Constants.TXT + " was not found. Will load all existing mods.\n"); + Log.Information($"{Constants.TXT} was not found. Will load all existing mods.\n"); } if (Directory.Exists(GamePath.MODS)) { // Add all scanned mods that have not been added to the load order yet - Console.Write("Scanning for mods..."); + Log.Information("Scanning for mods..."); mods.AddRange(ScanMods().Where(n => !mods.Any(m => EqualModNames(m.Name, n))) .Select(m => new ModInfo(m, defaultEnabled))); - Console.WriteLine(" DONE!\n"); + Log.Information("Found {ModsCount} mods.", mods.Count); } } if (!GamePath.IsXbox(Path.Combine(GamePath.FullGamePath))) return mods; - if (!ini.TryGetKey("Overrides.RebuildMLO", out _)) + if (!_iniData.TryGetKey("Overrides.RebuildMLO", out _)) return mods; - Console.Write($"Game specific patch: Disabling RebuildMLO for Xbox games..."); - - ini.Sections["Overrides"]["RebuildMLO"] = "0"; - iniParser.WriteFile(Constants.INI, ini); + Log.Warning("Game specific patch: Disabling RebuildMLO for Xbox games..."); - Console.WriteLine(" DONE!\n"); + _iniData.Sections["Overrides"]["RebuildMLO"] = "0"; + IniParser.WriteFile(Constants.INI, _iniData); return mods; } internal static async Task RunGeneration(List mods) { if (File.Exists(Constants.MLO)) { - Console.Write("Removing old MLO..."); + Log.Information("Removing old MLO..."); // Remove existing MLO file to avoid it being used if a new MLO won't be generated File.Delete(Constants.MLO); - - Console.WriteLine(" DONE!"); } // Remove previously repacked pars, to avoid unwanted side effects @@ -384,33 +383,31 @@ internal static async Task RunGeneration(List mods) { return; } - Console.WriteLine("Aborting: No mods were found, and .parless paths are disabled\n"); + Log.Warning("Aborting: No mods were found, and .parless paths are disabled"); } - Console.WriteLine("Aborting: No supported game was found in this directory\n"); + Log.Warning("Aborting: No supported game was found in this directory"); } private static void PostRun() { // Check if the ASI loader is not in the directory (possibly due to incorrect zip extraction) if (MissingDll()) { - Console.WriteLine($"Warning: \"{Constants.DINPUT8DLL}\" is missing from this directory. Shin Ryu Mod Manager will NOT function properly without this file\n"); + Log.Warning($"Warning: \"{Constants.DINPUT8DLL}\" is missing from this directory. Shin Ryu Mod Manager will NOT function properly without this file"); } // Check if the ASI is not in the directory if (MissingAsi()) { - Console.WriteLine($"Warning: \"{Constants.ASI}\" is missing from this directory. Shin Ryu Mod Manager will NOT function properly without this file\n"); + Log.Warning($"Warning: \"{Constants.ASI}\" is missing from this directory. Shin Ryu Mod Manager will NOT function properly without this file"); } // Calculate the checksum for the game's exe to inform the user if their version might be unsupported - if (ConsoleOutput.ShowWarnings && InvalidGameExe()) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Warning: Game version is unrecognized. Please use the latest Steam version of the game."); - Console.WriteLine($"Shin Ryu Mod Manager will still generate the load order, but the game might CRASH or not function properly\n"); - Console.ResetColor(); + if (LogLevel <= LogEventLevel.Warning && InvalidGameExe()) { + Log.Error("Warning: Game version is unrecognized. Please use the latest Steam version of the game."); + Log.Error("Shin Ryu Mod Manager will still generate the load order, but the game might CRASH or not function properly"); } if (!_isSilent) { - Console.WriteLine("Program finished. Press any key to exit..."); + Log.Information("Program finished. Press any key to exit..."); Console.ReadKey(); } } @@ -523,7 +520,7 @@ internal static async Task SaveModListAsync(List mods) { ini["SavedSettings"].AddKey("ModListImported", "true"); iniParser.WriteFile(Constants.INI, ini); } catch { - Console.WriteLine($"Could not delete {Constants.TXT_OLD}. This file should be deleted manually."); + Log.Warning($"Could not delete {Constants.TXT_OLD}. This file should be deleted manually."); } return result; @@ -668,7 +665,6 @@ private static async Task DownloadLibraryPackageAsync(string fileName, L return path; } catch (Exception ex) { - Debug.WriteLine(ex); Log.Error(ex, "Failed to download library!"); return string.Empty; diff --git a/ShinRyuModManager-CE/Settings.cs b/ShinRyuModManager-CE/Settings.cs index 0f37c1b..3423d67 100644 --- a/ShinRyuModManager-CE/Settings.cs +++ b/ShinRyuModManager-CE/Settings.cs @@ -20,4 +20,7 @@ public static class Settings { public const string LIBRARIES_INFO_REPO = "srmm-lib-info"; public const string LIBRARIES_INFO_REPO_FILE_PATH = "libraries.yaml"; public const string LIBRARIES_LIBMETA_FILE_NAME = "lib-meta.yaml"; + + //LOGGING + public const string LOGS_BASE_PATH = "srmm-logs"; } diff --git a/ShinRyuModManager-CE/ShinRyuModManager-CE.csproj b/ShinRyuModManager-CE/ShinRyuModManager-CE.csproj index a276f7c..17661c1 100644 --- a/ShinRyuModManager-CE/ShinRyuModManager-CE.csproj +++ b/ShinRyuModManager-CE/ShinRyuModManager-CE.csproj @@ -10,7 +10,7 @@ Debug;Release x64 false - 1.1.9 + 1.1.10 UserInterface\Assets\Icons\SRMM_icon.ico true @@ -35,6 +35,7 @@ + diff --git a/ShinRyuModManager-CE/Templates/IniStructs/IniKey.cs b/ShinRyuModManager-CE/Templates/IniStructs/IniKey.cs index a5467eb..3ab0e9c 100644 --- a/ShinRyuModManager-CE/Templates/IniStructs/IniKey.cs +++ b/ShinRyuModManager-CE/Templates/IniStructs/IniKey.cs @@ -1,9 +1,9 @@ -namespace ShinRyuModManager.Templates { - public readonly struct IniKey { - public string Name { get; init; } +namespace ShinRyuModManager.Templates; + +public readonly struct IniKey { + public string Name { get; init; } - public List Comments { get; init; } + public List Comments { get; init; } - public int DefaultValue { get; init; } - } -} + public int DefaultValue { get; init; } +} \ No newline at end of file diff --git a/ShinRyuModManager-CE/Templates/IniStructs/IniSection.cs b/ShinRyuModManager-CE/Templates/IniStructs/IniSection.cs index 84a8973..f61a6a9 100644 --- a/ShinRyuModManager-CE/Templates/IniStructs/IniSection.cs +++ b/ShinRyuModManager-CE/Templates/IniStructs/IniSection.cs @@ -1,9 +1,9 @@ -namespace ShinRyuModManager.Templates { - public readonly struct IniSection { - public string Name { get; init; } +namespace ShinRyuModManager.Templates; + +public readonly struct IniSection { + public string Name { get; init; } - public List Comments { get; init; } + public List Comments { get; init; } - public List Keys { get; init; } - } -} + public List Keys { get; init; } +} \ No newline at end of file diff --git a/ShinRyuModManager-CE/Templates/IniStructs/ParlessIni.cs b/ShinRyuModManager-CE/Templates/IniStructs/ParlessIni.cs index 2eeb48f..1f1e082 100644 --- a/ShinRyuModManager-CE/Templates/IniStructs/ParlessIni.cs +++ b/ShinRyuModManager-CE/Templates/IniStructs/ParlessIni.cs @@ -1,187 +1,187 @@ -namespace ShinRyuModManager.Templates { - public static class ParlessIni { - public const int CURRENT_VERSION = 7; - - public static List GetParlessSections() { - return [ - new IniSection { - Name = "Parless", - Comments = ["All values are 0 for false, 1 for true."], - Keys = [ - new IniKey { - Name = "IniVersion", - Comments = ["Ini version. Should not be changed manually."], - DefaultValue = CURRENT_VERSION - }, +namespace ShinRyuModManager.Templates; + +public static class ParlessIni { + public const int CURRENT_VERSION = 7; + + public static List GetParlessSections() { + return [ + new IniSection { + Name = "Parless", + Comments = ["All values are 0 for false, 1 for true."], + Keys = [ + new IniKey { + Name = "IniVersion", + Comments = ["Ini version. Should not be changed manually."], + DefaultValue = CURRENT_VERSION + }, - new IniKey { - Name = "ParlessEnabled", - Comments = ["Global switch for Parless. Set to 0 to disable everything."], - DefaultValue = 1 - }, + new IniKey { + Name = "ParlessEnabled", + Comments = ["Global switch for Parless. Set to 0 to disable everything."], + DefaultValue = 1 + }, - new IniKey { - Name = "TempDisabled", - Comments = [ - "Temporarily disables Parless for one run only. Overrides ParlessEnabled.", - "This will be set back to 0 whenever the game is launched with it set to 1." - ], - DefaultValue = 0 - } - ] - }, + new IniKey { + Name = "TempDisabled", + Comments = [ + "Temporarily disables Parless for one run only. Overrides ParlessEnabled.", + "This will be set back to 0 whenever the game is launched with it set to 1." + ], + DefaultValue = 0 + } + ] + }, - // Overrides + // Overrides - new IniSection { - Name = "Overrides", - Comments = [ - "General override order:", - "if LooseFilesEnabled is set to 1, files inside \".parless\" paths will override everything.", - "if ModsEnabled is set to 1, mod files will override files inside pars." - ], - Keys = [ - new IniKey { - Name = "LooseFilesEnabled", - Comments = [ - "Allows loading files from \".parless\" paths.", - "Files in these paths will override the mod files installed in /mods/", - "Example: files in /data/chara.parless/ will override the", - "files in /data/chara.par AND files in /chara/ in all mods." - ], - DefaultValue = 0 - }, + new IniSection { + Name = "Overrides", + Comments = [ + "General override order:", + "if LooseFilesEnabled is set to 1, files inside \".parless\" paths will override everything.", + "if ModsEnabled is set to 1, mod files will override files inside pars." + ], + Keys = [ + new IniKey { + Name = "LooseFilesEnabled", + Comments = [ + "Allows loading files from \".parless\" paths.", + "Files in these paths will override the mod files installed in /mods/", + "Example: files in /data/chara.parless/ will override the", + "files in /data/chara.par AND files in /chara/ in all mods." + ], + DefaultValue = 0 + }, - new IniKey { - Name = "ModsEnabled", - Comments = [ - "Allows loading files from the /mods/ directory.", - "Each mod has to be extracted in its own folder, where its contents", - "will mirror the game's /data/ directory. Pars should be extracted into folders.", - "Example: /mods/ExampleMod/chara/auth/c_am_kiryu/c_am_kiryu.gmd", - "will replace the /auth/c_am_kiryu/c_am_kiryu.gmd file inside /data/chara.par" - ], - DefaultValue = 1 - }, + new IniKey { + Name = "ModsEnabled", + Comments = [ + "Allows loading files from the /mods/ directory.", + "Each mod has to be extracted in its own folder, where its contents", + "will mirror the game's /data/ directory. Pars should be extracted into folders.", + "Example: /mods/ExampleMod/chara/auth/c_am_kiryu/c_am_kiryu.gmd", + "will replace the /auth/c_am_kiryu/c_am_kiryu.gmd file inside /data/chara.par" + ], + DefaultValue = 1 + }, - new IniKey { - Name = "RebuildMLO", - Comments = [ - "Removes the need to run ShinRyuModManager before launching your game,", - "should have little to no effect on the time it takes to launch,", - "and should help users avoid mistakenly not rebuilding.", - "Optional QOL feature to help you avoid having to re-run the mod manager every time." - ], - DefaultValue = 1 - }, + new IniKey { + Name = "RebuildMLO", + Comments = [ + "Removes the need to run ShinRyuModManager before launching your game,", + "should have little to no effect on the time it takes to launch,", + "and should help users avoid mistakenly not rebuilding.", + "Optional QOL feature to help you avoid having to re-run the mod manager every time." + ], + DefaultValue = 1 + }, - new IniKey { - Name = "Locale", - Comments = [ - "Changes the filepaths of localized pars to match the current locale.", - "Only needed if you're running a non-English version of the game.", - "English=0, Japanese=1, Chinese=2, Korean=3" - ], - DefaultValue = 0 - } - ] - }, + new IniKey { + Name = "Locale", + Comments = [ + "Changes the filepaths of localized pars to match the current locale.", + "Only needed if you're running a non-English version of the game.", + "English=0, Japanese=1, Chinese=2, Korean=3" + ], + DefaultValue = 0 + } + ] + }, - // RyuModManager + // RyuModManager - new IniSection { - Name = "RyuModManager", - Comments = [], - Keys = [ - new IniKey { - Name = "Verbose", - Comments = ["Print additional info, including all file paths that get added to the MLO"], - DefaultValue = 0 - }, + new IniSection { + Name = "RyuModManager", + Comments = [], + Keys = [ + new IniKey { + Name = "Verbose", + Comments = ["Print additional info, including all file paths that get added to the MLO"], + DefaultValue = 0 + }, - new IniKey { - Name = "CheckForUpdates", - Comments = ["Check for updates before exiting the program"], - DefaultValue = 1 - }, + new IniKey { + Name = "CheckForUpdates", + Comments = ["Check for updates before exiting the program"], + DefaultValue = 1 + }, - new IniKey { - Name = "ShowWarnings", - Comments = ["Show warnings whenever a mod was possibly not extracted correctly"], - DefaultValue = 1 - }, + new IniKey { + Name = "ShowWarnings", + Comments = ["Show warnings whenever a mod was possibly not extracted correctly"], + DefaultValue = 1 + }, - new IniKey { - Name = "LoadExternalModsOnly", - Comments = [ - "Only load mods from the /mods/_externalMods/ directory, and ignore the load order file.", - "That directory will be created automatically by external mod managers.", - "If this option is disabled, external mods will NOT be loaded, unless \"_externalMods\" is added manually to the load order file.", - "If the directory does not exist, then the load order will be used as normal." - ], - DefaultValue = 1 - } - ] - }, + new IniKey { + Name = "LoadExternalModsOnly", + Comments = [ + "Only load mods from the /mods/_externalMods/ directory, and ignore the load order file.", + "That directory will be created automatically by external mod managers.", + "If this option is disabled, external mods will NOT be loaded, unless \"_externalMods\" is added manually to the load order file.", + "If the directory does not exist, then the load order will be used as normal." + ], + DefaultValue = 1 + } + ] + }, - // Logs + // Logs - new IniSection { - Name = "Logs", - Comments = [], - Keys = [ - new IniKey { - Name = "LogMods", - Comments = ["Write filepaths for mods that get loaded into modOverrides.txt"], - DefaultValue = 0 - }, + new IniSection { + Name = "Logs", + Comments = [], + Keys = [ + new IniKey { + Name = "LogMods", + Comments = ["Write filepaths for mods that get loaded into modOverrides.txt"], + DefaultValue = 0 + }, - new IniKey { - Name = "LogParless", - Comments = ["Write filepaths for .parless paths that get loaded into parlessOverrides.txt"], - DefaultValue = 0 - }, + new IniKey { + Name = "LogParless", + Comments = ["Write filepaths for .parless paths that get loaded into parlessOverrides.txt"], + DefaultValue = 0 + }, - new IniKey { - Name = "LogAll", - Comments = ["Write filepaths for every file that gets loaded into allFilepaths.txt"], - DefaultValue = 0 - }, + new IniKey { + Name = "LogAll", + Comments = ["Write filepaths for every file that gets loaded into allFilepaths.txt"], + DefaultValue = 0 + }, - new IniKey { - Name = "IgnoreNonPaths", - Comments = ["Do not log any strings that are not proper filepaths"], - DefaultValue = 1 - } - ] - }, + new IniKey { + Name = "IgnoreNonPaths", + Comments = ["Do not log any strings that are not proper filepaths"], + DefaultValue = 1 + } + ] + }, - // Debug + // Debug - new IniSection { - Name = "Debug", - Comments = [], - Keys = [ - new IniKey { - Name = "ConsoleEnabled", - Comments = ["Enable the debugging console"], - DefaultValue = 0 - }, - new IniKey - { - Name = "ReloadingEnabled", - Comments = new List { "Enable reloading mod files by pressing CTRL+ALT+R" }, - DefaultValue = 0, - }, - new IniKey - { - Name = "V5FSArcadeSupport", - Comments = new List { "Enable modding Virtua Fighter 5 Final Showdown on Yakuza games who have it on their arcade." }, - DefaultValue = 0, - }, - ] - } - ]; - } + new IniSection { + Name = "Debug", + Comments = [], + Keys = [ + new IniKey { + Name = "ConsoleEnabled", + Comments = ["Enable the debugging console"], + DefaultValue = 0 + }, + new IniKey + { + Name = "ReloadingEnabled", + Comments = ["Enable reloading mod files by pressing CTRL+ALT+R"], + DefaultValue = 0, + }, + new IniKey + { + Name = "V5FSArcadeSupport", + Comments = ["Enable modding Virtua Fighter 5 Final Showdown on Yakuza games who have it on their arcade."], + DefaultValue = 0, + }, + ] + } + ]; } -} +} \ No newline at end of file diff --git a/ShinRyuModManager-CE/UserInterface/Assets/changelog.md b/ShinRyuModManager-CE/UserInterface/Assets/changelog.md index 8728bec..77a7ae2 100644 --- a/ShinRyuModManager-CE/UserInterface/Assets/changelog.md +++ b/ShinRyuModManager-CE/UserInterface/Assets/changelog.md @@ -1,3 +1,16 @@ +> ### **%{color:orange} Version 1.1.10 %** ### +* Finished overhaul of logging +* Moved SRMM logs to their own folder +* Allow logs to rotate +* Removed legacy logging system + +--- + +> ### **%{color:orange} Version 1.1.9 %** ### +* Made stream install search more robust + +--- + > ### **%{color:orange} Version 1.1.8 %** ### * Initial pass for allowing internationalization * Fixed minor style issues diff --git a/ShinRyuModManager-CE/UserInterface/ViewModels/MainWindowViewModel.cs b/ShinRyuModManager-CE/UserInterface/ViewModels/MainWindowViewModel.cs index 2d6ed83..e3d1d5c 100644 --- a/ShinRyuModManager-CE/UserInterface/ViewModels/MainWindowViewModel.cs +++ b/ShinRyuModManager-CE/UserInterface/ViewModels/MainWindowViewModel.cs @@ -18,7 +18,6 @@ public partial class MainWindowViewModel : ViewModelBase { public MainWindowViewModel() { Initialize(); - LoadModList(); } public void SelectMod(ModMeta mod) { diff --git a/ShinRyuModManager-CE/UserInterface/Views/MainWindow.axaml.cs b/ShinRyuModManager-CE/UserInterface/Views/MainWindow.axaml.cs index a3ceaa3..0fff1a1 100644 --- a/ShinRyuModManager-CE/UserInterface/Views/MainWindow.axaml.cs +++ b/ShinRyuModManager-CE/UserInterface/Views/MainWindow.axaml.cs @@ -6,6 +6,7 @@ using Avalonia.Platform.Storage; using Avalonia.Threading; using Serilog; +using Serilog.Events; using ShinRyuModManager.Helpers; using ShinRyuModManager.ModLoadOrder.Mods; using ShinRyuModManager.UserInterface.ViewModels; @@ -71,7 +72,7 @@ private async Task RunPreInitAsync() { _ = await MessageBoxWindow.Show(this, "Warning", "External mods folder detected. Please run Shin Ryu Mod Manager in CLI mode (use --cli parameter) and use the external mod manager instead."); } - if (ConsoleOutput.ShowWarnings) { + if (Program.LogLevel <= LogEventLevel.Warning) { if (Program.MissingDll()) { _ = await MessageBoxWindow.Show(this, "Warning", $"{Constants.DINPUT8DLL} is missing from this directory. Mods will NOT be applied without this file."); } diff --git a/Utils/ConsoleOutput.cs b/Utils/ConsoleOutput.cs deleted file mode 100644 index 6fa7327..0000000 --- a/Utils/ConsoleOutput.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Utils { - public class ConsoleOutput { - public static bool Verbose = false; - public static bool ShowWarnings = true; - - private static readonly List PrintQueue = []; - - private readonly int _id; - private readonly int _indent; - - public ConsoleOutput() { - _id = PrintQueue.Count; - _indent = 0; - PrintQueue.Add(""); - } - - public ConsoleOutput(int indent) : this() { - _indent = indent; - } - - public void Write(string text) { - PrintQueue[_id] += new string(' ', _indent) + text; - } - - public void WriteLine(string text = "") { - Write(text + "\n"); - } - - public void WriteIfVerbose(string text) { - if (Verbose) { - PrintQueue[_id] += new string(' ', _indent) + text; - } - } - - public void WriteLineIfVerbose(string text = "") { - WriteIfVerbose(text + "\n"); - } - - public void Flush() { - Console.WriteLine(PrintQueue[_id]); - PrintQueue[_id] = ""; - } - - public static void Clear() { - PrintQueue.Clear(); - } - } -}