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
16
Ryujinx.Common/System/ForceDedicatedGpu.cs
Normal 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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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; }
|
||||
|
|
@ -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
|
||||
{
|
||||
|
@ -439,7 +439,7 @@ namespace Ryujinx.Motion
|
|||
{
|
||||
Header header = new Header()
|
||||
{
|
||||
ID = (uint)clientId,
|
||||
Id = (uint)clientId,
|
||||
MagicString = Magic,
|
||||
Version = Version,
|
||||
Length = 0,
|
|
@ -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)
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Motion
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
public class MotionInput
|
||||
{
|
|
@ -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.
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Motion
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct ControllerDataRequest
|
||||
|
@ -18,7 +18,7 @@ namespace Ryujinx.Motion
|
|||
{
|
||||
public SharedResponse Shared;
|
||||
public byte Connected;
|
||||
public uint PacketID;
|
||||
public uint PacketId;
|
||||
public byte ExtraButtons;
|
||||
public byte MainButtons;
|
||||
public ushort PSExtraInput;
|
||||
|
@ -32,6 +32,7 @@ namespace Ryujinx.Motion
|
|||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public byte[] Touch2;
|
||||
|
||||
public ulong MotionTimestamp;
|
||||
public float AccelerometerX;
|
||||
public float AccelerometerY;
|
||||
|
@ -43,7 +44,7 @@ namespace Ryujinx.Motion
|
|||
|
||||
enum SubscriberType : byte
|
||||
{
|
||||
All = 0,
|
||||
All,
|
||||
Slot,
|
||||
Mac
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Motion
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ControllerInfoResponse
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Motion
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Header
|
||||
|
@ -9,6 +9,6 @@ namespace Ryujinx.Motion
|
|||
public ushort Version;
|
||||
public ushort Length;
|
||||
public uint Crc32;
|
||||
public uint ID;
|
||||
public uint Id;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace Ryujinx.Motion
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
public enum MessageType : uint
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Motion
|
||||
namespace Ryujinx.Modules.Motion
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct SharedResponse
|
||||
|
@ -18,28 +18,28 @@ namespace Ryujinx.Motion
|
|||
|
||||
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,
|
|
@ -1,12 +1,13 @@
|
|||
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
|
||||
{
|
||||
|
@ -22,7 +23,7 @@ namespace Ryujinx.Ui
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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
|
||||
// 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");
|
||||
|
||||
// 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();
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
namespace Ryujinx.Ui.App
|
||||
{
|
||||
public class ApplicationAddedEventArgs : EventArgs
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
namespace Ryujinx.Ui.App
|
||||
{
|
||||
public class ApplicationCountUpdatedEventArgs : EventArgs
|
||||
{
|
|
@ -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; }
|
|
@ -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,27 +463,17 @@ 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);
|
||||
|
||||
|
@ -471,7 +482,7 @@ namespace Ryujinx.Ui
|
|||
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)";
|
||||
|
9
Ryujinx/Ui/App/ApplicationMetadata.cs
Normal 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";
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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,12 +19,9 @@ 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"
|
||||
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" : "")
|
||||
|
@ -36,11 +33,13 @@ 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)
|
||||
|
@ -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,6 +80,7 @@ namespace Ryujinx.Ui
|
|||
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
|
||||
{
|
||||
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
|
||||
|
||||
bool okPressed = false;
|
||||
bool error = false;
|
||||
string inputText = args.InitialText ?? "";
|
||||
|
@ -84,7 +89,7 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
try
|
||||
{
|
||||
var swkbdDialog = new InputDialog(_parent)
|
||||
var swkbdDialog = new SwkbdAppletDialog(_parent)
|
||||
{
|
||||
Title = "Software Keyboard",
|
||||
Text = args.HeaderText,
|
||||
|
@ -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();
|
||||
}
|
89
Ryujinx/Ui/Applet/SwkbdAppletDialog.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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))
|
|
@ -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
|
116
Ryujinx/Ui/Helper/SortHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
Ryujinx/Ui/Helper/ThemeHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
198
Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
generated
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 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,9 +160,10 @@ 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);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// If the working directory exists and the committed directory doesn't,
|
||||
// the working directory will be loaded the next time the savedata is mounted
|
||||
if (!Directory.Exists(workingPath))
|
||||
|
@ -258,7 +171,8 @@ namespace Ryujinx.Ui
|
|||
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();
|
||||
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");
|
||||
|
@ -647,51 +493,39 @@ namespace Ryujinx.Ui
|
|||
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, _titleIdText, "cache", "cpu", "0"));
|
||||
DirectoryInfo backupDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
|
||||
|
||||
DirectoryInfo mainDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "cpu", "0"));
|
||||
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
|
||||
};
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using Gtk;
|
||||
using System.Reflection;
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
namespace Ryujinx.Ui.Widgets
|
||||
{
|
||||
internal class GtkDialog : MessageDialog
|
||||
{
|
||||
|
@ -11,13 +11,14 @@ namespace Ryujinx.Ui
|
|||
: 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;
|
||||
SecondaryUseMarkup = true;
|
||||
|
||||
Response += GtkDialog_Response;
|
||||
|
||||
SetSizeRequest(100, 20);
|
||||
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;
|
|
@ -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;
|
||||
|
|
@ -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.
|
122
Ryujinx/Ui/Widgets/UserErrorDialog.cs
Normal 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
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
73
Ryujinx/Ui/Windows/AboutWindow.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 readonly PlayerIndex _playerIndex;
|
||||
private readonly InputConfig _inputConfig;
|
||||
|
||||
private bool _isWaitingForInput;
|
||||
private VirtualFileSystem _virtualFileSystem;
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
[GUI] Adjustment _controllerDeadzoneLeft;
|
||||
|
@ -90,16 +90,13 @@ 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);
|
||||
|
||||
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,7 +612,6 @@ namespace Ryujinx.Ui
|
|||
if (joystickState.IsButtonDown(i))
|
||||
{
|
||||
Enum.TryParse($"Button{i}", out pressedButton);
|
||||
|
||||
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();
|
||||
}
|
|
@ -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;
|
|
@ -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,8 +535,8 @@ 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);
|
||||
|
||||
using (FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept))
|
||||
{
|
||||
fileChooser.Filter = new FileFilter();
|
||||
fileChooser.Filter.AddPattern("*.css");
|
||||
|
||||
|
@ -544,18 +544,16 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
_custThemePath.Buffer.Text = fileChooser.Filename;
|
||||
}
|
||||
|
||||
fileChooser.Dispose();
|
||||
|
||||
_browseThemePath.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void ConfigureController_Pressed(object sender, EventArgs args, PlayerIndex playerIndex)
|
||||
{
|
||||
((ToggleButton)sender).SetStateFlags(0, true);
|
||||
_browseThemePath.SetStateFlags(StateFlags.Normal, true);
|
||||
}
|
||||
|
||||
ControllerWindow controllerWin = new ControllerWindow(playerIndex, _virtualFileSystem);
|
||||
controllerWin.Show();
|
||||
private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex)
|
||||
{
|
||||
((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
|
||||
|
||||
new ControllerWindow(playerIndex).Show();
|
||||
}
|
||||
|
||||
private void SaveToggle_Activated(object sender, EventArgs args)
|
|
@ -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 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);
|
||||
}
|
||||
|
||||
if (_titleUpdateWindowData.Selected == "")
|
||||
{
|
||||
_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,12 +139,11 @@ 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.SelectMultiple = true;
|
||||
fileChooser.SetPosition(WindowPosition.Center);
|
||||
fileChooser.Filter = new FileFilter();
|
||||
fileChooser.Filter.AddPattern("*.nsp");
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
|
@ -159,8 +153,7 @@ namespace Ryujinx.Ui
|
|||
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();
|
||||
}
|
||||
|