Infra: Capitalisation Consistency (#6296)
* Rename Ryujinx.UI.Common * Rename Ryujinx.UI.LocaleGenerator * Update in Files AboutWindow * Configuration State * Rename projects * Ryujinx/UI * Fix build * Main remaining inconsistencies * HLE.UI Namespace * HLE.UI Files * Namespace * Ryujinx.UI.Common.Configuration.UI * Ryujinx.UI.Common,Configuration.UI Files * More instances
This commit is contained in:
parent
84d6e8d121
commit
f06d22d6f0
189 changed files with 648 additions and 648 deletions
99
src/Ryujinx.UI.Common/Helper/CommandLineState.cs
Normal file
99
src/Ryujinx.UI.Common/Helper/CommandLineState.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
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 OverrideHideCursor { 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;
|
||||
case "--hide-cursor":
|
||||
if (i + 1 >= args.Length)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
OverrideHideCursor = args[++i];
|
||||
break;
|
||||
default:
|
||||
LaunchPathArg = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Arguments = arguments.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
50
src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs
Normal file
50
src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
202
src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs
Normal file
202
src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs
Normal file
|
@ -0,0 +1,202 @@
|
|||
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 readonly string[] _fileExtensions = { ".nca", ".nro", ".nso", ".nsp", ".xci" };
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
private static readonly 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 ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
|
||||
{
|
||||
string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "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;
|
||||
}
|
||||
|
||||
var openCmd = key.OpenSubKey(@"shell\open\command");
|
||||
|
||||
string keyValue = (string)openCmd.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 the types don't already exist, there's nothing to do and we can call this operation successful.
|
||||
if (!AreMimeTypesRegisteredWindows())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Logger.Debug?.Print(LogClass.Application, $"Removing type association {ext}");
|
||||
Registry.CurrentUser.DeleteSubKeyTree(keyString);
|
||||
Logger.Debug?.Print(LogClass.Application, $"Removed type association {ext}");
|
||||
}
|
||||
else
|
||||
{
|
||||
using var key = Registry.CurrentUser.CreateSubKey(keyString);
|
||||
|
||||
if (key is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.Debug?.Print(LogClass.Application, $"Adding type association {ext}");
|
||||
using var openCmd = key.CreateSubKey(@"shell\open\command");
|
||||
openCmd.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
|
||||
Logger.Debug?.Print(LogClass.Application, $"Added type association {ext}");
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool registered = false;
|
||||
|
||||
foreach (string ext in _fileExtensions)
|
||||
{
|
||||
registered |= RegisterExtension(ext, uninstall);
|
||||
}
|
||||
|
||||
// Notify Explorer the file association has been changed.
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
62
src/Ryujinx.UI.Common/Helper/LinuxHelper.cs
Normal file
62
src/Ryujinx.UI.Common/Helper/LinuxHelper.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
public static class LinuxHelper
|
||||
{
|
||||
// NOTE: This value was determined by manual tests and might need to be increased again.
|
||||
public const int RecommendedVmMaxMapCount = 524288;
|
||||
public const string VmMaxMapCountPath = "/proc/sys/vm/max_map_count";
|
||||
public const string SysCtlConfigPath = "/etc/sysctl.d/99-Ryujinx.conf";
|
||||
public static int VmMaxMapCount => int.Parse(File.ReadAllText(VmMaxMapCountPath));
|
||||
public static string PkExecPath { get; } = GetBinaryPath("pkexec");
|
||||
|
||||
private static string GetBinaryPath(string binary)
|
||||
{
|
||||
string pathVar = Environment.GetEnvironmentVariable("PATH");
|
||||
|
||||
if (pathVar is null || string.IsNullOrEmpty(binary))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var searchPath in pathVar.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
string binaryPath = Path.Combine(searchPath, binary);
|
||||
|
||||
if (File.Exists(binaryPath))
|
||||
{
|
||||
return binaryPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int RunPkExec(string command)
|
||||
{
|
||||
if (PkExecPath == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
using Process process = new()
|
||||
{
|
||||
StartInfo =
|
||||
{
|
||||
FileName = PkExecPath,
|
||||
ArgumentList = { "sh", "-c", command },
|
||||
},
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
}
|
160
src/Ryujinx.UI.Common/Helper/ObjectiveC.cs
Normal file
160
src/Ryujinx.UI.Common/Helper/ObjectiveC.cs
Normal file
|
@ -0,0 +1,160 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
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 partial IntPtr sel_getUid(string name);
|
||||
|
||||
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
|
||||
private static partial IntPtr objc_getClass(string name);
|
||||
|
||||
[LibraryImport(ObjCRuntime)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector);
|
||||
|
||||
[LibraryImport(ObjCRuntime)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
|
||||
|
||||
[LibraryImport(ObjCRuntime)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
|
||||
|
||||
[LibraryImport(ObjCRuntime)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
|
||||
|
||||
[LibraryImport(ObjCRuntime)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
|
||||
|
||||
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
|
||||
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
|
||||
|
||||
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
|
||||
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
|
||||
|
||||
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
|
||||
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param);
|
||||
|
||||
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
|
||||
|
||||
public readonly struct Object
|
||||
{
|
||||
public readonly IntPtr ObjPtr;
|
||||
|
||||
private Object(IntPtr pointer)
|
||||
{
|
||||
ObjPtr = pointer;
|
||||
}
|
||||
|
||||
public Object(string name)
|
||||
{
|
||||
ObjPtr = objc_getClass(name);
|
||||
}
|
||||
|
||||
public void SendMessage(Selector selector)
|
||||
{
|
||||
objc_msgSend(ObjPtr, selector);
|
||||
}
|
||||
|
||||
public void SendMessage(Selector selector, byte value)
|
||||
{
|
||||
objc_msgSend(ObjPtr, selector, value);
|
||||
}
|
||||
|
||||
public void SendMessage(Selector selector, Object obj)
|
||||
{
|
||||
objc_msgSend(ObjPtr, selector, obj.ObjPtr);
|
||||
}
|
||||
|
||||
public void SendMessage(Selector selector, NSRect point)
|
||||
{
|
||||
objc_msgSend(ObjPtr, selector, point);
|
||||
}
|
||||
|
||||
public void SendMessage(Selector selector, double value)
|
||||
{
|
||||
objc_msgSend(ObjPtr, selector, value);
|
||||
}
|
||||
|
||||
public Object GetFromMessage(Selector selector)
|
||||
{
|
||||
return new Object(IntPtr_objc_msgSend(ObjPtr, selector));
|
||||
}
|
||||
|
||||
public Object GetFromMessage(Selector selector, Object obj)
|
||||
{
|
||||
return new Object(IntPtr_objc_msgSend(ObjPtr, selector, obj.ObjPtr));
|
||||
}
|
||||
|
||||
public Object GetFromMessage(Selector selector, NSString nsString)
|
||||
{
|
||||
return new Object(IntPtr_objc_msgSend(ObjPtr, selector, nsString.StrPtr));
|
||||
}
|
||||
|
||||
public Object GetFromMessage(Selector selector, string param)
|
||||
{
|
||||
return new Object(IntPtr_objc_msgSend(ObjPtr, selector, param));
|
||||
}
|
||||
|
||||
public bool GetBoolFromMessage(Selector selector, Object obj)
|
||||
{
|
||||
return bool_objc_msgSend(ObjPtr, selector, obj.ObjPtr);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Selector
|
||||
{
|
||||
public readonly IntPtr SelPtr;
|
||||
|
||||
private Selector(string name)
|
||||
{
|
||||
SelPtr = sel_getUid(name);
|
||||
}
|
||||
|
||||
public static implicit operator Selector(string value) => new(value);
|
||||
}
|
||||
|
||||
public readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
src/Ryujinx.UI.Common/Helper/OpenHelper.cs
Normal file
112
src/Ryujinx.UI.Common/Helper/OpenHelper.cs
Normal file
|
@ -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)]
|
||||
private static partial int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr apidl, uint dwFlags);
|
||||
|
||||
[LibraryImport("shell32.dll", SetLastError = true)]
|
||||
private static partial void ILFree(IntPtr pidlList);
|
||||
|
||||
[LibraryImport("shell32.dll", SetLastError = true)]
|
||||
private 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);
|
||||
ObjectiveC.Object nsUrl = new("NSURL");
|
||||
var urlPtr = nsUrl.GetFromMessage("fileURLWithPath:", nsStringPath);
|
||||
|
||||
ObjectiveC.Object nsArray = new("NSArray");
|
||||
ObjectiveC.Object urlArray = nsArray.GetFromMessage("arrayWithObject:", urlPtr);
|
||||
|
||||
ObjectiveC.Object nsWorkspace = new("NSWorkspace");
|
||||
ObjectiveC.Object sharedWorkspace = nsWorkspace.GetFromMessage("sharedWorkspace");
|
||||
|
||||
sharedWorkspace.SendMessage("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);
|
||||
ObjectiveC.Object nsUrl = new("NSURL");
|
||||
var urlPtr = nsUrl.GetFromMessage("URLWithString:", nsStringPath);
|
||||
|
||||
ObjectiveC.Object nsWorkspace = new("NSWorkspace");
|
||||
ObjectiveC.Object sharedWorkspace = nsWorkspace.GetFromMessage("sharedWorkspace");
|
||||
|
||||
sharedWorkspace.GetBoolFromMessage("openURL:", urlPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, $"Cannot open url \"{url}\" on this platform!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
src/Ryujinx.UI.Common/Helper/SetupValidator.cs
Normal file
114
src/Ryujinx.UI.Common/Helper/SetupValidator.cs
Normal file
|
@ -0,0 +1,114 @@
|
|||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
error = UserError.ApplicationNotFound;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
162
src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
Normal file
162
src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs
Normal file
|
@ -0,0 +1,162 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using ShellLink;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
{
|
||||
public static class ShortcutHelper
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void CreateShortcutWindows(string applicationFilePath, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
|
||||
{
|
||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
|
||||
iconPath += ".ico";
|
||||
|
||||
MemoryStream iconDataStream = new(iconData);
|
||||
var image = Image.Load(iconDataStream);
|
||||
image.Mutate(x => x.Resize(128, 128));
|
||||
SaveBitmapAsIcon(image, iconPath);
|
||||
|
||||
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath), iconPath, 0);
|
||||
shortcut.StringData.NameString = cleanedAppName;
|
||||
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
private static void CreateShortcutLinux(string applicationFilePath, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
|
||||
{
|
||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
|
||||
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
|
||||
iconPath += ".png";
|
||||
|
||||
var image = Image.Load<Rgba32>(iconData);
|
||||
image.SaveAsPng(iconPath);
|
||||
|
||||
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
||||
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath)}");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
|
||||
{
|
||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
||||
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist");
|
||||
var shortcutScript = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-launch-script.sh");
|
||||
// Macos .App folder
|
||||
string contentFolderPath = Path.Combine("/Applications", cleanedAppName + ".app", "Contents");
|
||||
string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
|
||||
|
||||
if (!Directory.Exists(scriptFolderPath))
|
||||
{
|
||||
Directory.CreateDirectory(scriptFolderPath);
|
||||
}
|
||||
|
||||
// Runner script
|
||||
const string ScriptName = "runner.sh";
|
||||
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
|
||||
using StreamWriter scriptFile = new(scriptPath);
|
||||
|
||||
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath));
|
||||
|
||||
// Set execute permission
|
||||
FileInfo fileInfo = new(scriptPath);
|
||||
fileInfo.UnixFileMode |= UnixFileMode.UserExecute;
|
||||
|
||||
// img
|
||||
string resourceFolderPath = Path.Combine(contentFolderPath, "Resources");
|
||||
if (!Directory.Exists(resourceFolderPath))
|
||||
{
|
||||
Directory.CreateDirectory(resourceFolderPath);
|
||||
}
|
||||
|
||||
const string IconName = "icon.png";
|
||||
var image = Image.Load<Rgba32>(iconData);
|
||||
image.SaveAsPng(Path.Combine(resourceFolderPath, IconName));
|
||||
|
||||
// plist file
|
||||
using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
|
||||
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
|
||||
}
|
||||
|
||||
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
|
||||
{
|
||||
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||||
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
|
||||
|
||||
CreateShortcutWindows(applicationFilePath, iconData, iconPath, cleanedAppName, desktopPath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
|
||||
|
||||
Directory.CreateDirectory(iconPath);
|
||||
CreateShortcutLinux(applicationFilePath, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
CreateShortcutMacos(applicationFilePath, iconData, desktopPath, cleanedAppName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
||||
}
|
||||
|
||||
private static string GetArgsString(string appFilePath)
|
||||
{
|
||||
// args are first defined as a list, for easier adjustments in the future
|
||||
var argsList = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
|
||||
{
|
||||
argsList.Add("--root-data-dir");
|
||||
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
|
||||
}
|
||||
|
||||
argsList.Add($"\"{appFilePath}\"");
|
||||
|
||||
return String.Join(" ", argsList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
|
||||
/// </summary>
|
||||
/// <param name="source">The source bitmap image that will be saved as an .ico file</param>
|
||||
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void SaveBitmapAsIcon(Image source, string filePath)
|
||||
{
|
||||
// Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
|
||||
byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
|
||||
using FileStream fs = new(filePath, FileMode.Create);
|
||||
|
||||
fs.Write(header);
|
||||
// Writing actual data
|
||||
source.Save(fs, PngFormat.Instance);
|
||||
// Getting data length (file length minus header)
|
||||
long dataLength = fs.Length - header.Length;
|
||||
// Write it in the correct place
|
||||
fs.Seek(14, SeekOrigin.Begin);
|
||||
fs.WriteByte((byte)dataLength);
|
||||
fs.WriteByte((byte)(dataLength >> 8));
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.UI.Common/Helper/TitleHelper.cs
Normal file
30
src/Ryujinx.UI.Common/Helper/TitleHelper.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
{
|
||||
public static class TitleHelper
|
||||
{
|
||||
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "")
|
||||
{
|
||||
if (activeProcess == null)
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
|
||||
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||
|
||||
string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||
|
||||
if (!string.IsNullOrEmpty(pauseString))
|
||||
{
|
||||
appTitle += $" ({pauseString})";
|
||||
}
|
||||
|
||||
return appTitle;
|
||||
}
|
||||
}
|
||||
}
|
219
src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs
Normal file
219
src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
{
|
||||
public static class ValueFormatUtils
|
||||
{
|
||||
private static readonly string[] _fileSizeUnitStrings =
|
||||
{
|
||||
"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", // Base 10 units, used for formatting and parsing
|
||||
"KB", "MB", "GB", "TB", "PB", "EB", // Base 2 units, used for parsing legacy values
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="FormatFileSize"/>.
|
||||
/// </summary>
|
||||
public enum FileSizeUnits
|
||||
{
|
||||
Auto = -1,
|
||||
Bytes = 0,
|
||||
Kibibytes = 1,
|
||||
Mebibytes = 2,
|
||||
Gibibytes = 3,
|
||||
Tebibytes = 4,
|
||||
Pebibytes = 5,
|
||||
Exbibytes = 6,
|
||||
Kilobytes = 7,
|
||||
Megabytes = 8,
|
||||
Gigabytes = 9,
|
||||
Terabytes = 10,
|
||||
Petabytes = 11,
|
||||
Exabytes = 12,
|
||||
}
|
||||
|
||||
private const double SizeBase10 = 1000;
|
||||
private const double SizeBase2 = 1024;
|
||||
private const int UnitEBIndex = 6;
|
||||
|
||||
#region Value formatters
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human-readable string from a <see cref="TimeSpan"/>.
|
||||
/// </summary>
|
||||
/// <param name="timeSpan">The <see cref="TimeSpan"/> to be formatted.</param>
|
||||
/// <returns>A formatted string that can be displayed in the UI.</returns>
|
||||
public static string FormatTimeSpan(TimeSpan? timeSpan)
|
||||
{
|
||||
if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
|
||||
{
|
||||
// Game was never played
|
||||
return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (timeSpan.Value.TotalDays < 1)
|
||||
{
|
||||
// Game was played for less than a day
|
||||
return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// Game was played for more than a day
|
||||
TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days));
|
||||
string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture);
|
||||
|
||||
return $"{timeSpan.Value.Days}d, {onlyTimeString}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human-readable string from a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
/// <param name="utcDateTime">The <see cref="DateTime"/> to be formatted. This is expected to be UTC-based.</param>
|
||||
/// <param name="culture">The <see cref="CultureInfo"/> that's used in formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.</param>
|
||||
/// <returns>A formatted string that can be displayed in the UI.</returns>
|
||||
public static string FormatDateTime(DateTime? utcDateTime, CultureInfo culture = null)
|
||||
{
|
||||
culture ??= CultureInfo.CurrentCulture;
|
||||
|
||||
if (!utcDateTime.HasValue)
|
||||
{
|
||||
// In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter.
|
||||
return "Never";
|
||||
}
|
||||
|
||||
return utcDateTime.Value.ToLocalTime().ToString(culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human-readable file size string.
|
||||
/// </summary>
|
||||
/// <param name="size">The file size in bytes.</param>
|
||||
/// <param name="forceUnit">Formats the passed size value as this unit, bypassing the automatic unit choice.</param>
|
||||
/// <returns>A human-readable file size string.</returns>
|
||||
public static string FormatFileSize(long size, FileSizeUnits forceUnit = FileSizeUnits.Auto)
|
||||
{
|
||||
if (size <= 0)
|
||||
{
|
||||
return $"0 {_fileSizeUnitStrings[0]}";
|
||||
}
|
||||
|
||||
int unitIndex = (int)forceUnit;
|
||||
if (forceUnit == FileSizeUnits.Auto)
|
||||
{
|
||||
unitIndex = Convert.ToInt32(Math.Floor(Math.Log(size, SizeBase10)));
|
||||
|
||||
// Apply an upper bound so that exabytes are the biggest unit used when formatting.
|
||||
if (unitIndex > UnitEBIndex)
|
||||
{
|
||||
unitIndex = UnitEBIndex;
|
||||
}
|
||||
}
|
||||
|
||||
double sizeRounded;
|
||||
|
||||
if (unitIndex > UnitEBIndex)
|
||||
{
|
||||
sizeRounded = Math.Round(size / Math.Pow(SizeBase10, unitIndex - UnitEBIndex), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
sizeRounded = Math.Round(size / Math.Pow(SizeBase2, unitIndex), 1);
|
||||
}
|
||||
|
||||
string sizeFormatted = sizeRounded.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
return $"{sizeFormatted} {_fileSizeUnitStrings[unitIndex]}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value parsers
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string generated by <see cref="FormatTimeSpan"/> and returns the original <see cref="TimeSpan"/>.
|
||||
/// </summary>
|
||||
/// <param name="timeSpanString">A string representing a <see cref="TimeSpan"/>.</param>
|
||||
/// <returns>A <see cref="TimeSpan"/> object. If the input string couldn't been parsed, <see cref="TimeSpan.Zero"/> is returned.</returns>
|
||||
public static TimeSpan ParseTimeSpan(string timeSpanString)
|
||||
{
|
||||
TimeSpan returnTimeSpan = TimeSpan.Zero;
|
||||
|
||||
// An input string can either look like "01:23:45" or "1d, 01:23:45" if the timespan represents a duration of more than a day.
|
||||
// Here, we split the input string to check if it's the former or the latter.
|
||||
var valueSplit = timeSpanString.Split(", ");
|
||||
if (valueSplit.Length > 1)
|
||||
{
|
||||
var dayPart = valueSplit[0].Split("d")[0];
|
||||
if (int.TryParse(dayPart, out int days))
|
||||
{
|
||||
returnTimeSpan = returnTimeSpan.Add(TimeSpan.FromDays(days));
|
||||
}
|
||||
}
|
||||
|
||||
if (TimeSpan.TryParse(valueSplit.Last(), out TimeSpan parsedTimeSpan))
|
||||
{
|
||||
returnTimeSpan = returnTimeSpan.Add(parsedTimeSpan);
|
||||
}
|
||||
|
||||
return returnTimeSpan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string generated by <see cref="FormatDateTime"/> and returns the original <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
/// <param name="dateTimeString">The string representing a <see cref="DateTime"/>.</param>
|
||||
/// <returns>A <see cref="DateTime"/> object. If the input string couldn't be parsed, <see cref="DateTime.UnixEpoch"/> is returned.</returns>
|
||||
public static DateTime ParseDateTime(string dateTimeString)
|
||||
{
|
||||
if (!DateTime.TryParse(dateTimeString, CultureInfo.CurrentCulture, out DateTime parsedDateTime))
|
||||
{
|
||||
// Games that were never played are supposed to appear before the oldest played games in the list,
|
||||
// so returning DateTime.UnixEpoch here makes sense.
|
||||
return DateTime.UnixEpoch;
|
||||
}
|
||||
|
||||
return parsedDateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string generated by <see cref="FormatFileSize"/> and returns a <see cref="long"/> representing a number of bytes.
|
||||
/// </summary>
|
||||
/// <param name="sizeString">A string representing a file size formatted with <see cref="FormatFileSize"/>.</param>
|
||||
/// <returns>A <see cref="long"/> representing a number of bytes.</returns>
|
||||
public static long ParseFileSize(string sizeString)
|
||||
{
|
||||
// Enumerating over the units backwards because otherwise, sizeString.EndsWith("B") would exit the loop in the first iteration.
|
||||
for (int i = _fileSizeUnitStrings.Length - 1; i >= 0; i--)
|
||||
{
|
||||
string unit = _fileSizeUnitStrings[i];
|
||||
if (!sizeString.EndsWith(unit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string numberString = sizeString.Split(" ")[0];
|
||||
if (!double.TryParse(numberString, CultureInfo.InvariantCulture, out double number))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
double sizeBase = SizeBase2;
|
||||
|
||||
// If the unit index is one that points to a base 10 unit in the FileSizeUnitStrings array, subtract 6 to arrive at a usable power value.
|
||||
if (i > UnitEBIndex)
|
||||
{
|
||||
i -= UnitEBIndex;
|
||||
sizeBase = SizeBase10;
|
||||
}
|
||||
|
||||
number *= Math.Pow(sizeBase, i);
|
||||
|
||||
return Convert.ToInt64(number);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue