diff --git a/Ryujinx.Common/System/ForceDedicatedGpu.cs b/Ryujinx.Common/System/ForceDedicatedGpu.cs new file mode 100644 index 00000000..60272f1a --- /dev/null +++ b/Ryujinx.Common/System/ForceDedicatedGpu.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.System +{ + public static class ForceDedicatedGpu + { + public static void Nvidia() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // NOTE: If the DLL exists, we can load it to force the usage of the dedicated Nvidia Gpu. + NativeLibrary.TryLoad("nvapi64.dll", out _); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs b/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs index fccda247..69ce145b 100644 --- a/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs +++ b/Ryujinx.Common/System/WindowsMultimediaTimerResolution.cs @@ -17,14 +17,14 @@ namespace Ryujinx.Common.System public uint wPeriodMax; }; - [DllImport("winmm.dll", SetLastError = true)] - private static extern uint timeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); + [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] + private static extern uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); - [DllImport("winmm.dll")] - private static extern uint timeBeginPeriod(uint uMilliseconds); + [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + private static extern uint TimeBeginPeriod(uint uMilliseconds); - [DllImport("winmm.dll")] - private static extern uint timeEndPeriod(uint uMilliseconds); + [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] + private static extern uint TimeEndPeriod(uint uMilliseconds); private uint _targetResolutionInMilliseconds; private bool _isActive; @@ -45,7 +45,7 @@ namespace Ryujinx.Common.System { TimeCaps timeCaps = default; - uint result = timeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); + uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); if (result != 0) { @@ -66,7 +66,7 @@ namespace Ryujinx.Common.System private void Activate() { - uint result = timeBeginPeriod(_targetResolutionInMilliseconds); + uint result = TimeBeginPeriod(_targetResolutionInMilliseconds); if (result != 0) { @@ -82,7 +82,7 @@ namespace Ryujinx.Common.System { if (_isActive) { - uint result = timeEndPeriod(_targetResolutionInMilliseconds); + uint result = TimeEndPeriod(_targetResolutionInMilliseconds); if (result != 0) { @@ -98,6 +98,7 @@ namespace Ryujinx.Common.System public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) @@ -108,4 +109,4 @@ namespace Ryujinx.Common.System } } } -} +} \ No newline at end of file diff --git a/Ryujinx/Configuration/DiscordIntegrationModule.cs b/Ryujinx/Modules/DiscordIntegrationModule.cs similarity index 97% rename from Ryujinx/Configuration/DiscordIntegrationModule.cs rename to Ryujinx/Modules/DiscordIntegrationModule.cs index 378e9c3f..3a91cf53 100644 --- a/Ryujinx/Configuration/DiscordIntegrationModule.cs +++ b/Ryujinx/Modules/DiscordIntegrationModule.cs @@ -1,15 +1,16 @@ using DiscordRPC; using Ryujinx.Common; +using Ryujinx.Configuration; using System; using System.Linq; -namespace Ryujinx.Configuration +namespace Ryujinx.Modules { static class DiscordIntegrationModule { private static DiscordRpcClient _discordClient; - private static string LargeDescription = "Ryujinx is a Nintendo Switch emulator."; + private const string LargeDescription = "Ryujinx is a Nintendo Switch emulator."; public static RichPresence DiscordPresence { get; private set; } @@ -17,7 +18,7 @@ namespace Ryujinx.Configuration { DiscordPresence = new RichPresence { - Assets = new Assets + Assets = new Assets { LargeImageKey = "ryujinx", LargeImageText = LargeDescription @@ -169,4 +170,4 @@ namespace Ryujinx.Configuration "051337133769a000", // RGB-Seizure }; } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/Client.cs b/Ryujinx/Modules/Motion/Client.cs similarity index 99% rename from Ryujinx/Motion/Client.cs rename to Ryujinx/Modules/Motion/Client.cs index 8c5ad20d..3f18fb7f 100644 --- a/Ryujinx/Motion/Client.cs +++ b/Ryujinx/Modules/Motion/Client.cs @@ -11,7 +11,7 @@ using System.Net.Sockets; using System.Numerics; using System.Threading.Tasks; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { public class Client : IDisposable { @@ -396,7 +396,7 @@ namespace Ryujinx.Motion } } - public unsafe void RequestData(int clientId, int slot) + public unsafe void RequestData(int clientId, int slot) { if (!_active) { @@ -439,7 +439,7 @@ namespace Ryujinx.Motion { Header header = new Header() { - ID = (uint)clientId, + Id = (uint)clientId, MagicString = Magic, Version = Version, Length = 0, @@ -456,4 +456,4 @@ namespace Ryujinx.Motion CloseClients(); } } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/MotionDevice.cs b/Ryujinx/Modules/Motion/MotionDevice.cs similarity index 93% rename from Ryujinx/Motion/MotionDevice.cs rename to Ryujinx/Modules/Motion/MotionDevice.cs index 2e2050bc..4fb9b422 100644 --- a/Ryujinx/Motion/MotionDevice.cs +++ b/Ryujinx/Modules/Motion/MotionDevice.cs @@ -3,7 +3,7 @@ using Ryujinx.Configuration; using System; using System.Numerics; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { public class MotionDevice { @@ -12,7 +12,7 @@ namespace Ryujinx.Motion public Vector3 Rotation { get; private set; } public float[] Orientation { get; private set; } - private Client _motionSource; + private readonly Client _motionSource; public MotionDevice(Client motionSource) { @@ -51,8 +51,8 @@ namespace Ryujinx.Motion } Gyroscope = Truncate(input.Gyroscrope * 0.0027f, 3); - Accelerometer = Truncate(input.Accelerometer, 3); - Rotation = Truncate(input.Rotation * 0.0027f, 3); + Accelerometer = Truncate(input.Accelerometer, 3); + Rotation = Truncate(input.Rotation * 0.0027f, 3); Matrix4x4 orientation = input.GetOrientation(); @@ -78,4 +78,4 @@ namespace Ryujinx.Motion return value; } } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/MotionInput.cs b/Ryujinx/Modules/Motion/MotionInput.cs similarity index 98% rename from Ryujinx/Motion/MotionInput.cs rename to Ryujinx/Modules/Motion/MotionInput.cs index f767d8cc..3e4d9768 100644 --- a/Ryujinx/Motion/MotionInput.cs +++ b/Ryujinx/Modules/Motion/MotionInput.cs @@ -1,7 +1,7 @@ using System; using System.Numerics; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { public class MotionInput { @@ -82,4 +82,4 @@ namespace Ryujinx.Motion return degree * (MathF.PI / 180); } } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/MotionSensorFilter.cs b/Ryujinx/Modules/Motion/MotionSensorFilter.cs similarity index 97% rename from Ryujinx/Motion/MotionSensorFilter.cs rename to Ryujinx/Modules/Motion/MotionSensorFilter.cs index 5173a191..b83e61a8 100644 --- a/Ryujinx/Motion/MotionSensorFilter.cs +++ b/Ryujinx/Modules/Motion/MotionSensorFilter.cs @@ -1,6 +1,6 @@ using System.Numerics; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { // MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm. // See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/ @@ -43,9 +43,7 @@ namespace Ryujinx.Motion /// /// Sample period. /// - public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) - { - } + public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) { } /// /// Initializes a new instance of the class. @@ -56,9 +54,7 @@ namespace Ryujinx.Motion /// /// Algorithm proportional gain. /// - public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) - { - } + public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) { } /// /// Initializes a new instance of the class. @@ -163,4 +159,4 @@ namespace Ryujinx.Motion Quaternion = Quaternion.Identity; } } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/Protocol/ControllerData.cs b/Ryujinx/Modules/Motion/Protocol/ControllerData.cs similarity index 64% rename from Ryujinx/Motion/Protocol/ControllerData.cs rename to Ryujinx/Modules/Motion/Protocol/ControllerData.cs index 4b4919a1..30972b86 100644 --- a/Ryujinx/Motion/Protocol/ControllerData.cs +++ b/Ryujinx/Modules/Motion/Protocol/ControllerData.cs @@ -1,13 +1,13 @@ using System.Runtime.InteropServices; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ControllerDataRequest { - public MessageType Type; + public MessageType Type; public SubscriberType SubscriberType; - public byte Slot; + public byte Slot; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] MacAddress; @@ -17,21 +17,22 @@ namespace Ryujinx.Motion public struct ControllerDataResponse { public SharedResponse Shared; - public byte Connected; - public uint PacketID; - public byte ExtraButtons; - public byte MainButtons; - public ushort PSExtraInput; - public ushort LeftStickXY; - public ushort RightStickXY; - public uint DPadAnalog; - public ulong MainButtonsAnalog; + public byte Connected; + public uint PacketId; + public byte ExtraButtons; + public byte MainButtons; + public ushort PSExtraInput; + public ushort LeftStickXY; + public ushort RightStickXY; + public uint DPadAnalog; + public ulong MainButtonsAnalog; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] Touch1; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] Touch2; + public ulong MotionTimestamp; public float AccelerometerX; public float AccelerometerY; @@ -43,8 +44,8 @@ namespace Ryujinx.Motion enum SubscriberType : byte { - All = 0, + All, Slot, Mac } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/Protocol/ControllerInfo.cs b/Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs similarity index 71% rename from Ryujinx/Motion/Protocol/ControllerInfo.cs rename to Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs index 34177ff8..63e2db5b 100644 --- a/Ryujinx/Motion/Protocol/ControllerInfo.cs +++ b/Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs @@ -1,21 +1,21 @@ using System.Runtime.InteropServices; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ControllerInfoResponse { - public SharedResponse Shared; - private byte _zero; + public SharedResponse Shared; + private byte _zero; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ControllerInfoRequest { public MessageType Type; - public int PortsCount; + public int PortsCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] PortIndices; } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/Protocol/Header.cs b/Ryujinx/Modules/Motion/Protocol/Header.cs similarity index 61% rename from Ryujinx/Motion/Protocol/Header.cs rename to Ryujinx/Modules/Motion/Protocol/Header.cs index 1f6ea705..39d8b531 100644 --- a/Ryujinx/Motion/Protocol/Header.cs +++ b/Ryujinx/Modules/Motion/Protocol/Header.cs @@ -1,14 +1,14 @@ using System.Runtime.InteropServices; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Header { - public uint MagicString; + public uint MagicString; public ushort Version; public ushort Length; - public uint Crc32; - public uint ID; + public uint Crc32; + public uint Id; } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/Protocol/MessageType.cs b/Ryujinx/Modules/Motion/Protocol/MessageType.cs similarity index 73% rename from Ryujinx/Motion/Protocol/MessageType.cs rename to Ryujinx/Modules/Motion/Protocol/MessageType.cs index 507910dd..3cbb6579 100644 --- a/Ryujinx/Motion/Protocol/MessageType.cs +++ b/Ryujinx/Modules/Motion/Protocol/MessageType.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { public enum MessageType : uint { @@ -6,4 +6,4 @@ Info, Data } -} +} \ No newline at end of file diff --git a/Ryujinx/Motion/Protocol/SharedResponse.cs b/Ryujinx/Modules/Motion/Protocol/SharedResponse.cs similarity index 70% rename from Ryujinx/Motion/Protocol/SharedResponse.cs rename to Ryujinx/Modules/Motion/Protocol/SharedResponse.cs index 8f918ccb..e5b03722 100644 --- a/Ryujinx/Motion/Protocol/SharedResponse.cs +++ b/Ryujinx/Modules/Motion/Protocol/SharedResponse.cs @@ -1,45 +1,45 @@ using System.Runtime.InteropServices; -namespace Ryujinx.Motion +namespace Ryujinx.Modules.Motion { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SharedResponse { - public MessageType Type; - public byte Slot; - public SlotState State; + public MessageType Type; + public byte Slot; + public SlotState State; public DeviceModelType ModelType; - public ConnectionType ConnectionType; + public ConnectionType ConnectionType; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - public byte[] MacAddress; + public byte[] MacAddress; public BatteryStatus BatteryStatus; } public enum SlotState : byte { - Disconnected = 0, + Disconnected, Reserved, Connected } public enum DeviceModelType : byte { - None = 0, + None, PartialGyro, FullGyro } public enum ConnectionType : byte { - None = 0, + None, USB, Bluetooth } public enum BatteryStatus : byte { - NA = 0, + NA, Dying, Low, Medium, @@ -48,4 +48,4 @@ namespace Ryujinx.Motion Charging, Charged } -} +} \ No newline at end of file diff --git a/Ryujinx/Updater/UpdateDialog.cs b/Ryujinx/Modules/Updater/UpdateDialog.cs similarity index 69% rename from Ryujinx/Updater/UpdateDialog.cs rename to Ryujinx/Modules/Updater/UpdateDialog.cs index ed49ad4a..cb38383a 100644 --- a/Ryujinx/Updater/UpdateDialog.cs +++ b/Ryujinx/Modules/Updater/UpdateDialog.cs @@ -1,28 +1,29 @@ using Gdk; using Gtk; using Mono.Unix; +using Ryujinx.Ui; using System; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; -namespace Ryujinx.Ui +namespace Ryujinx.Modules { public class UpdateDialog : Gtk.Window { #pragma warning disable CS0649, IDE0044 - [Builder.Object] public Label MainText; - [Builder.Object] public Label SecondaryText; + [Builder.Object] public Label MainText; + [Builder.Object] public Label SecondaryText; [Builder.Object] public LevelBar ProgressBar; - [Builder.Object] public Button YesButton; - [Builder.Object] public Button NoButton; + [Builder.Object] public Button YesButton; + [Builder.Object] public Button NoButton; #pragma warning restore CS0649, IDE0044 private readonly MainWindow _mainWindow; - private readonly string _buildUrl; - private bool _restartQuery; + private readonly string _buildUrl; + private bool _restartQuery; - public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } + public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx..Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetObject("UpdateDialog").Handle) { @@ -36,17 +37,17 @@ namespace Ryujinx.Ui ProgressBar.Hide(); - YesButton.Clicked += YesButton_Pressed; - NoButton.Clicked += NoButton_Pressed; + YesButton.Clicked += YesButton_Clicked; + NoButton.Clicked += NoButton_Clicked; } - private void YesButton_Pressed(object sender, EventArgs args) + private void YesButton_Clicked(object sender, EventArgs args) { if (_restartQuery) { string ryuName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Ryujinx.exe" : "Ryujinx"; string ryuExe = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName); - string ryuArg = String.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray()); + string ryuArg = string.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray()); if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -60,7 +61,7 @@ namespace Ryujinx.Ui } else { - this.Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close; + Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close; _mainWindow.ExitMenuItem.Sensitive = false; YesButton.Hide(); @@ -74,7 +75,7 @@ namespace Ryujinx.Ui } } - private void NoButton_Pressed(object sender, EventArgs args) + private void NoButton_Clicked(object sender, EventArgs args) { Updater.Running = false; _mainWindow.Window.Functions = WMFunction.All; @@ -82,7 +83,7 @@ namespace Ryujinx.Ui _mainWindow.ExitMenuItem.Sensitive = true; _mainWindow.UpdateMenuItem.Sensitive = true; - this.Dispose(); + Dispose(); } } -} +} \ No newline at end of file diff --git a/Ryujinx/Updater/UpdateDialog.glade b/Ryujinx/Modules/Updater/UpdateDialog.glade similarity index 100% rename from Ryujinx/Updater/UpdateDialog.glade rename to Ryujinx/Modules/Updater/UpdateDialog.glade diff --git a/Ryujinx/Updater/Updater.cs b/Ryujinx/Modules/Updater/Updater.cs similarity index 97% rename from Ryujinx/Updater/Updater.cs rename to Ryujinx/Modules/Updater/Updater.cs index 42b97cc8..35d67adf 100644 --- a/Ryujinx/Updater/Updater.cs +++ b/Ryujinx/Modules/Updater/Updater.cs @@ -5,6 +5,7 @@ using ICSharpCode.SharpZipLib.Zip; using Newtonsoft.Json.Linq; using Ryujinx.Common.Logging; using Ryujinx.Ui; +using Ryujinx.Ui.Widgets; using System; using System.IO; using System.Net; @@ -13,7 +14,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -namespace Ryujinx +namespace Ryujinx.Modules { public static class Updater { @@ -84,7 +85,7 @@ namespace Ryujinx { if (showVersionUpToDate) { - GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", ""); + GtkDialog.CreateUpdaterInfoDialog("You are already using the most updated version of Ryujinx!", ""); } return; @@ -115,7 +116,7 @@ namespace Ryujinx { if (showVersionUpToDate) { - GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", ""); + GtkDialog.CreateUpdaterInfoDialog("You are already using the most updated version of Ryujinx!", ""); } Running = false; diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 12db52a3..29043bb8 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -3,10 +3,12 @@ using Gtk; using OpenTK; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.System; using Ryujinx.Common.SystemInfo; using Ryujinx.Configuration; +using Ryujinx.Modules; using Ryujinx.Ui; -using Ryujinx.Ui.Diagnostic; +using Ryujinx.Ui.Widgets; using System; using System.IO; using System.Reflection; @@ -22,10 +24,11 @@ namespace Ryujinx static void Main(string[] args) { - // Parse Arguments - string launchPath = null; - string baseDirPath = null; - bool startFullscreen = false; + // Parse Arguments. + string launchPathArg = null; + string baseDirPathArg = null; + bool startFullscreenArg = false; + for (int i = 0; i < args.Length; ++i) { string arg = args[i]; @@ -35,28 +38,28 @@ namespace Ryujinx if (i + 1 >= args.Length) { Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'"); + continue; } - baseDirPath = args[++i]; + baseDirPathArg = args[++i]; } else if (arg == "-f" || arg == "--fullscreen") { - startFullscreen = true; + startFullscreenArg = true; } - else if (launchPath == null) + else if (launchPathArg == null) { - launchPath = arg; + launchPathArg = arg; } } - // Delete backup files after updating + // Delete backup files after updating. Task.Run(Updater.CleanupUpdate); Toolkit.Init(new ToolkitOptions { - Backend = PlatformBackend.PreferNative, - EnableHighResolution = true + Backend = PlatformBackend.PreferNative }); Version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; @@ -66,27 +69,27 @@ namespace Ryujinx string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); - // Hook unhandled exception and process exit events - GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + // Hook unhandled exception and process exit events. + GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); - AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => ProgramExit(); + AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit(); - // Setup base data directory - AppDataManager.Initialize(baseDirPath); + // Setup base data directory. + AppDataManager.Initialize(baseDirPathArg); - // Initialize the configuration + // Initialize the configuration. ConfigurationState.Initialize(); - // Initialize the logger system + // Initialize the logger system. LoggerModule.Initialize(); - // Initialize Discord integration + // Initialize Discord integration. DiscordIntegrationModule.Initialize(); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); - string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json"); + string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json"); - // Now load the configuration as the other subsystems are now registered + // Now load the configuration as the other subsystems are now registered. if (File.Exists(localConfigurationPath)) { ConfigurationPath = localConfigurationPath; @@ -105,35 +108,42 @@ namespace Ryujinx } else { - // No configuration, we load the default values and save it on disk + // No configuration, we load the default values and save it on disk. ConfigurationPath = appDataConfigurationPath; ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.ToFileFormat().SaveConfig(appDataConfigurationPath); } - if (startFullscreen) + if (startFullscreenArg) { ConfigurationState.Instance.Ui.StartFullscreen.Value = true; } + // Logging system informations. PrintSystemInfo(); + // Initialize Gtk. Application.Init(); + // Check if keys exists. bool hasGlobalProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); bool hasAltProdKeys = !AppDataManager.IsCustomBasePath && File.Exists(Path.Combine(AppDataManager.KeysDirPathAlt, "prod.keys")); - if (!hasGlobalProdKeys && !hasAltProdKeys && !Migration.IsMigrationNeeded()) + if (!hasGlobalProdKeys && !hasAltProdKeys) { UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys); } + // Force dedicated GPU if we can. + ForceDedicatedGpu.Nvidia(); + + // Show the main window UI. MainWindow mainWindow = new MainWindow(); mainWindow.Show(); - if (launchPath != null) + if (launchPathArg != null) { - mainWindow.LoadApplication(launchPath); + mainWindow.LoadApplication(launchPathArg); } if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) @@ -147,7 +157,6 @@ namespace Ryujinx private static void PrintSystemInfo() { Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); - Logger.Notice.Print(LogClass.Application, $"Operating System: {SystemInfo.Instance.OsDescription}"); Logger.Notice.Print(LogClass.Application, $"CPU: {SystemInfo.Instance.CpuName}"); Logger.Notice.Print(LogClass.Application, $"Total RAM: {SystemInfo.Instance.RamSizeInMB}"); @@ -161,25 +170,30 @@ namespace Ryujinx } } - private static void ProcessUnhandledException(Exception e, bool isTerminating) + private static void ProcessUnhandledException(Exception ex, bool isTerminating) { Ptc.Close(); PtcProfiler.Stop(); - string message = $"Unhandled exception caught: {e}"; + string message = $"Unhandled exception caught: {ex}"; Logger.Error?.PrintMsg(LogClass.Application, message); - if (Logger.Error == null) Logger.Notice.PrintMsg(LogClass.Application, message); + if (Logger.Error == null) + { + Logger.Notice.PrintMsg(LogClass.Application, message); + } if (isTerminating) { - ProgramExit(); + Exit(); } } - private static void ProgramExit() + public static void Exit() { + DiscordIntegrationModule.Exit(); + Ptc.Dispose(); PtcProfiler.Dispose(); diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 444cee84..7f0d23e7 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -55,53 +55,51 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/Ryujinx/Ui/AboutWindow.cs b/Ryujinx/Ui/AboutWindow.cs deleted file mode 100644 index 23c0d9af..00000000 --- a/Ryujinx/Ui/AboutWindow.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Gtk; -using System; -using System.Reflection; - -using GUI = Gtk.Builder.ObjectAttribute; - -namespace Ryujinx.Ui -{ - public class AboutWindow : Window - { -#pragma warning disable CS0649 -#pragma warning disable IDE0044 - [GUI] Label _versionText; - [GUI] Image _ryujinxLogo; - [GUI] Image _patreonLogo; - [GUI] Image _gitHubLogo; - [GUI] Image _discordLogo; - [GUI] Image _twitterLogo; -#pragma warning restore CS0649 -#pragma warning restore IDE0044 - - public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { } - - private AboutWindow(Builder builder) : base(builder.GetObject("_aboutWin").Handle) - { - builder.Autoconnect(this); - - this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - _ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png" , 100, 100); - _patreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 ); - _gitHubLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 ); - _discordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 ); - _twitterLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.TwitterLogo.png", 30 , 30 ); - - _versionText.Text = Program.Version; - } - - //Events - private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args) - { - UrlHelper.OpenUrl("https://ryujinx.org"); - } - - private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args) - { - UrlHelper.OpenUrl("https://www.patreon.com/ryujinx"); - } - - private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args) - { - UrlHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx"); - } - - private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args) - { - UrlHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc"); - } - - private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args) - { - UrlHelper.OpenUrl("https://twitter.com/RyujinxEmu"); - } - - private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args) - { - UrlHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); - } - - private void CloseToggle_Activated(object sender, EventArgs args) - { - Dispose(); - } - } -} \ No newline at end of file diff --git a/Ryujinx/Ui/AboutWindow.glade b/Ryujinx/Ui/AboutWindow.glade deleted file mode 100644 index 8a27f372..00000000 --- a/Ryujinx/Ui/AboutWindow.glade +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - False - False - True - center - 800 - 350 - dialog - - - - - - False - vertical - - - False - - - False - False - 0 - - - - - True - False - - - True - False - 10 - 15 - 10 - 15 - vertical - - - True - False - start - vertical - - - True - False - - - - True - True - 0 - - - - - True - False - center - vertical - - - True - False - Ryujinx - center - - - - - - False - True - 0 - - - - - True - False - (REE-YOU-JI-NX) - center - - - False - True - 1 - - - - - True - False - - - - True - False - Click to open the Ryujinx website in your default browser - www.ryujinx.org - center - - - - - - - - False - True - 5 - 2 - - - - - True - True - 1 - - - - - False - True - 0 - - - - - True - False - Version x.x.x (Commit Number) - center - - - False - True - 2 - 1 - - - - - True - False - MIT License - center - - - False - True - 5 - 2 - - - - - True - False - Ryujinx is not affiliated with Nintendo, -or any of its partners, in any way - center - - - - - - False - True - 5 - 3 - - - - - False - False - 0 - - - - - True - False - 25 - - - True - False - Click to open the Ryujinx Patreon page in your default browser - - - - True - False - vertical - - - - True - True - 0 - - - - - True - False - Patreon - - - False - True - 1 - - - - - - - True - True - 0 - - - - - True - False - Click to open the Ryujinx GitHub page in your default browser - - - - True - False - vertical - - - - True - True - 0 - - - - - True - False - GitHub - - - False - True - 1 - - - - - - - True - True - 1 - - - - - True - False - Click to open an invite to the Ryujinx Discord server in your default browser - - - - True - False - vertical - - - - True - True - 0 - - - - - True - False - Discord - - - False - True - 1 - - - - - - - True - True - 2 - - - - - True - False - Click to open the Ryujinx Twitter page in your default browser - - - - True - False - vertical - - - - True - True - 0 - - - - - True - False - Twitter - - - False - True - 1 - - - - - - - True - True - 3 - - - - - False - False - end - 2 - - - - - True - False - 0 - - - - - True - False - 10 - 10 - - - False - True - 1 - - - - - True - False - 15 - 10 - 40 - 15 - vertical - - - True - False - start - About - - - - - - False - True - 0 - - - - - True - False - start - 10 - Ryujinx is an emulator for the Nintendo Switch. -Please support us on Patreon. -Get all the latest news on our Twitter or Discord. -Developers interested in contributing can find out more on our Discord. - - - False - True - 5 - 1 - - - - - True - False - start - Created By: - - - - - - False - True - 5 - 2 - - - - - True - False - 10 - vertical - - - True - True - in - - - True - False - - - True - False - top - - - True - False - start - gdkchan -LDj3SNuD -Ac_K -Thog - 0 - - - True - True - 0 - - - - - True - False - start - »jD« -emmaus -Thealexbarney -Andy A (BaronKiko) - 0 - - - True - True - 1 - - - - - - - - - True - True - 0 - - - - - True - False - start - - - - True - False - end - 5 - All Contributors... - - - - - - - - False - False - 2 - - - - - True - True - 3 - - - - - True - True - 2 - - - - - True - True - 1 - - - - - - diff --git a/Ryujinx/Ui/ApplicationAddedEventArgs.cs b/Ryujinx/Ui/App/ApplicationAddedEventArgs.cs similarity index 84% rename from Ryujinx/Ui/ApplicationAddedEventArgs.cs rename to Ryujinx/Ui/App/ApplicationAddedEventArgs.cs index 5e09389b..53577e67 100644 --- a/Ryujinx/Ui/ApplicationAddedEventArgs.cs +++ b/Ryujinx/Ui/App/ApplicationAddedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.App { public class ApplicationAddedEventArgs : EventArgs { diff --git a/Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs b/Ryujinx/Ui/App/ApplicationCountUpdatedEventArgs.cs similarity index 88% rename from Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs rename to Ryujinx/Ui/App/ApplicationCountUpdatedEventArgs.cs index 78e2e50c..510ee786 100644 --- a/Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs +++ b/Ryujinx/Ui/App/ApplicationCountUpdatedEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.App { public class ApplicationCountUpdatedEventArgs : EventArgs { diff --git a/Ryujinx/Ui/ApplicationData.cs b/Ryujinx/Ui/App/ApplicationData.cs similarity index 87% rename from Ryujinx/Ui/ApplicationData.cs rename to Ryujinx/Ui/App/ApplicationData.cs index 34784c6c..3940abe9 100644 --- a/Ryujinx/Ui/ApplicationData.cs +++ b/Ryujinx/Ui/App/ApplicationData.cs @@ -1,10 +1,9 @@ -using LibHac; -using LibHac.Common; +using LibHac.Common; using LibHac.Ns; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.App { - public struct ApplicationData + public class ApplicationData { public bool Favorite { get; set; } public byte[] Icon { get; set; } @@ -19,4 +18,4 @@ namespace Ryujinx.Ui public string Path { get; set; } public BlitStruct ControlHolder { get; set; } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/App/ApplicationLibrary.cs similarity index 90% rename from Ryujinx/Ui/ApplicationLibrary.cs rename to Ryujinx/Ui/App/ApplicationLibrary.cs index 353b686e..fb0e0664 100644 --- a/Ryujinx/Ui/ApplicationLibrary.cs +++ b/Ryujinx/Ui/App/ApplicationLibrary.cs @@ -11,6 +11,7 @@ using Ryujinx.Configuration.System; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.Loaders.Npdm; +using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -20,24 +21,45 @@ using System.Text.Json; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.App { public class ApplicationLibrary { - public static event EventHandler ApplicationAdded; - public static event EventHandler ApplicationCountUpdated; + public event EventHandler ApplicationAdded; + public event EventHandler ApplicationCountUpdated; - private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png"); - private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png"); - private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png"); - private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png"); - private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png"); + private readonly byte[] _nspIcon; + private readonly byte[] _xciIcon; + private readonly byte[] _ncaIcon; + private readonly byte[] _nroIcon; + private readonly byte[] _nsoIcon; - private static VirtualFileSystem _virtualFileSystem; - private static Language _desiredTitleLanguage; - private static bool _loadingError; + private VirtualFileSystem _virtualFileSystem; + private Language _desiredTitleLanguage; + private bool _loadingError; - public static IEnumerable GetFilesInDirectory(string directory) + public ApplicationLibrary(VirtualFileSystem virtualFileSystem) + { + _virtualFileSystem = virtualFileSystem; + + _nspIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NSP.png"); + _xciIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_XCI.png"); + _ncaIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NCA.png"); + _nroIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NRO.png"); + _nsoIcon = GetResourceBytes("Ryujinx.Ui.Resources.Icon_NSO.png"); + } + + private byte[] GetResourceBytes(string resourceName) + { + Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); + byte[] resourceByteArray = new byte[resourceStream.Length]; + + resourceStream.Read(resourceByteArray); + + return resourceByteArray; + } + + public IEnumerable GetFilesInDirectory(string directory) { Stack stack = new Stack(); @@ -46,7 +68,7 @@ namespace Ryujinx.Ui while (stack.Count > 0) { string dir = stack.Pop(); - string[] content = { }; + string[] content = Array.Empty(); try { @@ -84,19 +106,18 @@ namespace Ryujinx.Ui } } - public static void ReadControlData(IFileSystem controlFs, Span outProperty) + public void ReadControlData(IFileSystem controlFs, Span outProperty) { controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFile.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); } - public static void LoadApplications(List appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage) + public void LoadApplications(List appDirs, Language desiredTitleLanguage) { int numApplicationsFound = 0; int numApplicationsLoaded = 0; _loadingError = false; - _virtualFileSystem = virtualFileSystem; _desiredTitleLanguage = desiredTitleLanguage; // Builds the applications list with paths to found applications @@ -442,36 +463,26 @@ namespace Ryujinx.Ui } } - protected static void OnApplicationAdded(ApplicationAddedEventArgs e) + protected void OnApplicationAdded(ApplicationAddedEventArgs e) { ApplicationAdded?.Invoke(null, e); } - protected static void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e) + protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e) { ApplicationCountUpdated?.Invoke(null, e); } - private static byte[] GetResourceBytes(string resourceName) - { - Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); - byte[] resourceByteArray = new byte[resourceStream.Length]; - - resourceStream.Read(resourceByteArray); - - return resourceByteArray; - } - - private static void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId) + private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId) { (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0); // Return the ControlFS controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); - titleId = controlNca?.Header.TitleId.ToString("x16"); + titleId = controlNca?.Header.TitleId.ToString("x16"); } - internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null) + internal ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null) { string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui"); string metadataFile = Path.Combine(metadataFolder, "metadata.json"); @@ -482,12 +493,7 @@ namespace Ryujinx.Ui { Directory.CreateDirectory(metadataFolder); - appMetadata = new ApplicationMetadata - { - Favorite = false, - TimePlayed = 0, - LastPlayed = "Never" - }; + appMetadata = new ApplicationMetadata(); using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough)) { @@ -503,12 +509,7 @@ namespace Ryujinx.Ui { Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults."); - appMetadata = new ApplicationMetadata - { - Favorite = false, - TimePlayed = 0, - LastPlayed = "Never" - }; + appMetadata = new ApplicationMetadata(); } if (modifyFunction != null) @@ -524,7 +525,7 @@ namespace Ryujinx.Ui return appMetadata; } - private static string ConvertSecondsToReadableString(double seconds) + private string ConvertSecondsToReadableString(double seconds) { const int secondsPerMinute = 60; const int secondsPerHour = secondsPerMinute * 60; @@ -552,9 +553,9 @@ namespace Ryujinx.Ui return readableString; } - private static void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher) + private void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher) { - Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); + _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); if (controlData.Titles.Length > (int)desiredTitleLanguage) { @@ -611,7 +612,7 @@ namespace Ryujinx.Ui } } - private static bool IsUpdateApplied(string titleId, out string version) + private bool IsUpdateApplied(string titleId, out string version) { string updatePath = "(unknown)"; diff --git a/Ryujinx/Ui/App/ApplicationMetadata.cs b/Ryujinx/Ui/App/ApplicationMetadata.cs new file mode 100644 index 00000000..2bd87c0e --- /dev/null +++ b/Ryujinx/Ui/App/ApplicationMetadata.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Ui.App +{ + public class ApplicationMetadata + { + public bool Favorite { get; set; } + public double TimePlayed { get; set; } + public string LastPlayed { get; set; } = "Never"; + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/ErrorAppletDialog.cs b/Ryujinx/Ui/Applet/ErrorAppletDialog.cs similarity index 77% rename from Ryujinx/Ui/ErrorAppletDialog.cs rename to Ryujinx/Ui/Applet/ErrorAppletDialog.cs index f7a548b3..a51d5324 100644 --- a/Ryujinx/Ui/ErrorAppletDialog.cs +++ b/Ryujinx/Ui/Applet/ErrorAppletDialog.cs @@ -1,16 +1,11 @@ using Gtk; -using System.Reflection; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Applet { internal class ErrorAppletDialog : MessageDialog { - internal static bool _isExitDialogOpen = false; - public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) { - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - int responseId = 0; if (buttons != null) diff --git a/Ryujinx/Ui/GtkHostUiHandler.cs b/Ryujinx/Ui/Applet/GtkHostUiHandler.cs similarity index 72% rename from Ryujinx/Ui/GtkHostUiHandler.cs rename to Ryujinx/Ui/Applet/GtkHostUiHandler.cs index 12ba81c4..804a1a27 100644 --- a/Ryujinx/Ui/GtkHostUiHandler.cs +++ b/Ryujinx/Ui/Applet/GtkHostUiHandler.cs @@ -1,12 +1,12 @@ using Gtk; -using Ryujinx.Common.Logging; using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.Ui.Widgets; using System; using System.Threading; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Applet { internal class GtkHostUiHandler : IHostUiHandler { @@ -19,16 +19,13 @@ namespace Ryujinx.Ui public bool DisplayMessageDialog(ControllerAppletUiArgs args) { - string playerCount = args.PlayerCountMin == args.PlayerCountMax - ? $"exactly {args.PlayerCountMin}" - : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; - string message = - $"Application requests {playerCount} player(s) with:\n\n" - + $"TYPES: {args.SupportedStyles}\n\n" - + $"PLAYERS: {string.Join(", ", args.SupportedPlayers)}\n\n" - + (args.IsDocked ? "Docked mode set. Handheld is also invalid.\n\n" : "") - + "Please reconfigure Input now and then press OK."; + string message = $"Application requests {playerCount} player(s) with:\n\n" + + $"TYPES: {args.SupportedStyles}\n\n" + + $"PLAYERS: {string.Join(", ", args.SupportedPlayers)}\n\n" + + (args.IsDocked ? "Docked mode set. Handheld is also invalid.\n\n" : "") + + "Please reconfigure Input now and then press OK."; return DisplayMessageDialog("Controller Applet", message); } @@ -36,17 +33,19 @@ namespace Ryujinx.Ui public bool DisplayMessageDialog(string title, string message) { ManualResetEvent dialogCloseEvent = new ManualResetEvent(false); + bool okPressed = false; Application.Invoke(delegate { MessageDialog msgDialog = null; + try { msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) { - Title = title, - Text = message, + Title = title, + Text = message, UseMarkup = true }; @@ -54,16 +53,21 @@ namespace Ryujinx.Ui msgDialog.Response += (object o, ResponseArgs args) => { - if (args.ResponseId == ResponseType.Ok) okPressed = true; + if (args.ResponseId == ResponseType.Ok) + { + okPressed = true; + } + dialogCloseEvent.Set(); msgDialog?.Dispose(); }; msgDialog.Show(); } - catch (Exception e) + catch (Exception ex) { - Logger.Error?.Print(LogClass.Application, $"Error displaying Message Dialog: {e}"); + GtkDialog.CreateErrorDialog($"Error displaying Message Dialog: {ex}"); + dialogCloseEvent.Set(); } }); @@ -76,24 +80,25 @@ namespace Ryujinx.Ui public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) { ManualResetEvent dialogCloseEvent = new ManualResetEvent(false); - bool okPressed = false; - bool error = false; + + bool okPressed = false; + bool error = false; string inputText = args.InitialText ?? ""; Application.Invoke(delegate { try { - var swkbdDialog = new InputDialog(_parent) + var swkbdDialog = new SwkbdAppletDialog(_parent) { - Title = "Software Keyboard", - Text = args.HeaderText, + Title = "Software Keyboard", + Text = args.HeaderText, SecondaryText = args.SubtitleText }; - swkbdDialog.InputEntry.Text = inputText; + swkbdDialog.InputEntry.Text = inputText; swkbdDialog.InputEntry.PlaceholderText = args.GuideText; - swkbdDialog.OkButton.Label = args.SubmitText; + swkbdDialog.OkButton.Label = args.SubmitText; swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); @@ -105,10 +110,11 @@ namespace Ryujinx.Ui swkbdDialog.Dispose(); } - catch (Exception e) + catch (Exception ex) { error = true; - Logger.Error?.Print(LogClass.Application, $"Error displaying Software Keyboard: {e}"); + + GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}"); } finally { @@ -126,12 +132,13 @@ namespace Ryujinx.Ui public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value) { device.UserChannelPersistence.ExecuteProgram(kind, value); - MainWindow.GlWidget?.Exit(); + ((MainWindow)_parent).GlRendererWidget?.Exit(); } public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) { ManualResetEvent dialogCloseEvent = new ManualResetEvent(false); + bool showDetails = false; Application.Invoke(delegate @@ -167,9 +174,9 @@ namespace Ryujinx.Ui msgDialog.Show(); } - catch (Exception e) + catch (Exception ex) { - Logger.Error?.Print(LogClass.Application, $"Error displaying ErrorApplet Dialog: {e}"); + GtkDialog.CreateErrorDialog($"Error displaying ErrorApplet Dialog: {ex}"); dialogCloseEvent.Set(); } @@ -180,4 +187,4 @@ namespace Ryujinx.Ui return showDetails; } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs b/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs new file mode 100644 index 00000000..7c14f0e8 --- /dev/null +++ b/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs @@ -0,0 +1,89 @@ +using Gtk; +using System; + +namespace Ryujinx.Ui.Applet +{ + public class SwkbdAppletDialog : MessageDialog + { + private int _inputMin; + private int _inputMax; + + private Predicate _checkLength; + + private readonly Label _validationInfo; + + public Entry InputEntry { get; } + public Button OkButton { get; } + public Button CancelButton { get; } + + public SwkbdAppletDialog(Window parent) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null) + { + SetDefaultSize(300, 0); + + _validationInfo = new Label() + { + Visible = false + }; + + InputEntry = new Entry() + { + Visible = true + }; + + InputEntry.Activated += OnInputActivated; + InputEntry.Changed += OnInputChanged; + + OkButton = (Button)AddButton("OK", ResponseType.Ok); + CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel); + + ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0); + ((Box)MessageArea).PackEnd(InputEntry, true, true, 4); + + SetInputLengthValidation(0, int.MaxValue); // Disable by default. + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + _validationInfo.Visible = false; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + _validationInfo.Visible = false; + + _checkLength = (length) => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + _validationInfo.Visible = true; + _validationInfo.Markup = $"Must be at least {_inputMin} characters long"; + + _checkLength = (length) => _inputMin <= length; + } + else + { + _validationInfo.Visible = true; + _validationInfo.Markup = $"Must be {_inputMin}-{_inputMax} characters long"; + + _checkLength = (length) => _inputMin <= length && length <= _inputMax; + } + + OnInputChanged(this, EventArgs.Empty); + } + + private void OnInputActivated(object sender, EventArgs e) + { + if (OkButton.IsSensitive) + { + Respond(ResponseType.Ok); + } + } + + private void OnInputChanged(object sender, EventArgs e) + { + OkButton.Sensitive = _checkLength(InputEntry.Text.Length); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/ApplicationMetadata.cs b/Ryujinx/Ui/ApplicationMetadata.cs deleted file mode 100644 index cdedf91b..00000000 --- a/Ryujinx/Ui/ApplicationMetadata.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Ui -{ - internal class ApplicationMetadata - { - public bool Favorite { get; set; } - public double TimePlayed { get; set; } - public string LastPlayed { get; set; } - } -} diff --git a/Ryujinx/Ui/Diagnostic/GuideDialog.cs b/Ryujinx/Ui/Diagnostic/GuideDialog.cs deleted file mode 100644 index c3a0dd38..00000000 --- a/Ryujinx/Ui/Diagnostic/GuideDialog.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Gtk; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -namespace Ryujinx.Ui.Diagnostic -{ - internal class GuideDialog : MessageDialog - { - internal static bool _isExitDialogOpen = false; - - public GuideDialog(string title, string mainText, string secondaryText) : base(null, DialogFlags.Modal, MessageType.Other, ButtonsType.None, null) - { - Title = title; - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - Text = mainText; - SecondaryText = secondaryText; - WindowPosition = WindowPosition.Center; - Response += GtkDialog_Response; - - Button guideButton = new Button(); - guideButton.Label = "Open the Setup Guide"; - - ContentArea.Add(guideButton); - - SetSizeRequest(100, 10); - ShowAll(); - } - - private void GtkDialog_Response(object sender, ResponseArgs args) - { - Dispose(); - } - } -} diff --git a/Ryujinx/Ui/Diagnostic/UserErrorDialog.cs b/Ryujinx/Ui/Diagnostic/UserErrorDialog.cs deleted file mode 100644 index ccea9229..00000000 --- a/Ryujinx/Ui/Diagnostic/UserErrorDialog.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Gtk; -using System.Reflection; - -namespace Ryujinx.Ui.Diagnostic -{ - internal class UserErrorDialog : MessageDialog - { - private static string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; - private const int OkResponseId = 0; - private const int SetupGuideResponseId = 1; - - private UserError _userError; - - private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null) - { - _userError = error; - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - WindowPosition = WindowPosition.Center; - Response += UserErrorDialog_Response; - - SetSizeRequest(120, 50); - - AddButton("OK", OkResponseId); - - bool isInSetupGuide = IsCoveredBySetupGuide(error); - - if (isInSetupGuide) - { - AddButton("Open the Setup Guide", SetupGuideResponseId); - } - - string errorCode = GetErrorCode(error); - - SecondaryUseMarkup = true; - - Title = $"Ryujinx error ({errorCode})"; - Text = $"{errorCode}: {GetErrorTitle(error)}"; - SecondaryText = GetErrorDescription(error); - - if (isInSetupGuide) - { - SecondaryText += "\nFor more information on how to fix this error, follow our Setup Guide."; - } - } - - private static string GetErrorCode(UserError error) - { - return $"RYU-{(uint)error:X4}"; - } - - private static string GetErrorTitle(UserError error) - { - switch (error) - { - case UserError.NoKeys: - return "Keys not found"; - case UserError.NoFirmware: - return "Firmware not found"; - case UserError.FirmwareParsingFailed: - return "Firmware parsing error"; - case UserError.ApplicationNotFound: - return "Application not found"; - case UserError.Unknown: - return "Unknown error"; - default: - return "Undefined error"; - } - } - - private static string GetErrorDescription(UserError error) - { - switch (error) - { - case UserError.NoKeys: - return "Ryujinx was unable to find your 'prod.keys' file"; - case UserError.NoFirmware: - return "Ryujinx was unable to find any firmwares installed"; - case UserError.FirmwareParsingFailed: - return "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys."; - case UserError.ApplicationNotFound: - return "Ryujinx couldn't find a valid application at the given path."; - case UserError.Unknown: - return "An unknown error occured!"; - default: - return "An undefined error occured! This shouldn't happen, please contact a dev!"; - } - } - - private static bool IsCoveredBySetupGuide(UserError error) - { - switch (error) - { - case UserError.NoKeys: - case UserError.NoFirmware: - case UserError.FirmwareParsingFailed: - return true; - default: - return false; - } - } - - private static string GetSetupGuideUrl(UserError error) - { - if (!IsCoveredBySetupGuide(error)) - { - return null; - } - - switch (error) - { - case UserError.NoKeys: - return SetupGuideUrl + "#initial-setup---placement-of-prodkeys"; - case UserError.NoFirmware: - return SetupGuideUrl + "#initial-setup-continued---installation-of-firmware"; - } - - return SetupGuideUrl; - } - - private void UserErrorDialog_Response(object sender, ResponseArgs args) - { - int responseId = (int)args.ResponseId; - - if (responseId == SetupGuideResponseId) - { - UrlHelper.OpenUrl(GetSetupGuideUrl(_userError)); - } - - Dispose(); - } - - public static void CreateUserErrorDialog(UserError error) - { - new UserErrorDialog(error).Run(); - } - } -} diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index c20cc78b..6115c22b 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -11,7 +11,8 @@ using Ryujinx.Configuration; using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE; using Ryujinx.HLE.HOS.Services.Hid; -using Ryujinx.Motion; +using Ryujinx.Modules.Motion; +using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.Threading; @@ -73,9 +74,9 @@ namespace Ryujinx.Ui _device = device; - this.Initialized += GLRenderer_Initialized; - this.Destroyed += GLRenderer_Destroyed; - this.ShuttingDown += GLRenderer_ShuttingDown; + Initialized += GLRenderer_Initialized; + Destroyed += GLRenderer_Destroyed; + ShuttingDown += GLRenderer_ShuttingDown; Initialize(); @@ -89,7 +90,7 @@ namespace Ryujinx.Ui | EventMask.KeyPressMask | EventMask.KeyReleaseMask)); - this.Shown += Renderer_Shown; + Shown += Renderer_Shown; _dsuClient = new Client(); diff --git a/Ryujinx/Ui/UrlHelper.cs b/Ryujinx/Ui/Helper/OpenHelper.cs similarity index 71% rename from Ryujinx/Ui/UrlHelper.cs rename to Ryujinx/Ui/Helper/OpenHelper.cs index 79eacc67..6ccf4de3 100644 --- a/Ryujinx/Ui/UrlHelper.cs +++ b/Ryujinx/Ui/Helper/OpenHelper.cs @@ -2,10 +2,20 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Helper { - static class UrlHelper + static class OpenHelper { + public static void OpenFolder(string path) + { + Process.Start(new ProcessStartInfo + { + FileName = path, + UseShellExecute = true, + Verb = "open" + }); + } + public static void OpenUrl(string url) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -26,4 +36,4 @@ namespace Ryujinx.Ui } } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/Diagnostic/SetupValidator.cs b/Ryujinx/Ui/Helper/SetupValidator.cs similarity index 98% rename from Ryujinx/Ui/Diagnostic/SetupValidator.cs rename to Ryujinx/Ui/Helper/SetupValidator.cs index c52dc2ef..45315f8f 100644 --- a/Ryujinx/Ui/Diagnostic/SetupValidator.cs +++ b/Ryujinx/Ui/Helper/SetupValidator.cs @@ -1,9 +1,10 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.Ui.Widgets; using System; using System.IO; -namespace Ryujinx.Ui.Diagnostic +namespace Ryujinx.Ui.Helper { /// /// Ensure installation validity diff --git a/Ryujinx/Ui/Helper/SortHelper.cs b/Ryujinx/Ui/Helper/SortHelper.cs new file mode 100644 index 00000000..78917fe7 --- /dev/null +++ b/Ryujinx/Ui/Helper/SortHelper.cs @@ -0,0 +1,116 @@ +using Gtk; +using System; + +namespace Ryujinx.Ui.Helper +{ + static class SortHelper + { + public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + string aValue = model.GetValue(a, 5).ToString(); + string bValue = model.GetValue(b, 5).ToString(); + + if (aValue.Length > 4 && aValue[^4..] == "mins") + { + aValue = (float.Parse(aValue[0..^5]) * 60).ToString(); + } + else if (aValue.Length > 3 && aValue[^3..] == "hrs") + { + aValue = (float.Parse(aValue[0..^4]) * 3600).ToString(); + } + else if (aValue.Length > 4 && aValue[^4..] == "days") + { + aValue = (float.Parse(aValue[0..^5]) * 86400).ToString(); + } + else + { + aValue = aValue[0..^1]; + } + + if (bValue.Length > 4 && bValue[^4..] == "mins") + { + bValue = (float.Parse(bValue[0..^5]) * 60).ToString(); + } + else if (bValue.Length > 3 && bValue[^3..] == "hrs") + { + bValue = (float.Parse(bValue[0..^4]) * 3600).ToString(); + } + else if (bValue.Length > 4 && bValue[^4..] == "days") + { + bValue = (float.Parse(bValue[0..^5]) * 86400).ToString(); + } + else + { + bValue = bValue[0..^1]; + } + + if (float.Parse(aValue) > float.Parse(bValue)) + { + return -1; + } + else if (float.Parse(bValue) > float.Parse(aValue)) + { + return 1; + } + else + { + return 0; + } + } + + public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + string aValue = model.GetValue(a, 6).ToString(); + string bValue = model.GetValue(b, 6).ToString(); + + if (aValue == "Never") + { + aValue = DateTime.UnixEpoch.ToString(); + } + + if (bValue == "Never") + { + bValue = DateTime.UnixEpoch.ToString(); + } + + return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue)); + } + + public static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b) + { + string aValue = model.GetValue(a, 8).ToString(); + string bValue = model.GetValue(b, 8).ToString(); + + if (aValue[^2..] == "GB") + { + aValue = (float.Parse(aValue[0..^2]) * 1024).ToString(); + } + else + { + aValue = aValue[0..^2]; + } + + if (bValue[^2..] == "GB") + { + bValue = (float.Parse(bValue[0..^2]) * 1024).ToString(); + } + else + { + bValue = bValue[0..^2]; + } + + if (float.Parse(aValue) > float.Parse(bValue)) + { + return -1; + } + else if (float.Parse(bValue) > float.Parse(aValue)) + { + return 1; + } + else + { + return 0; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/Helper/ThemeHelper.cs b/Ryujinx/Ui/Helper/ThemeHelper.cs new file mode 100644 index 00000000..fca713e6 --- /dev/null +++ b/Ryujinx/Ui/Helper/ThemeHelper.cs @@ -0,0 +1,35 @@ +using Gtk; +using Ryujinx.Common.Logging; +using Ryujinx.Configuration; +using System.IO; + +namespace Ryujinx.Ui.Helper +{ + static class ThemeHelper + { + public static void ApplyTheme() + { + if (!ConfigurationState.Instance.Ui.EnableCustomTheme) + { + return; + } + + if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css")) + { + CssProvider cssProvider = new CssProvider(); + + cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath); + + StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); + } + else + { + Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); + + ConfigurationState.Instance.Ui.CustomThemePath.Value = ""; + ConfigurationState.Instance.Ui.EnableCustomTheme.Value = false; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/InputDialog.cs b/Ryujinx/Ui/InputDialog.cs deleted file mode 100644 index a8dc80bf..00000000 --- a/Ryujinx/Ui/InputDialog.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Gtk; -using System; - -namespace Ryujinx.Ui -{ - public class InputDialog : MessageDialog - { - private int _inputMin, _inputMax; - private Predicate _checkLength; - private Label _validationInfo; - - public Entry InputEntry { get; } - public Button OkButton { get; } - public Button CancelButton { get; } - - public InputDialog(Window parent) - : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null) - { - SetDefaultSize(300, 0); - - _validationInfo = new Label() { Visible = false }; - - InputEntry = new Entry() { Visible = true }; - InputEntry.Activated += (object sender, EventArgs e) => { if (OkButton.IsSensitive) Respond(ResponseType.Ok); }; - InputEntry.Changed += OnInputChanged; - - OkButton = (Button)AddButton("OK", ResponseType.Ok); - CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel); - - ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0); - ((Box)MessageArea).PackEnd(InputEntry, true, true, 4); - - SetInputLengthValidation(0, int.MaxValue); // disable by default - } - - public void SetInputLengthValidation(int min, int max) - { - _inputMin = Math.Min(min, max); - _inputMax = Math.Max(min, max); - - _validationInfo.Visible = false; - - if (_inputMin <= 0 && _inputMax == int.MaxValue) // disable - { - _validationInfo.Visible = false; - _checkLength = (length) => true; - } - else if (_inputMin > 0 && _inputMax == int.MaxValue) - { - _validationInfo.Visible = true; - _validationInfo.Markup = $"Must be at least {_inputMin} characters long"; - _checkLength = (length) => _inputMin <= length; - } - else - { - _validationInfo.Visible = true; - _validationInfo.Markup = $"Must be {_inputMin}-{_inputMax} characters long"; - _checkLength = (length) => _inputMin <= length && length <= _inputMax; - } - - OnInputChanged(this, EventArgs.Empty); - } - - private void OnInputChanged(object sender, EventArgs e) - { - OkButton.Sensitive = _checkLength(InputEntry.Text.Length); - } - } -} \ No newline at end of file diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index f16158e2..58cd6401 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -13,11 +13,16 @@ using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; -using Ryujinx.Ui.Diagnostic; +using Ryujinx.Modules; +using Ryujinx.Ui.App; +using Ryujinx.Ui.Applet; +using Ryujinx.Ui.Helper; +using Ryujinx.Ui.Widgets; +using Ryujinx.Ui.Windows; using System; using System.Diagnostics; using System.IO; -using System.Reflection; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -27,26 +32,27 @@ namespace Ryujinx.Ui { public class MainWindow : Window { - private static VirtualFileSystem _virtualFileSystem; - private static ContentManager _contentManager; - private static UserChannelPersistence _userChannelPersistence; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ContentManager _contentManager; - private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; - private static HLE.Switch _emulationContext; + private UserChannelPersistence _userChannelPersistence; - private static GlRenderer _glWidget; - private static GtkHostUiHandler _uiHandler; + private HLE.Switch _emulationContext; - public static GlRenderer GlWidget => _glWidget; + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; - private static AutoResetEvent _deviceExitStatus = new AutoResetEvent(false); + private readonly ApplicationLibrary _applicationLibrary; + private readonly GtkHostUiHandler _uiHandler; + private readonly AutoResetEvent _deviceExitStatus; + private readonly ListStore _tableStore; - private static ListStore _tableStore; + private bool _updatingGameTable; + private bool _gameLoaded; + private bool _ending; - private static bool _updatingGameTable; - private static bool _gameLoaded; - private static string _gamePath; - private static bool _ending; + private string _currentEmulatedGamePath = null; + + public GlRenderer GlRendererWidget; #pragma warning disable CS0169, CS0649, IDE0044 @@ -93,50 +99,41 @@ namespace Ryujinx.Ui { builder.Autoconnect(this); + // Apply custom theme if needed. + ThemeHelper.ApplyTheme(); + + // Sets overridden fields. int monitorWidth = Display.PrimaryMonitor.Geometry.Width * Display.PrimaryMonitor.ScaleFactor; int monitorHeight = Display.PrimaryMonitor.Geometry.Height * Display.PrimaryMonitor.ScaleFactor; - this.DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280; - this.DefaultHeight = monitorHeight < 760 ? monitorHeight : 760; + DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280; + DefaultHeight = monitorHeight < 760 ? monitorHeight : 760; - this.WindowStateEvent += MainWindow_WindowStateEvent; - this.DeleteEvent += Window_Close; - _fullScreen.Activated += FullScreen_Toggled; + Title = $"Ryujinx {Program.Version}"; - this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - this.Title = $"Ryujinx {Program.Version}"; + // Hide emulation context status bar. + _statusBar.Hide(); - ApplicationLibrary.ApplicationAdded += Application_Added; - ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated; - GlRenderer.StatusUpdatedEvent += Update_StatusBar; + // Instanciate HLE objects. + _virtualFileSystem = VirtualFileSystem.CreateInstance(); + _contentManager = new ContentManager(_virtualFileSystem); + _userChannelPersistence = new UserChannelPersistence(); + + // Instanciate GUI objects. + _applicationLibrary = new ApplicationLibrary(_virtualFileSystem); + _uiHandler = new GtkHostUiHandler(this); + _deviceExitStatus = new AutoResetEvent(false); + + WindowStateEvent += WindowStateEvent_Changed; + DeleteEvent += Window_Close; + + _applicationLibrary.ApplicationAdded += Application_Added; + _applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated; _gameTable.ButtonReleaseEvent += Row_Clicked; + _fullScreen.Activated += FullScreen_Toggled; - // First we check that a migration isn't needed. (because VirtualFileSystem will create the new directory otherwise) - bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded); - if (!continueWithStartup) - { - End(null); - } - - _virtualFileSystem = VirtualFileSystem.CreateInstance(); - _userChannelPersistence = new UserChannelPersistence(); - _contentManager = new ContentManager(_virtualFileSystem); - - if (migrationNeeded) - { - bool migrationSuccessful = Migration.DoMigrationForStartup(this, _virtualFileSystem); - - if (!migrationSuccessful) - { - End(null); - } - } - - // Make sure that everything is loaded. - _virtualFileSystem.Reload(); - - ApplyTheme(); + GlRenderer.StatusUpdatedEvent += Update_StatusBar; if (ConfigurationState.Instance.Ui.StartFullscreen) { @@ -169,9 +166,9 @@ namespace Ryujinx.Ui typeof(string), typeof(BlitStruct)); - _tableStore.SetSortFunc(5, TimePlayedSort); - _tableStore.SetSortFunc(6, LastPlayedSort); - _tableStore.SetSortFunc(8, FileSizeSort); + _tableStore.SetSortFunc(5, SortHelper.TimePlayedSort); + _tableStore.SetSortFunc(6, SortHelper.LastPlayedSort); + _tableStore.SetSortFunc(8, SortHelper.FileSizeSort); int columnId = ConfigurationState.Instance.Ui.ColumnSort.SortColumnId; bool ascending = ConfigurationState.Instance.Ui.ColumnSort.SortAscending; @@ -193,39 +190,13 @@ namespace Ryujinx.Ui }; Task.Run(RefreshFirmwareLabel); - - _statusBar.Hide(); - - _uiHandler = new GtkHostUiHandler(this); - _gamePath = null; } - private void MainWindow_WindowStateEvent(object o, WindowStateEventArgs args) + private void WindowStateEvent_Changed(object o, WindowStateEventArgs args) { _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen"; } - internal static void ApplyTheme() - { - if (!ConfigurationState.Instance.Ui.EnableCustomTheme) - { - return; - } - - if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (System.IO.Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css")) - { - CssProvider cssProvider = new CssProvider(); - - cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath); - - StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); - } - else - { - Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); - } - } - private void UpdateColumns() { foreach (TreeViewColumn column in _gameTable.Columns) @@ -291,21 +262,59 @@ namespace Ryujinx.Ui } } - private HLE.Switch InitializeSwitchInstance() + private void InitializeSwitchInstance() { _virtualFileSystem.Reload(); - HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, _userChannelPersistence, InitializeRenderer(), InitializeAudioEngine()) + IRenderer renderer = new Renderer(); + IAalOutput audioEngine = new DummyAudioOut(); + + if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) + { + if (SoundIoAudioOut.IsSupported) + { + audioEngine = new SoundIoAudioOut(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) + { + if (OpenALAudioOut.IsSupported) + { + audioEngine = new OpenALAudioOut(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); + + if (SoundIoAudioOut.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + SaveConfig(); + + audioEngine = new SoundIoAudioOut(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + + _emulationContext = new HLE.Switch(_virtualFileSystem, _contentManager, _userChannelPersistence, renderer, audioEngine) { UiHandler = _uiHandler }; - instance.Initialize(); - - return instance; + _emulationContext.Initialize(); } - internal static void UpdateGameTable() + public void UpdateGameTable() { if (_updatingGameTable || _gameLoaded) { @@ -318,64 +327,68 @@ namespace Ryujinx.Ui Thread applicationLibraryThread = new Thread(() => { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, - _virtualFileSystem, ConfigurationState.Instance.System.Language); + _applicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, ConfigurationState.Instance.System.Language); _updatingGameTable = false; }); - applicationLibraryThread.Name = "GUI.ApplicationLibraryThread"; + applicationLibraryThread.Name = "GUI.ApplicationLibraryThread"; applicationLibraryThread.IsBackground = true; applicationLibraryThread.Start(); } - internal void LoadApplication(string path) + [Conditional("RELEASE")] + public void PerformanceCheck() + { + if (ConfigurationState.Instance.Logger.EnableDebug.Value) + { + MessageDialog debugWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have debug logging enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable debug logging. Would you like to disable debug logging now?" + }; + + if (debugWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Logger.EnableDebug.Value = false; + SaveConfig(); + } + + debugWarningDialog.Dispose(); + } + + if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) + { + MessageDialog shadersDumpWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have shader dumping enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?" + }; + + if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; + SaveConfig(); + } + + shadersDumpWarningDialog.Dispose(); + } + } + + public void LoadApplication(string path) { if (_gameLoaded) { - GtkDialog.CreateInfoDialog("Ryujinx", "A game has already been loaded", "Please close it first and try again."); + GtkDialog.CreateInfoDialog("A game has already been loaded", "Please close it first and try again."); } else { -#if !DEBUG - if (ConfigurationState.Instance.Logger.EnableDebug.Value) - { - MessageDialog debugWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Warning", - Text = "You have debug logging enabled, which is designed to be used by developers only.", - SecondaryText = "For optimal performance, it's recommended to disable debug logging. Would you like to disable debug logging now?" - }; + PerformanceCheck(); - if (debugWarningDialog.Run() == (int)ResponseType.Yes) - { - ConfigurationState.Instance.Logger.EnableDebug.Value = false; - SaveConfig(); - } - - debugWarningDialog.Dispose(); - } - - if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) - { - MessageDialog shadersDumpWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Warning", - Text = "You have shader dumping enabled, which is designed to be used by developers only.", - SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?" - }; - - if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes) - { - ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; - SaveConfig(); - } - - shadersDumpWarningDialog.Dispose(); - } -#endif Logger.RestartTime(); - HLE.Switch device = InitializeSwitchInstance(); + InitializeSwitchInstance(); UpdateGraphicsConfig(); @@ -389,34 +402,25 @@ namespace Ryujinx.Ui { if (userError == UserError.NoFirmware) { - MessageDialog shouldInstallFirmwareDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Info", - Text = "No Firmware Installed", - SecondaryText = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})" - }; + string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; - if (shouldInstallFirmwareDialog.Run() != (int)ResponseType.Yes) - { - shouldInstallFirmwareDialog.Dispose(); + ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); + if (responseDialog != ResponseType.Yes) + { UserErrorDialog.CreateUserErrorDialog(userError); - device.Dispose(); + _emulationContext.Dispose(); return; } - else - { - shouldInstallFirmwareDialog.Dispose(); - } } if (!SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) { UserErrorDialog.CreateUserErrorDialog(userError); - device.Dispose(); + _emulationContext.Dispose(); return; } @@ -428,15 +432,16 @@ namespace Ryujinx.Ui RefreshFirmwareLabel(); - GtkDialog.CreateInfoDialog("Ryujinx - Info", $"Firmware {firmwareVersion.VersionString} was installed", - $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."); + string message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; + + GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); } } else { UserErrorDialog.CreateUserErrorDialog(userError); - device.Dispose(); + _emulationContext.Dispose(); return; } @@ -456,12 +461,12 @@ namespace Ryujinx.Ui if (romFsFiles.Length > 0) { Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); - device.LoadCart(path, romFsFiles[0]); + _emulationContext.LoadCart(path, romFsFiles[0]); } else { Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); - device.LoadCart(path); + _emulationContext.LoadCart(path); } } else if (File.Exists(path)) @@ -470,22 +475,22 @@ namespace Ryujinx.Ui { case ".xci": Logger.Info?.Print(LogClass.Application, "Loading as XCI."); - device.LoadXci(path); + _emulationContext.LoadXci(path); break; case ".nca": Logger.Info?.Print(LogClass.Application, "Loading as NCA."); - device.LoadNca(path); + _emulationContext.LoadNca(path); break; case ".nsp": case ".pfs0": Logger.Info?.Print(LogClass.Application, "Loading as NSP."); - device.LoadNsp(path); + _emulationContext.LoadNsp(path); break; default: Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); try { - device.LoadProgram(path); + _emulationContext.LoadProgram(path); } catch (ArgumentOutOfRangeException) { @@ -497,23 +502,23 @@ namespace Ryujinx.Ui else { Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); - device.Dispose(); + + _emulationContext.Dispose(); return; } - _emulationContext = device; - _gamePath = path; + _currentEmulatedGamePath = path; _deviceExitStatus.Reset(); Translator.IsReadyForTranslation.Reset(); #if MACOS_BUILD - CreateGameWindow(device); + CreateGameWindow(); #else Thread windowThread = new Thread(() => { - CreateGameWindow(device); + CreateGameWindow(); }) { Name = "GUI.WindowThread" @@ -528,34 +533,34 @@ namespace Ryujinx.Ui _firmwareInstallFile.Sensitive = false; _firmwareInstallDirectory.Sensitive = false; - DiscordIntegrationModule.SwitchToPlayingState(device.Application.TitleIdText, device.Application.TitleName); + DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Application.TitleIdText, _emulationContext.Application.TitleName); - ApplicationLibrary.LoadAndSaveMetaData(device.Application.TitleIdText, appMetadata => + _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Application.TitleIdText, appMetadata => { appMetadata.LastPlayed = DateTime.UtcNow.ToString(); }); } } - private void CreateGameWindow(HLE.Switch device) + private void CreateGameWindow() { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); } - _glWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel); + GlRendererWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel); Application.Invoke(delegate { _viewBox.Remove(_gameTableWindow); - _glWidget.Expand = true; - _viewBox.Child = _glWidget; + GlRendererWidget.Expand = true; + _viewBox.Child = GlRendererWidget; - _glWidget.ShowAll(); - EditFooterForGameRender(); + GlRendererWidget.ShowAll(); + EditFooterForGameRenderer(); - if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) { ToggleExtraWidgets(false); } @@ -565,33 +570,34 @@ namespace Ryujinx.Ui } }); - _glWidget.WaitEvent.WaitOne(); + GlRendererWidget.WaitEvent.WaitOne(); - _glWidget.Start(); + GlRendererWidget.Start(); Ptc.Close(); PtcProfiler.Stop(); - device.Dispose(); + _emulationContext.Dispose(); _deviceExitStatus.Set(); // NOTE: Everything that is here will not be executed when you close the UI. Application.Invoke(delegate { - if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) { ToggleExtraWidgets(true); } - _viewBox.Remove(_glWidget); - _glWidget.Exit(); + _viewBox.Remove(GlRendererWidget); + GlRendererWidget.Exit(); - if(_glWidget.Window != this.Window && _glWidget.Window != null) + if(GlRendererWidget.Window != Window && GlRendererWidget.Window != null) { - _glWidget.Window.Dispose(); + GlRendererWidget.Window.Dispose(); } - _glWidget.Dispose(); + GlRendererWidget.Dispose(); + _windowsMultimediaTimerResolution?.Dispose(); _windowsMultimediaTimerResolution = null; @@ -599,11 +605,11 @@ namespace Ryujinx.Ui _gameTableWindow.Expand = true; - this.Window.Title = $"Ryujinx {Program.Version}"; + Window.Title = $"Ryujinx {Program.Version}"; _emulationContext = null; _gameLoaded = false; - _glWidget = null; + GlRendererWidget = null; DiscordIntegrationModule.SwitchToMainMenu(); @@ -627,7 +633,7 @@ namespace Ryujinx.Ui _statusBar.Hide(); } - private void EditFooterForGameRender() + private void EditFooterForGameRenderer() { _listStatusBox.Hide(); _statusBar.Show(); @@ -635,7 +641,7 @@ namespace Ryujinx.Ui public void ToggleExtraWidgets(bool show) { - if (_glWidget != null) + if (GlRendererWidget != null) { if (show) { @@ -651,11 +657,11 @@ namespace Ryujinx.Ui } } - private static void UpdateGameMetadata(string titleId) + private void UpdateGameMetadata(string titleId) { if (_gameLoaded) { - ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + _applicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => { DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; @@ -665,7 +671,7 @@ namespace Ryujinx.Ui } } - public static void UpdateGraphicsConfig() + public void UpdateGraphicsConfig() { int resScale = ConfigurationState.Instance.Graphics.ResScale; float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; @@ -676,12 +682,12 @@ namespace Ryujinx.Ui Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; } - public static void SaveConfig() + public void SaveConfig() { ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); } - private void End(HLE.Switch device) + private void End() { if (_ending) { @@ -690,14 +696,14 @@ namespace Ryujinx.Ui _ending = true; - if (device != null) + if (_emulationContext != null) { - UpdateGameMetadata(device.Application.TitleIdText); + UpdateGameMetadata(_emulationContext.Application.TitleIdText); - if (_glWidget != null) + if (GlRendererWidget != null) { - // We tell the widget that we are exiting - _glWidget.Exit(); + // We tell the widget that we are exiting. + GlRendererWidget.Exit(); // Wait for the other thread to dispose the HLE context before exiting. _deviceExitStatus.WaitOne(); @@ -706,64 +712,13 @@ namespace Ryujinx.Ui Dispose(); - DiscordIntegrationModule.Exit(); - - Ptc.Dispose(); - PtcProfiler.Dispose(); - - Logger.Shutdown(); - + Program.Exit(); Application.Quit(); } - private static IRenderer InitializeRenderer() - { - return new Renderer(); - } - - private static IAalOutput InitializeAudioEngine() - { - if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) - { - if (SoundIoAudioOut.IsSupported) - { - return new SoundIoAudioOut(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); - } - } - else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) - { - if (OpenALAudioOut.IsSupported) - { - return new OpenALAudioOut(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); - - if (SoundIoAudioOut.IsSupported) - { - Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); - - ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; - SaveConfig(); - - return new SoundIoAudioOut(); - } - else - { - Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); - } - } - } - - return new DummyAudioOut(); - } - - //Events + // + // Events + // private void Application_Added(object sender, ApplicationAddedEventArgs args) { Application.Invoke(delegate @@ -797,7 +752,8 @@ namespace Ryujinx.Ui _progressBar.Value = barValue; - if (args.NumAppsLoaded == args.NumAppsFound) // Reset the vertical scrollbar to the top when titles finish loading + // Reset the vertical scrollbar to the top when titles finish loading + if (args.NumAppsLoaded == args.NumAppsFound) { _gameTableWindow.Vadjustment.Value = 0; } @@ -831,13 +787,12 @@ namespace Ryujinx.Ui { _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path)); - string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); - - bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); + bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); _tableStore.SetValue(treeIter, 0, newToggleValue); - ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + _applicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => { appMetadata.Favorite = newToggleValue; }); @@ -856,6 +811,7 @@ namespace Ryujinx.Ui private void Row_Activated(object sender, RowActivatedArgs args) { _gameTableSelection.GetSelected(out TreeIter treeIter); + string path = (string)_tableStore.GetValue(treeIter, 9); LoadApplication(path); @@ -880,81 +836,76 @@ namespace Ryujinx.Ui private void Row_Clicked(object sender, ButtonReleaseEventArgs args) { - if (args.Event.Button != 3) return; + if (args.Event.Button != 3 /* Right Click */) + { + return; + } _gameTableSelection.GetSelected(out TreeIter treeIter); - if (treeIter.UserData == IntPtr.Zero) return; + if (treeIter.UserData == IntPtr.Zero) + { + return; + } + + string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString(); + string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0]; + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); BlitStruct controlData = (BlitStruct)_tableStore.GetValue(treeIter, 10); - GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, controlData, treeIter, _virtualFileSystem); - contextMenu.ShowAll(); - contextMenu.PopupAtPointer(null); + _ = new GameTableContextMenu(this, _virtualFileSystem, titleFilePath, titleName, titleId, controlData); } private void Load_Application_File(object sender, EventArgs args) { - FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); - - fileChooser.Filter = new FileFilter(); - fileChooser.Filter.AddPattern("*.nsp" ); - fileChooser.Filter.AddPattern("*.pfs0"); - fileChooser.Filter.AddPattern("*.xci" ); - fileChooser.Filter.AddPattern("*.nca" ); - fileChooser.Filter.AddPattern("*.nro" ); - fileChooser.Filter.AddPattern("*.nso" ); - - if (fileChooser.Run() == (int)ResponseType.Accept) + using (FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept)) { - LoadApplication(fileChooser.Filename); - } + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.nsp"); + fileChooser.Filter.AddPattern("*.pfs0"); + fileChooser.Filter.AddPattern("*.xci"); + fileChooser.Filter.AddPattern("*.nca"); + fileChooser.Filter.AddPattern("*.nro"); + fileChooser.Filter.AddPattern("*.nso"); - fileChooser.Dispose(); + if (fileChooser.Run() == (int)ResponseType.Accept) + { + LoadApplication(fileChooser.Filename); + } + } } private void Load_Application_Folder(object sender, EventArgs args) { - FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); - - if (fileChooser.Run() == (int)ResponseType.Accept) + using (FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept)) { - LoadApplication(fileChooser.Filename); + if (fileChooser.Run() == (int)ResponseType.Accept) + { + LoadApplication(fileChooser.Filename); + } } - - fileChooser.Dispose(); } private void Open_Ryu_Folder(object sender, EventArgs args) { - Process.Start(new ProcessStartInfo - { - FileName = AppDataManager.BaseDirPath, - UseShellExecute = true, - Verb = "open" - }); + OpenHelper.OpenFolder(AppDataManager.BaseDirPath); } private void OpenLogsFolder_Pressed(object sender, EventArgs args) { string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); - DirectoryInfo directory = new DirectoryInfo(logPath); - directory.Create(); + new DirectoryInfo(logPath).Create(); - Process.Start(new ProcessStartInfo - { - FileName = logPath, - UseShellExecute = true, - Verb = "open" - }); + OpenHelper.OpenFolder(logPath); } private void Exit_Pressed(object sender, EventArgs args) { if (!_gameLoaded || GtkDialog.CreateExitDialog()) { - End(_emulationContext); + End(); } } @@ -962,7 +913,7 @@ namespace Ryujinx.Ui { if (!_gameLoaded || GtkDialog.CreateExitDialog()) { - End(_emulationContext); + End(); } else { @@ -972,18 +923,12 @@ namespace Ryujinx.Ui private void StopEmulation_Pressed(object sender, EventArgs args) { - _glWidget?.Exit(); + GlRendererWidget?.Exit(); } private void Installer_File_Pressed(object o, EventArgs args) { - FileChooserDialog fileChooser = new FileChooserDialog("Choose the firmware file to open", - this, - FileChooserAction.Open, - "Cancel", - ResponseType.Cancel, - "Open", - ResponseType.Accept); + FileChooserDialog fileChooser = new FileChooserDialog("Choose the firmware file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); fileChooser.Filter = new FileFilter(); fileChooser.Filter.AddPattern("*.zip"); @@ -994,51 +939,15 @@ namespace Ryujinx.Ui private void Installer_Directory_Pressed(object o, EventArgs args) { - FileChooserDialog directoryChooser = new FileChooserDialog("Choose the firmware directory to open", - this, - FileChooserAction.SelectFolder, - "Cancel", - ResponseType.Cancel, - "Open", - ResponseType.Accept); + FileChooserDialog directoryChooser = new FileChooserDialog("Choose the firmware directory to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); HandleInstallerDialog(directoryChooser); } - private void RefreshFirmwareLabel() - { - SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion(); - - GLib.Idle.Add(new GLib.IdleHandler(() => - { - _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; - - return false; - })); - } - - private void HandleRelaunch() - { - if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart) - { - _userChannelPersistence.ShouldRestart = false; - - LoadApplication(_gamePath); - } - else - { - // otherwise, clear state. - _userChannelPersistence = new UserChannelPersistence(); - _gamePath = null; - } - } - private void HandleInstallerDialog(FileChooserDialog fileChooser) { if (fileChooser.Run() == (int)ResponseType.Accept) { - MessageDialog dialog = null; - try { string filename = fileChooser.Filename; @@ -1047,19 +956,11 @@ namespace Ryujinx.Ui SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename); + string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; + if (firmwareVersion == null) { - dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); - - dialog.Text = "Firmware not found."; - - dialog.SecondaryText = $"A valid system firmware was not found in {filename}."; - - Logger.Error?.Print(LogClass.Application, $"A valid system firmware was not found in {filename}."); - - dialog.Run(); - dialog.Hide(); - dialog.Dispose(); + GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}."); return; } @@ -1070,80 +971,49 @@ namespace Ryujinx.Ui if (currentVersion != null) { - dialogMessage += $"This will replace the current system version {currentVersion.VersionString}. "; + dialogMessage += $"\n\nThis will replace the current system version {currentVersion.VersionString}. "; } - dialogMessage += "Do you want to continue?"; + dialogMessage += "\n\nDo you want to continue?"; - dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, false, ""); + ResponseType responseInstallDialog = (ResponseType)GtkDialog.CreateConfirmationDialog(dialogTitle, dialogMessage).Run(); - dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; - dialog.SecondaryText = dialogMessage; + MessageDialog waitingDialog = GtkDialog.CreateWaitingDialog(dialogTitle, "Installing firmware..."); - int response = dialog.Run(); - - dialog.Dispose(); - - dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, false, ""); - - dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; - - dialog.SecondaryText = "Installing firmware..."; - - if (response == (int)ResponseType.Yes) + if (responseInstallDialog == ResponseType.Yes) { Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); Thread thread = new Thread(() => { - GLib.Idle.Add(new GLib.IdleHandler(() => + Application.Invoke(delegate { - dialog.Run(); - return false; - })); + waitingDialog.Run(); + + }); try { _contentManager.InstallFirmware(filename); - GLib.Idle.Add(new GLib.IdleHandler(() => + Application.Invoke(delegate { - dialog.Dispose(); + waitingDialog.Dispose(); - dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); + string message = $"System version {firmwareVersion.VersionString} successfully installed."; - dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; - - dialog.SecondaryText = $"System version {firmwareVersion.VersionString} successfully installed."; - - Logger.Info?.Print(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed."); - - dialog.Run(); - dialog.Dispose(); - - return false; - })); + GtkDialog.CreateInfoDialog(dialogTitle, message); + Logger.Info?.Print(LogClass.Application, message); + }); } catch (Exception ex) { - GLib.Idle.Add(new GLib.IdleHandler(() => + Application.Invoke(delegate { - dialog.Dispose(); + waitingDialog.Dispose(); - dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); - - dialog.Text = $"Install Firmware {firmwareVersion.VersionString} Failed."; - - dialog.SecondaryText = $"An error occured while installing system version {firmwareVersion.VersionString}." + - " Please check logs for more info."; - - Logger.Error?.Print(LogClass.Application, ex.Message); - - dialog.Run(); - dialog.Dispose(); - - return false; - })); + GtkDialog.CreateErrorDialog(ex.Message); + }); } finally { @@ -1154,28 +1024,10 @@ namespace Ryujinx.Ui thread.Name = "GUI.FirmwareInstallerThread"; thread.Start(); } - else - { - dialog.Dispose(); - } } catch (Exception ex) { - if (dialog != null) - { - dialog.Dispose(); - } - - dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, ""); - - dialog.Text = "Parsing Firmware Failed."; - - dialog.SecondaryText = "An error occured while parsing firmware. Please check the logs for more info."; - - Logger.Error?.Print(LogClass.Application, ex.Message); - - dialog.Run(); - dialog.Dispose(); + GtkDialog.CreateErrorDialog(ex.Message); } } else @@ -1184,11 +1036,35 @@ namespace Ryujinx.Ui } } + private void RefreshFirmwareLabel() + { + SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion(); + + Application.Invoke(delegate + { + _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; + }); + } + + private void HandleRelaunch() + { + if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart) + { + _userChannelPersistence.ShouldRestart = false; + + LoadApplication(_currentEmulatedGamePath); + } + else + { + // otherwise, clear state. + _userChannelPersistence = new UserChannelPersistence(); + _currentEmulatedGamePath = null; + } + } + private void FullScreen_Toggled(object sender, EventArgs args) { - bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen); - - if (!fullScreenToggled) + if (!Window.State.HasFlag(Gdk.WindowState.Fullscreen)) { Fullscreen(); @@ -1211,13 +1087,15 @@ namespace Ryujinx.Ui private void Settings_Pressed(object sender, EventArgs args) { - SettingsWindow settingsWin = new SettingsWindow(_virtualFileSystem, _contentManager); - settingsWin.Show(); + new SettingsWindow(this, _virtualFileSystem, _contentManager).Show(); } private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args) { - _emulationContext.System.SimulateWakeUpMessage(); + if (_emulationContext != null) + { + _emulationContext.System.SimulateWakeUpMessage(); + } } private void Update_Pressed(object sender, EventArgs args) @@ -1230,8 +1108,7 @@ namespace Ryujinx.Ui private void About_Pressed(object sender, EventArgs args) { - AboutWindow aboutWin = new AboutWindow(); - aboutWin.Show(); + new AboutWindow().Show(); } private void Fav_Toggled(object sender, EventArgs args) @@ -1318,113 +1195,5 @@ namespace Ryujinx.Ui { UpdateGameTable(); } - - private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b) - { - string aValue = model.GetValue(a, 5).ToString(); - string bValue = model.GetValue(b, 5).ToString(); - - if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "mins") - { - aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 60).ToString(); - } - else if (aValue.Length > 3 && aValue.Substring(aValue.Length - 3) == "hrs") - { - aValue = (float.Parse(aValue.Substring(0, aValue.Length - 4)) * 3600).ToString(); - } - else if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "days") - { - aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 86400).ToString(); - } - else - { - aValue = aValue.Substring(0, aValue.Length - 1); - } - - if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "mins") - { - bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 60).ToString(); - } - else if (bValue.Length > 3 && bValue.Substring(bValue.Length - 3) == "hrs") - { - bValue = (float.Parse(bValue.Substring(0, bValue.Length - 4)) * 3600).ToString(); - } - else if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "days") - { - bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 86400).ToString(); - } - else - { - bValue = bValue.Substring(0, bValue.Length - 1); - } - - if (float.Parse(aValue) > float.Parse(bValue)) - { - return -1; - } - else if (float.Parse(bValue) > float.Parse(aValue)) - { - return 1; - } - else - { - return 0; - } - } - - private static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b) - { - string aValue = model.GetValue(a, 6).ToString(); - string bValue = model.GetValue(b, 6).ToString(); - - if (aValue == "Never") - { - aValue = DateTime.UnixEpoch.ToString(); - } - - if (bValue == "Never") - { - bValue = DateTime.UnixEpoch.ToString(); - } - - return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue)); - } - - private static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b) - { - string aValue = model.GetValue(a, 8).ToString(); - string bValue = model.GetValue(b, 8).ToString(); - - if (aValue.Substring(aValue.Length - 2) == "GB") - { - aValue = (float.Parse(aValue[0..^2]) * 1024).ToString(); - } - else - { - aValue = aValue[0..^2]; - } - - if (bValue.Substring(bValue.Length - 2) == "GB") - { - bValue = (float.Parse(bValue[0..^2]) * 1024).ToString(); - } - else - { - bValue = bValue[0..^2]; - } - - if (float.Parse(aValue) > float.Parse(bValue)) - { - return -1; - } - else if (float.Parse(bValue) > float.Parse(aValue)) - { - return 1; - } - else - { - return 0; - } - } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/Migration.cs b/Ryujinx/Ui/Migration.cs deleted file mode 100644 index a7a49105..00000000 --- a/Ryujinx/Ui/Migration.cs +++ /dev/null @@ -1,189 +0,0 @@ -using Gtk; -using LibHac; -using Ryujinx.Common.Configuration; -using Ryujinx.HLE.FileSystem; -using System; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace Ryujinx.Ui -{ - internal class Migration - { - private VirtualFileSystem _virtualFileSystem; - - public Migration(VirtualFileSystem virtualFileSystem) - { - _virtualFileSystem = virtualFileSystem; - } - - public static bool PromptIfMigrationNeededForStartup(Window parentWindow, out bool isMigrationNeeded) - { - if (!IsMigrationNeeded()) - { - isMigrationNeeded = false; - - return true; - } - - isMigrationNeeded = true; - - int dialogResponse; - - using (MessageDialog dialog = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Question, - ButtonsType.YesNo, "What's this?")) - { - dialog.Title = "Data Migration Needed"; - dialog.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - dialog.Text = - "The folder structure of Ryujinx's RyuFs folder has been updated and renamed to \"Ryujinx\". " + - "Your RyuFs folder must be copied and migrated to the new \"Ryujinx\" structure. Would you like to do the migration now?\n\n" + - "Select \"Yes\" to automatically perform the migration. Your old RyuFs folder will remain as it is.\n\n" + - "Selecting \"No\" will exit Ryujinx without changing anything."; - - dialogResponse = dialog.Run(); - } - - return dialogResponse == (int)ResponseType.Yes; - } - - public static bool DoMigrationForStartup(MainWindow parentWindow, VirtualFileSystem virtualFileSystem) - { - try - { - Migration migration = new Migration(virtualFileSystem); - int saveCount = migration.Migrate(); - - using MessageDialog dialogSuccess = new MessageDialog(parentWindow, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, null) - { - Title = "Migration Success", - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), - Text = $"Data migration was successful. {saveCount} saves were migrated.", - }; - - dialogSuccess.Run(); - - return true; - } - catch (HorizonResultException ex) - { - GtkDialog.CreateErrorDialog(ex.Message); - - return false; - } - } - - // Returns the number of saves migrated - public int Migrate() - { - // Make sure FsClient is initialized - _virtualFileSystem.Reload(); - - string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - - string oldBasePath = Path.Combine(appDataPath, "RyuFs"); - string newBasePath = Path.Combine(appDataPath, "Ryujinx"); - - string oldSaveDir = Path.Combine(oldBasePath, "nand/user/save"); - - CopyRyuFs(oldBasePath, newBasePath); - - SaveImporter importer = new SaveImporter(oldSaveDir, _virtualFileSystem.FsClient); - - return importer.Import(); - } - - private static void CopyRyuFs(string oldPath, string newPath) - { - Directory.CreateDirectory(newPath); - - CopyExcept(oldPath, newPath, "nand", "bis", "sdmc", "sdcard"); - - string oldNandPath = Path.Combine(oldPath, "nand"); - string newNandPath = Path.Combine(newPath, "bis"); - - CopyExcept(oldNandPath, newNandPath, "system", "user"); - - string oldSdPath = Path.Combine(oldPath, "sdmc"); - string newSdPath = Path.Combine(newPath, "sdcard"); - - CopyDirectory(oldSdPath, newSdPath); - - string oldSystemPath = Path.Combine(oldNandPath, "system"); - string newSystemPath = Path.Combine(newNandPath, "system"); - - CopyExcept(oldSystemPath, newSystemPath, "save"); - - string oldUserPath = Path.Combine(oldNandPath, "user"); - string newUserPath = Path.Combine(newNandPath, "user"); - - CopyExcept(oldUserPath, newUserPath, "save"); - } - - private static void CopyExcept(string srcPath, string dstPath, params string[] exclude) - { - exclude = exclude.Select(x => x.ToLowerInvariant()).ToArray(); - - DirectoryInfo srcDir = new DirectoryInfo(srcPath); - - if (!srcDir.Exists) - { - return; - } - - Directory.CreateDirectory(dstPath); - - foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories()) - { - if (exclude.Contains(subDir.Name.ToLowerInvariant())) - { - continue; - } - - CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name)); - } - - foreach (FileInfo file in srcDir.EnumerateFiles()) - { - file.CopyTo(Path.Combine(dstPath, file.Name)); - } - } - - private static void CopyDirectory(string srcPath, string dstPath) - { - Directory.CreateDirectory(dstPath); - - DirectoryInfo srcDir = new DirectoryInfo(srcPath); - - if (!srcDir.Exists) - { - return; - } - - Directory.CreateDirectory(dstPath); - - foreach (DirectoryInfo subDir in srcDir.EnumerateDirectories()) - { - CopyDirectory(subDir.FullName, Path.Combine(dstPath, subDir.Name)); - } - - foreach (FileInfo file in srcDir.EnumerateFiles()) - { - file.CopyTo(Path.Combine(dstPath, file.Name)); - } - } - - public static bool IsMigrationNeeded() - { - if (AppDataManager.IsCustomBasePath) return false; - - string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - - string oldBasePath = Path.Combine(appDataPath, "RyuFs"); - string newBasePath = Path.Combine(appDataPath, "Ryujinx"); - - return Directory.Exists(oldBasePath) && !Directory.Exists(newBasePath); - } - } -} diff --git a/Ryujinx/Ui/assets/JoyConLeft.svg b/Ryujinx/Ui/Resources/Controller_JoyConLeft.svg similarity index 100% rename from Ryujinx/Ui/assets/JoyConLeft.svg rename to Ryujinx/Ui/Resources/Controller_JoyConLeft.svg diff --git a/Ryujinx/Ui/assets/JoyConPair.svg b/Ryujinx/Ui/Resources/Controller_JoyConPair.svg similarity index 100% rename from Ryujinx/Ui/assets/JoyConPair.svg rename to Ryujinx/Ui/Resources/Controller_JoyConPair.svg diff --git a/Ryujinx/Ui/assets/JoyConRight.svg b/Ryujinx/Ui/Resources/Controller_JoyConRight.svg similarity index 100% rename from Ryujinx/Ui/assets/JoyConRight.svg rename to Ryujinx/Ui/Resources/Controller_JoyConRight.svg diff --git a/Ryujinx/Ui/assets/ProCon.svg b/Ryujinx/Ui/Resources/Controller_ProCon.svg similarity index 100% rename from Ryujinx/Ui/assets/ProCon.svg rename to Ryujinx/Ui/Resources/Controller_ProCon.svg diff --git a/Ryujinx/Ui/assets/NCAIcon.png b/Ryujinx/Ui/Resources/Icon_NCA.png similarity index 100% rename from Ryujinx/Ui/assets/NCAIcon.png rename to Ryujinx/Ui/Resources/Icon_NCA.png diff --git a/Ryujinx/Ui/assets/NROIcon.png b/Ryujinx/Ui/Resources/Icon_NRO.png similarity index 100% rename from Ryujinx/Ui/assets/NROIcon.png rename to Ryujinx/Ui/Resources/Icon_NRO.png diff --git a/Ryujinx/Ui/assets/NSOIcon.png b/Ryujinx/Ui/Resources/Icon_NSO.png similarity index 100% rename from Ryujinx/Ui/assets/NSOIcon.png rename to Ryujinx/Ui/Resources/Icon_NSO.png diff --git a/Ryujinx/Ui/assets/NSPIcon.png b/Ryujinx/Ui/Resources/Icon_NSP.png similarity index 100% rename from Ryujinx/Ui/assets/NSPIcon.png rename to Ryujinx/Ui/Resources/Icon_NSP.png diff --git a/Ryujinx/Ui/assets/XCIIcon.png b/Ryujinx/Ui/Resources/Icon_XCI.png similarity index 100% rename from Ryujinx/Ui/assets/XCIIcon.png rename to Ryujinx/Ui/Resources/Icon_XCI.png diff --git a/Ryujinx/Ui/assets/DiscordLogo.png b/Ryujinx/Ui/Resources/Logo_Discord.png similarity index 100% rename from Ryujinx/Ui/assets/DiscordLogo.png rename to Ryujinx/Ui/Resources/Logo_Discord.png diff --git a/Ryujinx/Ui/assets/GitHubLogo.png b/Ryujinx/Ui/Resources/Logo_GitHub.png similarity index 100% rename from Ryujinx/Ui/assets/GitHubLogo.png rename to Ryujinx/Ui/Resources/Logo_GitHub.png diff --git a/Ryujinx/Ui/assets/PatreonLogo.png b/Ryujinx/Ui/Resources/Logo_Patreon.png similarity index 100% rename from Ryujinx/Ui/assets/PatreonLogo.png rename to Ryujinx/Ui/Resources/Logo_Patreon.png diff --git a/Ryujinx/Ui/assets/Icon.png b/Ryujinx/Ui/Resources/Logo_Ryujinx.png similarity index 100% rename from Ryujinx/Ui/assets/Icon.png rename to Ryujinx/Ui/Resources/Logo_Ryujinx.png diff --git a/Ryujinx/Ui/assets/TwitterLogo.png b/Ryujinx/Ui/Resources/Logo_Twitter.png similarity index 100% rename from Ryujinx/Ui/assets/TwitterLogo.png rename to Ryujinx/Ui/Resources/Logo_Twitter.png diff --git a/Ryujinx/Ui/SaveImporter.cs b/Ryujinx/Ui/SaveImporter.cs deleted file mode 100644 index 946b6cd3..00000000 --- a/Ryujinx/Ui/SaveImporter.cs +++ /dev/null @@ -1,219 +0,0 @@ -using LibHac; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Shim; -using LibHac.FsSystem; -using LibHac.Ncm; -using Ryujinx.HLE.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; - -using ApplicationId = LibHac.Ncm.ApplicationId; - -namespace Ryujinx.Ui -{ - internal class SaveImporter - { - private FileSystemClient FsClient { get; } - private string ImportPath { get; } - - public SaveImporter(string importPath, FileSystemClient destFsClient) - { - ImportPath = importPath; - FsClient = destFsClient; - } - - // Returns the number of saves imported - public int Import() - { - return ImportSaves(FsClient, ImportPath); - } - - private static int ImportSaves(FileSystemClient fsClient, string rootSaveDir) - { - if (!Directory.Exists(rootSaveDir)) - { - return 0; - } - - SaveFinder finder = new SaveFinder(); - finder.FindSaves(rootSaveDir); - - foreach (SaveToImport save in finder.Saves) - { - Result importResult = ImportSave(fsClient, save); - - if (importResult.IsFailure()) - { - throw new HorizonResultException(importResult, $"Error importing save {save.Path}"); - } - } - - return finder.Saves.Count; - } - - private static Result ImportSave(FileSystemClient fs, SaveToImport save) - { - SaveDataAttribute key = save.Attribute; - - Result result = fs.CreateSaveData(new ApplicationId(key.ProgramId.Value), key.UserId, key.ProgramId.Value, 0, 0, 0); - if (result.IsFailure()) return result; - - bool isOldMounted = false; - bool isNewMounted = false; - - try - { - result = fs.Register("OldSave".ToU8Span(), new LocalFileSystem(save.Path)); - if (result.IsFailure()) return result; - - isOldMounted = true; - - result = fs.MountSaveData("NewSave".ToU8Span(), new ApplicationId(key.ProgramId.Value), key.UserId); - if (result.IsFailure()) return result; - - isNewMounted = true; - - result = fs.CopyDirectory("OldSave:/", "NewSave:/"); - if (result.IsFailure()) return result; - - result = fs.Commit("NewSave".ToU8Span()); - } - finally - { - if (isOldMounted) - { - fs.Unmount("OldSave".ToU8Span()); - } - - if (isNewMounted) - { - fs.Unmount("NewSave".ToU8Span()); - } - } - - return result; - } - - private class SaveFinder - { - public List Saves { get; } = new List(); - - public void FindSaves(string rootPath) - { - foreach (string subDir in Directory.EnumerateDirectories(rootPath)) - { - if (TryGetUInt64(subDir, out ulong saveDataId)) - { - SearchSaveId(subDir, saveDataId); - } - } - } - - private void SearchSaveId(string path, ulong saveDataId) - { - foreach (string subDir in Directory.EnumerateDirectories(path)) - { - if (TryGetUserId(subDir, out UserId userId)) - { - SearchUser(subDir, saveDataId, userId); - } - } - } - - private void SearchUser(string path, ulong saveDataId, UserId userId) - { - foreach (string subDir in Directory.EnumerateDirectories(path)) - { - if (TryGetUInt64(subDir, out ulong titleId) && TryGetDataPath(subDir, out string dataPath)) - { - SaveDataAttribute attribute = new SaveDataAttribute - { - Type = SaveDataType.Account, - UserId = userId, - ProgramId = new ProgramId(titleId) - }; - - SaveToImport save = new SaveToImport(dataPath, attribute); - - Saves.Add(save); - } - } - } - - private static bool TryGetDataPath(string path, out string dataPath) - { - string committedPath = Path.Combine(path, "0"); - string workingPath = Path.Combine(path, "1"); - - if (Directory.Exists(committedPath) && Directory.EnumerateFileSystemEntries(committedPath).Any()) - { - dataPath = committedPath; - return true; - } - - if (Directory.Exists(workingPath) && Directory.EnumerateFileSystemEntries(workingPath).Any()) - { - dataPath = workingPath; - return true; - } - - dataPath = default; - return false; - } - - private static bool TryGetUInt64(string path, out ulong converted) - { - string name = Path.GetFileName(path); - - if (name.Length == 16) - { - try - { - converted = Convert.ToUInt64(name, 16); - return true; - } - catch { } - } - - converted = default; - return false; - } - - private static bool TryGetUserId(string path, out UserId userId) - { - string name = Path.GetFileName(path); - - if (name.Length == 32) - { - try - { - UInt128 id = new UInt128(name); - - userId = Unsafe.As(ref id); - return true; - } - catch { } - } - - userId = default; - return false; - } - } - - private class SaveToImport - { - public string Path { get; } - public SaveDataAttribute Attribute { get; } - - public SaveToImport(string path, SaveDataAttribute attribute) - { - Path = path; - Attribute = attribute; - } - } - } -} diff --git a/Ryujinx/Ui/StatusUpdatedEventArgs.cs b/Ryujinx/Ui/StatusUpdatedEventArgs.cs index a028c8f8..c9fba77b 100644 --- a/Ryujinx/Ui/StatusUpdatedEventArgs.cs +++ b/Ryujinx/Ui/StatusUpdatedEventArgs.cs @@ -21,4 +21,4 @@ namespace Ryujinx.Ui GpuName = gpuName; } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs new file mode 100644 index 00000000..4b903d6c --- /dev/null +++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs @@ -0,0 +1,198 @@ +using Gtk; + +namespace Ryujinx.Ui.Widgets +{ + public partial class GameTableContextMenu : Menu + { + private MenuItem _openSaveUserDirMenuItem; + private MenuItem _openSaveDeviceDirMenuItem; + private MenuItem _openSaveBcatDirMenuItem; + private MenuItem _manageTitleUpdatesMenuItem; + private MenuItem _manageDlcMenuItem; + private MenuItem _openTitleModDirMenuItem; + private Menu _extractSubMenu; + private MenuItem _extractMenuItem; + private MenuItem _extractRomFsMenuItem; + private MenuItem _extractExeFsMenuItem; + private MenuItem _extractLogoMenuItem; + private Menu _manageSubMenu; + private MenuItem _manageCacheMenuItem; + private MenuItem _purgePtcCacheMenuItem; + private MenuItem _purgeShaderCacheMenuItem; + private MenuItem _openPtcDirMenuItem; + private MenuItem _openShaderCacheDirMenuItem; + + private void InitializeComponent() + { + // + // _openSaveUserDirMenuItem + // + _openSaveUserDirMenuItem = new MenuItem("Open User Save Directory") + { + TooltipText = "Open the directory which contains Application's User Saves." + }; + _openSaveUserDirMenuItem.Activated += OpenSaveUserDir_Clicked; + + // + // _openSaveDeviceDirMenuItem + // + _openSaveDeviceDirMenuItem = new MenuItem("Open Device Save Directory") + { + TooltipText = "Open the directory which contains Application's Device Saves." + }; + _openSaveDeviceDirMenuItem.Activated += OpenSaveDeviceDir_Clicked; + + // + // _openSaveBcatDirMenuItem + // + _openSaveBcatDirMenuItem = new MenuItem("Open BCAT Save Directory") + { + TooltipText = "Open the directory which contains Application's BCAT Saves." + }; + _openSaveBcatDirMenuItem.Activated += OpenSaveBcatDir_Clicked; + + // + // _manageTitleUpdatesMenuItem + // + _manageTitleUpdatesMenuItem = new MenuItem("Manage Title Updates") + { + TooltipText = "Open the Title Update management window" + }; + _manageTitleUpdatesMenuItem.Activated += ManageTitleUpdates_Clicked; + + // + // _manageDlcMenuItem + // + _manageDlcMenuItem = new MenuItem("Manage DLC") + { + TooltipText = "Open the DLC management window" + }; + _manageDlcMenuItem.Activated += ManageDlc_Clicked; + + // + // _openTitleModDirMenuItem + // + _openTitleModDirMenuItem = new MenuItem("Open Mods Directory") + { + TooltipText = "Open the directory which contains Application's Mods." + }; + _openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked; + + // + // _extractSubMenu + // + _extractSubMenu = new Menu(); + + // + // _extractMenuItem + // + _extractMenuItem = new MenuItem("Extract Data") + { + Submenu = _extractSubMenu + }; + + // + // _extractRomFsMenuItem + // + _extractRomFsMenuItem = new MenuItem("RomFS") + { + TooltipText = "Extract the RomFS section from Application's current config (including updates)." + }; + _extractRomFsMenuItem.Activated += ExtractRomFs_Clicked; + + // + // _extractExeFsMenuItem + // + _extractExeFsMenuItem = new MenuItem("ExeFS") + { + TooltipText = "Extract the ExeFS section from Application's current config (including updates)." + }; + _extractExeFsMenuItem.Activated += ExtractExeFs_Clicked; + + // + // _extractLogoMenuItem + // + _extractLogoMenuItem = new MenuItem("Logo") + { + TooltipText = "Extract the Logo section from Application's current config (including updates)." + }; + _extractLogoMenuItem.Activated += ExtractLogo_Clicked; + + // + // _manageSubMenu + // + _manageSubMenu = new Menu(); + + // + // _manageCacheMenuItem + // + _manageCacheMenuItem = new MenuItem("Cache Management") + { + Submenu = _manageSubMenu + }; + + // + // _purgePtcCacheMenuItem + // + _purgePtcCacheMenuItem = new MenuItem("Purge PPTC Cache") + { + TooltipText = "Delete the Application's PPTC cache." + }; + _purgePtcCacheMenuItem.Activated += PurgePtcCache_Clicked; + + // + // _purgeShaderCacheMenuItem + // + _purgeShaderCacheMenuItem = new MenuItem("Purge Shader Cache") + { + TooltipText = "Delete the Application's shader cache." + }; + _purgeShaderCacheMenuItem.Activated += PurgeShaderCache_Clicked; + + // + // _openPtcDirMenuItem + // + _openPtcDirMenuItem = new MenuItem("Open PPTC Directory") + { + TooltipText = "Open the directory which contains the Application's PPTC cache." + }; + _openPtcDirMenuItem.Activated += OpenPtcDir_Clicked; + + // + // _openShaderCacheDirMenuItem + // + _openShaderCacheDirMenuItem = new MenuItem("Open Shader Cache Directory") + { + TooltipText = "Open the directory which contains the Application's shader cache." + }; + _openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked; + + ShowComponent(); + } + + private void ShowComponent() + { + _extractSubMenu.Append(_extractExeFsMenuItem); + _extractSubMenu.Append(_extractRomFsMenuItem); + _extractSubMenu.Append(_extractLogoMenuItem); + + _manageSubMenu.Append(_purgePtcCacheMenuItem); + _manageSubMenu.Append(_purgeShaderCacheMenuItem); + _manageSubMenu.Append(_openPtcDirMenuItem); + _manageSubMenu.Append(_openShaderCacheDirMenuItem); + + Add(_openSaveUserDirMenuItem); + Add(_openSaveDeviceDirMenuItem); + Add(_openSaveBcatDirMenuItem); + Add(new SeparatorMenuItem()); + Add(_manageTitleUpdatesMenuItem); + Add(_manageDlcMenuItem); + Add(_openTitleModDirMenuItem); + Add(new SeparatorMenuItem()); + Add(_manageCacheMenuItem); + Add(_extractMenuItem); + + ShowAll(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs similarity index 56% rename from Ryujinx/Ui/GameTableContextMenu.cs rename to Ryujinx/Ui/Widgets/GameTableContextMenu.cs index 58c40791..5ee8baa3 100644 --- a/Ryujinx/Ui/GameTableContextMenu.cs +++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs @@ -11,159 +11,66 @@ using LibHac.Ncm; using LibHac.Ns; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; +using Ryujinx.Ui.Helper; +using Ryujinx.Ui.Windows; using System; using System.Buffers; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; -using System.Reflection; using System.Threading; using static LibHac.Fs.ApplicationSaveDataManagement; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Widgets { - public class GameTableContextMenu : Menu + public partial class GameTableContextMenu : Menu { - private readonly ListStore _gameTableStore; - private readonly TreeIter _rowIter; - private readonly VirtualFileSystem _virtualFileSystem; - + private readonly MainWindow _parent; + private readonly VirtualFileSystem _virtualFileSystem; private readonly BlitStruct _controlData; + private readonly string _titleFilePath; + private readonly string _titleName; + private readonly string _titleIdText; + private readonly ulong _titleId; + private MessageDialog _dialog; private bool _cancel; - public GameTableContextMenu(ListStore gameTableStore, BlitStruct controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) + public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleFilePath, string titleName, string titleId, BlitStruct controlData) { - _gameTableStore = gameTableStore; - _rowIter = rowIter; + _parent = parent; + + InitializeComponent(); + _virtualFileSystem = virtualFileSystem; + _titleFilePath = titleFilePath; + _titleName = titleName; + _titleIdText = titleId; _controlData = controlData; - MenuItem openSaveUserDir = new MenuItem("Open User Save Directory") + if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId)) { - Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0, - TooltipText = "Open the directory which contains Application's User Saves." - }; + GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id"); - MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory") - { - Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0, - TooltipText = "Open the directory which contains Application's Device Saves." - }; + return; + } - MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory") - { - Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0, - TooltipText = "Open the directory which contains Application's BCAT Saves." - }; + _openSaveUserDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; + _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; + _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; - MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates") - { - TooltipText = "Open the Title Update management window" - }; + string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower(); + bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; - MenuItem manageDlc = new MenuItem("Manage DLC") - { - TooltipText = "Open the DLC management window" - }; + _extractRomFsMenuItem.Sensitive = hasNca; + _extractExeFsMenuItem.Sensitive = hasNca; + _extractLogoMenuItem.Sensitive = hasNca; - MenuItem openTitleModDir = new MenuItem("Open Mods Directory") - { - TooltipText = "Open the directory which contains Application's Mods." - }; - - string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower(); - bool hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci"; - - MenuItem extractMenu = new MenuItem("Extract Data"); - - MenuItem extractRomFs = new MenuItem("RomFS") - { - Sensitive = hasNca, - TooltipText = "Extract the RomFS section from Application's current config (including updates)." - }; - - MenuItem extractExeFs = new MenuItem("ExeFS") - { - Sensitive = hasNca, - TooltipText = "Extract the ExeFS section from Application's current config (including updates)." - }; - - MenuItem extractLogo = new MenuItem("Logo") - { - Sensitive = hasNca, - TooltipText = "Extract the Logo section from Application's current config (including updates)." - }; - - Menu extractSubMenu = new Menu(); - - extractSubMenu.Append(extractExeFs); - extractSubMenu.Append(extractRomFs); - extractSubMenu.Append(extractLogo); - - extractMenu.Submenu = extractSubMenu; - - MenuItem managePtcMenu = new MenuItem("Cache Management"); - - MenuItem purgePtcCache = new MenuItem("Purge PPTC Cache") - { - TooltipText = "Delete the Application's PPTC cache." - }; - - MenuItem purgeShaderCache = new MenuItem("Purge Shader Cache") - { - TooltipText = "Delete the Application's shader cache." - }; - - MenuItem openPtcDir = new MenuItem("Open PPTC Directory") - { - TooltipText = "Open the directory which contains the Application's PPTC cache." - }; - - MenuItem openShaderCacheDir = new MenuItem("Open Shader Cache Directory") - { - TooltipText = "Open the directory which contains the Application's shader cache." - }; - - Menu manageSubMenu = new Menu(); - - manageSubMenu.Append(purgePtcCache); - manageSubMenu.Append(purgeShaderCache); - manageSubMenu.Append(openPtcDir); - manageSubMenu.Append(openShaderCacheDir); - - managePtcMenu.Submenu = manageSubMenu; - - openSaveUserDir.Activated += OpenSaveUserDir_Clicked; - openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked; - openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked; - manageTitleUpdates.Activated += ManageTitleUpdates_Clicked; - manageDlc.Activated += ManageDlc_Clicked; - openTitleModDir.Activated += OpenTitleModDir_Clicked; - extractRomFs.Activated += ExtractRomFs_Clicked; - extractExeFs.Activated += ExtractExeFs_Clicked; - extractLogo.Activated += ExtractLogo_Clicked; - purgePtcCache.Activated += PurgePtcCache_Clicked; - purgeShaderCache.Activated += PurgeShaderCache_Clicked; - openPtcDir.Activated += OpenPtcDir_Clicked; - openShaderCacheDir.Activated += OpenShaderCacheDir_Clicked; - - this.Add(openSaveUserDir); - this.Add(openSaveDeviceDir); - this.Add(openSaveBcatDir); - this.Add(new SeparatorMenuItem()); - this.Add(manageTitleUpdates); - this.Add(manageDlc); - this.Add(openTitleModDir); - this.Add(new SeparatorMenuItem()); - this.Add(managePtcMenu); - this.Add(extractMenu); + PopupAtPointer(null); } private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct controlHolder, SaveDataFilter filter, out ulong saveDataId) @@ -178,7 +85,6 @@ namespace Ryujinx.Ui using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) { Title = "Ryujinx", - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), Text = $"There is no savedata for {titleName} [{titleId:x16}]", SecondaryText = "Would you like to create savedata for this game?", WindowPosition = WindowPosition.Center @@ -191,7 +97,7 @@ namespace Ryujinx.Ui ref ApplicationControlProperty control = ref controlHolder.Value; - if (LibHac.Utilities.IsEmpty(controlHolder.ByteSpan)) + if (Utilities.IsEmpty(controlHolder.ByteSpan)) { // If the current application doesn't have a loaded control property, create a dummy one // and set the savedata sizes so a user savedata will be created. @@ -201,11 +107,10 @@ namespace Ryujinx.Ui control.UserAccountSaveDataSize = 0x4000; control.UserAccountSaveDataJournalSize = 0x4000; - Logger.Warning?.Print(LogClass.Application, - "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); } - Uid user = new Uid(1, 0); + Uid user = new Uid(1, 0); // TODO: Remove Hardcoded value. result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user); @@ -232,8 +137,15 @@ namespace Ryujinx.Ui return false; } - private string GetSaveDataDirectory(ulong saveDataId) + private void OpenSaveDir(SaveDataFilter saveDataFilter) { + saveDataFilter.SetProgramId(new ProgramId(_titleId)); + + if (!TryFindSaveData(_titleName, _titleId, _controlData, saveDataFilter, out ulong saveDataId)) + { + return; + } + string saveRootPath = System.IO.Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); if (!Directory.Exists(saveRootPath)) @@ -248,17 +160,19 @@ namespace Ryujinx.Ui // If the committed directory exists, that path will be loaded the next time the savedata is mounted if (Directory.Exists(committedPath)) { - return committedPath; + OpenHelper.OpenFolder(committedPath); } - - // If the working directory exists and the committed directory doesn't, - // the working directory will be loaded the next time the savedata is mounted - if (!Directory.Exists(workingPath)) + else { - Directory.CreateDirectory(workingPath); - } + // If the working directory exists and the committed directory doesn't, + // the working directory will be loaded the next time the savedata is mounted + if (!Directory.Exists(workingPath)) + { + Directory.CreateDirectory(workingPath); + } - return workingPath; + OpenHelper.OpenFolder(workingPath); + } } private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) @@ -266,24 +180,21 @@ namespace Ryujinx.Ui FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to extract into", null, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Extract", ResponseType.Accept); fileChooser.SetPosition(WindowPosition.Center); - int response = fileChooser.Run(); - string destination = fileChooser.Filename; + ResponseType response = (ResponseType)fileChooser.Run(); + string destination = fileChooser.Filename; fileChooser.Dispose(); - if (response == (int)ResponseType.Accept) + if (response == ResponseType.Accept) { Thread extractorThread = new Thread(() => { - string sourceFile = _gameTableStore.GetValue(_rowIter, 9).ToString(); - Gtk.Application.Invoke(delegate { _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) { Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), - SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(sourceFile)}...", + SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", WindowPosition = WindowPosition.Center }; @@ -295,18 +206,18 @@ namespace Ryujinx.Ui } }); - using (FileStream file = new FileStream(sourceFile, FileMode.Open, FileAccess.Read)) + using (FileStream file = new FileStream(_titleFilePath, FileMode.Open, FileAccess.Read)) { Nca mainNca = null; Nca patchNca = null; - if ((System.IO.Path.GetExtension(sourceFile).ToLower() == ".nsp") || - (System.IO.Path.GetExtension(sourceFile).ToLower() == ".pfs0") || - (System.IO.Path.GetExtension(sourceFile).ToLower() == ".xci")) + if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") || + (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") || + (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci")) { PartitionFileSystem pfs; - if (System.IO.Path.GetExtension(sourceFile) == ".xci") + if (System.IO.Path.GetExtension(_titleFilePath) == ".xci") { Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); @@ -338,7 +249,7 @@ namespace Ryujinx.Ui } } } - else if (System.IO.Path.GetExtension(sourceFile).ToLower() == ".nca") + else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca") { mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage()); } @@ -355,7 +266,6 @@ namespace Ryujinx.Ui return; } - (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); if (updatePatchNca != null) @@ -370,8 +280,8 @@ namespace Ryujinx.Ui FileSystemClient fsClient = _virtualFileSystem.FsClient; - string source = DateTime.Now.ToFileTime().ToString().Substring(10); - string output = DateTime.Now.ToFileTime().ToString().Substring(10); + string source = DateTime.Now.ToFileTime().ToString()[10..]; + string output = DateTime.Now.ToFileTime().ToString()[10..]; fsClient.Register(source.ToU8Span(), ncaFileSystem); fsClient.Register(output.ToU8Span(), new LocalFileSystem(destination)); @@ -400,7 +310,6 @@ namespace Ryujinx.Ui MessageDialog dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) { Title = "Ryujinx - NCA Section Extractor", - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), SecondaryText = "Extraction has completed successfully.", WindowPosition = WindowPosition.Center }; @@ -510,111 +419,49 @@ namespace Ryujinx.Ui return Result.Success; } + // // Events + // private void OpenSaveUserDir_Clicked(object sender, EventArgs args) { - string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + SaveDataFilter saveDataFilter = new SaveDataFilter(); + saveDataFilter.SetUserId(new UserId(1, 0)); // TODO: Remove Hardcoded value. - if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) - { - GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID"); - - return; - } - - SaveDataFilter filter = new SaveDataFilter(); - filter.SetUserId(new UserId(1, 0)); - - OpenSaveDir(titleName, titleIdNumber, filter); - } - - private void OpenSaveDir(string titleName, ulong titleId, SaveDataFilter filter) - { - filter.SetProgramId(new ProgramId(titleId)); - - if (!TryFindSaveData(titleName, titleId, _controlData, filter, out ulong saveDataId)) - { - return; - } - - string saveDir = GetSaveDataDirectory(saveDataId); - - Process.Start(new ProcessStartInfo - { - FileName = saveDir, - UseShellExecute = true, - Verb = "open" - }); + OpenSaveDir(saveDataFilter); } private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args) { - string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + SaveDataFilter saveDataFilter = new SaveDataFilter(); + saveDataFilter.SetSaveDataType(SaveDataType.Device); - if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) - { - GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID"); - - return; - } - - SaveDataFilter filter = new SaveDataFilter(); - filter.SetSaveDataType(SaveDataType.Device); - - OpenSaveDir(titleName, titleIdNumber, filter); + OpenSaveDir(saveDataFilter); } private void OpenSaveBcatDir_Clicked(object sender, EventArgs args) { - string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + SaveDataFilter saveDataFilter = new SaveDataFilter(); + saveDataFilter.SetSaveDataType(SaveDataType.Bcat); - if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) - { - GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID"); - - return; - } - - SaveDataFilter filter = new SaveDataFilter(); - filter.SetSaveDataType(SaveDataType.Bcat); - - OpenSaveDir(titleName, titleIdNumber, filter); + OpenSaveDir(saveDataFilter); } private void ManageTitleUpdates_Clicked(object sender, EventArgs args) { - string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); - - TitleUpdateWindow titleUpdateWindow = new TitleUpdateWindow(titleId, titleName, _virtualFileSystem); - titleUpdateWindow.Show(); + new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show(); } private void ManageDlc_Clicked(object sender, EventArgs args) { - string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); - - DlcWindow dlcWindow = new DlcWindow(titleId, titleName, _virtualFileSystem); - dlcWindow.Show(); + new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show(); } private void OpenTitleModDir_Clicked(object sender, EventArgs args) { - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + string modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath(); + string titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, _titleIdText); - var modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath(); - var titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId); - - Process.Start(new ProcessStartInfo - { - FileName = titleModsPath, - UseShellExecute = true, - Verb = "open" - }); + OpenHelper.OpenFolder(titleModsPath); } private void ExtractRomFs_Clicked(object sender, EventArgs args) @@ -634,8 +481,7 @@ namespace Ryujinx.Ui private void OpenPtcDir_Clicked(object sender, EventArgs args) { - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); - string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu"); + string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu"); string mainPath = System.IO.Path.Combine(ptcDir, "0"); string backupPath = System.IO.Path.Combine(ptcDir, "1"); @@ -646,52 +492,40 @@ namespace Ryujinx.Ui Directory.CreateDirectory(mainPath); Directory.CreateDirectory(backupPath); } - - Process.Start(new ProcessStartInfo - { - FileName = ptcDir, - UseShellExecute = true, - Verb = "open" - }); + + OpenHelper.OpenFolder(ptcDir); } private void OpenShaderCacheDir_Clicked(object sender, EventArgs args) { - string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); - string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader"); + string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"); if (!Directory.Exists(shaderCacheDir)) { Directory.CreateDirectory(shaderCacheDir); } - Process.Start(new ProcessStartInfo - { - FileName = shaderCacheDir, - UseShellExecute = true, - Verb = "open" - }); + OpenHelper.OpenFolder(shaderCacheDir); } private void PurgePtcCache_Clicked(object sender, EventArgs args) { - string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n"); - string titleId = tableEntry[1].ToLower(); - - DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "0")); - DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "1")); - - MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Warning", - Text = $"You are about to delete the PPTC cache for '{tableEntry[0]}'. Are you sure you want to proceed?", - WindowPosition = WindowPosition.Center - }; + DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0")); + DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1")); + + MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the PPTC cache for :\n\n{_titleName}\n\nAre you sure you want to proceed?"); List cacheFiles = new List(); - if (mainDir.Exists) { cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); } - if (backupDir.Exists) { cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); } + if (mainDir.Exists) + { + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + } + + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + } if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) { @@ -703,7 +537,7 @@ namespace Ryujinx.Ui } catch(Exception e) { - Logger.Error?.Print(LogClass.Application, $"Error purging PPTC cache {file.Name}: {e}"); + GtkDialog.CreateErrorDialog($"Error purging PPTC cache {file.Name}: {e}"); } } } @@ -713,21 +547,16 @@ namespace Ryujinx.Ui private void PurgeShaderCache_Clicked(object sender, EventArgs args) { - string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n"); - string titleId = tableEntry[1].ToLower(); + DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader")); - DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader")); - - MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) - { - Title = "Ryujinx - Warning", - Text = $"You are about to delete the shader cache for '{tableEntry[0]}'. Are you sure you want to proceed?", - WindowPosition = WindowPosition.Center - }; + MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n{_titleName}\n\nAre you sure you want to proceed?"); List cacheDirectory = new List(); - if (shaderCacheDir.Exists) { cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*")); } + if (shaderCacheDir.Exists) + { + cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*")); + } if (cacheDirectory.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) { @@ -739,7 +568,7 @@ namespace Ryujinx.Ui } catch (Exception e) { - Logger.Error?.Print(LogClass.Application, $"Error purging shader cache {directory.Name}: {e}"); + GtkDialog.CreateErrorDialog($"Error purging shader cache {directory.Name}: {e}"); } } } @@ -747,4 +576,4 @@ namespace Ryujinx.Ui warningDialog.Dispose(); } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/GtkDialog.cs b/Ryujinx/Ui/Widgets/GtkDialog.cs similarity index 63% rename from Ryujinx/Ui/GtkDialog.cs rename to Ryujinx/Ui/Widgets/GtkDialog.cs index f86b7016..e603383a 100644 --- a/Ryujinx/Ui/GtkDialog.cs +++ b/Ryujinx/Ui/Widgets/GtkDialog.cs @@ -1,7 +1,7 @@ using Gtk; -using System.Reflection; +using Ryujinx.Common.Logging; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Widgets { internal class GtkDialog : MessageDialog { @@ -10,14 +10,15 @@ namespace Ryujinx.Ui private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) : base(null, DialogFlags.Modal, messageType, buttonsType, null) { - Title = title; - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - Text = mainText; - SecondaryText = secondaryText; - WindowPosition = WindowPosition.Center; - Response += GtkDialog_Response; + Title = title; + Text = mainText; + SecondaryText = secondaryText; + WindowPosition = WindowPosition.Center; + SecondaryUseMarkup = true; - SetSizeRequest(100, 20); + Response += GtkDialog_Response; + + SetSizeRequest(200, 20); } private void GtkDialog_Response(object sender, ResponseArgs args) @@ -25,9 +26,19 @@ namespace Ryujinx.Ui Dispose(); } - internal static void CreateInfoDialog(string title, string mainText, string secondaryText) + internal static void CreateInfoDialog(string mainText, string secondaryText) { - new GtkDialog(title, mainText, secondaryText, MessageType.Info).Run(); + new GtkDialog("Ryujinx - Info", mainText, secondaryText, MessageType.Info).Run(); + } + + internal static void CreateUpdaterInfoDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Updater", mainText, secondaryText, MessageType.Info).Run(); + } + + internal static MessageDialog CreateWaitingDialog(string mainText, string secondaryText) + { + return new GtkDialog("Ryujinx - Waiting", mainText, secondaryText, MessageType.Info, ButtonsType.None); } internal static void CreateWarningDialog(string mainText, string secondaryText) @@ -37,6 +48,8 @@ namespace Ryujinx.Ui internal static void CreateErrorDialog(string errorMessage) { + Logger.Error?.Print(LogClass.Application, errorMessage); + new GtkDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage, MessageType.Error).Run(); } @@ -48,10 +61,14 @@ namespace Ryujinx.Ui internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText) { if (_isChoiceDialogOpen) + { return false; + } _isChoiceDialogOpen = true; + ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run(); + _isChoiceDialogOpen = false; return response == ResponseType.Yes; diff --git a/Ryujinx/Ui/ProfileDialog.cs b/Ryujinx/Ui/Widgets/ProfileDialog.cs similarity index 82% rename from Ryujinx/Ui/ProfileDialog.cs rename to Ryujinx/Ui/Widgets/ProfileDialog.cs index 2b26cbef..86667572 100644 --- a/Ryujinx/Ui/ProfileDialog.cs +++ b/Ryujinx/Ui/Widgets/ProfileDialog.cs @@ -1,10 +1,9 @@ using Gtk; using System; -using System.Reflection; using GUI = Gtk.Builder.ObjectAttribute; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Widgets { public class ProfileDialog : Dialog { @@ -15,18 +14,16 @@ namespace Ryujinx.Ui [GUI] Label _errorMessage; #pragma warning restore CS0649, IDE0044 - public ProfileDialog() : this(new Builder("Ryujinx.Ui.ProfileDialog.glade")) { } + public ProfileDialog() : this(new Builder("Ryujinx.Ui.Widgets.ProfileDialog.glade")) { } private ProfileDialog(Builder builder) : base(builder.GetObject("_profileDialog").Handle) { builder.Autoconnect(this); - - Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); } private void OkToggle_Activated(object sender, EventArgs args) { - ((ToggleButton)sender).SetStateFlags(0, true); + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); bool validFileName = true; diff --git a/Ryujinx/Ui/ProfileDialog.glade b/Ryujinx/Ui/Widgets/ProfileDialog.glade similarity index 100% rename from Ryujinx/Ui/ProfileDialog.glade rename to Ryujinx/Ui/Widgets/ProfileDialog.glade diff --git a/Ryujinx/Ui/Diagnostic/UserError.cs b/Ryujinx/Ui/Widgets/UserError.cs similarity index 96% rename from Ryujinx/Ui/Diagnostic/UserError.cs rename to Ryujinx/Ui/Widgets/UserError.cs index eaa1bc83..08695571 100644 --- a/Ryujinx/Ui/Diagnostic/UserError.cs +++ b/Ryujinx/Ui/Widgets/UserError.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui.Diagnostic +namespace Ryujinx.Ui.Widgets { /// /// Represent a common error that could be reported to the user by the emulator. @@ -36,4 +36,4 @@ /// Unknown = 0xDEAD } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/Widgets/UserErrorDialog.cs b/Ryujinx/Ui/Widgets/UserErrorDialog.cs new file mode 100644 index 00000000..095b88a3 --- /dev/null +++ b/Ryujinx/Ui/Widgets/UserErrorDialog.cs @@ -0,0 +1,122 @@ +using Gtk; +using Ryujinx.Ui.Helper; + +namespace Ryujinx.Ui.Widgets +{ + internal class UserErrorDialog : MessageDialog + { + private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; + private const int OkResponseId = 0; + private const int SetupGuideResponseId = 1; + + private readonly UserError _userError; + + private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null) + { + _userError = error; + + WindowPosition = WindowPosition.Center; + SecondaryUseMarkup = true; + + Response += UserErrorDialog_Response; + + SetSizeRequest(120, 50); + + AddButton("OK", OkResponseId); + + bool isInSetupGuide = IsCoveredBySetupGuide(error); + + if (isInSetupGuide) + { + AddButton("Open the Setup Guide", SetupGuideResponseId); + } + + string errorCode = GetErrorCode(error); + + SecondaryUseMarkup = true; + + Title = $"Ryujinx error ({errorCode})"; + Text = $"{errorCode}: {GetErrorTitle(error)}"; + SecondaryText = GetErrorDescription(error); + + if (isInSetupGuide) + { + SecondaryText += "\nFor more information on how to fix this error, follow our Setup Guide."; + } + } + + private string GetErrorCode(UserError error) + { + return $"RYU-{(uint)error:X4}"; + } + + private string GetErrorTitle(UserError error) + { + return error switch + { + UserError.NoKeys => "Keys not found", + UserError.NoFirmware => "Firmware not found", + UserError.FirmwareParsingFailed => "Firmware parsing error", + UserError.ApplicationNotFound => "Application not found", + UserError.Unknown => "Unknown error", + _ => "Undefined error", + }; + } + + private string GetErrorDescription(UserError error) + { + return error switch + { + UserError.NoKeys => "Ryujinx was unable to find your 'prod.keys' file", + UserError.NoFirmware => "Ryujinx was unable to find any firmwares installed", + UserError.FirmwareParsingFailed => "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + UserError.ApplicationNotFound => "Ryujinx couldn't find a valid application at the given path.", + UserError.Unknown => "An unknown error occured!", + _ => "An undefined error occured! This shouldn't happen, please contact a dev!", + }; + } + + private static bool IsCoveredBySetupGuide(UserError error) + { + return error switch + { + UserError.NoKeys or + UserError.NoFirmware or + UserError.FirmwareParsingFailed => true, + _ => false, + }; + } + + private static string GetSetupGuideUrl(UserError error) + { + if (!IsCoveredBySetupGuide(error)) + { + return null; + } + + return error switch + { + UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", + UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", + _ => SetupGuideUrl, + }; + } + + private void UserErrorDialog_Response(object sender, ResponseArgs args) + { + int responseId = (int)args.ResponseId; + + if (responseId == SetupGuideResponseId) + { + OpenHelper.OpenUrl(GetSetupGuideUrl(_userError)); + } + + Dispose(); + } + + public static void CreateUserErrorDialog(UserError error) + { + new UserErrorDialog(error).Run(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/Windows/AboutWindow.Designer.cs b/Ryujinx/Ui/Windows/AboutWindow.Designer.cs new file mode 100644 index 00000000..99ce9333 --- /dev/null +++ b/Ryujinx/Ui/Windows/AboutWindow.Designer.cs @@ -0,0 +1,467 @@ +using Gtk; +using Pango; +using System.Reflection; + +namespace Ryujinx.Ui.Windows +{ + public partial class AboutWindow : Window + { + private Box _mainBox; + private Box _leftBox; + private Box _logoBox; + private Image _ryujinxLogo; + private Box _logoTextBox; + private Label _ryujinxLabel; + private Label _ryujinxPhoneticLabel; + private EventBox _ryujinxLink; + private Label _ryujinxLinkLabel; + private Label _versionLabel; + private Label _disclaimerLabel; + private Box _socialBox; + private EventBox _patreonEventBox; + private Box _patreonBox; + private Image _patreonLogo; + private Label _patreonLabel; + private EventBox _githubEventBox; + private Box _githubBox; + private Image _githubLogo; + private Label _githubLabel; + private Box _discordBox; + private EventBox _discordEventBox; + private Image _discordLogo; + private Label _discordLabel; + private EventBox _twitterEventBox; + private Box _twitterBox; + private Image _twitterLogo; + private Label _twitterLabel; + private Separator _separator; + private Box _rightBox; + private Label _aboutLabel; + private Label _aboutDescriptionLabel; + private Label _createdByLabel; + private TextView _createdByText; + private EventBox _contributorsEventBox; + private Label _contributorsLinkLabel; + private Label _patreonNamesLabel; + private ScrolledWindow _patreonNamesScrolled; + private TextView _patreonNamesText; + + private void InitializeComponent() + { + +#pragma warning disable CS0612 + + // + // AboutWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 800; + DefaultHeight = 450; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Horizontal, 0); + + // + // _leftBox + // + _leftBox = new Box(Orientation.Vertical, 0) + { + Margin = 15, + MarginLeft = 30, + MarginRight = 0 + }; + + // + // _logoBox + // + _logoBox = new Box(Orientation.Horizontal, 0); + + // + // _ryujinxLogo + // + _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png", 100, 100)) + { + Margin = 10, + MarginLeft = 15 + }; + + // + // _logoTextBox + // + _logoTextBox = new Box(Orientation.Vertical, 0); + + // + // _ryujinxLabel + // + _ryujinxLabel = new Label("Ryujinx") + { + MarginTop = 15, + Justify = Justification.Center, + Attributes = new AttrList() + }; + _ryujinxLabel.Attributes.Insert(new Pango.AttrScale(2.7f)); + + // + // _ryujinxPhoneticLabel + // + _ryujinxPhoneticLabel = new Label("(REE-YOU-JI-NX)") + { + Justify = Justification.Center + }; + + // + // _ryujinxLink + // + _ryujinxLink = new EventBox() + { + Margin = 5 + }; + _ryujinxLink.ButtonPressEvent += RyujinxButton_Pressed; + + // + // _ryujinxLinkLabel + // + _ryujinxLinkLabel = new Label("www.ryujinx.org") + { + TooltipText = "Click to open the Ryujinx website in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList() + }; + _ryujinxLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _versionLabel + // + _versionLabel = new Label(Program.Version) + { + Expand = true, + Justify = Justification.Center, + Margin = 5 + }; + + // + // _disclaimerLabel + // + _disclaimerLabel = new Label("Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.") + { + Expand = true, + Justify = Justification.Center, + Margin = 5, + Attributes = new AttrList() + }; + _disclaimerLabel.Attributes.Insert(new Pango.AttrScale(0.8f)); + + // + // _socialBox + // + _socialBox = new Box(Orientation.Horizontal, 0) + { + Margin = 25, + MarginBottom = 10 + }; + + // + // _patreonEventBox + // + _patreonEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx Patreon page in your default browser." + }; + _patreonEventBox.ButtonPressEvent += PatreonButton_Pressed; + + // + // _patreonBox + // + _patreonBox = new Box(Orientation.Vertical, 0); + + // + // _patreonLogo + // + _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Patreon.png", 30, 30)) + { + Margin = 10 + }; + + // + // _patreonLabel + // + _patreonLabel = new Label("Patreon") + { + Justify = Justification.Center + }; + + // + // _githubEventBox + // + _githubEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx GitHub page in your default browser." + }; + _githubEventBox.ButtonPressEvent += GitHubButton_Pressed; + + // + // _githubBox + // + _githubBox = new Box(Orientation.Vertical, 0); + + // + // _githubLogo + // + _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_GitHub.png", 30, 30)) + { + Margin = 10 + }; + + // + // _githubLabel + // + _githubLabel = new Label("GitHub") + { + Justify = Justification.Center + }; + + // + // _discordBox + // + _discordBox = new Box(Orientation.Vertical, 0); + + // + // _discordEventBox + // + _discordEventBox = new EventBox() + { + TooltipText = "Click to open an invite to the Ryujinx Discord server in your default browser." + }; + _discordEventBox.ButtonPressEvent += DiscordButton_Pressed; + + // + // _discordLogo + // + _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Discord.png", 30, 30)) + { + Margin = 10 + }; + + // + // _discordLabel + // + _discordLabel = new Label("Discord") + { + Justify = Justification.Center + }; + + // + // _twitterEventBox + // + _twitterEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx Twitter page in your default browser." + }; + _twitterEventBox.ButtonPressEvent += TwitterButton_Pressed; + + // + // _twitterBox + // + _twitterBox = new Box(Orientation.Vertical, 0); + + // + // _twitterLogo + // + _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Twitter.png", 30, 30)) + { + Margin = 10 + }; + + // + // _twitterLabel + // + _twitterLabel = new Label("Twitter") + { + Justify = Justification.Center + }; + + // + // _separator + // + _separator = new Separator(Orientation.Vertical) + { + Margin = 15 + }; + + // + // _rightBox + // + _rightBox = new Box(Orientation.Vertical, 0) + { + Margin = 15, + MarginTop = 40 + }; + + // + // _aboutLabel + // + _aboutLabel = new Label("About :") + { + Halign = Align.Start, + Attributes = new AttrList() + }; + _aboutLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _aboutLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _aboutDescriptionLabel + // + _aboutDescriptionLabel = new Label("Ryujinx is an emulator for the Nintendo Switch™.\n" + + "Please support us on Patreon.\n" + + "Get all the latest news on our Twitter or Discord.\n" + + "Developers interested in contributing can find out more on our GitHub or Discord.") + { + Margin = 15, + Halign = Align.Start + }; + + // + // _createdByLabel + // + _createdByLabel = new Label("Maintained by :") + { + Halign = Align.Start, + Attributes = new AttrList() + }; + _createdByLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _createdByLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _createdByText + // + _createdByText = new TextView() + { + WrapMode = Gtk.WrapMode.Word, + Editable = false, + CursorVisible = false, + Margin = 15, + MarginRight = 30 + }; + _createdByText.Buffer.Text = "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD« and more..."; + + // + // _contributorsEventBox + // + _contributorsEventBox = new EventBox(); + _contributorsEventBox.ButtonPressEvent += ContributorsButton_Pressed; + + // + // _contributorsLinkLabel + // + _contributorsLinkLabel = new Label("See All Contributors...") + { + TooltipText = "Click to open the Contributors page in your default browser.", + MarginRight = 30, + Halign = Align.End, + Attributes = new AttrList() + }; + _contributorsLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _patreonNamesLabel + // + _patreonNamesLabel = new Label("Supported on Patreon by :") + { + Halign = Align.Start, + Attributes = new AttrList() + }; + _patreonNamesLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _patreonNamesLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _patreonNamesScrolled + // + _patreonNamesScrolled = new ScrolledWindow() + { + Margin = 15, + MarginRight = 30, + Expand = true, + ShadowType = ShadowType.In + }; + _patreonNamesScrolled.SetPolicy(PolicyType.Never, PolicyType.Automatic); + + // + // _patreonNamesText + // + _patreonNamesText = new TextView() + { + WrapMode = Gtk.WrapMode.Word + }; + _patreonNamesText.Buffer.Text = "Loading..."; + _patreonNamesText.SetProperty("editable", new GLib.Value(false)); + +#pragma warning restore CS0612 + + ShowComponent(); + } + + private void ShowComponent() + { + _logoBox.Add(_ryujinxLogo); + + _ryujinxLink.Add(_ryujinxLinkLabel); + + _logoTextBox.Add(_ryujinxLabel); + _logoTextBox.Add(_ryujinxPhoneticLabel); + _logoTextBox.Add(_ryujinxLink); + + _logoBox.Add(_logoTextBox); + + _patreonBox.Add(_patreonLogo); + _patreonBox.Add(_patreonLabel); + _patreonEventBox.Add(_patreonBox); + + _githubBox.Add(_githubLogo); + _githubBox.Add(_githubLabel); + _githubEventBox.Add(_githubBox); + + _discordBox.Add(_discordLogo); + _discordBox.Add(_discordLabel); + _discordEventBox.Add(_discordBox); + + _twitterBox.Add(_twitterLogo); + _twitterBox.Add(_twitterLabel); + _twitterEventBox.Add(_twitterBox); + + _socialBox.Add(_patreonEventBox); + _socialBox.Add(_githubEventBox); + _socialBox.Add(_discordEventBox); + _socialBox.Add(_twitterEventBox); + + _leftBox.Add(_logoBox); + _leftBox.Add(_versionLabel); + _leftBox.Add(_disclaimerLabel); + _leftBox.Add(_socialBox); + + _contributorsEventBox.Add(_contributorsLinkLabel); + _patreonNamesScrolled.Add(_patreonNamesText); + + _rightBox.Add(_aboutLabel); + _rightBox.Add(_aboutDescriptionLabel); + _rightBox.Add(_createdByLabel); + _rightBox.Add(_createdByText); + _rightBox.Add(_contributorsEventBox); + _rightBox.Add(_patreonNamesLabel); + _rightBox.Add(_patreonNamesScrolled); + + _mainBox.Add(_leftBox); + _mainBox.Add(_separator); + _mainBox.Add(_rightBox); + + Add(_mainBox); + + ShowAll(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/Windows/AboutWindow.cs b/Ryujinx/Ui/Windows/AboutWindow.cs new file mode 100644 index 00000000..ab93e41d --- /dev/null +++ b/Ryujinx/Ui/Windows/AboutWindow.cs @@ -0,0 +1,73 @@ +using Gtk; +using Ryujinx.Common.Utilities; +using Ryujinx.Ui.Helper; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Threading.Tasks; + +namespace Ryujinx.Ui.Windows +{ + public partial class AboutWindow : Window + { + public AboutWindow() : base($"Ryujinx {Program.Version} - About") + { + InitializeComponent(); + + _ = DownloadPatronsJson(); + } + + private async Task DownloadPatronsJson() + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + _patreonNamesText.Buffer.Text = "Connection Error."; + } + + HttpClient httpClient = new HttpClient(); + + try + { + string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); + + _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString)); + } + catch + { + _patreonNamesText.Buffer.Text = "API Error."; + } + } + + // + // Events + // + private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://ryujinx.org"); + } + + private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://www.patreon.com/ryujinx"); + } + + private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx"); + } + + private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc"); + } + + private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://twitter.com/RyujinxEmu"); + } + + private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); + } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs similarity index 94% rename from Ryujinx/Ui/ControllerWindow.cs rename to Ryujinx/Ui/Windows/ControllerWindow.cs index 77c8db0c..7b0f7cf8 100644 --- a/Ryujinx/Ui/ControllerWindow.cs +++ b/Ryujinx/Ui/Windows/ControllerWindow.cs @@ -4,7 +4,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Utilities; using Ryujinx.Configuration; -using Ryujinx.HLE.FileSystem; +using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -15,14 +15,14 @@ using System.Threading; using GUI = Gtk.Builder.ObjectAttribute; using Key = Ryujinx.Configuration.Hid.Key; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Windows { public class ControllerWindow : Window { - private PlayerIndex _playerIndex; - private InputConfig _inputConfig; - private bool _isWaitingForInput; - private VirtualFileSystem _virtualFileSystem; + private readonly PlayerIndex _playerIndex; + private readonly InputConfig _inputConfig; + + private bool _isWaitingForInput; #pragma warning disable CS0649, IDE0044 [GUI] Adjustment _controllerDeadzoneLeft; @@ -90,17 +90,14 @@ namespace Ryujinx.Ui [GUI] Image _controllerImage; #pragma warning restore CS0649, IDE0044 - public ControllerWindow(PlayerIndex controllerId, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.ControllerWindow.glade"), controllerId, virtualFileSystem) { } + public ControllerWindow(PlayerIndex controllerId) : this(new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { } - private ControllerWindow(Builder builder, PlayerIndex controllerId, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_controllerWin").Handle) + private ControllerWindow(Builder builder, PlayerIndex controllerId) : base(builder.GetObject("_controllerWin").Handle) { builder.Autoconnect(this); - this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - - _playerIndex = controllerId; - _virtualFileSystem = virtualFileSystem; - _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex); + _playerIndex = controllerId; + _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex); Title = $"Ryujinx - Controller Settings - {_playerIndex}"; @@ -119,7 +116,7 @@ namespace Ryujinx.Ui _controllerType.Active = 0; // Set initial value to first in list. - //Bind Events + // Bind Events. _lStickX.Clicked += Button_Pressed; _lStickY.Clicked += Button_Pressed; _lStickUp.Clicked += Button_Pressed; @@ -153,12 +150,15 @@ namespace Ryujinx.Ui _rSl.Clicked += Button_Pressed; _rSr.Clicked += Button_Pressed; - // Setup current values + // Setup current values. UpdateInputDeviceList(); SetAvailableOptions(); ClearValues(); - if (_inputDevice.ActiveId != null) SetCurrentValues(); + if (_inputDevice.ActiveId != null) + { + SetCurrentValues(); + } } private void UpdateInputDeviceList() @@ -193,7 +193,7 @@ namespace Ryujinx.Ui { if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard")) { - this.ShowAll(); + ShowAll(); _leftStickController.Hide(); _rightStickController.Hide(); _deadZoneLeftBox.Hide(); @@ -202,7 +202,7 @@ namespace Ryujinx.Ui } else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) { - this.ShowAll(); + ShowAll(); _leftStickKeyboard.Hide(); _rightStickKeyboard.Hide(); } @@ -249,21 +249,13 @@ namespace Ryujinx.Ui break; } - switch (_controllerType.ActiveId) + _controllerImage.Pixbuf = _controllerType.ActiveId switch { - case "ProController": - _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.svg", 400, 400); - break; - case "JoyconLeft": - _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyConLeft.svg", 400, 400); - break; - case "JoyconRight": - _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyConRight.svg", 400, 400); - break; - default: - _controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyConPair.svg", 400, 400); - break; - } + "ProController" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_ProCon.svg", 400, 400), + "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConLeft.svg", 400, 400), + "JoyconRight" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConRight.svg", 400, 400), + _ => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConPair.svg", 400, 400), + }; } private void ClearValues() @@ -620,8 +612,7 @@ namespace Ryujinx.Ui if (joystickState.IsButtonDown(i)) { Enum.TryParse($"Button{i}", out pressedButton); - - return true; + return true; } } @@ -674,7 +665,9 @@ namespace Ryujinx.Ui return path; } - //Events + // + // Events + // private void InputDevice_Changed(object sender, EventArgs args) { SetAvailableOptions(); @@ -692,7 +685,7 @@ namespace Ryujinx.Ui { UpdateInputDeviceList(); - _refreshInputDevicesButton.SetStateFlags(0, true); + _refreshInputDevicesButton.SetStateFlags(StateFlags.Normal, true); } private void Button_Pressed(object sender, EventArgs args) @@ -719,7 +712,7 @@ namespace Ryujinx.Ui { Application.Invoke(delegate { - button.SetStateFlags(0, true); + button.SetStateFlags(StateFlags.Normal, true); }); _isWaitingForInput = false; @@ -731,7 +724,7 @@ namespace Ryujinx.Ui Application.Invoke(delegate { button.Label = pressedKey.ToString(); - button.SetStateFlags(0, true); + button.SetStateFlags(StateFlags.Normal, true); }); } else if (_inputDevice.ActiveId.StartsWith("controller")) @@ -745,7 +738,7 @@ namespace Ryujinx.Ui { Application.Invoke(delegate { - button.SetStateFlags(0, true); + button.SetStateFlags(StateFlags.Normal, true); }); _isWaitingForInput = false; @@ -757,7 +750,7 @@ namespace Ryujinx.Ui Application.Invoke(delegate { button.Label = pressedButton.ToString(); - button.SetStateFlags(0, true); + button.SetStateFlags(StateFlags.Normal, true); }); } @@ -788,7 +781,7 @@ namespace Ryujinx.Ui private void ProfileLoad_Activated(object sender, EventArgs args) { - ((ToggleButton)sender).SetStateFlags(0, true); + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) return; @@ -940,7 +933,7 @@ namespace Ryujinx.Ui private void ProfileAdd_Activated(object sender, EventArgs args) { - ((ToggleButton)sender).SetStateFlags(0, true); + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); if (_inputDevice.ActiveId == "disabled") return; @@ -973,7 +966,7 @@ namespace Ryujinx.Ui private void ProfileRemove_Activated(object sender, EventArgs args) { - ((ToggleButton) sender).SetStateFlags(0, true); + ((ToggleButton) sender).SetStateFlags(StateFlags.Normal, true); if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null) return; @@ -1021,7 +1014,7 @@ namespace Ryujinx.Ui // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; - MainWindow.SaveConfig(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); Dispose(); } diff --git a/Ryujinx/Ui/ControllerWindow.glade b/Ryujinx/Ui/Windows/ControllerWindow.glade similarity index 100% rename from Ryujinx/Ui/ControllerWindow.glade rename to Ryujinx/Ui/Windows/ControllerWindow.glade diff --git a/Ryujinx/Ui/DlcWindow.cs b/Ryujinx/Ui/Windows/DlcWindow.cs similarity index 88% rename from Ryujinx/Ui/DlcWindow.cs rename to Ryujinx/Ui/Windows/DlcWindow.cs index 29e96b07..13a63088 100644 --- a/Ryujinx/Ui/DlcWindow.cs +++ b/Ryujinx/Ui/Windows/DlcWindow.cs @@ -1,13 +1,12 @@ using Gtk; -using LibHac; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; +using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -16,7 +15,7 @@ using System.Text; using GUI = Gtk.Builder.ObjectAttribute; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Windows { public class DlcWindow : Window { @@ -31,9 +30,9 @@ namespace Ryujinx.Ui [GUI] TreeSelection _dlcTreeSelection; #pragma warning restore CS0649, IDE0044 - public DlcWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.DlcWindow.glade"), titleId, titleName, virtualFileSystem) { } + public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } - private DlcWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_dlcWindow").Handle) + private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_dlcWindow").Handle) { builder.Autoconnect(this); @@ -51,10 +50,7 @@ namespace Ryujinx.Ui _dlcContainerList = new List(); } - _dlcTreeView.Model = new TreeStore( - typeof(bool), - typeof(string), - typeof(string)); + _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string)); CellRendererToggle enableToggle = new CellRendererToggle(); enableToggle.Toggled += (sender, args) => @@ -104,17 +100,9 @@ namespace Ryujinx.Ui { return new Nca(_virtualFileSystem.KeySet, ncaStorage); } - catch (InvalidDataException exception) + catch (Exception exception) { - Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {containerPath}"); - - GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing."); - } - catch (MissingKeyException exception) - { - Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {containerPath}"); - - GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", $"Your key set is missing a key with the name: {exception.Name}"); + GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}"); } return null; diff --git a/Ryujinx/Ui/DlcWindow.glade b/Ryujinx/Ui/Windows/DlcWindow.glade similarity index 100% rename from Ryujinx/Ui/DlcWindow.glade rename to Ryujinx/Ui/Windows/DlcWindow.glade diff --git a/Ryujinx/Ui/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs similarity index 90% rename from Ryujinx/Ui/SettingsWindow.cs rename to Ryujinx/Ui/Windows/SettingsWindow.cs index d17bce60..a44abfcc 100644 --- a/Ryujinx/Ui/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -6,20 +6,20 @@ using Ryujinx.Configuration; using Ryujinx.Configuration.System; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.Ui.Helper; using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; using System.Threading.Tasks; using GUI = Gtk.Builder.ObjectAttribute; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Windows { public class SettingsWindow : Window { - private readonly VirtualFileSystem _virtualFileSystem; + private readonly MainWindow _parent; private readonly ListStore _gameDirsBoxStore; private readonly ListStore _audioBackendStore; private readonly TimeZoneContentManager _timeZoneContentManager; @@ -86,36 +86,34 @@ namespace Ryujinx.Ui #pragma warning restore CS0649, IDE0044 - public SettingsWindow(VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(new Builder("Ryujinx.Ui.SettingsWindow.glade"), virtualFileSystem, contentManager) { } + public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } - private SettingsWindow(Builder builder, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle) + private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle) { + _parent = parent; + builder.Autoconnect(this); - this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); - - _virtualFileSystem = virtualFileSystem; - _timeZoneContentManager = new TimeZoneContentManager(); _timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None); _validTzRegions = new HashSet(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly. - //Bind Events - _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player1); - _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player2); - _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player3); - _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player4); - _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player5); - _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player6); - _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player7); - _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8); - _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld); + // Bind Events. + _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1); + _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2); + _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3); + _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4); + _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5); + _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6); + _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7); + _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8); + _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld); _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; - //Setup Currents + // Setup Currents. if (ConfigurationState.Instance.Logger.EnableFileLog) { _fileLogToggle.Click(); @@ -419,12 +417,14 @@ namespace Ryujinx.Ui ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1); } - MainWindow.SaveConfig(); - MainWindow.UpdateGraphicsConfig(); - MainWindow.ApplyTheme(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + _parent.UpdateGraphicsConfig(); + ThemeHelper.ApplyTheme(); } - //Events + // + // Events + // private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e) { if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text)) @@ -439,7 +439,7 @@ namespace Ryujinx.Ui return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region ((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr - ((string)compl.Model.GetValue(iter, 0)).Substring(3).StartsWith(key); // offset + ((string)compl.Model.GetValue(iter, 0))[3..].StartsWith(key); // offset } private void SystemTimeSpin_ValueChanged(object sender, EventArgs e) @@ -511,7 +511,7 @@ namespace Ryujinx.Ui _addGameDirBox.Buffer.Text = ""; - ((ToggleButton)sender).SetStateFlags(0, true); + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); } private void RemoveDir_Pressed(object sender, EventArgs args) @@ -523,7 +523,7 @@ namespace Ryujinx.Ui _gameDirsBoxStore.Remove(ref treeIter); } - ((ToggleButton)sender).SetStateFlags(0, true); + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); } private void CustThemeToggle_Activated(object sender, EventArgs args) @@ -535,27 +535,25 @@ namespace Ryujinx.Ui private void BrowseThemeDir_Pressed(object sender, EventArgs args) { - FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept); - - fileChooser.Filter = new FileFilter(); - fileChooser.Filter.AddPattern("*.css"); - - if (fileChooser.Run() == (int)ResponseType.Accept) + using (FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept)) { - _custThemePath.Buffer.Text = fileChooser.Filename; + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.css"); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _custThemePath.Buffer.Text = fileChooser.Filename; + } } - fileChooser.Dispose(); - - _browseThemePath.SetStateFlags(0, true); + _browseThemePath.SetStateFlags(StateFlags.Normal, true); } - private void ConfigureController_Pressed(object sender, EventArgs args, PlayerIndex playerIndex) + private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex) { - ((ToggleButton)sender).SetStateFlags(0, true); + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); - ControllerWindow controllerWin = new ControllerWindow(playerIndex, _virtualFileSystem); - controllerWin.Show(); + new ControllerWindow(playerIndex).Show(); } private void SaveToggle_Activated(object sender, EventArgs args) @@ -574,4 +572,4 @@ namespace Ryujinx.Ui Dispose(); } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/SettingsWindow.glade b/Ryujinx/Ui/Windows/SettingsWindow.glade similarity index 100% rename from Ryujinx/Ui/SettingsWindow.glade rename to Ryujinx/Ui/Windows/SettingsWindow.glade diff --git a/Ryujinx/Ui/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs similarity index 69% rename from Ryujinx/Ui/TitleUpdateWindow.cs rename to Ryujinx/Ui/Windows/TitleUpdateWindow.cs index 54b5b262..647dea19 100644 --- a/Ryujinx/Ui/TitleUpdateWindow.cs +++ b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs @@ -1,5 +1,4 @@ using Gtk; -using LibHac; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -7,9 +6,9 @@ using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.Ns; using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; +using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.IO; @@ -19,16 +18,18 @@ using System.Text; using GUI = Gtk.Builder.ObjectAttribute; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; -namespace Ryujinx.Ui +namespace Ryujinx.Ui.Windows { public class TitleUpdateWindow : Window { + private readonly MainWindow _parent; private readonly VirtualFileSystem _virtualFileSystem; private readonly string _titleId; private readonly string _updateJsonPath; - private TitleUpdateMetadata _titleUpdateWindowData; - private Dictionary _radioButtonToPathDictionary; + private TitleUpdateMetadata _titleUpdateWindowData; + + private readonly Dictionary _radioButtonToPathDictionary; #pragma warning disable CS0649, IDE0044 [GUI] Label _baseTitleInfoLabel; @@ -36,10 +37,12 @@ namespace Ryujinx.Ui [GUI] RadioButton _noUpdateRadioButton; #pragma warning restore CS0649, IDE0044 - public TitleUpdateWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.TitleUpdateWindow.glade"), titleId, titleName, virtualFileSystem) { } + public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } - private TitleUpdateWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_titleUpdateWindow").Handle) + private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_titleUpdateWindow").Handle) { + _parent = parent; + builder.Autoconnect(this); _titleId = titleId; @@ -61,20 +64,26 @@ namespace Ryujinx.Ui } _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]"; - _noUpdateRadioButton.Active = true; - + foreach (string path in _titleUpdateWindowData.Paths) { - AddUpdate(path, false); + AddUpdate(path); } - foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected)) + if (_titleUpdateWindowData.Selected == "") { - update.Active = true; + _noUpdateRadioButton.Active = true; + } + else + { + foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected)) + { + update.Active = true; + } } } - private void AddUpdate(string path, bool showErrorDialog = true) + private void AddUpdate(string path) { if (File.Exists(path)) { @@ -107,23 +116,9 @@ namespace Ryujinx.Ui GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!"); } } - catch (InvalidDataException exception) + catch (Exception exception) { - Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {path}"); - - if (showErrorDialog) - { - GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing."); - } - } - catch (MissingKeyException exception) - { - Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}"); - - if (showErrorDialog) - { - GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", $"Your key set is missing a key with the name: {exception.Name}"); - } + GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}"); } } } @@ -144,23 +139,21 @@ namespace Ryujinx.Ui private void AddButton_Clicked(object sender, EventArgs args) { - FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept) + using (FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept)) { - SelectMultiple = true, - Filter = new FileFilter() - }; - fileChooser.SetPosition(WindowPosition.Center); - fileChooser.Filter.AddPattern("*.nsp"); + fileChooser.SelectMultiple = true; + fileChooser.SetPosition(WindowPosition.Center); + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.nsp"); - if (fileChooser.Run() == (int)ResponseType.Accept) - { - foreach (string path in fileChooser.Filenames) + if (fileChooser.Run() == (int)ResponseType.Accept) { - AddUpdate(path); + foreach (string path in fileChooser.Filenames) + { + AddUpdate(path); + } } } - - fileChooser.Dispose(); } private void RemoveButton_Clicked(object sender, EventArgs args) @@ -196,7 +189,8 @@ namespace Ryujinx.Ui dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); } - MainWindow.UpdateGameTable(); + _parent.UpdateGameTable(); + Dispose(); } diff --git a/Ryujinx/Ui/TitleUpdateWindow.glade b/Ryujinx/Ui/Windows/TitleUpdateWindow.glade similarity index 100% rename from Ryujinx/Ui/TitleUpdateWindow.glade rename to Ryujinx/Ui/Windows/TitleUpdateWindow.glade