From 4b81e8bce007a2b6fb6484cdb668fc923bab587b Mon Sep 17 00:00:00 2001 From: Sour Date: Sun, 17 Dec 2023 12:39:53 +0900 Subject: [PATCH] UI: Display error messages when command line arguments/files are invalid/not found --- UI/Config/ConfigAttributes.cs | 6 ++-- UI/Config/ConfigManager.cs | 47 ++++++++++++++++---------- UI/Config/NesConfig.cs | 2 ++ UI/Config/SmsConfig.cs | 4 +++ UI/Config/SnesConfig.cs | 1 + UI/Localization/resources.en.xml | 1 + UI/Utilities/CommandLineHelper.cs | 28 ++++++++++----- UI/Utilities/DisplayMessageHelper.cs | 44 ++++++++++++++++++++++++ UI/Utilities/LoadRomHelper.cs | 3 ++ UI/Utilities/MouseManager.cs | 2 +- UI/Utilities/ShortcutHandler.cs | 12 ++++--- UI/Windows/CommandLineHelpWindow.axaml | 2 +- UI/Windows/MainWindow.axaml.cs | 3 +- 13 files changed, 120 insertions(+), 35 deletions(-) create mode 100644 UI/Utilities/DisplayMessageHelper.cs diff --git a/UI/Config/ConfigAttributes.cs b/UI/Config/ConfigAttributes.cs index d6deaf69b..059b1ac30 100644 --- a/UI/Config/ConfigAttributes.cs +++ b/UI/Config/ConfigAttributes.cs @@ -20,11 +20,11 @@ public MinMaxAttribute(object min, object max) public class ValidValuesAttribute : Attribute { - public uint[] ValidValues { get; set; } + public Enum[] ValidValues { get; set; } - public ValidValuesAttribute(params uint[] validValues) + public ValidValuesAttribute(params object[] validValues) { - this.ValidValues = validValues; + this.ValidValues = validValues.Cast().ToArray(); } } } diff --git a/UI/Config/ConfigManager.cs b/UI/Config/ConfigManager.cs index 889c17383..56c1ff150 100644 --- a/UI/Config/ConfigManager.cs +++ b/UI/Config/ConfigManager.cs @@ -74,12 +74,12 @@ public static void LoadConfig() public static MesenTheme ActiveTheme { get; private set; } - private static void ApplySetting(object instance, PropertyInfo property, string value) + private static bool ApplySetting(object instance, PropertyInfo property, string value) { Type t = property.PropertyType; try { if(!property.CanWrite) { - return; + return false; } if(t == typeof(int) || t == typeof(uint) || t == typeof(double)) { @@ -88,26 +88,24 @@ private static void ApplySetting(object instance, PropertyInfo property, string if(int.TryParse(value, out int result)) { if(result >= (int)minMaxAttribute.Min && result <= (int)minMaxAttribute.Max) { property.SetValue(instance, result); + } else { + return false; } } } else if(t == typeof(uint)) { if(uint.TryParse(value, out uint result)) { if(result >= (uint)(int)minMaxAttribute.Min && result <= (uint)(int)minMaxAttribute.Max) { property.SetValue(instance, result); + } else { + return false; } } } else if(t == typeof(double)) { if(double.TryParse(value, out double result)) { if(result >= (double)minMaxAttribute.Min && result <= (double)minMaxAttribute.Max) { property.SetValue(instance, result); - } - } - } - } else { - if(property.GetCustomAttribute() is ValidValuesAttribute validValuesAttribute) { - if(uint.TryParse(value, out uint result)) { - if(validValuesAttribute.ValidValues.Contains(result)) { - property.SetValue(instance, result); + } else { + return false; } } } @@ -115,18 +113,31 @@ private static void ApplySetting(object instance, PropertyInfo property, string } else if(t == typeof(bool)) { if(bool.TryParse(value, out bool boolValue)) { property.SetValue(instance, boolValue); + } else { + return false; } } else if(t.IsEnum) { - int indexOf = Enum.GetNames(t).Select((enumValue) => enumValue.ToLower()).ToList().IndexOf(value.ToLower()); - if(indexOf >= 0) { - property.SetValue(instance, indexOf); + if(Enum.TryParse(t, value, true, out object? enumValue)) { + if(property.GetCustomAttribute() is ValidValuesAttribute validValuesAttribute) { + if(validValuesAttribute.ValidValues.Contains(enumValue)) { + property.SetValue(instance, enumValue); + } else { + return false; + } + } else { + property.SetValue(instance, enumValue); + } + } else { + return false; } } } catch { + return false; } + return true; } - public static void ProcessSwitch(string switchArg) + public static bool ProcessSwitch(string switchArg) { Regex regex = new Regex("([a-z0-9_A-Z.]+)=([a-z0-9_A-Z.\\-]+)"); Match match = regex.Match(switchArg); @@ -140,20 +151,22 @@ public static void ProcessSwitch(string switchArg) property = cfg.GetType().GetProperty(switchPath[i], BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if(property == null) { //Invalid switch name - return; + return false; } if(i < switchPath.Length - 1) { cfg = property.GetValue(cfg); if(cfg == null) { //Invalid - return; + return false; } } else { - ApplySetting(cfg, property, switchValue); + return ApplySetting(cfg, property, switchValue); } } } + + return false; } public static void ResetHomeFolder() diff --git a/UI/Config/NesConfig.cs b/UI/Config/NesConfig.cs index 6f1ebb9bf..9c3c5dd9c 100644 --- a/UI/Config/NesConfig.cs +++ b/UI/Config/NesConfig.cs @@ -33,7 +33,9 @@ public class NesConfig : BaseConfig [Reactive] public bool AutoConfigureInput { get; set; } = true; //General + [ValidValues(ConsoleRegion.Auto, ConsoleRegion.Ntsc, ConsoleRegion.Pal, ConsoleRegion.Dendy)] [Reactive] public ConsoleRegion Region { get; set; } = ConsoleRegion.Auto; + [Reactive] public bool EnableHdPacks { get; set; } = true; [Reactive] public bool DisableGameDatabase { get; set; } = false; [Reactive] public bool FdsAutoLoadDisk { get; set; } = true; diff --git a/UI/Config/SmsConfig.cs b/UI/Config/SmsConfig.cs index 34bbda818..2f951f615 100644 --- a/UI/Config/SmsConfig.cs +++ b/UI/Config/SmsConfig.cs @@ -16,8 +16,12 @@ public class SmsConfig : BaseConfig [Reactive] public SmsControllerConfig Port1 { get; set; } = new(); [Reactive] public SmsControllerConfig Port2 { get; set; } = new(); + [ValidValues(ConsoleRegion.Auto, ConsoleRegion.Ntsc, ConsoleRegion.Pal)] [Reactive] public ConsoleRegion Region { get; set; } = ConsoleRegion.Auto; + + [ValidValues(ConsoleRegion.Auto, ConsoleRegion.Ntsc, ConsoleRegion.NtscJapan, ConsoleRegion.Pal)] [Reactive] public ConsoleRegion GameGearRegion { get; set; } = ConsoleRegion.Auto; + [Reactive] public RamState RamPowerOnState { get; set; } = RamState.Random; [Reactive] public SmsRevision Revision { get; set; } = SmsRevision.Compatibility; diff --git a/UI/Config/SnesConfig.cs b/UI/Config/SnesConfig.cs index ca37218dc..0280dc186 100644 --- a/UI/Config/SnesConfig.cs +++ b/UI/Config/SnesConfig.cs @@ -25,6 +25,7 @@ public class SnesConfig : BaseConfig [Reactive] public SnesControllerConfig Port2C { get; set; } = new SnesControllerConfig(); [Reactive] public SnesControllerConfig Port2D { get; set; } = new SnesControllerConfig(); + [ValidValues(ConsoleRegion.Auto, ConsoleRegion.Ntsc, ConsoleRegion.Pal)] [Reactive] public ConsoleRegion Region { get; set; } = ConsoleRegion.Auto; //Video diff --git a/UI/Localization/resources.en.xml b/UI/Localization/resources.en.xml index a0969cb99..e9d3cf444 100644 --- a/UI/Localization/resources.en.xml +++ b/UI/Localization/resources.en.xml @@ -1586,6 +1586,7 @@ E An error has occurred while trying to check for updates. Check your internet connection and try again. Error details: {0} Automatic updates are not enabled on this build - please download the latest version of the code and recompile Mesen to get the latest updates. File not found: {0} + Invalid argument: {0} You are running the latest version of Mesen. Patch and reset the current game? diff --git a/UI/Utilities/CommandLineHelper.cs b/UI/Utilities/CommandLineHelper.cs index 8f3cf21f8..cbf734a85 100644 --- a/UI/Utilities/CommandLineHelper.cs +++ b/UI/Utilities/CommandLineHelper.cs @@ -3,6 +3,7 @@ using Mesen.Debugger.ViewModels; using Mesen.Debugger.Windows; using Mesen.Interop; +using Mesen.Localization; using Mesen.Utilities; using Mesen.ViewModels; using Mesen.Windows; @@ -31,6 +32,8 @@ public class CommandLineHelper public List LuaScriptsToLoad { get; private set; } = new(); public List FilesToLoad { get; private set; } = new(); + private List _errorMessages = new(); + public CommandLineHelper(string[] args, bool forStartup) { ProcessCommandLineArgs(args, forStartup); @@ -51,7 +54,7 @@ private void ProcessCommandLineArgs(string[] args, bool forStartup) case ".lua": LuaScriptsToLoad.Add(absPath); break; default: FilesToLoad.Add(absPath); break; } - } else { + } else if(arg.StartsWith("-") || arg.StartsWith("/")) { string switchArg = ConvertArg(arg).ToLowerInvariant(); switch(switchArg) { case "novideo": NoVideo = true; break; @@ -88,10 +91,14 @@ private void ProcessCommandLineArgs(string[] args, bool forStartup) TestRunnerTimeout = timeout; } } else { - ConfigManager.ProcessSwitch(switchArg); + if(!ConfigManager.ProcessSwitch(switchArg)) { + _errorMessages.Add(ResourceHelper.GetMessage("InvalidArgument", arg)); + } } break; } + } else { + _errorMessages.Add(ResourceHelper.GetMessage("FileNotFound", arg)); } } } @@ -154,6 +161,10 @@ public void LoadFiles() foreach(string file in FilesToLoad) { LoadRomHelper.LoadFile(file); } + + foreach(string msg in _errorMessages) { + DisplayMessageHelper.DisplayMessage("Error", msg); + } } public static Dictionary GetAvailableSwitches() @@ -175,6 +186,7 @@ public static Dictionary GetAvailableSwitches() result["Snes"] = GetSwichesForObject("snes.", typeof(SnesConfig)); result["Game Boy"] = GetSwichesForObject("gameBoy.", typeof(GameboyConfig)); result["PC Engine"] = GetSwichesForObject("pcEngine.", typeof(PcEngineConfig)); + result["SMS"] = GetSwichesForObject("sms.", typeof(SmsConfig)); return result; } @@ -193,17 +205,17 @@ private static string GetSwichesForObject(string prefix, Type type) MinMaxAttribute? minMaxAttribute = info.GetCustomAttribute(typeof(MinMaxAttribute)) as MinMaxAttribute; if(minMaxAttribute != null) { sb.AppendLine("--" + prefix + name + "=[" + minMaxAttribute.Min.ToString() + " - " + minMaxAttribute.Max.ToString() + "]"); - } else { - ValidValuesAttribute? validValuesAttribute = info.GetCustomAttribute(typeof(ValidValuesAttribute)) as ValidValuesAttribute; - if(validValuesAttribute != null) { - sb.AppendLine("--" + prefix + name + "=[" + string.Join(" | ", validValuesAttribute.ValidValues) + "]"); - } } } else if(info.PropertyType == typeof(bool)) { sb.AppendLine("--" + prefix + name + "=[true | false]"); } else if(info.PropertyType.IsEnum) { if(info.PropertyType != typeof(ControllerType)) { - sb.AppendLine("--" + prefix + name + "=[" + string.Join(" | ", Enum.GetNames(info.PropertyType)) + "]"); + ValidValuesAttribute? validValuesAttribute = info.GetCustomAttribute(typeof(ValidValuesAttribute)) as ValidValuesAttribute; + if(validValuesAttribute != null) { + sb.AppendLine("--" + prefix + name + "=[" + string.Join(" | ", validValuesAttribute.ValidValues.Select(v => Enum.GetName(info.PropertyType, v))) + "]"); + } else { + sb.AppendLine("--" + prefix + name + "=[" + string.Join(" | ", Enum.GetNames(info.PropertyType)) + "]"); + } } } else if(info.PropertyType.IsClass && !info.PropertyType.IsGenericType) { string content = GetSwichesForObject(prefix + name + ".", info.PropertyType); diff --git a/UI/Utilities/DisplayMessageHelper.cs b/UI/Utilities/DisplayMessageHelper.cs new file mode 100644 index 000000000..afcced49a --- /dev/null +++ b/UI/Utilities/DisplayMessageHelper.cs @@ -0,0 +1,44 @@ +using Avalonia.Threading; +using Mesen.Config; +using Mesen.Interop; +using Mesen.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Mesen.Utilities; + +public class DisplayMessageHelper +{ + private static int _taskId = 0; + + public static void DisplayMessage(string title, string message, string? param1 = null) + { + if(EmuApi.IsRunning() || ConfigManager.Config.Preferences.GameSelectionScreenMode == GameSelectionMode.Disabled) { + EmuApi.DisplayMessage(title, message, param1); + } else { + //Temporarily hide selection screen to allow displaying messages + MainWindowViewModel.Instance.RecentGames.Visible = false; + + EmuApi.DisplayMessage(title, message, param1); + + //Prevent multiple calls from causing the game selection screen from appearing too quickly + int counter = Interlocked.Increment(ref _taskId); + + Task.Run(async () => { + await Task.Delay(3100); + + //Show game selection screen after ~3 seconds + //This allows the message to be visible to the user + Dispatcher.UIThread.Post(() => { + if(_taskId == counter && !EmuApi.IsRunning()) { + MainWindowViewModel.Instance.RecentGames.Visible = true; + } + }); + }); + } + } +} diff --git a/UI/Utilities/LoadRomHelper.cs b/UI/Utilities/LoadRomHelper.cs index e78182242..b20d8958f 100644 --- a/UI/Utilities/LoadRomHelper.cs +++ b/UI/Utilities/LoadRomHelper.cs @@ -2,6 +2,7 @@ using Avalonia.Threading; using Mesen.Config; using Mesen.Interop; +using Mesen.Localization; using Mesen.ViewModels; using Mesen.Windows; using System; @@ -145,6 +146,8 @@ public static void LoadFile(string filename) } else { LoadRom(filename); } + } else { + DisplayMessageHelper.DisplayMessage("Error", ResourceHelper.GetMessage("FileNotFound", filename)); } } } diff --git a/UI/Utilities/MouseManager.cs b/UI/Utilities/MouseManager.cs index e9a66d0ca..917134ddc 100644 --- a/UI/Utilities/MouseManager.cs +++ b/UI/Utilities/MouseManager.cs @@ -233,7 +233,7 @@ private CursorIcon MouseIcon private void CaptureMouse() { if(!_mouseCaptured && AllowMouseCapture) { - EmuApi.DisplayMessage("Input", ResourceHelper.GetMessage("MouseModeEnabled")); + DisplayMessageHelper.DisplayMessage("Input", ResourceHelper.GetMessage("MouseModeEnabled")); _mouseCaptured = true; PixelPoint topLeft = _renderer.PointToScreen(new Point()); diff --git a/UI/Utilities/ShortcutHandler.cs b/UI/Utilities/ShortcutHandler.cs index 607badd34..8856595a5 100644 --- a/UI/Utilities/ShortcutHandler.cs +++ b/UI/Utilities/ShortcutHandler.cs @@ -301,10 +301,14 @@ enum VideoLayer private void ToggleVideoLayer(VideoLayer layer) { + if(!EmuApi.IsRunning()) { + return; + } + (Func? get, Action? set) = GetFlagSetterGetter(layer); if(get != null && set != null) { set(!get()); - EmuApi.DisplayMessage("Debug", ResourceHelper.GetMessage(get() ? "VideoLayerDisabled" : "VideoLayerEnabled", ResourceHelper.GetEnumText(layer))); + DisplayMessageHelper.DisplayMessage("Debug", ResourceHelper.GetMessage(get() ? "VideoLayerDisabled" : "VideoLayerEnabled", ResourceHelper.GetEnumText(layer))); ConfigManager.Config.Snes.ApplyConfig(); ConfigManager.Config.Nes.ApplyConfig(); ConfigManager.Config.Gameboy.ApplyConfig(); @@ -336,7 +340,7 @@ private void EnableAllLayers() ConfigManager.Config.PcEngine.ApplyConfig(); ConfigManager.Config.Sms.ApplyConfig(); - EmuApi.DisplayMessage("Debug", ResourceHelper.GetMessage("AllLayersEnabled")); + DisplayMessageHelper.DisplayMessage("Debug", ResourceHelper.GetMessage("AllLayersEnabled")); } private void SetEmulationSpeed(uint emulationSpeed) @@ -345,9 +349,9 @@ private void SetEmulationSpeed(uint emulationSpeed) ConfigManager.Config.Emulation.ApplyConfig(); if(emulationSpeed == 0) { - EmuApi.DisplayMessage("EmulationSpeed", "EmulationMaximumSpeed"); + DisplayMessageHelper.DisplayMessage("EmulationSpeed", "EmulationMaximumSpeed"); } else { - EmuApi.DisplayMessage("EmulationSpeed", "EmulationSpeedPercent", emulationSpeed.ToString()); + DisplayMessageHelper.DisplayMessage("EmulationSpeed", "EmulationSpeedPercent", emulationSpeed.ToString()); } } diff --git a/UI/Windows/CommandLineHelpWindow.axaml b/UI/Windows/CommandLineHelpWindow.axaml index 54cc00e88..2d64dc9ba 100644 --- a/UI/Windows/CommandLineHelpWindow.axaml +++ b/UI/Windows/CommandLineHelpWindow.axaml @@ -10,7 +10,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="Mesen.Windows.CommandLineHelpWindow" - Width="650" Height="400" + Width="700" Height="400" Title="{l:Translate wndTitle}" CanResize="False" Name="root" diff --git a/UI/Windows/MainWindow.axaml.cs b/UI/Windows/MainWindow.axaml.cs index 2e60bda10..d7866c34a 100644 --- a/UI/Windows/MainWindow.axaml.cs +++ b/UI/Windows/MainWindow.axaml.cs @@ -21,6 +21,7 @@ using Avalonia.Input.Platform; using System.Collections.Generic; using Mesen.Controls; +using Mesen.Localization; namespace Mesen.Windows { @@ -176,7 +177,7 @@ private void OnDrop(object? sender, DragEventArgs e) LoadRomHelper.LoadFile(filename); Activate(); } else { - EmuApi.DisplayMessage("Error", "File not found: " + filename); + DisplayMessageHelper.DisplayMessage("Error", ResourceHelper.GetMessage("FileNotFound", filename)); } } }