aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Ui.Common/Helper
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Ui.Common/Helper
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Ui.Common/Helper')
-rw-r--r--src/Ryujinx.Ui.Common/Helper/CommandLineState.cs88
-rw-r--r--src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs50
-rw-r--r--src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs198
-rw-r--r--src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs97
-rw-r--r--src/Ryujinx.Ui.Common/Helper/OpenHelper.cs112
-rw-r--r--src/Ryujinx.Ui.Common/Helper/SetupValidator.cs118
6 files changed, 663 insertions, 0 deletions
diff --git a/src/Ryujinx.Ui.Common/Helper/CommandLineState.cs b/src/Ryujinx.Ui.Common/Helper/CommandLineState.cs
new file mode 100644
index 00000000..8ca7fba1
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/CommandLineState.cs
@@ -0,0 +1,88 @@
+using Ryujinx.Common.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.Ui.Common.Helper
+{
+ public static class CommandLineState
+ {
+ public static string[] Arguments { get; private set; }
+
+ public static bool? OverrideDockedMode { get; private set; }
+ public static string OverrideGraphicsBackend { get; private set; }
+ public static string BaseDirPathArg { get; private set; }
+ public static string Profile { get; private set; }
+ public static string LaunchPathArg { get; private set; }
+ public static bool StartFullscreenArg { get; private set; }
+
+ public static void ParseArguments(string[] args)
+ {
+ List<string> arguments = new();
+
+ // Parse Arguments.
+ for (int i = 0; i < args.Length; ++i)
+ {
+ string arg = args[i];
+
+ switch (arg)
+ {
+ case "-r":
+ case "--root-data-dir":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ BaseDirPathArg = args[++i];
+
+ arguments.Add(arg);
+ arguments.Add(args[i]);
+ break;
+ case "-p":
+ case "--profile":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ Profile = args[++i];
+
+ arguments.Add(arg);
+ arguments.Add(args[i]);
+ break;
+ case "-f":
+ case "--fullscreen":
+ StartFullscreenArg = true;
+
+ arguments.Add(arg);
+ break;
+ case "-g":
+ case "--graphics-backend":
+ if (i + 1 >= args.Length)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+ continue;
+ }
+
+ OverrideGraphicsBackend = args[++i];
+ break;
+ case "--docked-mode":
+ OverrideDockedMode = true;
+ break;
+ case "--handheld-mode":
+ OverrideDockedMode = false;
+ break;
+ default:
+ LaunchPathArg = arg;
+ break;
+ }
+ }
+
+ Arguments = arguments.ToArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs b/src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs
new file mode 100644
index 00000000..4eb3b79c
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs
@@ -0,0 +1,50 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.Ui.Common.Helper
+{
+ public static partial class ConsoleHelper
+ {
+ public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
+
+ public static void SetConsoleWindowState(bool show)
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ SetConsoleWindowStateWindows(show);
+ }
+ else if (show == false)
+ {
+ Logger.Warning?.Print(LogClass.Application, "OS doesn't support hiding console window");
+ }
+ }
+
+ [SupportedOSPlatform("windows")]
+ private static void SetConsoleWindowStateWindows(bool show)
+ {
+ const int SW_HIDE = 0;
+ const int SW_SHOW = 5;
+
+ IntPtr hWnd = GetConsoleWindow();
+
+ if (hWnd == IntPtr.Zero)
+ {
+ Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist");
+ return;
+ }
+
+ ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
+ }
+
+ [SupportedOSPlatform("windows")]
+ [LibraryImport("kernel32")]
+ private static partial IntPtr GetConsoleWindow();
+
+ [SupportedOSPlatform("windows")]
+ [LibraryImport("user32")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool ShowWindow(IntPtr hWnd, int nCmdShow);
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
new file mode 100644
index 00000000..4f4b2524
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
@@ -0,0 +1,198 @@
+using Microsoft.Win32;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.Ui.Common.Helper
+{
+ public static partial class FileAssociationHelper
+ {
+ private static string[] _fileExtensions = new string[] { ".nca", ".nro", ".nso", ".nsp", ".xci" };
+
+ [SupportedOSPlatform("linux")]
+ private static string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
+
+ private const int SHCNE_ASSOCCHANGED = 0x8000000;
+ private const int SHCNF_FLUSH = 0x1000;
+
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
+
+ public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild();
+
+ [SupportedOSPlatform("linux")]
+ private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));
+
+ [SupportedOSPlatform("linux")]
+ private static bool InstallLinuxMimeTypes(bool uninstall = false)
+ {
+ string installKeyword = uninstall ? "uninstall" : "install";
+
+ if (!AreMimeTypesRegisteredLinux())
+ {
+ string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
+ string additionalArgs = !uninstall ? "--novendor" : "";
+
+ using Process mimeProcess = new();
+
+ mimeProcess.StartInfo.FileName = "xdg-mime";
+ mimeProcess.StartInfo.Arguments = $"{installKeyword} {additionalArgs} --mode user {mimeTypesFile}";
+
+ mimeProcess.Start();
+ mimeProcess.WaitForExit();
+
+ if (mimeProcess.ExitCode != 0)
+ {
+ Logger.Error?.PrintMsg(LogClass.Application, $"Unable to {installKeyword} mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
+
+ return false;
+ }
+
+ using Process updateMimeProcess = new();
+
+ updateMimeProcess.StartInfo.FileName = "update-mime-database";
+ updateMimeProcess.StartInfo.Arguments = _mimeDbPath;
+
+ updateMimeProcess.Start();
+ updateMimeProcess.WaitForExit();
+
+ if (updateMimeProcess.ExitCode != 0)
+ {
+ Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
+ }
+ }
+
+ return true;
+ }
+
+ [SupportedOSPlatform("windows")]
+ private static bool AreMimeTypesRegisteredWindows()
+ {
+ static bool CheckRegistering(string ext)
+ {
+ RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}");
+
+ if (key is null)
+ {
+ return false;
+ }
+
+ key.OpenSubKey(@"shell\open\command");
+
+ string keyValue = (string)key.GetValue("");
+
+ return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
+ }
+
+ bool registered = false;
+
+ foreach (string ext in _fileExtensions)
+ {
+ registered |= CheckRegistering(ext);
+ }
+
+ return registered;
+ }
+
+ [SupportedOSPlatform("windows")]
+ private static bool InstallWindowsMimeTypes(bool uninstall = false)
+ {
+ static bool RegisterExtension(string ext, bool uninstall = false)
+ {
+ string keyString = @$"Software\Classes\{ext}";
+
+ if (uninstall)
+ {
+ if (!AreMimeTypesRegisteredWindows())
+ {
+ return false;
+ }
+
+ Registry.CurrentUser.DeleteSubKeyTree(keyString);
+ }
+ else
+ {
+ RegistryKey key = Registry.CurrentUser.CreateSubKey(keyString);
+ if (key is null)
+ {
+ return false;
+ }
+
+ key.CreateSubKey(@"shell\open\command");
+
+ key.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
+ key.Close();
+ }
+
+ // Notify Explorer the file association has been changed.
+ SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
+
+ return true;
+ }
+
+ bool registered = false;
+
+ foreach (string ext in _fileExtensions)
+ {
+ registered |= RegisterExtension(ext, uninstall);
+ }
+
+ return registered;
+ }
+
+ public static bool AreMimeTypesRegistered()
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ return AreMimeTypesRegisteredLinux();
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return AreMimeTypesRegisteredWindows();
+ }
+
+ // TODO: Add macOS support.
+
+ return false;
+ }
+
+ public static bool Install()
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ return InstallLinuxMimeTypes();
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return InstallWindowsMimeTypes();
+ }
+
+ // TODO: Add macOS support.
+
+ return false;
+ }
+
+ public static bool Uninstall()
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ return InstallLinuxMimeTypes(true);
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return InstallWindowsMimeTypes(true);
+ }
+
+ // TODO: Add macOS support.
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs b/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs
new file mode 100644
index 00000000..234f7597
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs
@@ -0,0 +1,97 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+namespace Ryujinx.Ui.Common.Helper
+{
+ [SupportedOSPlatform("macos")]
+ public static partial class ObjectiveC
+ {
+ private const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
+
+ [LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
+ private static unsafe partial IntPtr sel_getUid(string name);
+
+ [LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr objc_getClass(string name);
+
+ [LibraryImport(ObjCRuntime)]
+ public static partial void objc_msgSend(IntPtr receiver, Selector selector);
+
+ [LibraryImport(ObjCRuntime)]
+ public static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
+
+ [LibraryImport(ObjCRuntime)]
+ public static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
+
+ [LibraryImport(ObjCRuntime)]
+ public static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
+
+ [LibraryImport(ObjCRuntime)]
+ public static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param);
+
+ [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
+
+ public struct Selector
+ {
+ public readonly IntPtr SelPtr;
+
+ public unsafe Selector(string name)
+ {
+ SelPtr = sel_getUid(name);
+ }
+
+ public static implicit operator Selector(string value) => new(value);
+ }
+
+ public struct NSString
+ {
+ public readonly IntPtr StrPtr;
+
+ public NSString(string aString)
+ {
+ IntPtr nsString = objc_getClass("NSString");
+ StrPtr = IntPtr_objc_msgSend(nsString, "stringWithUTF8String:", aString);
+ }
+
+ public static implicit operator IntPtr(NSString nsString) => nsString.StrPtr;
+ }
+
+ public readonly struct NSPoint
+ {
+ public readonly double X;
+ public readonly double Y;
+
+ public NSPoint(double x, double y)
+ {
+ X = x;
+ Y = y;
+ }
+ }
+
+ public readonly struct NSRect
+ {
+ public readonly NSPoint Pos;
+ public readonly NSPoint Size;
+
+ public NSRect(double x, double y, double width, double height)
+ {
+ Pos = new NSPoint(x, y);
+ Size = new NSPoint(width, height);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Ui.Common/Helper/OpenHelper.cs b/src/Ryujinx.Ui.Common/Helper/OpenHelper.cs
new file mode 100644
index 00000000..5b2e8663
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/OpenHelper.cs
@@ -0,0 +1,112 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Ui.Common.Helper
+{
+ public static partial class OpenHelper
+ {
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ public static partial int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr apidl, uint dwFlags);
+
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ public static partial void ILFree(IntPtr pidlList);
+
+ [LibraryImport("shell32.dll", SetLastError = true)]
+ public static partial IntPtr ILCreateFromPathW([MarshalAs(UnmanagedType.LPWStr)] string pszPath);
+
+ public static void OpenFolder(string path)
+ {
+ if (Directory.Exists(path))
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = path,
+ UseShellExecute = true,
+ Verb = "open"
+ });
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"Directory \"{path}\" doesn't exist!");
+ }
+ }
+
+ public static void LocateFile(string path)
+ {
+ if (File.Exists(path))
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ IntPtr pidlList = ILCreateFromPathW(path);
+ if (pidlList != IntPtr.Zero)
+ {
+ try
+ {
+ Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
+ }
+ finally
+ {
+ ILFree(pidlList);
+ }
+ }
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ ObjectiveC.NSString nsStringPath = new(path);
+ IntPtr nsUrl = ObjectiveC.objc_getClass("NSURL");
+ var urlPtr = ObjectiveC.IntPtr_objc_msgSend(nsUrl, "fileURLWithPath:", nsStringPath);
+
+ IntPtr nsArray = ObjectiveC.objc_getClass("NSArray");
+ IntPtr urlArray = ObjectiveC.IntPtr_objc_msgSend(nsArray, "arrayWithObject:", urlPtr);
+
+ IntPtr nsWorkspace = ObjectiveC.objc_getClass("NSWorkspace");
+ IntPtr sharedWorkspace = ObjectiveC.IntPtr_objc_msgSend(nsWorkspace, "sharedWorkspace");
+
+ ObjectiveC.objc_msgSend(sharedWorkspace, "activateFileViewerSelectingURLs:", urlArray);
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ Process.Start("dbus-send", $"--session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:\"file://{path}\" string:\"\"");
+ }
+ else
+ {
+ OpenFolder(Path.GetDirectoryName(path));
+ }
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"File \"{path}\" doesn't exist!");
+ }
+ }
+
+ public static void OpenUrl(string url)
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ Process.Start(new ProcessStartInfo("cmd", $"/c start {url.Replace("&", "^&")}"));
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ Process.Start("xdg-open", url);
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ ObjectiveC.NSString nsStringPath = new(url);
+ IntPtr nsUrl = ObjectiveC.objc_getClass("NSURL");
+ var urlPtr = ObjectiveC.IntPtr_objc_msgSend(nsUrl, "URLWithString:", nsStringPath);
+
+ IntPtr nsWorkspace = ObjectiveC.objc_getClass("NSWorkspace");
+ IntPtr sharedWorkspace = ObjectiveC.IntPtr_objc_msgSend(nsWorkspace, "sharedWorkspace");
+
+ ObjectiveC.bool_objc_msgSend(sharedWorkspace, "openURL:", urlPtr);
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"Cannot open url \"{url}\" on this platform!");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Ui.Common/Helper/SetupValidator.cs b/src/Ryujinx.Ui.Common/Helper/SetupValidator.cs
new file mode 100644
index 00000000..3d779fdf
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/SetupValidator.cs
@@ -0,0 +1,118 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using System;
+using System.IO;
+
+namespace Ryujinx.Ui.Common.Helper
+{
+ /// <summary>
+ /// Ensure installation validity
+ /// </summary>
+ public static class SetupValidator
+ {
+ public static bool IsFirmwareValid(ContentManager contentManager, out UserError error)
+ {
+ bool hasFirmware = contentManager.GetCurrentFirmwareVersion() != null;
+
+ if (hasFirmware)
+ {
+ error = UserError.Success;
+
+ return true;
+ }
+ else
+ {
+ error = UserError.NoFirmware;
+
+ return false;
+ }
+ }
+
+ public static bool CanFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out SystemVersion firmwareVersion)
+ {
+ try
+ {
+ firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath);
+ }
+ catch (Exception)
+ {
+ firmwareVersion = null;
+ }
+
+ return error == UserError.NoFirmware && Path.GetExtension(baseApplicationPath).ToLowerInvariant() == ".xci" && firmwareVersion != null;
+ }
+
+ public static bool TryFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out UserError outError)
+ {
+ if (error == UserError.NoFirmware)
+ {
+ string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
+
+ // If the target app to start is a XCI, try to install firmware from it
+ if (baseApplicationExtension == ".xci")
+ {
+ SystemVersion firmwareVersion;
+
+ try
+ {
+ firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath);
+ }
+ catch (Exception)
+ {
+ firmwareVersion = null;
+ }
+
+ // The XCI is a valid firmware package, try to install the firmware from it!
+ if (firmwareVersion != null)
+ {
+ try
+ {
+ Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
+
+ contentManager.InstallFirmware(baseApplicationPath);
+
+ Logger.Info?.Print(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed.");
+
+ outError = UserError.Success;
+
+ return true;
+ }
+ catch (Exception) { }
+ }
+
+ outError = error;
+
+ return false;
+ }
+ }
+
+ outError = error;
+
+ return false;
+ }
+
+ public static bool CanStartApplication(ContentManager contentManager, string baseApplicationPath, out UserError error)
+ {
+ if (Directory.Exists(baseApplicationPath) || File.Exists(baseApplicationPath))
+ {
+ string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
+
+ // NOTE: We don't force homebrew developers to install a system firmware.
+ if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso")
+ {
+ error = UserError.Success;
+
+ return true;
+ }
+
+ return IsFirmwareValid(contentManager, out error);
+ }
+ else
+ {
+ error = UserError.ApplicationNotFound;
+
+ return false;
+ }
+ }
+ }
+}