diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml
index 64b30e211..fee589297 100644
--- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml
+++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml
@@ -6,140 +6,166 @@
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs
index 64377ba51..6f93676e2 100644
--- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs
+++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs
@@ -1,7 +1,7 @@
using Avalonia.Controls;
-using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.Input;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
@@ -26,7 +26,6 @@ namespace Ryujinx.Ava.UI.Controls
{
public class ApplicationContextMenu : MenuFlyout
{
-
public ApplicationContextMenu()
{
InitializeComponent();
@@ -36,39 +35,35 @@ namespace Ryujinx.Ava.UI.Controls
{
AvaloniaXamlLoader.Load(this);
}
+
+ public static RelayCommand ToggleFavorite { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
- public void ToggleFavorite_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
+ ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
+ {
+ appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
+ });
- viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
-
- ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
- {
- appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
- });
-
- viewModel.RefreshView();
- }
-
- public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
- }
-
- public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- OpenSaveDirectory(viewModel, SaveDataType.Device, default);
- }
-
- public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
- }
+ viewModel.RefreshView();
+ }
+ );
+
+ public static RelayCommand OpenUserSaveDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low))
+ );
+
+ public static RelayCommand OpenDeviceSaveDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => OpenSaveDirectory(viewModel, SaveDataType.Device, default));
+
+ public static RelayCommand OpenBcatSaveDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => OpenSaveDirectory(viewModel, SaveDataType.Bcat, default));
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
{
@@ -76,159 +71,151 @@ namespace Ryujinx.Ava.UI.Controls
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
}
-
- public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await TitleUpdateManagerView.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
- }
-
- public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await DownloadableContentManagerView.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
- }
-
- public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await StyleableAppWindow.ShowAsync(
+
+ public static AsyncRelayCommand OpenTitleUpdateManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => TitleUpdateManagerView.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication)
+ );
+
+ public static AsyncRelayCommand OpenDownloadableContentManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => DownloadableContentManagerView.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication)
+ );
+
+ public static AsyncRelayCommand OpenCheatManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => StyleableAppWindow.ShowAsync(
new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path
)
- );
- }
+ ));
+
+ public static RelayCommand OpenModsDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string modsBasePath = ModLoader.GetModsBasePath();
+ string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
- public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
+ OpenHelper.OpenFolder(titleModsPath);
+ });
+
+ public static RelayCommand OpenSdModsDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string sdModsBasePath = ModLoader.GetSdModsBasePath();
+ string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
- string modsBasePath = ModLoader.GetModsBasePath();
- string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
+ OpenHelper.OpenFolder(titleModsPath);
+ });
- OpenHelper.OpenFolder(titleModsPath);
- }
-
- public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
- string sdModsBasePath = ModLoader.GetSdModsBasePath();
- string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
-
- OpenHelper.OpenFolder(titleModsPath);
- }
-
- public async void OpenModManager_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
+ public static AsyncRelayCommand OpenModManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
await ModManagerView.Show(
- viewModel.SelectedApplication.Id,
- viewModel.SelectedApplication.IdBase,
- viewModel.ApplicationLibrary,
+ viewModel.SelectedApplication.Id,
+ viewModel.SelectedApplication.IdBase,
+ viewModel.ApplicationLibrary,
viewModel.SelectedApplication.Name);
- }
-
- public async void PurgePtcCache_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
- UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
- LocaleManager.Instance[LocaleKeys.DialogWarning],
- LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name)
- );
-
- if (result == UserResult.Yes)
+ });
+
+ public static AsyncRelayCommand PurgePtcCache { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
{
- DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
- DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
+ UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
+ LocaleManager.Instance[LocaleKeys.DialogWarning],
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name)
+ );
- List cacheFiles = [];
-
- if (mainDir.Exists)
+ if (result == UserResult.Yes)
{
- cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
- }
+ DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
- if (backupDir.Exists)
- {
- cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
- }
+ List cacheFiles = [];
- if (cacheFiles.Count > 0)
- {
- foreach (FileInfo file in cacheFiles)
+ if (mainDir.Exists)
{
- try
+ cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
+ }
+
+ if (backupDir.Exists)
+ {
+ cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
+ }
+
+ if (cacheFiles.Count > 0)
+ {
+ foreach (FileInfo file in cacheFiles)
{
- file.Delete();
- }
- catch (Exception ex)
- {
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
+ }
}
}
}
- }
- }
-
- public async void NukePtcCache_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
- UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
- LocaleManager.Instance[LocaleKeys.DialogWarning],
- LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCNukeMessage, viewModel.SelectedApplication.Name)
- );
-
- if (result == UserResult.Yes)
+ });
+
+ public static AsyncRelayCommand NukePtcCache { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
{
- DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
- DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
+ UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
+ LocaleManager.Instance[LocaleKeys.DialogWarning],
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCNukeMessage, viewModel.SelectedApplication.Name)
+ );
- List cacheFiles = [];
-
- if (mainDir.Exists)
+ if (result == UserResult.Yes)
{
- cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
- cacheFiles.AddRange(mainDir.EnumerateFiles("*.info"));
- }
+ DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
- if (backupDir.Exists)
- {
- cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
- cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
- }
+ List cacheFiles = [];
- if (cacheFiles.Count > 0)
- {
- foreach (FileInfo file in cacheFiles)
+ if (mainDir.Exists)
{
- try
+ cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
+ cacheFiles.AddRange(mainDir.EnumerateFiles("*.info"));
+ }
+
+ if (backupDir.Exists)
+ {
+ cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
+ cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
+ }
+
+ if (cacheFiles.Count > 0)
+ {
+ foreach (FileInfo file in cacheFiles)
{
- file.Delete();
- }
- catch (Exception ex)
- {
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
+ }
}
}
}
- }
- }
-
- public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
+ });
+
+ public static AsyncRelayCommand PurgeShaderCache { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name)
@@ -274,158 +261,143 @@ namespace Ryujinx.Ava.UI.Controls
}
}
}
- }
- }
-
- public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
- string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
- string mainDir = Path.Combine(ptcDir, "0");
- string backupDir = Path.Combine(ptcDir, "1");
-
- if (!Directory.Exists(ptcDir))
+ }
+ });
+
+ public static RelayCommand OpenPtcDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
{
- Directory.CreateDirectory(ptcDir);
- Directory.CreateDirectory(mainDir);
- Directory.CreateDirectory(backupDir);
- }
+ string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
+ string mainDir = Path.Combine(ptcDir, "0");
+ string backupDir = Path.Combine(ptcDir, "1");
- OpenHelper.OpenFolder(ptcDir);
- }
-
- public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- {
- string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString.ToLower(), "cache", "shader");
-
- if (!Directory.Exists(shaderCacheDir))
+ if (!Directory.Exists(ptcDir))
{
- Directory.CreateDirectory(shaderCacheDir);
+ Directory.CreateDirectory(ptcDir);
+ Directory.CreateDirectory(mainDir);
+ Directory.CreateDirectory(backupDir);
}
- OpenHelper.OpenFolder(shaderCacheDir);
- }
- }
-
- public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- {
- await ApplicationHelper.ExtractSection(
- viewModel.StorageProvider,
- NcaSectionType.Code,
- viewModel.SelectedApplication.Path,
- viewModel.SelectedApplication.Name);
- }
- }
-
- public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await ApplicationHelper.ExtractSection(
- viewModel.StorageProvider,
- NcaSectionType.Data,
- viewModel.SelectedApplication.Path,
- viewModel.SelectedApplication.Name);
- }
-
- public async void ExtractAocRomFs_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
- DownloadableContentModel selectedDlc = await DlcSelectView.Show(viewModel.SelectedApplication.Id, viewModel.ApplicationLibrary);
-
- if (selectedDlc is not null)
- {
- await ApplicationHelper.ExtractAoc(
- viewModel.StorageProvider,
- selectedDlc.ContainerPath,
- selectedDlc.FileName);
- }
- }
-
- public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args)
- {
- if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- return;
-
- IReadOnlyList result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
- {
- Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
- AllowMultiple = false,
+ OpenHelper.OpenFolder(ptcDir);
});
+
+ public static RelayCommand OpenShaderCacheDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString.ToLower(), "cache", "shader");
- if (result.Count == 0)
- return;
+ if (!Directory.Exists(shaderCacheDir))
+ {
+ Directory.CreateDirectory(shaderCacheDir);
+ }
- ApplicationHelper.ExtractSection(
- result[0].Path.LocalPath,
- NcaSectionType.Logo,
- viewModel.SelectedApplication.Path,
- viewModel.SelectedApplication.Name);
+ OpenHelper.OpenFolder(shaderCacheDir);
+ });
+
+ public static AsyncRelayCommand ExtractApplicationExeFs { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await ApplicationHelper.ExtractSection(
+ viewModel.StorageProvider,
+ NcaSectionType.Code,
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name);
+ });
+
+ public static AsyncRelayCommand ExtractApplicationRomFs { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await ApplicationHelper.ExtractSection(
+ viewModel.StorageProvider,
+ NcaSectionType.Data,
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name);
+ });
+
+ public static AsyncRelayCommand ExtractApplicationAocRomFs { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ DownloadableContentModel selectedDlc = await DlcSelectView.Show(viewModel.SelectedApplication.Id, viewModel.ApplicationLibrary);
+
+ if (selectedDlc is not null)
+ {
+ await ApplicationHelper.ExtractAoc(
+ viewModel.StorageProvider,
+ selectedDlc.ContainerPath,
+ selectedDlc.FileName);
+ }
+ });
+
+ public static AsyncRelayCommand ExtractApplicationLogo { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ IReadOnlyList result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
+ {
+ Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
+ AllowMultiple = false,
+ });
- IStorageFile iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png");
- await using Stream fileStream = await iconFile.OpenWriteAsync();
+ if (result.Count == 0)
+ return;
- using SKBitmap bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon)
- .Resize(new SKSizeI(512, 512), SKFilterQuality.High);
+ ApplicationHelper.ExtractSection(
+ result[0].Path.LocalPath,
+ NcaSectionType.Logo,
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name);
- using SKData png = bitmap.Encode(SKEncodedImageFormat.Png, 100);
+ IStorageFile iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png");
+ await using Stream fileStream = await iconFile.OpenWriteAsync();
- png.SaveTo(fileStream);
- }
+ using SKBitmap bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon)
+ .Resize(new SKSizeI(512, 512), SKFilterQuality.High);
- public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- ShortcutHelper.CreateAppShortcut(
+ using SKData png = bitmap.Encode(SKEncodedImageFormat.Png, 100);
+
+ png.SaveTo(fileStream);
+ });
+
+ public static RelayCommand CreateApplicationShortcut { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => ShortcutHelper.CreateAppShortcut(
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon
- );
- }
+ ));
+
+ public static AsyncRelayCommand EditGameConfiguration { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
- public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- {
- await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
+ // just checking for file presence
+ viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
- // just checking for file presence
- viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
-
- viewModel.RefreshView();
- }
- }
-
- public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await CompatibilityListWindow.Show(viewModel.SelectedApplication.IdString);
- }
-
- public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await ApplicationDataView.Show(viewModel.SelectedApplication);
- }
-
- public async void RunApplication_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await viewModel.LoadApplication(viewModel.SelectedApplication);
- }
-
- public async void TrimXCI_Click(object sender, RoutedEventArgs args)
- {
- if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
- await viewModel.TrimXCIFile(viewModel.SelectedApplication.Path);
- }
+ viewModel.RefreshView();
+ });
+
+ public static AsyncRelayCommand OpenApplicationCompatibility { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => CompatibilityListWindow.Show(viewModel.SelectedApplication.IdString));
+
+ public static AsyncRelayCommand OpenApplicationData { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => ApplicationDataView.Show(viewModel.SelectedApplication));
+
+ public static AsyncRelayCommand RunApplication { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => viewModel.LoadApplication(viewModel.SelectedApplication));
+
+ public static AsyncRelayCommand TrimXci { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => viewModel.TrimXCIFile(viewModel.SelectedApplication.Path));
}
}
diff --git a/src/Ryujinx/UI/Helpers/Commands.cs b/src/Ryujinx/UI/Helpers/Commands.cs
index 868b49158..7a65c61a1 100644
--- a/src/Ryujinx/UI/Helpers/Commands.cs
+++ b/src/Ryujinx/UI/Helpers/Commands.cs
@@ -9,12 +9,12 @@ namespace Ryujinx.Ava.UI.Helpers
{
public static RelayCommand Create(Action action)
=> new(action);
- public static RelayCommand CreateConditional(Action action, Func canExecute)
+ public static RelayCommand CreateConditional(Func canExecute, Action action)
=> new(action, canExecute);
public static RelayCommand Create(Action action)
=> new(action);
- public static RelayCommand CreateConditional(Action action, Predicate canExecute)
+ public static RelayCommand CreateConditional(Predicate canExecute, Action action)
=> new(action, canExecute);
public static AsyncRelayCommand Create(Func action)
@@ -31,18 +31,18 @@ namespace Ryujinx.Ava.UI.Helpers
public static AsyncRelayCommand CreateSilentFail(Func action)
=> new(action, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
- public static AsyncRelayCommand CreateConditional(Func action, Func canExecute)
+ public static AsyncRelayCommand CreateConditional(Func canExecute, Func action)
=> new(action, canExecute, AsyncRelayCommandOptions.None);
- public static AsyncRelayCommand CreateConcurrentConditional(Func action, Func canExecute)
+ public static AsyncRelayCommand CreateConcurrentConditional(Func canExecute, Func action)
=> new(action, canExecute, AsyncRelayCommandOptions.AllowConcurrentExecutions);
- public static AsyncRelayCommand CreateSilentFailConditional(Func action, Func canExecute)
+ public static AsyncRelayCommand CreateSilentFailConditional(Func canExecute, Func action)
=> new(action, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
- public static AsyncRelayCommand CreateConditional(Func action, Predicate canExecute)
+ public static AsyncRelayCommand CreateConditional(Predicate canExecute, Func action)
=> new(action, canExecute, AsyncRelayCommandOptions.None);
- public static AsyncRelayCommand CreateConcurrentConditional(Func action, Predicate canExecute)
+ public static AsyncRelayCommand CreateConcurrentConditional(Predicate canExecute, Func action)
=> new(action, canExecute, AsyncRelayCommandOptions.AllowConcurrentExecutions);
- public static AsyncRelayCommand CreateSilentFailConditional(Func action, Predicate canExecute)
+ public static AsyncRelayCommand CreateSilentFailConditional(Predicate canExecute, Func action)
=> new(action, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
}
}
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index f265f4c06..61af0db34 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -12,9 +12,12 @@ using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Gommon;
using LibHac.Common;
+using LibHac.Fs;
using LibHac.Ns;
+using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Systems;
using Ryujinx.Ava.UI.Controls;
@@ -25,6 +28,7 @@ using Ryujinx.Ava.UI.Renderer;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Systems.AppLibrary;
using Ryujinx.Ava.Systems.Configuration;
+using Ryujinx.Ava.UI.Views.Dialog;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@@ -51,7 +55,9 @@ using System.Threading;
using System.Threading.Tasks;
using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
+using Path = System.IO.Path;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
+using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -1955,5 +1961,373 @@ namespace Ryujinx.Ava.UI.ViewModels
}
#endregion
+
+ #region Context Menu commands
+
+ public static RelayCommand ToggleFavorite { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
+
+ ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
+ {
+ appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
+ });
+
+ viewModel.RefreshView();
+ }
+ );
+
+ public static RelayCommand OpenUserSaveDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ OpenSaveDirectory(viewModel, SaveDataType.Account, new LibHac.Fs.UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low))
+ );
+
+ public static RelayCommand OpenDeviceSaveDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => OpenSaveDirectory(viewModel, SaveDataType.Device, default));
+
+ public static RelayCommand OpenBcatSaveDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => OpenSaveDirectory(viewModel, SaveDataType.Bcat, default));
+
+ private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, LibHac.Fs.UserId userId)
+ {
+ SaveDataFilter saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
+
+ ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
+ }
+
+ public static AsyncRelayCommand OpenTitleUpdateManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => TitleUpdateManagerView.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication)
+ );
+
+ public static AsyncRelayCommand OpenDownloadableContentManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => DownloadableContentManagerView.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication)
+ );
+
+ public static AsyncRelayCommand OpenCheatManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => StyleableAppWindow.ShowAsync(
+ new CheatWindow(
+ viewModel.VirtualFileSystem,
+ viewModel.SelectedApplication.IdString,
+ viewModel.SelectedApplication.Name,
+ viewModel.SelectedApplication.Path
+ )
+ ));
+
+ public static RelayCommand OpenModsDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string modsBasePath = ModLoader.GetModsBasePath();
+ string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
+
+ OpenHelper.OpenFolder(titleModsPath);
+ });
+
+ public static RelayCommand OpenSdModsDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string sdModsBasePath = ModLoader.GetSdModsBasePath();
+ string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
+
+ OpenHelper.OpenFolder(titleModsPath);
+ });
+
+ public static AsyncRelayCommand OpenModManager { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await ModManagerView.Show(
+ viewModel.SelectedApplication.Id,
+ viewModel.SelectedApplication.IdBase,
+ viewModel.ApplicationLibrary,
+ viewModel.SelectedApplication.Name);
+ });
+
+ public static AsyncRelayCommand PurgePtcCache { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
+ LocaleManager.Instance[LocaleKeys.DialogWarning],
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name)
+ );
+
+ if (result == UserResult.Yes)
+ {
+ DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
+
+ List cacheFiles = [];
+
+ if (mainDir.Exists)
+ {
+ cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
+ }
+
+ if (backupDir.Exists)
+ {
+ cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
+ }
+
+ if (cacheFiles.Count > 0)
+ {
+ foreach (FileInfo file in cacheFiles)
+ {
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
+ }
+ }
+ }
+ }
+ });
+
+ public static AsyncRelayCommand NukePtcCache { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
+ LocaleManager.Instance[LocaleKeys.DialogWarning],
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCNukeMessage, viewModel.SelectedApplication.Name)
+ );
+
+ if (result == UserResult.Yes)
+ {
+ DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
+
+ List cacheFiles = [];
+
+ if (mainDir.Exists)
+ {
+ cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
+ cacheFiles.AddRange(mainDir.EnumerateFiles("*.info"));
+ }
+
+ if (backupDir.Exists)
+ {
+ cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
+ cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
+ }
+
+ if (cacheFiles.Count > 0)
+ {
+ foreach (FileInfo file in cacheFiles)
+ {
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
+ }
+ }
+ }
+ }
+ });
+
+ public static AsyncRelayCommand PurgeShaderCache { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
+ LocaleManager.Instance[LocaleKeys.DialogWarning],
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name)
+ );
+
+ if (result == UserResult.Yes)
+ {
+ DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
+
+ List oldCacheDirectories = [];
+ List newCacheFiles = [];
+
+ if (shaderCacheDir.Exists)
+ {
+ oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
+ newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
+ newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
+ }
+
+ if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0))
+ {
+ foreach (DirectoryInfo directory in oldCacheDirectories)
+ {
+ try
+ {
+ directory.Delete(true);
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
+ }
+ }
+
+ foreach (FileInfo file in newCacheFiles)
+ {
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception ex)
+ {
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
+ }
+ }
+ }
+ }
+ });
+
+ public static RelayCommand OpenPtcDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
+ string mainDir = Path.Combine(ptcDir, "0");
+ string backupDir = Path.Combine(ptcDir, "1");
+
+ if (!Directory.Exists(ptcDir))
+ {
+ Directory.CreateDirectory(ptcDir);
+ Directory.CreateDirectory(mainDir);
+ Directory.CreateDirectory(backupDir);
+ }
+
+ OpenHelper.OpenFolder(ptcDir);
+ });
+
+ public static RelayCommand OpenShaderCacheDirectory { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel =>
+ {
+ string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString.ToLower(), "cache", "shader");
+
+ if (!Directory.Exists(shaderCacheDir))
+ {
+ Directory.CreateDirectory(shaderCacheDir);
+ }
+
+ OpenHelper.OpenFolder(shaderCacheDir);
+ });
+
+ public static AsyncRelayCommand ExtractApplicationExeFs { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await ApplicationHelper.ExtractSection(
+ viewModel.StorageProvider,
+ NcaSectionType.Code,
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name);
+ });
+
+ public static AsyncRelayCommand ExtractApplicationRomFs { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await ApplicationHelper.ExtractSection(
+ viewModel.StorageProvider,
+ NcaSectionType.Data,
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name);
+ });
+
+ public static AsyncRelayCommand ExtractApplicationAocRomFs { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ DownloadableContentModel selectedDlc = await DlcSelectView.Show(viewModel.SelectedApplication.Id, viewModel.ApplicationLibrary);
+
+ if (selectedDlc is not null)
+ {
+ await ApplicationHelper.ExtractAoc(
+ viewModel.StorageProvider,
+ selectedDlc.ContainerPath,
+ selectedDlc.FileName);
+ }
+ });
+
+ public static AsyncRelayCommand ExtractApplicationLogo { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ IReadOnlyList result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
+ {
+ Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
+ AllowMultiple = false,
+ });
+
+ if (result.Count == 0)
+ return;
+
+ ApplicationHelper.ExtractSection(
+ result[0].Path.LocalPath,
+ NcaSectionType.Logo,
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name);
+
+ IStorageFile iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png");
+ await using Stream fileStream = await iconFile.OpenWriteAsync();
+
+ using SKBitmap bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon)
+ .Resize(new SKSizeI(512, 512), SKFilterQuality.High);
+
+ using SKData png = bitmap.Encode(SKEncodedImageFormat.Png, 100);
+
+ png.SaveTo(fileStream);
+ });
+
+ public static RelayCommand CreateApplicationShortcut { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => ShortcutHelper.CreateAppShortcut(
+ viewModel.SelectedApplication.Path,
+ viewModel.SelectedApplication.Name,
+ viewModel.SelectedApplication.IdString,
+ viewModel.SelectedApplication.Icon
+ ));
+
+ public static AsyncRelayCommand EditGameConfiguration { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ async viewModel =>
+ {
+ await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
+
+ // just checking for file presence
+ viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
+
+ viewModel.RefreshView();
+ });
+
+ public static AsyncRelayCommand OpenApplicationCompatibility { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => CompatibilityListWindow.Show(viewModel.SelectedApplication.IdString));
+
+ public static AsyncRelayCommand OpenApplicationData { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => ApplicationDataView.Show(viewModel.SelectedApplication));
+
+ public static AsyncRelayCommand RunApplication { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => viewModel.LoadApplication(viewModel.SelectedApplication));
+
+ public static AsyncRelayCommand TrimXci { get; } =
+ Commands.CreateConditional(vm => vm?.SelectedApplication != null,
+ viewModel => viewModel.TrimXCIFile(viewModel.SelectedApplication.Path));
+
+ #endregion
}
}