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

View file

@ -1,15 +1,16 @@
using DiscordRPC;
using Ryujinx.Common;
using Ryujinx.Configuration;
using System;
using System.Linq;
namespace Ryujinx.Configuration
namespace Ryujinx.Modules
{
static class DiscordIntegrationModule
{
private static DiscordRpcClient _discordClient;
private static string LargeDescription = "Ryujinx is a Nintendo Switch emulator.";
private const string LargeDescription = "Ryujinx is a Nintendo Switch emulator.";
public static RichPresence DiscordPresence { get; private set; }
@ -17,7 +18,7 @@ namespace Ryujinx.Configuration
{
DiscordPresence = new RichPresence
{
Assets = new Assets
Assets = new Assets
{
LargeImageKey = "ryujinx",
LargeImageText = LargeDescription
@ -169,4 +170,4 @@ namespace Ryujinx.Configuration
"051337133769a000", // RGB-Seizure
};
}
}
}

View file

@ -11,7 +11,7 @@ using System.Net.Sockets;
using System.Numerics;
using System.Threading.Tasks;
namespace Ryujinx.Motion
namespace Ryujinx.Modules.Motion
{
public class Client : IDisposable
{
@ -396,7 +396,7 @@ namespace Ryujinx.Motion
}
}
public unsafe void RequestData(int clientId, int slot)
public unsafe void RequestData(int clientId, int slot)
{
if (!_active)
{
@ -439,7 +439,7 @@ namespace Ryujinx.Motion
{
Header header = new Header()
{
ID = (uint)clientId,
Id = (uint)clientId,
MagicString = Magic,
Version = Version,
Length = 0,
@ -456,4 +456,4 @@ namespace Ryujinx.Motion
CloseClients();
}
}
}
}

View file

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

View file

@ -1,7 +1,7 @@
using System;
using System.Numerics;
namespace Ryujinx.Motion
namespace Ryujinx.Modules.Motion
{
public class MotionInput
{
@ -82,4 +82,4 @@ namespace Ryujinx.Motion
return degree * (MathF.PI / 180);
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using Ryujinx.Common.Logging;
using Ryujinx.Ui;
using Ryujinx.Ui.Widgets;
using System;
using System.IO;
using System.Net;
@ -13,7 +14,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Ryujinx
namespace Ryujinx.Modules
{
public static class Updater
{
@ -84,7 +85,7 @@ namespace Ryujinx
{
if (showVersionUpToDate)
{
GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", "");
GtkDialog.CreateUpdaterInfoDialog("You are already using the most updated version of Ryujinx!", "");
}
return;
@ -115,7 +116,7 @@ namespace Ryujinx
{
if (showVersionUpToDate)
{
GtkDialog.CreateInfoDialog("Ryujinx - Updater", "You are already using the most updated version of Ryujinx!", "");
GtkDialog.CreateUpdaterInfoDialog("You are already using the most updated version of Ryujinx!", "");
}
Running = false;

View file

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

View file

@ -55,53 +55,51 @@
</PropertyGroup>
<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\ProfileDialog.glade" />
<None Remove="Ui\SettingsWindow.glade" />
<None Remove="Ui\TitleUpdateWindow.glade" />
<None Remove="Ui\UpdateDialog.glade" />
<None Remove="Ui\Resources\Controller_JoyConLeft.svg" />
<None Remove="Ui\Resources\Controller_JoyConPair.svg" />
<None Remove="Ui\Resources\Controller_JoyConRight.svg" />
<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>
<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\ProfileDialog.glade" />
<EmbeddedResource Include="Ui\SettingsWindow.glade" />
<EmbeddedResource Include="Ui\DlcWindow.glade" />
<EmbeddedResource Include="Ui\TitleUpdateWindow.glade" />
<EmbeddedResource Include="Updater\UpdateDialog.glade" />
<EmbeddedResource Include="Ui\Resources\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Ui\Resources\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Ui\Resources\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Ui\Resources\Controller_ProCon.svg" />
<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>
</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;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.App
{
public class ApplicationAddedEventArgs : EventArgs
{

View file

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

View file

@ -1,10 +1,9 @@
using LibHac;
using LibHac.Common;
using LibHac.Common;
using LibHac.Ns;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.App
{
public struct ApplicationData
public class ApplicationData
{
public bool Favorite { get; set; }
public byte[] Icon { get; set; }
@ -19,4 +18,4 @@ namespace Ryujinx.Ui
public string Path { get; set; }
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
}
}
}

View file

@ -11,6 +11,7 @@ using Ryujinx.Configuration.System;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
using System.IO;
@ -20,24 +21,45 @@ using System.Text.Json;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.App
{
public class ApplicationLibrary
{
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
public static event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png");
private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
private readonly byte[] _nspIcon;
private readonly byte[] _xciIcon;
private readonly byte[] _ncaIcon;
private readonly byte[] _nroIcon;
private readonly byte[] _nsoIcon;
private static VirtualFileSystem _virtualFileSystem;
private static Language _desiredTitleLanguage;
private static bool _loadingError;
private VirtualFileSystem _virtualFileSystem;
private Language _desiredTitleLanguage;
private bool _loadingError;
public static IEnumerable<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>();
@ -46,7 +68,7 @@ namespace Ryujinx.Ui
while (stack.Count > 0)
{
string dir = stack.Pop();
string[] content = { };
string[] content = Array.Empty<string>();
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();
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 numApplicationsLoaded = 0;
_loadingError = false;
_virtualFileSystem = virtualFileSystem;
_desiredTitleLanguage = desiredTitleLanguage;
// Builds the applications list with paths to found applications
@ -442,36 +463,26 @@ namespace Ryujinx.Ui
}
}
protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
protected void OnApplicationAdded(ApplicationAddedEventArgs e)
{
ApplicationAdded?.Invoke(null, e);
}
protected static void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
{
ApplicationCountUpdated?.Invoke(null, e);
}
private static byte[] GetResourceBytes(string resourceName)
{
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
byte[] resourceByteArray = new byte[resourceStream.Length];
resourceStream.Read(resourceByteArray);
return resourceByteArray;
}
private static void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
{
(_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0);
// Return the ControlFS
controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
titleId = controlNca?.Header.TitleId.ToString("x16");
titleId = controlNca?.Header.TitleId.ToString("x16");
}
internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
internal ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
{
string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
@ -482,12 +493,7 @@ namespace Ryujinx.Ui
{
Directory.CreateDirectory(metadataFolder);
appMetadata = new ApplicationMetadata
{
Favorite = false,
TimePlayed = 0,
LastPlayed = "Never"
};
appMetadata = new ApplicationMetadata();
using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough))
{
@ -503,12 +509,7 @@ namespace Ryujinx.Ui
{
Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
appMetadata = new ApplicationMetadata
{
Favorite = false,
TimePlayed = 0,
LastPlayed = "Never"
};
appMetadata = new ApplicationMetadata();
}
if (modifyFunction != null)
@ -524,7 +525,7 @@ namespace Ryujinx.Ui
return appMetadata;
}
private static string ConvertSecondsToReadableString(double seconds)
private string ConvertSecondsToReadableString(double seconds)
{
const int secondsPerMinute = 60;
const int secondsPerHour = secondsPerMinute * 60;
@ -552,9 +553,9 @@ namespace Ryujinx.Ui
return readableString;
}
private static void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
private void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
{
Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Titles.Length > (int)desiredTitleLanguage)
{
@ -611,7 +612,7 @@ namespace Ryujinx.Ui
}
}
private static bool IsUpdateApplied(string titleId, out string version)
private bool IsUpdateApplied(string titleId, out string version)
{
string updatePath = "(unknown)";

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 System.Reflection;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.Applet
{
internal class ErrorAppletDialog : MessageDialog
{
internal static bool _isExitDialogOpen = false;
public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null)
{
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
int responseId = 0;
if (buttons != null)

View file

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

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

View file

@ -2,10 +2,20 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.Helper
{
static class UrlHelper
static class OpenHelper
{
public static void OpenFolder(string path)
{
Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = true,
Verb = "open"
});
}
public static void OpenUrl(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -26,4 +36,4 @@ namespace Ryujinx.Ui
}
}
}
}
}

View file

@ -1,9 +1,10 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.Ui.Widgets;
using System;
using System.IO;
namespace Ryujinx.Ui.Diagnostic
namespace Ryujinx.Ui.Helper
{
/// <summary>
/// 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

@ -21,4 +21,4 @@ namespace Ryujinx.Ui
GpuName = gpuName;
}
}
}
}

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 Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Helper;
using Ryujinx.Ui.Windows;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using static LibHac.Fs.ApplicationSaveDataManagement;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.Widgets
{
public class GameTableContextMenu : Menu
public partial class GameTableContextMenu : Menu
{
private readonly ListStore _gameTableStore;
private readonly TreeIter _rowIter;
private readonly VirtualFileSystem _virtualFileSystem;
private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem;
private readonly BlitStruct<ApplicationControlProperty> _controlData;
private readonly string _titleFilePath;
private readonly string _titleName;
private readonly string _titleIdText;
private readonly ulong _titleId;
private MessageDialog _dialog;
private bool _cancel;
public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem)
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
{
_gameTableStore = gameTableStore;
_rowIter = rowIter;
_parent = parent;
InitializeComponent();
_virtualFileSystem = virtualFileSystem;
_titleFilePath = titleFilePath;
_titleName = titleName;
_titleIdText = titleId;
_controlData = controlData;
MenuItem openSaveUserDir = new MenuItem("Open User Save Directory")
if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
{
Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0,
TooltipText = "Open the directory which contains Application's User Saves."
};
GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory")
{
Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0,
TooltipText = "Open the directory which contains Application's Device Saves."
};
return;
}
MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory")
{
Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0,
TooltipText = "Open the directory which contains Application's BCAT Saves."
};
_openSaveUserDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
_openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
_openSaveBcatDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates")
{
TooltipText = "Open the Title Update management window"
};
string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
MenuItem manageDlc = new MenuItem("Manage DLC")
{
TooltipText = "Open the DLC management window"
};
_extractRomFsMenuItem.Sensitive = hasNca;
_extractExeFsMenuItem.Sensitive = hasNca;
_extractLogoMenuItem.Sensitive = hasNca;
MenuItem openTitleModDir = new MenuItem("Open Mods Directory")
{
TooltipText = "Open the directory which contains Application's Mods."
};
string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower();
bool hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci";
MenuItem extractMenu = new MenuItem("Extract Data");
MenuItem extractRomFs = new MenuItem("RomFS")
{
Sensitive = hasNca,
TooltipText = "Extract the RomFS section from Application's current config (including updates)."
};
MenuItem extractExeFs = new MenuItem("ExeFS")
{
Sensitive = hasNca,
TooltipText = "Extract the ExeFS section from Application's current config (including updates)."
};
MenuItem extractLogo = new MenuItem("Logo")
{
Sensitive = hasNca,
TooltipText = "Extract the Logo section from Application's current config (including updates)."
};
Menu extractSubMenu = new Menu();
extractSubMenu.Append(extractExeFs);
extractSubMenu.Append(extractRomFs);
extractSubMenu.Append(extractLogo);
extractMenu.Submenu = extractSubMenu;
MenuItem managePtcMenu = new MenuItem("Cache Management");
MenuItem purgePtcCache = new MenuItem("Purge PPTC Cache")
{
TooltipText = "Delete the Application's PPTC cache."
};
MenuItem purgeShaderCache = new MenuItem("Purge Shader Cache")
{
TooltipText = "Delete the Application's shader cache."
};
MenuItem openPtcDir = new MenuItem("Open PPTC Directory")
{
TooltipText = "Open the directory which contains the Application's PPTC cache."
};
MenuItem openShaderCacheDir = new MenuItem("Open Shader Cache Directory")
{
TooltipText = "Open the directory which contains the Application's shader cache."
};
Menu manageSubMenu = new Menu();
manageSubMenu.Append(purgePtcCache);
manageSubMenu.Append(purgeShaderCache);
manageSubMenu.Append(openPtcDir);
manageSubMenu.Append(openShaderCacheDir);
managePtcMenu.Submenu = manageSubMenu;
openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked;
manageTitleUpdates.Activated += ManageTitleUpdates_Clicked;
manageDlc.Activated += ManageDlc_Clicked;
openTitleModDir.Activated += OpenTitleModDir_Clicked;
extractRomFs.Activated += ExtractRomFs_Clicked;
extractExeFs.Activated += ExtractExeFs_Clicked;
extractLogo.Activated += ExtractLogo_Clicked;
purgePtcCache.Activated += PurgePtcCache_Clicked;
purgeShaderCache.Activated += PurgeShaderCache_Clicked;
openPtcDir.Activated += OpenPtcDir_Clicked;
openShaderCacheDir.Activated += OpenShaderCacheDir_Clicked;
this.Add(openSaveUserDir);
this.Add(openSaveDeviceDir);
this.Add(openSaveBcatDir);
this.Add(new SeparatorMenuItem());
this.Add(manageTitleUpdates);
this.Add(manageDlc);
this.Add(openTitleModDir);
this.Add(new SeparatorMenuItem());
this.Add(managePtcMenu);
this.Add(extractMenu);
PopupAtPointer(null);
}
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<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)
{
Title = "Ryujinx",
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
Text = $"There is no savedata for {titleName} [{titleId:x16}]",
SecondaryText = "Would you like to create savedata for this game?",
WindowPosition = WindowPosition.Center
@ -191,7 +97,7 @@ namespace Ryujinx.Ui
ref ApplicationControlProperty control = ref controlHolder.Value;
if (LibHac.Utilities.IsEmpty(controlHolder.ByteSpan))
if (Utilities.IsEmpty(controlHolder.ByteSpan))
{
// If the current application doesn't have a loaded control property, create a dummy one
// and set the savedata sizes so a user savedata will be created.
@ -201,11 +107,10 @@ namespace Ryujinx.Ui
control.UserAccountSaveDataSize = 0x4000;
control.UserAccountSaveDataJournalSize = 0x4000;
Logger.Warning?.Print(LogClass.Application,
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
}
Uid user = new Uid(1, 0);
Uid user = new Uid(1, 0); // TODO: Remove Hardcoded value.
result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
@ -232,8 +137,15 @@ namespace Ryujinx.Ui
return false;
}
private string GetSaveDataDirectory(ulong saveDataId)
private void OpenSaveDir(SaveDataFilter saveDataFilter)
{
saveDataFilter.SetProgramId(new ProgramId(_titleId));
if (!TryFindSaveData(_titleName, _titleId, _controlData, saveDataFilter, out ulong saveDataId))
{
return;
}
string saveRootPath = System.IO.Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
if (!Directory.Exists(saveRootPath))
@ -248,17 +160,19 @@ namespace Ryujinx.Ui
// If the committed directory exists, that path will be loaded the next time the savedata is mounted
if (Directory.Exists(committedPath))
{
return committedPath;
OpenHelper.OpenFolder(committedPath);
}
// If the working directory exists and the committed directory doesn't,
// the working directory will be loaded the next time the savedata is mounted
if (!Directory.Exists(workingPath))
else
{
Directory.CreateDirectory(workingPath);
}
// If the working directory exists and the committed directory doesn't,
// the working directory will be loaded the next time the savedata is mounted
if (!Directory.Exists(workingPath))
{
Directory.CreateDirectory(workingPath);
}
return workingPath;
OpenHelper.OpenFolder(workingPath);
}
}
private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0)
@ -266,24 +180,21 @@ namespace Ryujinx.Ui
FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to extract into", null, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Extract", ResponseType.Accept);
fileChooser.SetPosition(WindowPosition.Center);
int response = fileChooser.Run();
string destination = fileChooser.Filename;
ResponseType response = (ResponseType)fileChooser.Run();
string destination = fileChooser.Filename;
fileChooser.Dispose();
if (response == (int)ResponseType.Accept)
if (response == ResponseType.Accept)
{
Thread extractorThread = new Thread(() =>
{
string sourceFile = _gameTableStore.GetValue(_rowIter, 9).ToString();
Gtk.Application.Invoke(delegate
{
_dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null)
{
Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(sourceFile)}...",
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
WindowPosition = WindowPosition.Center
};
@ -295,18 +206,18 @@ namespace Ryujinx.Ui
}
});
using (FileStream file = new FileStream(sourceFile, FileMode.Open, FileAccess.Read))
using (FileStream file = new FileStream(_titleFilePath, FileMode.Open, FileAccess.Read))
{
Nca mainNca = null;
Nca patchNca = null;
if ((System.IO.Path.GetExtension(sourceFile).ToLower() == ".nsp") ||
(System.IO.Path.GetExtension(sourceFile).ToLower() == ".pfs0") ||
(System.IO.Path.GetExtension(sourceFile).ToLower() == ".xci"))
if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
{
PartitionFileSystem pfs;
if (System.IO.Path.GetExtension(sourceFile) == ".xci")
if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
{
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
@ -338,7 +249,7 @@ namespace Ryujinx.Ui
}
}
}
else if (System.IO.Path.GetExtension(sourceFile).ToLower() == ".nca")
else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
{
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
}
@ -355,7 +266,6 @@ namespace Ryujinx.Ui
return;
}
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null)
@ -370,8 +280,8 @@ namespace Ryujinx.Ui
FileSystemClient fsClient = _virtualFileSystem.FsClient;
string source = DateTime.Now.ToFileTime().ToString().Substring(10);
string output = DateTime.Now.ToFileTime().ToString().Substring(10);
string source = DateTime.Now.ToFileTime().ToString()[10..];
string output = DateTime.Now.ToFileTime().ToString()[10..];
fsClient.Register(source.ToU8Span(), ncaFileSystem);
fsClient.Register(output.ToU8Span(), new LocalFileSystem(destination));
@ -400,7 +310,6 @@ namespace Ryujinx.Ui
MessageDialog dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null)
{
Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
SecondaryText = "Extraction has completed successfully.",
WindowPosition = WindowPosition.Center
};
@ -510,111 +419,49 @@ namespace Ryujinx.Ui
return Result.Success;
}
//
// Events
//
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetUserId(new UserId(1, 0)); // TODO: Remove Hardcoded value.
if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
return;
}
SaveDataFilter filter = new SaveDataFilter();
filter.SetUserId(new UserId(1, 0));
OpenSaveDir(titleName, titleIdNumber, filter);
}
private void OpenSaveDir(string titleName, ulong titleId, SaveDataFilter filter)
{
filter.SetProgramId(new ProgramId(titleId));
if (!TryFindSaveData(titleName, titleId, _controlData, filter, out ulong saveDataId))
{
return;
}
string saveDir = GetSaveDataDirectory(saveDataId);
Process.Start(new ProcessStartInfo
{
FileName = saveDir,
UseShellExecute = true,
Verb = "open"
});
OpenSaveDir(saveDataFilter);
}
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetSaveDataType(SaveDataType.Device);
if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
return;
}
SaveDataFilter filter = new SaveDataFilter();
filter.SetSaveDataType(SaveDataType.Device);
OpenSaveDir(titleName, titleIdNumber, filter);
OpenSaveDir(saveDataFilter);
}
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetSaveDataType(SaveDataType.Bcat);
if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
return;
}
SaveDataFilter filter = new SaveDataFilter();
filter.SetSaveDataType(SaveDataType.Bcat);
OpenSaveDir(titleName, titleIdNumber, filter);
OpenSaveDir(saveDataFilter);
}
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
TitleUpdateWindow titleUpdateWindow = new TitleUpdateWindow(titleId, titleName, _virtualFileSystem);
titleUpdateWindow.Show();
new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
}
private void ManageDlc_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
DlcWindow dlcWindow = new DlcWindow(titleId, titleName, _virtualFileSystem);
dlcWindow.Show();
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
}
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
{
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
string modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, _titleIdText);
var modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath();
var titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
Process.Start(new ProcessStartInfo
{
FileName = titleModsPath,
UseShellExecute = true,
Verb = "open"
});
OpenHelper.OpenFolder(titleModsPath);
}
private void ExtractRomFs_Clicked(object sender, EventArgs args)
@ -634,8 +481,7 @@ namespace Ryujinx.Ui
private void OpenPtcDir_Clicked(object sender, EventArgs args)
{
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu");
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
string mainPath = System.IO.Path.Combine(ptcDir, "0");
string backupPath = System.IO.Path.Combine(ptcDir, "1");
@ -646,52 +492,40 @@ namespace Ryujinx.Ui
Directory.CreateDirectory(mainPath);
Directory.CreateDirectory(backupPath);
}
Process.Start(new ProcessStartInfo
{
FileName = ptcDir,
UseShellExecute = true,
Verb = "open"
});
OpenHelper.OpenFolder(ptcDir);
}
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
{
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
Directory.CreateDirectory(shaderCacheDir);
}
Process.Start(new ProcessStartInfo
{
FileName = shaderCacheDir,
UseShellExecute = true,
Verb = "open"
});
OpenHelper.OpenFolder(shaderCacheDir);
}
private void PurgePtcCache_Clicked(object sender, EventArgs args)
{
string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n");
string titleId = tableEntry[1].ToLower();
DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "1"));
MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
{
Title = "Ryujinx - Warning",
Text = $"You are about to delete the PPTC cache for '{tableEntry[0]}'. Are you sure you want to proceed?",
WindowPosition = WindowPosition.Center
};
DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the PPTC cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
List<FileInfo> cacheFiles = new List<FileInfo>();
if (mainDir.Exists) { cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); }
if (backupDir.Exists) { cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); }
if (mainDir.Exists)
{
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
{
@ -703,7 +537,7 @@ namespace Ryujinx.Ui
}
catch(Exception e)
{
Logger.Error?.Print(LogClass.Application, $"Error purging PPTC cache {file.Name}: {e}");
GtkDialog.CreateErrorDialog($"Error purging PPTC cache {file.Name}: {e}");
}
}
}
@ -713,21 +547,16 @@ namespace Ryujinx.Ui
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
{
string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n");
string titleId = tableEntry[1].ToLower();
DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader"));
MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
{
Title = "Ryujinx - Warning",
Text = $"You are about to delete the shader cache for '{tableEntry[0]}'. Are you sure you want to proceed?",
WindowPosition = WindowPosition.Center
};
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
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)
{
@ -739,7 +568,7 @@ namespace Ryujinx.Ui
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, $"Error purging shader cache {directory.Name}: {e}");
GtkDialog.CreateErrorDialog($"Error purging shader cache {directory.Name}: {e}");
}
}
}
@ -747,4 +576,4 @@ namespace Ryujinx.Ui
warningDialog.Dispose();
}
}
}
}

