gui: Refactoring Part 1 (#1859)

* gui: Refactoring Part 1

* Fix ProfileDialog.glade path

* Fix Application.Quit assert

* Fix TitleUpdateWindow parent

* Fix TitleUpdate selected item

* Remove extra line in TitleUpdateWindow

* Fix empty assign of Enum.TryParse

* Add Patrons list in the About Window

* update about error messages
This commit is contained in:
Ac_K 2021-01-08 09:14:13 +01:00 committed by GitHub
parent 72e94bb089
commit a9cb31e75f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 1979 additions and 2549 deletions

View file

@ -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 _);
}
}
}
}

View file

@ -17,14 +17,14 @@ namespace Ryujinx.Common.System
public uint wPeriodMax; public uint wPeriodMax;
}; };
[DllImport("winmm.dll", SetLastError = true)] [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)]
private static extern uint timeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); private static extern uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps);
[DllImport("winmm.dll")] [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
private static extern uint timeBeginPeriod(uint uMilliseconds); private static extern uint TimeBeginPeriod(uint uMilliseconds);
[DllImport("winmm.dll")] [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
private static extern uint timeEndPeriod(uint uMilliseconds); private static extern uint TimeEndPeriod(uint uMilliseconds);
private uint _targetResolutionInMilliseconds; private uint _targetResolutionInMilliseconds;
private bool _isActive; private bool _isActive;
@ -45,7 +45,7 @@ namespace Ryujinx.Common.System
{ {
TimeCaps timeCaps = default; TimeCaps timeCaps = default;
uint result = timeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf<TimeCaps>()); uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf<TimeCaps>());
if (result != 0) if (result != 0)
{ {
@ -66,7 +66,7 @@ namespace Ryujinx.Common.System
private void Activate() private void Activate()
{ {
uint result = timeBeginPeriod(_targetResolutionInMilliseconds); uint result = TimeBeginPeriod(_targetResolutionInMilliseconds);
if (result != 0) if (result != 0)
{ {
@ -82,7 +82,7 @@ namespace Ryujinx.Common.System
{ {
if (_isActive) if (_isActive)
{ {
uint result = timeEndPeriod(_targetResolutionInMilliseconds); uint result = TimeEndPeriod(_targetResolutionInMilliseconds);
if (result != 0) if (result != 0)
{ {
@ -98,6 +98,7 @@ namespace Ryujinx.Common.System
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)

View file

@ -1,15 +1,16 @@
using DiscordRPC; using DiscordRPC;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Configuration;
using System; using System;
using System.Linq; using System.Linq;
namespace Ryujinx.Configuration namespace Ryujinx.Modules
{ {
static class DiscordIntegrationModule static class DiscordIntegrationModule
{ {
private static DiscordRpcClient _discordClient; 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; } public static RichPresence DiscordPresence { get; private set; }
@ -17,7 +18,7 @@ namespace Ryujinx.Configuration
{ {
DiscordPresence = new RichPresence DiscordPresence = new RichPresence
{ {
Assets = new Assets Assets = new Assets
{ {
LargeImageKey = "ryujinx", LargeImageKey = "ryujinx",
LargeImageText = LargeDescription LargeImageText = LargeDescription

View file

@ -11,7 +11,7 @@ using System.Net.Sockets;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
public class Client : IDisposable 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) if (!_active)
{ {
@ -439,7 +439,7 @@ namespace Ryujinx.Motion
{ {
Header header = new Header() Header header = new Header()
{ {
ID = (uint)clientId, Id = (uint)clientId,
MagicString = Magic, MagicString = Magic,
Version = Version, Version = Version,
Length = 0, Length = 0,

View file

@ -3,7 +3,7 @@ using Ryujinx.Configuration;
using System; using System;
using System.Numerics; using System.Numerics;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
public class MotionDevice public class MotionDevice
{ {
@ -12,7 +12,7 @@ namespace Ryujinx.Motion
public Vector3 Rotation { get; private set; } public Vector3 Rotation { get; private set; }
public float[] Orientation { get; private set; } public float[] Orientation { get; private set; }
private Client _motionSource; private readonly Client _motionSource;
public MotionDevice(Client motionSource) public MotionDevice(Client motionSource)
{ {
@ -51,8 +51,8 @@ namespace Ryujinx.Motion
} }
Gyroscope = Truncate(input.Gyroscrope * 0.0027f, 3); Gyroscope = Truncate(input.Gyroscrope * 0.0027f, 3);
Accelerometer = Truncate(input.Accelerometer, 3); Accelerometer = Truncate(input.Accelerometer, 3);
Rotation = Truncate(input.Rotation * 0.0027f, 3); Rotation = Truncate(input.Rotation * 0.0027f, 3);
Matrix4x4 orientation = input.GetOrientation(); Matrix4x4 orientation = input.GetOrientation();

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
public class MotionInput public class MotionInput
{ {

View file

@ -1,6 +1,6 @@
using System.Numerics; using System.Numerics;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
// MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm. // MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm.
// See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/ // See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
@ -43,9 +43,7 @@ namespace Ryujinx.Motion
/// <param name="samplePeriod"> /// <param name="samplePeriod">
/// Sample period. /// Sample period.
/// </param> /// </param>
public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) { }
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class. /// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
@ -56,9 +54,7 @@ namespace Ryujinx.Motion
/// <param name="kp"> /// <param name="kp">
/// Algorithm proportional gain. /// Algorithm proportional gain.
/// </param> /// </param>
public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) { }
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class. /// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.

View file

@ -1,13 +1,13 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ControllerDataRequest struct ControllerDataRequest
{ {
public MessageType Type; public MessageType Type;
public SubscriberType SubscriberType; public SubscriberType SubscriberType;
public byte Slot; public byte Slot;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] MacAddress; public byte[] MacAddress;
@ -17,21 +17,22 @@ namespace Ryujinx.Motion
public struct ControllerDataResponse public struct ControllerDataResponse
{ {
public SharedResponse Shared; public SharedResponse Shared;
public byte Connected; public byte Connected;
public uint PacketID; public uint PacketId;
public byte ExtraButtons; public byte ExtraButtons;
public byte MainButtons; public byte MainButtons;
public ushort PSExtraInput; public ushort PSExtraInput;
public ushort LeftStickXY; public ushort LeftStickXY;
public ushort RightStickXY; public ushort RightStickXY;
public uint DPadAnalog; public uint DPadAnalog;
public ulong MainButtonsAnalog; public ulong MainButtonsAnalog;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Touch1; public byte[] Touch1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Touch2; public byte[] Touch2;
public ulong MotionTimestamp; public ulong MotionTimestamp;
public float AccelerometerX; public float AccelerometerX;
public float AccelerometerY; public float AccelerometerY;
@ -43,7 +44,7 @@ namespace Ryujinx.Motion
enum SubscriberType : byte enum SubscriberType : byte
{ {
All = 0, All,
Slot, Slot,
Mac Mac
} }

View file

@ -1,19 +1,19 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoResponse public struct ControllerInfoResponse
{ {
public SharedResponse Shared; public SharedResponse Shared;
private byte _zero; private byte _zero;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoRequest public struct ControllerInfoRequest
{ {
public MessageType Type; public MessageType Type;
public int PortsCount; public int PortsCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] PortIndices; public byte[] PortIndices;

View file

@ -1,14 +1,14 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Header public struct Header
{ {
public uint MagicString; public uint MagicString;
public ushort Version; public ushort Version;
public ushort Length; public ushort Length;
public uint Crc32; public uint Crc32;
public uint ID; public uint Id;
} }
} }

View file

@ -1,4 +1,4 @@
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
public enum MessageType : uint public enum MessageType : uint
{ {

View file

@ -1,45 +1,45 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Motion namespace Ryujinx.Modules.Motion
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SharedResponse public struct SharedResponse
{ {
public MessageType Type; public MessageType Type;
public byte Slot; public byte Slot;
public SlotState State; public SlotState State;
public DeviceModelType ModelType; public DeviceModelType ModelType;
public ConnectionType ConnectionType; public ConnectionType ConnectionType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] MacAddress; public byte[] MacAddress;
public BatteryStatus BatteryStatus; public BatteryStatus BatteryStatus;
} }
public enum SlotState : byte public enum SlotState : byte
{ {
Disconnected = 0, Disconnected,
Reserved, Reserved,
Connected Connected
} }
public enum DeviceModelType : byte public enum DeviceModelType : byte
{ {
None = 0, None,
PartialGyro, PartialGyro,
FullGyro FullGyro
} }
public enum ConnectionType : byte public enum ConnectionType : byte
{ {
None = 0, None,
USB, USB,
Bluetooth Bluetooth
} }
public enum BatteryStatus : byte public enum BatteryStatus : byte
{ {
NA = 0, NA,
Dying, Dying,
Low, Low,
Medium, Medium,

View file

@ -1,28 +1,29 @@
using Gdk; using Gdk;
using Gtk; using Gtk;
using Mono.Unix; using Mono.Unix;
using Ryujinx.Ui;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Ui namespace Ryujinx.Modules
{ {
public class UpdateDialog : Gtk.Window public class UpdateDialog : Gtk.Window
{ {
#pragma warning disable CS0649, IDE0044 #pragma warning disable CS0649, IDE0044
[Builder.Object] public Label MainText; [Builder.Object] public Label MainText;
[Builder.Object] public Label SecondaryText; [Builder.Object] public Label SecondaryText;
[Builder.Object] public LevelBar ProgressBar; [Builder.Object] public LevelBar ProgressBar;
[Builder.Object] public Button YesButton; [Builder.Object] public Button YesButton;
[Builder.Object] public Button NoButton; [Builder.Object] public Button NoButton;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
private readonly MainWindow _mainWindow; private readonly MainWindow _mainWindow;
private readonly string _buildUrl; private readonly string _buildUrl;
private bool _restartQuery; 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) private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetObject("UpdateDialog").Handle)
{ {
@ -36,17 +37,17 @@ namespace Ryujinx.Ui
ProgressBar.Hide(); ProgressBar.Hide();
YesButton.Clicked += YesButton_Pressed; YesButton.Clicked += YesButton_Clicked;
NoButton.Clicked += NoButton_Pressed; NoButton.Clicked += NoButton_Clicked;
} }
private void YesButton_Pressed(object sender, EventArgs args) private void YesButton_Clicked(object sender, EventArgs args)
{ {
if (_restartQuery) if (_restartQuery)
{ {
string ryuName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Ryujinx.exe" : "Ryujinx"; string ryuName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Ryujinx.exe" : "Ryujinx";
string ryuExe = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName); 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)) if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
@ -60,7 +61,7 @@ namespace Ryujinx.Ui
} }
else else
{ {
this.Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close; Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close;
_mainWindow.ExitMenuItem.Sensitive = false; _mainWindow.ExitMenuItem.Sensitive = false;
YesButton.Hide(); 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; Updater.Running = false;
_mainWindow.Window.Functions = WMFunction.All; _mainWindow.Window.Functions = WMFunction.All;
@ -82,7 +83,7 @@ namespace Ryujinx.Ui
_mainWindow.ExitMenuItem.Sensitive = true; _mainWindow.ExitMenuItem.Sensitive = true;
_mainWindow.UpdateMenuItem.Sensitive = true; _mainWindow.UpdateMenuItem.Sensitive = true;
this.Dispose(); Dispose();
} }
} }
} }

View file

@ -5,6 +5,7 @@ using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Ui; using Ryujinx.Ui;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
@ -13,7 +14,7 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx namespace Ryujinx.Modules
{ {
public static class Updater public static class Updater
{ {
@ -84,7 +85,7 @@ namespace Ryujinx
{ {
if (showVersionUpToDate) 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; return;
@ -115,7 +116,7 @@ namespace Ryujinx
{ {
if (showVersionUpToDate) 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; Running = false;

View file

@ -3,10 +3,12 @@ using Gtk;
using OpenTK; using OpenTK;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.System;
using Ryujinx.Common.SystemInfo; using Ryujinx.Common.SystemInfo;
using Ryujinx.Configuration; using Ryujinx.Configuration;
using Ryujinx.Modules;
using Ryujinx.Ui; using Ryujinx.Ui;
using Ryujinx.Ui.Diagnostic; using Ryujinx.Ui.Widgets;
using System; using System;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -22,10 +24,11 @@ namespace Ryujinx
static void Main(string[] args) static void Main(string[] args)
{ {
// Parse Arguments // Parse Arguments.
string launchPath = null; string launchPathArg = null;
string baseDirPath = null; string baseDirPathArg = null;
bool startFullscreen = false; bool startFullscreenArg = false;
for (int i = 0; i < args.Length; ++i) for (int i = 0; i < args.Length; ++i)
{ {
string arg = args[i]; string arg = args[i];
@ -35,28 +38,28 @@ namespace Ryujinx
if (i + 1 >= args.Length) if (i + 1 >= args.Length)
{ {
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'"); Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue; continue;
} }
baseDirPath = args[++i]; baseDirPathArg = args[++i];
} }
else if (arg == "-f" || arg == "--fullscreen") 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); Task.Run(Updater.CleanupUpdate);
Toolkit.Init(new ToolkitOptions Toolkit.Init(new ToolkitOptions
{ {
Backend = PlatformBackend.PreferNative, Backend = PlatformBackend.PreferNative
EnableHighResolution = true
}); });
Version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; Version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
@ -66,27 +69,27 @@ namespace Ryujinx
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
// Hook unhandled exception and process exit events // Hook unhandled exception and process exit events.
GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); 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.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 // Setup base data directory.
AppDataManager.Initialize(baseDirPath); AppDataManager.Initialize(baseDirPathArg);
// Initialize the configuration // Initialize the configuration.
ConfigurationState.Initialize(); ConfigurationState.Initialize();
// Initialize the logger system // Initialize the logger system.
LoggerModule.Initialize(); LoggerModule.Initialize();
// Initialize Discord integration // Initialize Discord integration.
DiscordIntegrationModule.Initialize(); DiscordIntegrationModule.Initialize();
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); 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)) if (File.Exists(localConfigurationPath))
{ {
ConfigurationPath = localConfigurationPath; ConfigurationPath = localConfigurationPath;
@ -105,35 +108,42 @@ namespace Ryujinx
} }
else 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; ConfigurationPath = appDataConfigurationPath;
ConfigurationState.Instance.LoadDefault(); ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(appDataConfigurationPath); ConfigurationState.Instance.ToFileFormat().SaveConfig(appDataConfigurationPath);
} }
if (startFullscreen) if (startFullscreenArg)
{ {
ConfigurationState.Instance.Ui.StartFullscreen.Value = true; ConfigurationState.Instance.Ui.StartFullscreen.Value = true;
} }
// Logging system informations.
PrintSystemInfo(); PrintSystemInfo();
// Initialize Gtk.
Application.Init(); Application.Init();
// Check if keys exists.
bool hasGlobalProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); bool hasGlobalProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
bool hasAltProdKeys = !AppDataManager.IsCustomBasePath && File.Exists(Path.Combine(AppDataManager.KeysDirPathAlt, "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); UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys);
} }
// Force dedicated GPU if we can.
ForceDedicatedGpu.Nvidia();
// Show the main window UI.
MainWindow mainWindow = new MainWindow(); MainWindow mainWindow = new MainWindow();
mainWindow.Show(); mainWindow.Show();
if (launchPath != null) if (launchPathArg != null)
{ {
mainWindow.LoadApplication(launchPath); mainWindow.LoadApplication(launchPathArg);
} }
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
@ -147,7 +157,6 @@ namespace Ryujinx
private static void PrintSystemInfo() private static void PrintSystemInfo()
{ {
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
Logger.Notice.Print(LogClass.Application, $"Operating System: {SystemInfo.Instance.OsDescription}"); Logger.Notice.Print(LogClass.Application, $"Operating System: {SystemInfo.Instance.OsDescription}");
Logger.Notice.Print(LogClass.Application, $"CPU: {SystemInfo.Instance.CpuName}"); Logger.Notice.Print(LogClass.Application, $"CPU: {SystemInfo.Instance.CpuName}");
Logger.Notice.Print(LogClass.Application, $"Total RAM: {SystemInfo.Instance.RamSizeInMB}"); 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(); Ptc.Close();
PtcProfiler.Stop(); PtcProfiler.Stop();
string message = $"Unhandled exception caught: {e}"; string message = $"Unhandled exception caught: {ex}";
Logger.Error?.PrintMsg(LogClass.Application, message); 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) if (isTerminating)
{ {
ProgramExit(); Exit();
} }
} }
private static void ProgramExit() public static void Exit()
{ {
DiscordIntegrationModule.Exit();
Ptc.Dispose(); Ptc.Dispose();
PtcProfiler.Dispose(); PtcProfiler.Dispose();

View file

@ -55,53 +55,51 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Ui\AboutWindow.glade" />
<None Remove="Ui\assets\JoyConLeft.svg" />
<None Remove="Ui\assets\JoyConPair.svg" />
<None Remove="Ui\assets\JoyConRight.svg" />
<None Remove="Ui\assets\ProCon.svg" />
<None Remove="Ui\assets\NCAIcon.png" />
<None Remove="Ui\assets\NROIcon.png" />
<None Remove="Ui\assets\NSOIcon.png" />
<None Remove="Ui\assets\NSPIcon.png" />
<None Remove="Ui\assets\XCIIcon.png" />
<None Remove="Ui\assets\DiscordLogo.png" />
<None Remove="Ui\assets\GitHubLogo.png" />
<None Remove="Ui\assets\PatreonLogo.png" />
<None Remove="Ui\assets\Icon.png" />
<None Remove="Ui\assets\TwitterLogo.png" />
<None Remove="Ui\ControllerWindow.glade" />
<None Remove="Ui\DlcWindow.glade" />
<None Remove="Ui\MainWindow.glade" /> <None Remove="Ui\MainWindow.glade" />
<None Remove="Ui\ProfileDialog.glade" /> <None Remove="Ui\Resources\Controller_JoyConLeft.svg" />
<None Remove="Ui\SettingsWindow.glade" /> <None Remove="Ui\Resources\Controller_JoyConPair.svg" />
<None Remove="Ui\TitleUpdateWindow.glade" /> <None Remove="Ui\Resources\Controller_JoyConRight.svg" />
<None Remove="Ui\UpdateDialog.glade" /> <None Remove="Ui\Resources\Controller_ProCon.svg" />
<None Remove="Ui\Resources\Icon_NCA.png" />
<None Remove="Ui\Resources\Icon_NRO.png" />
<None Remove="Ui\Resources\Icon_NSO.png" />
<None Remove="Ui\Resources\Icon_NSP.png" />
<None Remove="Ui\Resources\Icon_XCI.png" />
<None Remove="Ui\Resources\Logo_Discord.png" />
<None Remove="Ui\Resources\Logo_GitHub.png" />
<None Remove="Ui\Resources\Logo_Patreon.png" />
<None Remove="Ui\Resources\Logo_Ryujinx.png" />
<None Remove="Ui\Resources\Logo_Twitter.png" />
<None Remove="Ui\Widgets\ProfileDialog.glade" />
<None Remove="Ui\Windows\ControllerWindow.glade" />
<None Remove="Ui\Windows\DlcWindow.glade" />
<None Remove="Ui\Windows\SettingsWindow.glade" />
<None Remove="Ui\Windows\TitleUpdateWindow.glade" />
<None Remove="Modules\Updater\UpdateDialog.glade" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Ui\AboutWindow.glade" />
<EmbeddedResource Include="Ui\assets\JoyConLeft.svg" />
<EmbeddedResource Include="Ui\assets\JoyConPair.svg" />
<EmbeddedResource Include="Ui\assets\JoyConRight.svg" />
<EmbeddedResource Include="Ui\assets\ProCon.svg" />
<EmbeddedResource Include="Ui\assets\NCAIcon.png" />
<EmbeddedResource Include="Ui\assets\NROIcon.png" />
<EmbeddedResource Include="Ui\assets\NSOIcon.png" />
<EmbeddedResource Include="Ui\assets\NSPIcon.png" />
<EmbeddedResource Include="Ui\assets\XCIIcon.png" />
<EmbeddedResource Include="Ui\assets\DiscordLogo.png" />
<EmbeddedResource Include="Ui\assets\GitHubLogo.png" />
<EmbeddedResource Include="Ui\assets\PatreonLogo.png" />
<EmbeddedResource Include="Ui\assets\Icon.png" />
<EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
<EmbeddedResource Include="Ui\ControllerWindow.glade" />
<EmbeddedResource Include="Ui\MainWindow.glade" /> <EmbeddedResource Include="Ui\MainWindow.glade" />
<EmbeddedResource Include="Ui\ProfileDialog.glade" /> <EmbeddedResource Include="Ui\Resources\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Ui\SettingsWindow.glade" /> <EmbeddedResource Include="Ui\Resources\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Ui\DlcWindow.glade" /> <EmbeddedResource Include="Ui\Resources\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Ui\TitleUpdateWindow.glade" /> <EmbeddedResource Include="Ui\Resources\Controller_ProCon.svg" />
<EmbeddedResource Include="Updater\UpdateDialog.glade" /> <EmbeddedResource Include="Ui\Resources\Icon_NCA.png" />
<EmbeddedResource Include="Ui\Resources\Icon_NRO.png" />
<EmbeddedResource Include="Ui\Resources\Icon_NSO.png" />
<EmbeddedResource Include="Ui\Resources\Icon_NSP.png" />
<EmbeddedResource Include="Ui\Resources\Icon_XCI.png" />
<EmbeddedResource Include="Ui\Resources\Logo_Discord.png" />
<EmbeddedResource Include="Ui\Resources\Logo_GitHub.png" />
<EmbeddedResource Include="Ui\Resources\Logo_Patreon.png" />
<EmbeddedResource Include="Ui\Resources\Logo_Ryujinx.png" />
<EmbeddedResource Include="Ui\Resources\Logo_Twitter.png" />
<EmbeddedResource Include="Ui\Widgets\ProfileDialog.glade" />
<EmbeddedResource Include="Ui\Windows\ControllerWindow.glade" />
<EmbeddedResource Include="Ui\Windows\DlcWindow.glade" />
<EmbeddedResource Include="Ui\Windows\SettingsWindow.glade" />
<EmbeddedResource Include="Ui\Windows\TitleUpdateWindow.glade" />
<EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -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();
}
}
}

View file

@ -1,574 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkDialog" id="_aboutWin">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center</property>
<property name="default_width">800</property>
<property name="default_height">350</property>
<property name="type_hint">dialog</property>
<child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bigBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="leftBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">15</property>
<property name="margin_top">10</property>
<property name="margin_bottom">15</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="_ryujinxLogo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Ryujinx</property>
<property name="justify">center</property>
<attributes>
<attribute name="scale" value="2.7000000000000002"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">(REE-YOU-JI-NX)</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEventBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="button-press-event" handler="RyujinxButton_Pressed" swapped="no"/>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Click to open the Ryujinx website in your default browser</property>
<property name="label" translatable="yes">www.ryujinx.org</property>
<property name="justify">center</property>
<attributes>
<attribute name="underline" value="True"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="_versionText">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Version x.x.x (Commit Number)</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="license">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">MIT License</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="disclaimer">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Ryujinx is not affiliated with Nintendo,
or any of its partners, in any way</property>
<property name="justify">center</property>
<attributes>
<attribute name="scale" value="0.80000000000000004"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">25</property>
<child>
<object class="GtkEventBox" id="PatreonButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Click to open the Ryujinx Patreon page in your default browser</property>
<signal name="button-press-event" handler="PatreonButton_Pressed" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="_patreonLogo">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Patreon</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="GitHubButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Click to open the Ryujinx GitHub page in your default browser</property>
<signal name="button-press-event" handler="GitHubButton_Pressed" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="_gitHubLogo">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">GitHub</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="DiscordButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Click to open an invite to the Ryujinx Discord server in your default browser</property>
<signal name="button-press-event" handler="DiscordButton_Pressed" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="_discordLogo">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Discord</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="TwitterButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Click to open the Ryujinx Twitter page in your default browser</property>
<signal name="button-press-event" handler="TwitterButton_Pressed" swapped="no"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="_twitterLogo">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Twitter</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="rightBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">15</property>
<property name="margin_right">10</property>
<property name="margin_top">40</property>
<property name="margin_bottom">15</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">About</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">10</property>
<property name="label" translatable="yes">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.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Created By:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="baseline_position">top</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">gdkchan
LDj3SNuD
Ac_K
Thog</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">»jD«
emmaus
Thealexbarney
Andy A (BaronKiko)</property>
<property name="yalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="ContributorsButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<signal name="button-press-event" handler="ContributorsButton_Pressed" swapped="no"/>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="margin_right">5</property>
<property name="label" translatable="yes">All Contributors...</property>
<attributes>
<attribute name="underline" value="True"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View file

@ -1,6 +1,6 @@
using System; using System;
namespace Ryujinx.Ui namespace Ryujinx.Ui.App
{ {
public class ApplicationAddedEventArgs : EventArgs public class ApplicationAddedEventArgs : EventArgs
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
namespace Ryujinx.Ui namespace Ryujinx.Ui.App
{ {
public class ApplicationCountUpdatedEventArgs : EventArgs public class ApplicationCountUpdatedEventArgs : EventArgs
{ {

View file

@ -1,10 +1,9 @@
using LibHac; using LibHac.Common;
using LibHac.Common;
using LibHac.Ns; using LibHac.Ns;
namespace Ryujinx.Ui namespace Ryujinx.Ui.App
{ {
public struct ApplicationData public class ApplicationData
{ {
public bool Favorite { get; set; } public bool Favorite { get; set; }
public byte[] Icon { get; set; } public byte[] Icon { get; set; }

View file

@ -11,6 +11,7 @@ using Ryujinx.Configuration.System;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -20,24 +21,45 @@ using System.Text.Json;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui namespace Ryujinx.Ui.App
{ {
public class ApplicationLibrary public class ApplicationLibrary
{ {
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded; public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
public static event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated; public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png"); private readonly byte[] _nspIcon;
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png"); private readonly byte[] _xciIcon;
private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png"); private readonly byte[] _ncaIcon;
private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png"); private readonly byte[] _nroIcon;
private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png"); private readonly byte[] _nsoIcon;
private static VirtualFileSystem _virtualFileSystem; private VirtualFileSystem _virtualFileSystem;
private static Language _desiredTitleLanguage; private Language _desiredTitleLanguage;
private static bool _loadingError; private bool _loadingError;
public static IEnumerable<string> 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<string> GetFilesInDirectory(string directory)
{ {
Stack<string> stack = new Stack<string>(); Stack<string> stack = new Stack<string>();
@ -46,7 +68,7 @@ namespace Ryujinx.Ui
while (stack.Count > 0) while (stack.Count > 0)
{ {
string dir = stack.Pop(); string dir = stack.Pop();
string[] content = { }; string[] content = Array.Empty<string>();
try try
{ {
@ -84,19 +106,18 @@ namespace Ryujinx.Ui
} }
} }
public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty) public void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
{ {
controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
controlFile.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); controlFile.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
} }
public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage) public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
{ {
int numApplicationsFound = 0; int numApplicationsFound = 0;
int numApplicationsLoaded = 0; int numApplicationsLoaded = 0;
_loadingError = false; _loadingError = false;
_virtualFileSystem = virtualFileSystem;
_desiredTitleLanguage = desiredTitleLanguage; _desiredTitleLanguage = desiredTitleLanguage;
// Builds the applications list with paths to found applications // 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); ApplicationAdded?.Invoke(null, e);
} }
protected static void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e) protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
{ {
ApplicationCountUpdated?.Invoke(null, e); ApplicationCountUpdated?.Invoke(null, e);
} }
private static byte[] GetResourceBytes(string resourceName) private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
{
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)
{ {
(_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0); (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0);
// Return the ControlFS // Return the ControlFS
controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); 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<ApplicationMetadata> modifyFunction = null) internal ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
{ {
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui"); string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
string metadataFile = Path.Combine(metadataFolder, "metadata.json"); string metadataFile = Path.Combine(metadataFolder, "metadata.json");
@ -482,12 +493,7 @@ namespace Ryujinx.Ui
{ {
Directory.CreateDirectory(metadataFolder); Directory.CreateDirectory(metadataFolder);
appMetadata = new ApplicationMetadata appMetadata = new ApplicationMetadata();
{
Favorite = false,
TimePlayed = 0,
LastPlayed = "Never"
};
using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough)) 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."); Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
appMetadata = new ApplicationMetadata appMetadata = new ApplicationMetadata();
{
Favorite = false,
TimePlayed = 0,
LastPlayed = "Never"
};
} }
if (modifyFunction != null) if (modifyFunction != null)
@ -524,7 +525,7 @@ namespace Ryujinx.Ui
return appMetadata; return appMetadata;
} }
private static string ConvertSecondsToReadableString(double seconds) private string ConvertSecondsToReadableString(double seconds)
{ {
const int secondsPerMinute = 60; const int secondsPerMinute = 60;
const int secondsPerHour = secondsPerMinute * 60; const int secondsPerHour = secondsPerMinute * 60;
@ -552,9 +553,9 @@ namespace Ryujinx.Ui
return readableString; 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) 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)"; string updatePath = "(unknown)";

View file

@ -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";
}
}

View file

@ -1,16 +1,11 @@
using Gtk; using Gtk;
using System.Reflection;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Applet
{ {
internal class ErrorAppletDialog : MessageDialog 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) 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; int responseId = 0;
if (buttons != null) if (buttons != null)

View file

@ -1,12 +1,12 @@
using Gtk; using Gtk;
using Ryujinx.Common.Logging;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.Threading; using System.Threading;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Applet
{ {
internal class GtkHostUiHandler : IHostUiHandler internal class GtkHostUiHandler : IHostUiHandler
{ {
@ -19,16 +19,13 @@ namespace Ryujinx.Ui
public bool DisplayMessageDialog(ControllerAppletUiArgs args) public bool DisplayMessageDialog(ControllerAppletUiArgs args)
{ {
string playerCount = args.PlayerCountMin == args.PlayerCountMax string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
? $"exactly {args.PlayerCountMin}"
: $"{args.PlayerCountMin}-{args.PlayerCountMax}";
string message = string message = $"Application requests <b>{playerCount}</b> player(s) with:\n\n"
$"Application requests <b>{playerCount}</b> player(s) with:\n\n" + $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n"
+ $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n" + $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n"
+ $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n" + (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "")
+ (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "") + "<i>Please reconfigure Input now and then press OK.</i>";
+ "<i>Please reconfigure Input now and then press OK.</i>";
return DisplayMessageDialog("Controller Applet", message); return DisplayMessageDialog("Controller Applet", message);
} }
@ -36,17 +33,19 @@ namespace Ryujinx.Ui
public bool DisplayMessageDialog(string title, string message) public bool DisplayMessageDialog(string title, string message)
{ {
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false); ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
bool okPressed = false; bool okPressed = false;
Application.Invoke(delegate Application.Invoke(delegate
{ {
MessageDialog msgDialog = null; MessageDialog msgDialog = null;
try try
{ {
msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null)
{ {
Title = title, Title = title,
Text = message, Text = message,
UseMarkup = true UseMarkup = true
}; };
@ -54,16 +53,21 @@ namespace Ryujinx.Ui
msgDialog.Response += (object o, ResponseArgs args) => msgDialog.Response += (object o, ResponseArgs args) =>
{ {
if (args.ResponseId == ResponseType.Ok) okPressed = true; if (args.ResponseId == ResponseType.Ok)
{
okPressed = true;
}
dialogCloseEvent.Set(); dialogCloseEvent.Set();
msgDialog?.Dispose(); msgDialog?.Dispose();
}; };
msgDialog.Show(); 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(); dialogCloseEvent.Set();
} }
}); });
@ -76,24 +80,25 @@ namespace Ryujinx.Ui
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
{ {
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false); ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
bool okPressed = false;
bool error = false; bool okPressed = false;
bool error = false;
string inputText = args.InitialText ?? ""; string inputText = args.InitialText ?? "";
Application.Invoke(delegate Application.Invoke(delegate
{ {
try try
{ {
var swkbdDialog = new InputDialog(_parent) var swkbdDialog = new SwkbdAppletDialog(_parent)
{ {
Title = "Software Keyboard", Title = "Software Keyboard",
Text = args.HeaderText, Text = args.HeaderText,
SecondaryText = args.SubtitleText SecondaryText = args.SubtitleText
}; };
swkbdDialog.InputEntry.Text = inputText; swkbdDialog.InputEntry.Text = inputText;
swkbdDialog.InputEntry.PlaceholderText = args.GuideText; swkbdDialog.InputEntry.PlaceholderText = args.GuideText;
swkbdDialog.OkButton.Label = args.SubmitText; swkbdDialog.OkButton.Label = args.SubmitText;
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
@ -105,10 +110,11 @@ namespace Ryujinx.Ui
swkbdDialog.Dispose(); swkbdDialog.Dispose();
} }
catch (Exception e) catch (Exception ex)
{ {
error = true; error = true;
Logger.Error?.Print(LogClass.Application, $"Error displaying Software Keyboard: {e}");
GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}");
} }
finally finally
{ {
@ -126,12 +132,13 @@ namespace Ryujinx.Ui
public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value) public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value)
{ {
device.UserChannelPersistence.ExecuteProgram(kind, value); device.UserChannelPersistence.ExecuteProgram(kind, value);
MainWindow.GlWidget?.Exit(); ((MainWindow)_parent).GlRendererWidget?.Exit();
} }
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
{ {
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false); ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
bool showDetails = false; bool showDetails = false;
Application.Invoke(delegate Application.Invoke(delegate
@ -167,9 +174,9 @@ namespace Ryujinx.Ui
msgDialog.Show(); 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(); dialogCloseEvent.Set();
} }

View file

@ -0,0 +1,89 @@
using Gtk;
using System;
namespace Ryujinx.Ui.Applet
{
public class SwkbdAppletDialog : MessageDialog
{
private int _inputMin;
private int _inputMax;
private Predicate<int> _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 = $"<i>Must be at least {_inputMin} characters long</i>";
_checkLength = (length) => _inputMin <= length;
}
else
{
_validationInfo.Visible = true;
_validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
_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);
}
}
}

View file

@ -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; }
}
}

View file

@ -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();
}
}
}

View file

@ -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 += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>";
}
}
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();
}
}
}

View file

@ -11,7 +11,8 @@ using Ryujinx.Configuration;
using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.Motion; using Ryujinx.Modules.Motion;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -73,9 +74,9 @@ namespace Ryujinx.Ui
_device = device; _device = device;
this.Initialized += GLRenderer_Initialized; Initialized += GLRenderer_Initialized;
this.Destroyed += GLRenderer_Destroyed; Destroyed += GLRenderer_Destroyed;
this.ShuttingDown += GLRenderer_ShuttingDown; ShuttingDown += GLRenderer_ShuttingDown;
Initialize(); Initialize();
@ -89,7 +90,7 @@ namespace Ryujinx.Ui
| EventMask.KeyPressMask | EventMask.KeyPressMask
| EventMask.KeyReleaseMask)); | EventMask.KeyReleaseMask));
this.Shown += Renderer_Shown; Shown += Renderer_Shown;
_dsuClient = new Client(); _dsuClient = new Client();

View file

@ -2,10 +2,20 @@
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; 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) public static void OpenUrl(string url)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

View file

@ -1,9 +1,10 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.IO; using System.IO;
namespace Ryujinx.Ui.Diagnostic namespace Ryujinx.Ui.Helper
{ {
/// <summary> /// <summary>
/// Ensure installation validity /// Ensure installation validity

View file

@ -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;
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -1,69 +0,0 @@
using Gtk;
using System;
namespace Ryujinx.Ui
{
public class InputDialog : MessageDialog
{
private int _inputMin, _inputMax;
private Predicate<int> _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 = $"<i>Must be at least {_inputMin} characters long</i>";
_checkLength = (length) => _inputMin <= length;
}
else
{
_validationInfo.Visible = true;
_validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
_checkLength = (length) => _inputMin <= length && length <= _inputMax;
}
OnInputChanged(this, EventArgs.Empty);
}
private void OnInputChanged(object sender, EventArgs e)
{
OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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);
}
}
}

View file

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -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<SaveToImport> Saves { get; } = new List<SaveToImport>();
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<UInt128, UserId>(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;
}
}
}
}

View file

@ -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();
}
}
}

View file

@ -11,159 +11,66 @@ using LibHac.Ncm;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Helper;
using Ryujinx.Ui.Windows;
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Reflection;
using System.Threading; using System.Threading;
using static LibHac.Fs.ApplicationSaveDataManagement; 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 MainWindow _parent;
private readonly TreeIter _rowIter; private readonly VirtualFileSystem _virtualFileSystem;
private readonly VirtualFileSystem _virtualFileSystem;
private readonly BlitStruct<ApplicationControlProperty> _controlData; private readonly BlitStruct<ApplicationControlProperty> _controlData;
private readonly string _titleFilePath;
private readonly string _titleName;
private readonly string _titleIdText;
private readonly ulong _titleId;
private MessageDialog _dialog; private MessageDialog _dialog;
private bool _cancel; private bool _cancel;
public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
{ {
_gameTableStore = gameTableStore; _parent = parent;
_rowIter = rowIter;
InitializeComponent();
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_titleFilePath = titleFilePath;
_titleName = titleName;
_titleIdText = titleId;
_controlData = controlData; _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, GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
TooltipText = "Open the directory which contains Application's User Saves."
};
MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory") return;
{ }
Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0,
TooltipText = "Open the directory which contains Application's Device Saves."
};
MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory") _openSaveUserDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
{ _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0, _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
TooltipText = "Open the directory which contains Application's BCAT Saves."
};
MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates") string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
{ bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
TooltipText = "Open the Title Update management window"
};
MenuItem manageDlc = new MenuItem("Manage DLC") _extractRomFsMenuItem.Sensitive = hasNca;
{ _extractExeFsMenuItem.Sensitive = hasNca;
TooltipText = "Open the DLC management window" _extractLogoMenuItem.Sensitive = hasNca;
};
MenuItem openTitleModDir = new MenuItem("Open Mods Directory") PopupAtPointer(null);
{
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);
} }
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId) private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> 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) using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null)
{ {
Title = "Ryujinx", Title = "Ryujinx",
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
Text = $"There is no savedata for {titleName} [{titleId:x16}]", Text = $"There is no savedata for {titleName} [{titleId:x16}]",
SecondaryText = "Would you like to create savedata for this game?", SecondaryText = "Would you like to create savedata for this game?",
WindowPosition = WindowPosition.Center WindowPosition = WindowPosition.Center
@ -191,7 +97,7 @@ namespace Ryujinx.Ui
ref ApplicationControlProperty control = ref controlHolder.Value; 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 // 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. // and set the savedata sizes so a user savedata will be created.
@ -201,11 +107,10 @@ namespace Ryujinx.Ui
control.UserAccountSaveDataSize = 0x4000; control.UserAccountSaveDataSize = 0x4000;
control.UserAccountSaveDataJournalSize = 0x4000; control.UserAccountSaveDataJournalSize = 0x4000;
Logger.Warning?.Print(LogClass.Application, 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.");
"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); result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
@ -232,8 +137,15 @@ namespace Ryujinx.Ui
return false; 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}"); string saveRootPath = System.IO.Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
if (!Directory.Exists(saveRootPath)) 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 the committed directory exists, that path will be loaded the next time the savedata is mounted
if (Directory.Exists(committedPath)) if (Directory.Exists(committedPath))
{ {
return committedPath; OpenHelper.OpenFolder(committedPath);
} }
else
// 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); // 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) 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); FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to extract into", null, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Extract", ResponseType.Accept);
fileChooser.SetPosition(WindowPosition.Center); fileChooser.SetPosition(WindowPosition.Center);
int response = fileChooser.Run(); ResponseType response = (ResponseType)fileChooser.Run();
string destination = fileChooser.Filename; string destination = fileChooser.Filename;
fileChooser.Dispose(); fileChooser.Dispose();
if (response == (int)ResponseType.Accept) if (response == ResponseType.Accept)
{ {
Thread extractorThread = new Thread(() => Thread extractorThread = new Thread(() =>
{ {
string sourceFile = _gameTableStore.GetValue(_rowIter, 9).ToString();
Gtk.Application.Invoke(delegate Gtk.Application.Invoke(delegate
{ {
_dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null)
{ {
Title = "Ryujinx - NCA Section Extractor", 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(_titleFilePath)}...",
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(sourceFile)}...",
WindowPosition = WindowPosition.Center 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 mainNca = null;
Nca patchNca = null; Nca patchNca = null;
if ((System.IO.Path.GetExtension(sourceFile).ToLower() == ".nsp") || if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
(System.IO.Path.GetExtension(sourceFile).ToLower() == ".pfs0") || (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
(System.IO.Path.GetExtension(sourceFile).ToLower() == ".xci")) (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
{ {
PartitionFileSystem pfs; PartitionFileSystem pfs;
if (System.IO.Path.GetExtension(sourceFile) == ".xci") if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
{ {
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); 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()); mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
} }
@ -355,7 +266,6 @@ namespace Ryujinx.Ui
return; return;
} }
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null) if (updatePatchNca != null)
@ -370,8 +280,8 @@ namespace Ryujinx.Ui
FileSystemClient fsClient = _virtualFileSystem.FsClient; FileSystemClient fsClient = _virtualFileSystem.FsClient;
string source = DateTime.Now.ToFileTime().ToString().Substring(10); string source = DateTime.Now.ToFileTime().ToString()[10..];
string output = DateTime.Now.ToFileTime().ToString().Substring(10); string output = DateTime.Now.ToFileTime().ToString()[10..];
fsClient.Register(source.ToU8Span(), ncaFileSystem); fsClient.Register(source.ToU8Span(), ncaFileSystem);
fsClient.Register(output.ToU8Span(), new LocalFileSystem(destination)); 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) MessageDialog dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null)
{ {
Title = "Ryujinx - NCA Section Extractor", Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
SecondaryText = "Extraction has completed successfully.", SecondaryText = "Extraction has completed successfully.",
WindowPosition = WindowPosition.Center WindowPosition = WindowPosition.Center
}; };
@ -510,111 +419,49 @@ namespace Ryujinx.Ui
return Result.Success; return Result.Success;
} }
//
// Events // Events
//
private void OpenSaveUserDir_Clicked(object sender, EventArgs args) private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
{ {
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; SaveDataFilter saveDataFilter = new SaveDataFilter();
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); saveDataFilter.SetUserId(new UserId(1, 0)); // TODO: Remove Hardcoded value.
if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) OpenSaveDir(saveDataFilter);
{
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"
});
} }
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args) private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
{ {
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; SaveDataFilter saveDataFilter = new SaveDataFilter();
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); saveDataFilter.SetSaveDataType(SaveDataType.Device);
if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) OpenSaveDir(saveDataFilter);
{
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);
} }
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args) private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
{ {
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; SaveDataFilter saveDataFilter = new SaveDataFilter();
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); saveDataFilter.SetSaveDataType(SaveDataType.Bcat);
if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) OpenSaveDir(saveDataFilter);
{
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);
} }
private void ManageTitleUpdates_Clicked(object sender, EventArgs args) private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
{ {
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
TitleUpdateWindow titleUpdateWindow = new TitleUpdateWindow(titleId, titleName, _virtualFileSystem);
titleUpdateWindow.Show();
} }
private void ManageDlc_Clicked(object sender, EventArgs args) private void ManageDlc_Clicked(object sender, EventArgs args)
{ {
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
DlcWindow dlcWindow = new DlcWindow(titleId, titleName, _virtualFileSystem);
dlcWindow.Show();
} }
private void OpenTitleModDir_Clicked(object sender, EventArgs args) 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(); OpenHelper.OpenFolder(titleModsPath);
var titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
Process.Start(new ProcessStartInfo
{
FileName = titleModsPath,
UseShellExecute = true,
Verb = "open"
});
} }
private void ExtractRomFs_Clicked(object sender, EventArgs args) private void ExtractRomFs_Clicked(object sender, EventArgs args)
@ -634,8 +481,7 @@ namespace Ryujinx.Ui
private void OpenPtcDir_Clicked(object sender, EventArgs args) 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, _titleIdText, "cache", "cpu");
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu");
string mainPath = System.IO.Path.Combine(ptcDir, "0"); string mainPath = System.IO.Path.Combine(ptcDir, "0");
string backupPath = System.IO.Path.Combine(ptcDir, "1"); string backupPath = System.IO.Path.Combine(ptcDir, "1");
@ -647,51 +493,39 @@ namespace Ryujinx.Ui
Directory.CreateDirectory(backupPath); Directory.CreateDirectory(backupPath);
} }
Process.Start(new ProcessStartInfo OpenHelper.OpenFolder(ptcDir);
{
FileName = ptcDir,
UseShellExecute = true,
Verb = "open"
});
} }
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args) 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, _titleIdText, "cache", "shader");
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
if (!Directory.Exists(shaderCacheDir)) if (!Directory.Exists(shaderCacheDir))
{ {
Directory.CreateDirectory(shaderCacheDir); Directory.CreateDirectory(shaderCacheDir);
} }
Process.Start(new ProcessStartInfo OpenHelper.OpenFolder(shaderCacheDir);
{
FileName = shaderCacheDir,
UseShellExecute = true,
Verb = "open"
});
} }
private void PurgePtcCache_Clicked(object sender, EventArgs args) private void PurgePtcCache_Clicked(object sender, EventArgs args)
{ {
string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n"); DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
string titleId = tableEntry[1].ToLower(); DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "0")); MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the PPTC cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
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
};
List<FileInfo> cacheFiles = new List<FileInfo>(); List<FileInfo> cacheFiles = new List<FileInfo>();
if (mainDir.Exists) { cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); } if (mainDir.Exists)
if (backupDir.Exists) { cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); } {
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
{ {
@ -703,7 +537,7 @@ namespace Ryujinx.Ui
} }
catch(Exception e) 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) private void PurgeShaderCache_Clicked(object sender, EventArgs args)
{ {
string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n"); DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
string titleId = tableEntry[1].ToLower();
DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader")); MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
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
};
List<DirectoryInfo> cacheDirectory = new List<DirectoryInfo>(); List<DirectoryInfo> cacheDirectory = new List<DirectoryInfo>();
if (shaderCacheDir.Exists) { cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*")); } if (shaderCacheDir.Exists)
{
cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*"));
}
if (cacheDirectory.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) if (cacheDirectory.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
{ {
@ -739,7 +568,7 @@ namespace Ryujinx.Ui
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error?.Print(LogClass.Application, $"Error purging shader cache {directory.Name}: {e}"); GtkDialog.CreateErrorDialog($"Error purging shader cache {directory.Name}: {e}");
} }
} }
} }

View file

@ -1,7 +1,7 @@
using Gtk; using Gtk;
using System.Reflection; using Ryujinx.Common.Logging;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Widgets
{ {
internal class GtkDialog : MessageDialog 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) private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok)
: base(null, DialogFlags.Modal, messageType, buttonsType, null) : base(null, DialogFlags.Modal, messageType, buttonsType, null)
{ {
Title = title; Title = title;
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); Text = mainText;
Text = mainText; SecondaryText = secondaryText;
SecondaryText = secondaryText; WindowPosition = WindowPosition.Center;
WindowPosition = WindowPosition.Center; SecondaryUseMarkup = true;
Response += GtkDialog_Response;
SetSizeRequest(100, 20); Response += GtkDialog_Response;
SetSizeRequest(200, 20);
} }
private void GtkDialog_Response(object sender, ResponseArgs args) private void GtkDialog_Response(object sender, ResponseArgs args)
@ -25,9 +26,19 @@ namespace Ryujinx.Ui
Dispose(); 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) internal static void CreateWarningDialog(string mainText, string secondaryText)
@ -37,6 +48,8 @@ namespace Ryujinx.Ui
internal static void CreateErrorDialog(string errorMessage) 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(); 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) internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText)
{ {
if (_isChoiceDialogOpen) if (_isChoiceDialogOpen)
{
return false; return false;
}
_isChoiceDialogOpen = true; _isChoiceDialogOpen = true;
ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run(); ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run();
_isChoiceDialogOpen = false; _isChoiceDialogOpen = false;
return response == ResponseType.Yes; return response == ResponseType.Yes;

View file

@ -1,10 +1,9 @@
using Gtk; using Gtk;
using System; using System;
using System.Reflection;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Widgets
{ {
public class ProfileDialog : Dialog public class ProfileDialog : Dialog
{ {
@ -15,18 +14,16 @@ namespace Ryujinx.Ui
[GUI] Label _errorMessage; [GUI] Label _errorMessage;
#pragma warning restore CS0649, IDE0044 #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) private ProfileDialog(Builder builder) : base(builder.GetObject("_profileDialog").Handle)
{ {
builder.Autoconnect(this); builder.Autoconnect(this);
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
} }
private void OkToggle_Activated(object sender, EventArgs args) private void OkToggle_Activated(object sender, EventArgs args)
{ {
((ToggleButton)sender).SetStateFlags(0, true); ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
bool validFileName = true; bool validFileName = true;

View file

@ -1,4 +1,4 @@
namespace Ryujinx.Ui.Diagnostic namespace Ryujinx.Ui.Widgets
{ {
/// <summary> /// <summary>
/// Represent a common error that could be reported to the user by the emulator. /// Represent a common error that could be reported to the user by the emulator.

View file

@ -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 += "\n<b>For more information on how to fix this error, follow our Setup Guide.</b>";
}
}
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();
}
}
}

467
Ryujinx/Ui/Windows/AboutWindow.Designer.cs generated Normal file
View file

@ -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();
}
}
}

View file

@ -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<string[]>(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");
}
}
}

