ApplicationContextMenu.axaml.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. using Avalonia.Controls;
  2. using Avalonia.Interactivity;
  3. using Avalonia.Markup.Xaml;
  4. using Avalonia.Platform.Storage;
  5. using LibHac.Fs;
  6. using LibHac.Tools.FsSystem.NcaUtils;
  7. using Ryujinx.Ava.Common;
  8. using Ryujinx.Ava.Common.Locale;
  9. using Ryujinx.Ava.Common.Models;
  10. using Ryujinx.Ava.UI.Helpers;
  11. using Ryujinx.Ava.UI.ViewModels;
  12. using Ryujinx.Ava.UI.Windows;
  13. using Ryujinx.Ava.Utilities;
  14. using Ryujinx.Ava.Utilities.AppLibrary;
  15. using Ryujinx.Ava.Utilities.Compat;
  16. using Ryujinx.Common.Configuration;
  17. using Ryujinx.Common.Helper;
  18. using Ryujinx.HLE.HOS;
  19. using SkiaSharp;
  20. using System;
  21. using System.Collections.Generic;
  22. using System.IO;
  23. using Path = System.IO.Path;
  24. namespace Ryujinx.Ava.UI.Controls
  25. {
  26. public class ApplicationContextMenu : MenuFlyout
  27. {
  28. public ApplicationContextMenu()
  29. {
  30. InitializeComponent();
  31. }
  32. private void InitializeComponent()
  33. {
  34. AvaloniaXamlLoader.Load(this);
  35. }
  36. public void ToggleFavorite_Click(object sender, RoutedEventArgs args)
  37. {
  38. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  39. return;
  40. viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
  41. ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
  42. {
  43. appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
  44. });
  45. viewModel.RefreshView();
  46. }
  47. public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
  48. {
  49. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  50. OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
  51. }
  52. public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args)
  53. {
  54. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  55. OpenSaveDirectory(viewModel, SaveDataType.Device, default);
  56. }
  57. public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
  58. {
  59. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  60. OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
  61. }
  62. private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
  63. {
  64. SaveDataFilter saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
  65. ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
  66. }
  67. public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args)
  68. {
  69. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  70. await TitleUpdateWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
  71. }
  72. public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args)
  73. {
  74. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  75. await DownloadableContentManagerWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
  76. }
  77. public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
  78. {
  79. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  80. await StyleableAppWindow.ShowAsync(
  81. new CheatWindow(
  82. viewModel.VirtualFileSystem,
  83. viewModel.SelectedApplication.IdString,
  84. viewModel.SelectedApplication.Name,
  85. viewModel.SelectedApplication.Path
  86. )
  87. );
  88. }
  89. public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
  90. {
  91. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  92. return;
  93. string modsBasePath = ModLoader.GetModsBasePath();
  94. string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
  95. OpenHelper.OpenFolder(titleModsPath);
  96. }
  97. public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
  98. {
  99. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  100. return;
  101. string sdModsBasePath = ModLoader.GetSdModsBasePath();
  102. string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
  103. OpenHelper.OpenFolder(titleModsPath);
  104. }
  105. public async void OpenModManager_Click(object sender, RoutedEventArgs args)
  106. {
  107. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  108. await ModManagerWindow.Show(
  109. viewModel.SelectedApplication.Id,
  110. viewModel.SelectedApplication.IdBase,
  111. viewModel.ApplicationLibrary,
  112. viewModel.SelectedApplication.Name);
  113. }
  114. public async void PurgePtcCache_Click(object sender, RoutedEventArgs args)
  115. {
  116. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  117. return;
  118. UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
  119. LocaleManager.Instance[LocaleKeys.DialogWarning],
  120. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name)
  121. );
  122. if (result == UserResult.Yes)
  123. {
  124. DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
  125. DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
  126. List<FileInfo> cacheFiles = [];
  127. if (mainDir.Exists)
  128. {
  129. cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
  130. }
  131. if (backupDir.Exists)
  132. {
  133. cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
  134. }
  135. if (cacheFiles.Count > 0)
  136. {
  137. foreach (FileInfo file in cacheFiles)
  138. {
  139. try
  140. {
  141. file.Delete();
  142. }
  143. catch (Exception ex)
  144. {
  145. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
  146. }
  147. }
  148. }
  149. }
  150. }
  151. public async void NukePtcCache_Click(object sender, RoutedEventArgs args)
  152. {
  153. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  154. return;
  155. UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
  156. LocaleManager.Instance[LocaleKeys.DialogWarning],
  157. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCNukeMessage, viewModel.SelectedApplication.Name)
  158. );
  159. if (result == UserResult.Yes)
  160. {
  161. DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
  162. DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
  163. List<FileInfo> cacheFiles = [];
  164. if (mainDir.Exists)
  165. {
  166. cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
  167. cacheFiles.AddRange(mainDir.EnumerateFiles("*.info"));
  168. }
  169. if (backupDir.Exists)
  170. {
  171. cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
  172. cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
  173. }
  174. if (cacheFiles.Count > 0)
  175. {
  176. foreach (FileInfo file in cacheFiles)
  177. {
  178. try
  179. {
  180. file.Delete();
  181. }
  182. catch (Exception ex)
  183. {
  184. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
  185. }
  186. }
  187. }
  188. }
  189. }
  190. public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args)
  191. {
  192. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  193. return;
  194. UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
  195. LocaleManager.Instance[LocaleKeys.DialogWarning],
  196. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name)
  197. );
  198. if (result == UserResult.Yes)
  199. {
  200. DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
  201. List<DirectoryInfo> oldCacheDirectories = [];
  202. List<FileInfo> newCacheFiles = [];
  203. if (shaderCacheDir.Exists)
  204. {
  205. oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
  206. newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
  207. newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
  208. }
  209. if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0))
  210. {
  211. foreach (DirectoryInfo directory in oldCacheDirectories)
  212. {
  213. try
  214. {
  215. directory.Delete(true);
  216. }
  217. catch (Exception ex)
  218. {
  219. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
  220. }
  221. }
  222. foreach (FileInfo file in newCacheFiles)
  223. {
  224. try
  225. {
  226. file.Delete();
  227. }
  228. catch (Exception ex)
  229. {
  230. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
  231. }
  232. }
  233. }
  234. }
  235. }
  236. public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args)
  237. {
  238. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  239. return;
  240. string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
  241. string mainDir = Path.Combine(ptcDir, "0");
  242. string backupDir = Path.Combine(ptcDir, "1");
  243. if (!Directory.Exists(ptcDir))
  244. {
  245. Directory.CreateDirectory(ptcDir);
  246. Directory.CreateDirectory(mainDir);
  247. Directory.CreateDirectory(backupDir);
  248. }
  249. OpenHelper.OpenFolder(ptcDir);
  250. }
  251. public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args)
  252. {
  253. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  254. {
  255. string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString.ToLower(), "cache", "shader");
  256. if (!Directory.Exists(shaderCacheDir))
  257. {
  258. Directory.CreateDirectory(shaderCacheDir);
  259. }
  260. OpenHelper.OpenFolder(shaderCacheDir);
  261. }
  262. }
  263. public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args)
  264. {
  265. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  266. {
  267. await ApplicationHelper.ExtractSection(
  268. viewModel.StorageProvider,
  269. NcaSectionType.Code,
  270. viewModel.SelectedApplication.Path,
  271. viewModel.SelectedApplication.Name);
  272. }
  273. }
  274. public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args)
  275. {
  276. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  277. await ApplicationHelper.ExtractSection(
  278. viewModel.StorageProvider,
  279. NcaSectionType.Data,
  280. viewModel.SelectedApplication.Path,
  281. viewModel.SelectedApplication.Name);
  282. }
  283. public async void ExtractAocRomFs_Click(object sender, RoutedEventArgs args)
  284. {
  285. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  286. return;
  287. DownloadableContentModel selectedDlc = await DlcSelectView.Show(viewModel.SelectedApplication.Id, viewModel.ApplicationLibrary);
  288. if (selectedDlc is not null)
  289. {
  290. await ApplicationHelper.ExtractAoc(
  291. viewModel.StorageProvider,
  292. selectedDlc.ContainerPath,
  293. selectedDlc.FileName);
  294. }
  295. }
  296. public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args)
  297. {
  298. if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  299. return;
  300. IReadOnlyList<IStorageFolder> result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
  301. {
  302. Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
  303. AllowMultiple = false,
  304. });
  305. if (result.Count == 0)
  306. return;
  307. ApplicationHelper.ExtractSection(
  308. result[0].Path.LocalPath,
  309. NcaSectionType.Logo,
  310. viewModel.SelectedApplication.Path,
  311. viewModel.SelectedApplication.Name);
  312. IStorageFile iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png");
  313. await using Stream fileStream = await iconFile.OpenWriteAsync();
  314. using SKBitmap bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon)
  315. .Resize(new SKSizeI(512, 512), SKFilterQuality.High);
  316. using SKData png = bitmap.Encode(SKEncodedImageFormat.Png, 100);
  317. png.SaveTo(fileStream);
  318. }
  319. public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
  320. {
  321. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  322. ShortcutHelper.CreateAppShortcut(
  323. viewModel.SelectedApplication.Path,
  324. viewModel.SelectedApplication.Name,
  325. viewModel.SelectedApplication.IdString,
  326. viewModel.SelectedApplication.Icon
  327. );
  328. }
  329. public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
  330. {
  331. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  332. {
  333. await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
  334. // just checking for file presence
  335. viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
  336. viewModel.RefreshView();
  337. }
  338. }
  339. public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
  340. {
  341. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  342. await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
  343. }
  344. public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
  345. {
  346. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  347. await ApplicationDataView.Show(viewModel.SelectedApplication);
  348. }
  349. public async void RunApplication_Click(object sender, RoutedEventArgs args)
  350. {
  351. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  352. await viewModel.LoadApplication(viewModel.SelectedApplication);
  353. }
  354. public async void TrimXCI_Click(object sender, RoutedEventArgs args)
  355. {
  356. if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
  357. await viewModel.TrimXCIFile(viewModel.SelectedApplication.Path);
  358. }
  359. }
  360. }