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 }