View file

@ -1,7 +1,7 @@
using Gtk;
using System.Reflection;
using Ryujinx.Common.Logging;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.Widgets
{
internal class GtkDialog : MessageDialog
{
@ -10,14 +10,15 @@ namespace Ryujinx.Ui
private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok)
: base(null, DialogFlags.Modal, messageType, buttonsType, null)
{
Title = title;
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
Text = mainText;
SecondaryText = secondaryText;
WindowPosition = WindowPosition.Center;
Response += GtkDialog_Response;
Title = title;
Text = mainText;
SecondaryText = secondaryText;
WindowPosition = WindowPosition.Center;
SecondaryUseMarkup = true;
SetSizeRequest(100, 20);
Response += GtkDialog_Response;
SetSizeRequest(200, 20);
}
private void GtkDialog_Response(object sender, ResponseArgs args)
@ -25,9 +26,19 @@ namespace Ryujinx.Ui
Dispose();
}
internal static void CreateInfoDialog(string title, string mainText, string secondaryText)
internal static void CreateInfoDialog(string mainText, string secondaryText)
{
new GtkDialog(title, mainText, secondaryText, MessageType.Info).Run();
new GtkDialog("Ryujinx - Info", mainText, secondaryText, MessageType.Info).Run();
}
internal static void CreateUpdaterInfoDialog(string mainText, string secondaryText)
{
new GtkDialog("Ryujinx - Updater", mainText, secondaryText, MessageType.Info).Run();
}
internal static MessageDialog CreateWaitingDialog(string mainText, string secondaryText)
{
return new GtkDialog("Ryujinx - Waiting", mainText, secondaryText, MessageType.Info, ButtonsType.None);
}
internal static void CreateWarningDialog(string mainText, string secondaryText)
@ -37,6 +48,8 @@ namespace Ryujinx.Ui
internal static void CreateErrorDialog(string errorMessage)
{
Logger.Error?.Print(LogClass.Application, errorMessage);
new GtkDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage, MessageType.Error).Run();
}
@ -48,10 +61,14 @@ namespace Ryujinx.Ui
internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText)
{
if (_isChoiceDialogOpen)
{
return false;
}
_isChoiceDialogOpen = true;
ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run();
_isChoiceDialogOpen = false;
return response == ResponseType.Yes;

View file

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

View file

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

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

View file

@ -1,13 +1,12 @@
using Gtk;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
using System.IO;
@ -16,7 +15,7 @@ using System.Text;
using GUI = Gtk.Builder.ObjectAttribute;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.Windows
{
public class DlcWindow : Window
{
@ -31,9 +30,9 @@ namespace Ryujinx.Ui
[GUI] TreeSelection _dlcTreeSelection;
#pragma warning restore CS0649, IDE0044
public DlcWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.DlcWindow.glade"), titleId, titleName, virtualFileSystem) { }
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
private DlcWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_dlcWindow").Handle)
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_dlcWindow").Handle)
{
builder.Autoconnect(this);
@ -51,10 +50,7 @@ namespace Ryujinx.Ui
_dlcContainerList = new List<DlcContainer>();
}
_dlcTreeView.Model = new TreeStore(
typeof(bool),
typeof(string),
typeof(string));
_dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string));
CellRendererToggle enableToggle = new CellRendererToggle();
enableToggle.Toggled += (sender, args) =>
@ -104,17 +100,9 @@ namespace Ryujinx.Ui
{
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (InvalidDataException exception)
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {containerPath}");
GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
}
catch (MissingKeyException exception)
{
Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {containerPath}");
GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", $"Your key set is missing a key with the name: {exception.Name}");
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}");
}
return null;

