/ src / Ryujinx.UI.Common / Helper / ShortcutHelper.cs
ShortcutHelper.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Common.Configuration;
  3  using ShellLink;
  4  using SkiaSharp;
  5  using System;
  6  using System.Collections.Generic;
  7  using System.IO;
  8  using System.Runtime.Versioning;
  9  
 10  namespace Ryujinx.UI.Common.Helper
 11  {
 12      public static class ShortcutHelper
 13      {
 14          [SupportedOSPlatform("windows")]
 15          private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
 16          {
 17              string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
 18              iconPath += ".ico";
 19  
 20              MemoryStream iconDataStream = new(iconData);
 21              using var image = SKBitmap.Decode(iconDataStream);
 22              image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
 23              SaveBitmapAsIcon(image, iconPath);
 24  
 25              var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
 26              shortcut.StringData.NameString = cleanedAppName;
 27              shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
 28          }
 29  
 30          [SupportedOSPlatform("linux")]
 31          private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
 32          {
 33              string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
 34              var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop");
 35              iconPath += ".png";
 36  
 37              var image = SKBitmap.Decode(iconData);
 38              using var data = image.Encode(SKEncodedImageFormat.Png, 100);
 39              using var file = File.OpenWrite(iconPath);
 40              data.SaveTo(file);
 41  
 42              using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
 43              outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
 44          }
 45  
 46          [SupportedOSPlatform("macos")]
 47          private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
 48          {
 49              string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
 50              var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist");
 51              var shortcutScript = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-launch-script.sh");
 52              // Macos .App folder
 53              string contentFolderPath = Path.Combine("/Applications", cleanedAppName + ".app", "Contents");
 54              string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
 55  
 56              if (!Directory.Exists(scriptFolderPath))
 57              {
 58                  Directory.CreateDirectory(scriptFolderPath);
 59              }
 60  
 61              // Runner script
 62              const string ScriptName = "runner.sh";
 63              string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
 64              using StreamWriter scriptFile = new(scriptPath);
 65  
 66              scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
 67  
 68              // Set execute permission
 69              FileInfo fileInfo = new(scriptPath);
 70              fileInfo.UnixFileMode |= UnixFileMode.UserExecute;
 71  
 72              // img
 73              string resourceFolderPath = Path.Combine(contentFolderPath, "Resources");
 74              if (!Directory.Exists(resourceFolderPath))
 75              {
 76                  Directory.CreateDirectory(resourceFolderPath);
 77              }
 78  
 79              const string IconName = "icon.png";
 80              var image = SKBitmap.Decode(iconData);
 81              using var data = image.Encode(SKEncodedImageFormat.Png, 100);
 82              using var file = File.OpenWrite(Path.Combine(resourceFolderPath, IconName));
 83              data.SaveTo(file);
 84  
 85              // plist file
 86              using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
 87              outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
 88          }
 89  
 90          public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
 91          {
 92              string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
 93              string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
 94  
 95              if (OperatingSystem.IsWindows())
 96              {
 97                  string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
 98  
 99                  CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
100  
101                  return;
102              }
103  
104              if (OperatingSystem.IsLinux())
105              {
106                  string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
107  
108                  Directory.CreateDirectory(iconPath);
109                  CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
110  
111                  return;
112              }
113  
114              if (OperatingSystem.IsMacOS())
115              {
116                  CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
117  
118                  return;
119              }
120  
121              throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
122          }
123  
124          private static string GetArgsString(string appFilePath, string applicationId)
125          {
126              // args are first defined as a list, for easier adjustments in the future
127              var argsList = new List<string>();
128  
129              if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
130              {
131                  argsList.Add("--root-data-dir");
132                  argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
133              }
134  
135              if (appFilePath.ToLower().EndsWith(".xci"))
136              {
137                  argsList.Add("--application-id");
138                  argsList.Add($"\"{applicationId}\"");
139              }
140  
141              argsList.Add($"\"{appFilePath}\"");
142  
143              return String.Join(" ", argsList);
144          }
145  
146          /// <summary>
147          /// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
148          /// </summary>
149          /// <param name="source">The source bitmap image that will be saved as an .ico file</param>
150          /// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
151          [SupportedOSPlatform("windows")]
152          private static void SaveBitmapAsIcon(SKBitmap source, string filePath)
153          {
154              // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
155              byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
156              using FileStream fs = new(filePath, FileMode.Create);
157  
158              fs.Write(header);
159              // Writing actual data
160              using var data = source.Encode(SKEncodedImageFormat.Png, 100);
161              data.SaveTo(fs);
162              // Getting data length (file length minus header)
163              long dataLength = fs.Length - header.Length;
164              // Write it in the correct place
165              fs.Seek(14, SeekOrigin.Begin);
166              fs.WriteByte((byte)dataLength);
167              fs.WriteByte((byte)(dataLength >> 8));
168              fs.WriteByte((byte)(dataLength >> 16));
169              fs.WriteByte((byte)(dataLength >> 24));
170          }
171      }
172  }