View file

@ -4,7 +4,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Configuration; using Ryujinx.Configuration;
using Ryujinx.HLE.FileSystem; using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -15,14 +15,14 @@ using System.Threading;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
using Key = Ryujinx.Configuration.Hid.Key; using Key = Ryujinx.Configuration.Hid.Key;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Windows
{ {
public class ControllerWindow : Window public class ControllerWindow : Window
{ {
private PlayerIndex _playerIndex; private readonly PlayerIndex _playerIndex;
private InputConfig _inputConfig; private readonly InputConfig _inputConfig;
private bool _isWaitingForInput;
private VirtualFileSystem _virtualFileSystem; private bool _isWaitingForInput;
#pragma warning disable CS0649, IDE0044 #pragma warning disable CS0649, IDE0044
[GUI] Adjustment _controllerDeadzoneLeft; [GUI] Adjustment _controllerDeadzoneLeft;
@ -90,17 +90,14 @@ namespace Ryujinx.Ui
[GUI] Image _controllerImage; [GUI] Image _controllerImage;
#pragma warning restore CS0649, IDE0044 #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); builder.Autoconnect(this);
this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); _playerIndex = controllerId;
_inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);
_playerIndex = controllerId;
_virtualFileSystem = virtualFileSystem;
_inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);
Title = $"Ryujinx - Controller Settings - {_playerIndex}"; Title = $"Ryujinx - Controller Settings - {_playerIndex}";
@ -119,7 +116,7 @@ namespace Ryujinx.Ui
_controllerType.Active = 0; // Set initial value to first in list. _controllerType.Active = 0; // Set initial value to first in list.
//Bind Events // Bind Events.
_lStickX.Clicked += Button_Pressed; _lStickX.Clicked += Button_Pressed;
_lStickY.Clicked += Button_Pressed; _lStickY.Clicked += Button_Pressed;
_lStickUp.Clicked += Button_Pressed; _lStickUp.Clicked += Button_Pressed;
@ -153,12 +150,15 @@ namespace Ryujinx.Ui
_rSl.Clicked += Button_Pressed; _rSl.Clicked += Button_Pressed;
_rSr.Clicked += Button_Pressed; _rSr.Clicked += Button_Pressed;
// Setup current values // Setup current values.
UpdateInputDeviceList(); UpdateInputDeviceList();
SetAvailableOptions(); SetAvailableOptions();
ClearValues(); ClearValues();
if (_inputDevice.ActiveId != null) SetCurrentValues(); if (_inputDevice.ActiveId != null)
{
SetCurrentValues();
}
} }
private void UpdateInputDeviceList() private void UpdateInputDeviceList()
@ -193,7 +193,7 @@ namespace Ryujinx.Ui
{ {
if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard")) if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard"))
{ {
this.ShowAll(); ShowAll();
_leftStickController.Hide(); _leftStickController.Hide();
_rightStickController.Hide(); _rightStickController.Hide();
_deadZoneLeftBox.Hide(); _deadZoneLeftBox.Hide();
@ -202,7 +202,7 @@ namespace Ryujinx.Ui
} }
else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller"))
{ {
this.ShowAll(); ShowAll();
_leftStickKeyboard.Hide(); _leftStickKeyboard.Hide();
_rightStickKeyboard.Hide(); _rightStickKeyboard.Hide();
} }
@ -249,21 +249,13 @@ namespace Ryujinx.Ui
break; break;
} }
switch (_controllerType.ActiveId) _controllerImage.Pixbuf = _controllerType.ActiveId switch
{ {
case "ProController": "ProController" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_ProCon.svg", 400, 400),
_controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.svg", 400, 400); "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConLeft.svg", 400, 400),
break; "JoyconRight" => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConRight.svg", 400, 400),
case "JoyconLeft": _ => new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Controller_JoyConPair.svg", 400, 400),
_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;
}
} }
private void ClearValues() private void ClearValues()
@ -620,8 +612,7 @@ namespace Ryujinx.Ui
if (joystickState.IsButtonDown(i)) if (joystickState.IsButtonDown(i))
{ {
Enum.TryParse($"Button{i}", out pressedButton); Enum.TryParse($"Button{i}", out pressedButton);
return true;
return true;
} }
} }
@ -674,7 +665,9 @@ namespace Ryujinx.Ui
return path; return path;
} }
//Events //
// Events
//
private void InputDevice_Changed(object sender, EventArgs args) private void InputDevice_Changed(object sender, EventArgs args)
{ {
SetAvailableOptions(); SetAvailableOptions();
@ -692,7 +685,7 @@ namespace Ryujinx.Ui
{ {
UpdateInputDeviceList(); UpdateInputDeviceList();
_refreshInputDevicesButton.SetStateFlags(0, true); _refreshInputDevicesButton.SetStateFlags(StateFlags.Normal, true);
} }
private void Button_Pressed(object sender, EventArgs args) private void Button_Pressed(object sender, EventArgs args)
@ -719,7 +712,7 @@ namespace Ryujinx.Ui
{ {
Application.Invoke(delegate Application.Invoke(delegate
{ {
button.SetStateFlags(0, true); button.SetStateFlags(StateFlags.Normal, true);
}); });
_isWaitingForInput = false; _isWaitingForInput = false;
@ -731,7 +724,7 @@ namespace Ryujinx.Ui
Application.Invoke(delegate Application.Invoke(delegate
{ {
button.Label = pressedKey.ToString(); button.Label = pressedKey.ToString();
button.SetStateFlags(0, true); button.SetStateFlags(StateFlags.Normal, true);
}); });
} }
else if (_inputDevice.ActiveId.StartsWith("controller")) else if (_inputDevice.ActiveId.StartsWith("controller"))
@ -745,7 +738,7 @@ namespace Ryujinx.Ui
{ {
Application.Invoke(delegate Application.Invoke(delegate
{ {
button.SetStateFlags(0, true); button.SetStateFlags(StateFlags.Normal, true);
}); });
_isWaitingForInput = false; _isWaitingForInput = false;
@ -757,7 +750,7 @@ namespace Ryujinx.Ui
Application.Invoke(delegate Application.Invoke(delegate
{ {
button.Label = pressedButton.ToString(); 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) 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; if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) return;
@ -940,7 +933,7 @@ namespace Ryujinx.Ui
private void ProfileAdd_Activated(object sender, EventArgs args) private void ProfileAdd_Activated(object sender, EventArgs args)
{ {
((ToggleButton)sender).SetStateFlags(0, true); ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
if (_inputDevice.ActiveId == "disabled") return; if (_inputDevice.ActiveId == "disabled") return;
@ -973,7 +966,7 @@ namespace Ryujinx.Ui
private void ProfileRemove_Activated(object sender, EventArgs args) 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; 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. // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
MainWindow.SaveConfig(); ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
Dispose(); Dispose();
} }

View file

@ -1,13 +1,12 @@
using Gtk; using Gtk;
using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -16,7 +15,7 @@ using System.Text;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Windows
{ {
public class DlcWindow : Window public class DlcWindow : Window
{ {
@ -31,9 +30,9 @@ namespace Ryujinx.Ui
[GUI] TreeSelection _dlcTreeSelection; [GUI] TreeSelection _dlcTreeSelection;
#pragma warning restore CS0649, IDE0044 #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); builder.Autoconnect(this);
@ -51,10 +50,7 @@ namespace Ryujinx.Ui
_dlcContainerList = new List<DlcContainer>(); _dlcContainerList = new List<DlcContainer>();
} }
_dlcTreeView.Model = new TreeStore( _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string));
typeof(bool),
typeof(string),
typeof(string));
CellRendererToggle enableToggle = new CellRendererToggle(); CellRendererToggle enableToggle = new CellRendererToggle();
enableToggle.Toggled += (sender, args) => enableToggle.Toggled += (sender, args) =>
@ -104,17 +100,9 @@ namespace Ryujinx.Ui
{ {
return new Nca(_virtualFileSystem.KeySet, ncaStorage); return new Nca(_virtualFileSystem.KeySet, ncaStorage);
} }
catch (InvalidDataException exception) catch (Exception exception)
{ {
Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {containerPath}"); GtkDialog.CreateErrorDialog($"{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}");
} }
return null; return null;

View file

@ -6,20 +6,20 @@ using Ryujinx.Configuration;
using Ryujinx.Configuration.System; using Ryujinx.Configuration.System;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.Ui.Helper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Windows
{ {
public class SettingsWindow : Window public class SettingsWindow : Window
{ {
private readonly VirtualFileSystem _virtualFileSystem; private readonly MainWindow _parent;
private readonly ListStore _gameDirsBoxStore; private readonly ListStore _gameDirsBoxStore;
private readonly ListStore _audioBackendStore; private readonly ListStore _audioBackendStore;
private readonly TimeZoneContentManager _timeZoneContentManager; private readonly TimeZoneContentManager _timeZoneContentManager;
@ -86,36 +86,34 @@ namespace Ryujinx.Ui
#pragma warning restore CS0649, IDE0044 #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); builder.Autoconnect(this);
this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
_virtualFileSystem = virtualFileSystem;
_timeZoneContentManager = new TimeZoneContentManager(); _timeZoneContentManager = new TimeZoneContentManager();
_timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None); _timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None);
_validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly. _validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly.
//Bind Events // Bind Events.
_configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player1); _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1);
_configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player2); _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2);
_configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player3); _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3);
_configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player4); _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4);
_configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player5); _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5);
_configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player6); _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6);
_configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player7); _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7);
_configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8); _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8);
_configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld); _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld);
_systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut;
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
//Setup Currents // Setup Currents.
if (ConfigurationState.Instance.Logger.EnableFileLog) if (ConfigurationState.Instance.Logger.EnableFileLog)
{ {
_fileLogToggle.Click(); _fileLogToggle.Click();
@ -419,12 +417,14 @@ namespace Ryujinx.Ui
ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1); ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1);
} }
MainWindow.SaveConfig(); ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig(); _parent.UpdateGraphicsConfig();
MainWindow.ApplyTheme(); ThemeHelper.ApplyTheme();
} }
//Events //
// Events
//
private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e) private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e)
{ {
if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text)) if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text))
@ -439,7 +439,7 @@ namespace Ryujinx.Ui
return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region 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, 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) private void SystemTimeSpin_ValueChanged(object sender, EventArgs e)
@ -511,7 +511,7 @@ namespace Ryujinx.Ui
_addGameDirBox.Buffer.Text = ""; _addGameDirBox.Buffer.Text = "";
((ToggleButton)sender).SetStateFlags(0, true); ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
} }
private void RemoveDir_Pressed(object sender, EventArgs args) private void RemoveDir_Pressed(object sender, EventArgs args)
@ -523,7 +523,7 @@ namespace Ryujinx.Ui
_gameDirsBoxStore.Remove(ref treeIter); _gameDirsBoxStore.Remove(ref treeIter);
} }
((ToggleButton)sender).SetStateFlags(0, true); ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
} }
private void CustThemeToggle_Activated(object sender, EventArgs args) private void CustThemeToggle_Activated(object sender, EventArgs args)
@ -535,27 +535,25 @@ namespace Ryujinx.Ui
private void BrowseThemeDir_Pressed(object sender, EventArgs args) 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); using (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)
{ {
_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(StateFlags.Normal, true);
_browseThemePath.SetStateFlags(0, 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); new ControllerWindow(playerIndex).Show();
controllerWin.Show();
} }
private void SaveToggle_Activated(object sender, EventArgs args) private void SaveToggle_Activated(object sender, EventArgs args)

