FileAssociationHelper.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. using Microsoft.Win32;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Logging;
  4. using System;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Runtime.InteropServices;
  9. using System.Runtime.Versioning;
  10. namespace Ryujinx.Common.Helper
  11. {
  12. public static partial class FileAssociationHelper
  13. {
  14. private static readonly string[] _fileExtensions = [".nca", ".nro", ".nso", ".nsp", ".xci"];
  15. [SupportedOSPlatform("linux")]
  16. private static readonly string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
  17. private const int SHCNE_ASSOCCHANGED = 0x8000000;
  18. private const int SHCNF_FLUSH = 0x1000;
  19. [LibraryImport("shell32.dll", SetLastError = true)]
  20. public static partial void SHChangeNotify(uint wEventId, uint uFlags, nint dwItem1, nint dwItem2);
  21. public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows());
  22. public static bool AreMimeTypesRegistered
  23. {
  24. get
  25. {
  26. if (OperatingSystem.IsLinux())
  27. {
  28. return AreMimeTypesRegisteredLinux();
  29. }
  30. if (OperatingSystem.IsWindows())
  31. {
  32. return AreMimeTypesRegisteredWindows();
  33. }
  34. // TODO: Add macOS support.
  35. return false;
  36. }
  37. }
  38. [SupportedOSPlatform("linux")]
  39. private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));
  40. [SupportedOSPlatform("linux")]
  41. private static bool InstallLinuxMimeTypes(bool uninstall = false)
  42. {
  43. string installKeyword = uninstall ? "uninstall" : "install";
  44. if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
  45. {
  46. string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml");
  47. string additionalArgs = !uninstall ? "--novendor" : string.Empty;
  48. using Process mimeProcess = new();
  49. mimeProcess.StartInfo.FileName = "xdg-mime";
  50. mimeProcess.StartInfo.Arguments = $"{installKeyword} {additionalArgs} --mode user {mimeTypesFile}";
  51. mimeProcess.Start();
  52. mimeProcess.WaitForExit();
  53. if (mimeProcess.ExitCode != 0)
  54. {
  55. Logger.Error?.PrintMsg(LogClass.Application, $"Unable to {installKeyword} mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
  56. return false;
  57. }
  58. using Process updateMimeProcess = new();
  59. updateMimeProcess.StartInfo.FileName = "update-mime-database";
  60. updateMimeProcess.StartInfo.Arguments = _mimeDbPath;
  61. updateMimeProcess.Start();
  62. updateMimeProcess.WaitForExit();
  63. if (updateMimeProcess.ExitCode != 0)
  64. {
  65. Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
  66. }
  67. }
  68. return true;
  69. }
  70. [SupportedOSPlatform("windows")]
  71. private static bool AreMimeTypesRegisteredWindows()
  72. {
  73. return _fileExtensions.Aggregate(false,
  74. (current, ext) => current | CheckRegistering(ext)
  75. );
  76. static bool CheckRegistering(string ext)
  77. {
  78. RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}");
  79. RegistryKey openCmd = key?.OpenSubKey(@"shell\open\command");
  80. if (openCmd is null)
  81. {
  82. return false;
  83. }
  84. string keyValue = (string)openCmd.GetValue(string.Empty);
  85. return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
  86. }
  87. }
  88. [SupportedOSPlatform("windows")]
  89. private static bool InstallWindowsMimeTypes(bool uninstall = false)
  90. {
  91. bool registered = _fileExtensions.Aggregate(false,
  92. (current, ext) => current | RegisterExtension(ext, uninstall)
  93. );
  94. // Notify Explorer the file association has been changed.
  95. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, nint.Zero, nint.Zero);
  96. return registered;
  97. static bool RegisterExtension(string ext, bool uninstall = false)
  98. {
  99. string keyString = @$"Software\Classes\{ext}";
  100. if (uninstall)
  101. {
  102. // If the types don't already exist, there's nothing to do, and we can call this operation successful.
  103. if (!AreMimeTypesRegisteredWindows())
  104. {
  105. return true;
  106. }
  107. Logger.Debug?.Print(LogClass.Application, $"Removing type association {ext}");
  108. Registry.CurrentUser.DeleteSubKeyTree(keyString);
  109. Logger.Debug?.Print(LogClass.Application, $"Removed type association {ext}");
  110. }
  111. else
  112. {
  113. using RegistryKey key = Registry.CurrentUser.CreateSubKey(keyString);
  114. if (key is null)
  115. {
  116. return false;
  117. }
  118. Logger.Debug?.Print(LogClass.Application, $"Adding type association {ext}");
  119. using RegistryKey openCmd = key.CreateSubKey(@"shell\open\command");
  120. openCmd.SetValue(string.Empty, $"\"{Environment.ProcessPath}\" \"%1\"");
  121. Logger.Debug?.Print(LogClass.Application, $"Added type association {ext}");
  122. }
  123. return true;
  124. }
  125. }
  126. public static bool Install()
  127. {
  128. if (OperatingSystem.IsLinux())
  129. {
  130. return InstallLinuxMimeTypes();
  131. }
  132. if (OperatingSystem.IsWindows())
  133. {
  134. return InstallWindowsMimeTypes();
  135. }
  136. // TODO: Add macOS support.
  137. return false;
  138. }
  139. public static bool Uninstall()
  140. {
  141. if (OperatingSystem.IsLinux())
  142. {
  143. return InstallLinuxMimeTypes(true);
  144. }
  145. if (OperatingSystem.IsWindows())
  146. {
  147. return InstallWindowsMimeTypes(true);
  148. }
  149. // TODO: Add macOS support.
  150. return false;
  151. }
  152. }
  153. }