View file

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

View file

@ -1,5 +1,4 @@
using Gtk;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@ -7,9 +6,9 @@ using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ns;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
using System.IO;
@ -19,16 +18,18 @@ using System.Text;
using GUI = Gtk.Builder.ObjectAttribute;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui
namespace Ryujinx.Ui.Windows
{
public class TitleUpdateWindow : Window
{
private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem;
private readonly string _titleId;
private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData;
private Dictionary<RadioButton, string> _radioButtonToPathDictionary;
private TitleUpdateMetadata _titleUpdateWindowData;
private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary;
#pragma warning disable CS0649, IDE0044
[GUI] Label _baseTitleInfoLabel;
@ -36,10 +37,12 @@ namespace Ryujinx.Ui
[GUI] RadioButton _noUpdateRadioButton;
#pragma warning restore CS0649, IDE0044
public TitleUpdateWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.TitleUpdateWindow.glade"), titleId, titleName, virtualFileSystem) { }
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
private TitleUpdateWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_titleUpdateWindow").Handle)
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_titleUpdateWindow").Handle)
{
_parent = parent;
builder.Autoconnect(this);
_titleId = titleId;
@ -61,20 +64,26 @@ namespace Ryujinx.Ui
}
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
_noUpdateRadioButton.Active = true;
foreach (string path in _titleUpdateWindowData.Paths)
{
AddUpdate(path, false);
AddUpdate(path);
}
foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected))
if (_titleUpdateWindowData.Selected == "")
{
update.Active = true;
_noUpdateRadioButton.Active = true;
}
else
{
foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected))
{
update.Active = true;
}
}
}
private void AddUpdate(string path, bool showErrorDialog = true)
private void AddUpdate(string path)
{
if (File.Exists(path))
{
@ -107,23 +116,9 @@ namespace Ryujinx.Ui
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
}
}
catch (InvalidDataException exception)
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {path}");
if (showErrorDialog)
{
GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
}
}
catch (MissingKeyException exception)
{
Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}");
if (showErrorDialog)
{
GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", $"Your key set is missing a key with the name: {exception.Name}");
}
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
}
}
}
@ -144,23 +139,21 @@ namespace Ryujinx.Ui
private void AddButton_Clicked(object sender, EventArgs args)
{
FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept)
using (FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept))
{
SelectMultiple = true,
Filter = new FileFilter()
};
fileChooser.SetPosition(WindowPosition.Center);
fileChooser.Filter.AddPattern("*.nsp");
fileChooser.SelectMultiple = true;
fileChooser.SetPosition(WindowPosition.Center);
fileChooser.Filter = new FileFilter();
fileChooser.Filter.AddPattern("*.nsp");
if (fileChooser.Run() == (int)ResponseType.Accept)
{
foreach (string path in fileChooser.Filenames)
if (fileChooser.Run() == (int)ResponseType.Accept)
{
AddUpdate(path);
foreach (string path in fileChooser.Filenames)
{
AddUpdate(path);
}
}
}
fileChooser.Dispose();
}
private void RemoveButton_Clicked(object sender, EventArgs args)
@ -196,7 +189,8 @@ namespace Ryujinx.Ui
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
}
MainWindow.UpdateGameTable();
_parent.UpdateGameTable();
Dispose();
}