View file

@ -1,5 +1,4 @@
using Gtk; using Gtk;
using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
@ -7,9 +6,9 @@ using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.NcaUtils;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -19,16 +18,18 @@ using System.Text;
using GUI = Gtk.Builder.ObjectAttribute; using GUI = Gtk.Builder.ObjectAttribute;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui namespace Ryujinx.Ui.Windows
{ {
public class TitleUpdateWindow : Window public class TitleUpdateWindow : Window
{ {
private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly string _titleId; private readonly string _titleId;
private readonly string _updateJsonPath; private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
private Dictionary<RadioButton, string> _radioButtonToPathDictionary;
private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary;
#pragma warning disable CS0649, IDE0044 #pragma warning disable CS0649, IDE0044
[GUI] Label _baseTitleInfoLabel; [GUI] Label _baseTitleInfoLabel;
@ -36,10 +37,12 @@ namespace Ryujinx.Ui
[GUI] RadioButton _noUpdateRadioButton; [GUI] RadioButton _noUpdateRadioButton;
#pragma warning restore CS0649, IDE0044 #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); builder.Autoconnect(this);
_titleId = titleId; _titleId = titleId;
@ -61,20 +64,26 @@ namespace Ryujinx.Ui
} }
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]"; _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
_noUpdateRadioButton.Active = true;
foreach (string path in _titleUpdateWindowData.Paths) 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)) 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!"); 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}"); GtkDialog.CreateErrorDialog($"{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}");
}
} }
} }
} }
@ -144,23 +139,21 @@ namespace Ryujinx.Ui
private void AddButton_Clicked(object sender, EventArgs args) 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, fileChooser.SelectMultiple = true;
Filter = new FileFilter() fileChooser.SetPosition(WindowPosition.Center);
}; fileChooser.Filter = new FileFilter();
fileChooser.SetPosition(WindowPosition.Center); fileChooser.Filter.AddPattern("*.nsp");
fileChooser.Filter.AddPattern("*.nsp");
if (fileChooser.Run() == (int)ResponseType.Accept) if (fileChooser.Run() == (int)ResponseType.Accept)
{
foreach (string path in fileChooser.Filenames)
{ {
AddUpdate(path); foreach (string path in fileChooser.Filenames)
{
AddUpdate(path);
}
} }
} }
fileChooser.Dispose();
} }
private void RemoveButton_Clicked(object sender, EventArgs args) private void RemoveButton_Clicked(object sender, EventArgs args)
@ -196,7 +189,8 @@ namespace Ryujinx.Ui
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
} }
MainWindow.UpdateGameTable(); _parent.UpdateGameTable();
Dispose(); Dispose();
} }