From d68ec8692e3b2c3f0c47986c58ff2dd5d1316517 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Wed, 8 Feb 2023 21:43:53 -0500 Subject: [PATCH] Fix fuck ups --- .../DownloadableContentManagerViewModel.cs | 2 +- .../UI/ViewModels/MainWindowViewModel.cs | 273 ++++++-------- .../UI/ViewModels/TitleUpdateViewModel.cs | 357 ++++++++++-------- 3 files changed, 299 insertions(+), 333 deletions(-) diff --git a/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs b/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs index b068956a7..619b4e8f4 100644 --- a/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -185,7 +185,7 @@ namespace Ryujinx.Ava.UI.ViewModels { Dispatcher.UIThread.InvokeAsync(async () => { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, containerPath)); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath)); }); } diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index 43c47658d..373839193 100644 --- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -5,14 +5,17 @@ using Avalonia.Media; using Avalonia.Threading; using DynamicData; using DynamicData.Binding; +using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Tools.Fs; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; @@ -87,7 +90,7 @@ namespace Ryujinx.Ava.UI.ViewModels private float _volume; private string _backendText; - private bool _canUpdate; + private bool _canUpdate = true; private Cursor _cursor; private string _title; private string _currentEmulatedGamePath; @@ -176,11 +179,10 @@ namespace Ryujinx.Ava.UI.ViewModels public bool CanUpdate { - get => _canUpdate; + get => _canUpdate && EnableNonGameRunningControls && Modules.Updater.CanUpdate(false); set { _canUpdate = value; - OnPropertyChanged(); } } @@ -342,6 +344,12 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public bool EnabledUserSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; + + public bool EnabledDeviceSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; + + public bool EnabledBcatSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + public string LoadHeading { get => _loadHeading; @@ -675,6 +683,11 @@ namespace Ryujinx.Ava.UI.ViewModels get => ConsoleHelper.SetConsoleWindowStateSupported; } + public bool ManageFileTypesVisible + { + get => FileAssociationHelper.IsTypeAssociationSupported; + } + public ObservableCollection Applications { get => _applications; @@ -733,19 +746,14 @@ namespace Ryujinx.Ava.UI.ViewModels { get { - switch (ConfigurationState.Instance.Ui.GridSize) + return ConfigurationState.Instance.Ui.GridSize.Value switch { - case 1: - return 78; - case 2: - return 100; - case 3: - return 120; - case 4: - return 140; - default: - return 16; - } + 1 => 78, + 2 => 100, + 3 => 120, + 4 => 140, + _ => 16, + }; } } @@ -753,19 +761,14 @@ namespace Ryujinx.Ava.UI.ViewModels { get { - switch (ConfigurationState.Instance.Ui.GridSize) + return ConfigurationState.Instance.Ui.GridSize.Value switch { - case 1: - return 120; - case 2: - return ShowNames ? 210 : 150; - case 3: - return ShowNames ? 240 : 180; - case 4: - return ShowNames ? 280 : 220; - default: - return 16; - } + 1 => 120, + 2 => ShowNames ? 210 : 150, + 3 => ShowNames ? 240 : 180, + 4 => ShowNames ? 280 : 220, + _ => 16, + }; } } @@ -870,7 +873,7 @@ namespace Ryujinx.Ava.UI.ViewModels public Action SwitchToGameControl { get; private set; } public Action SetMainContent { get; private set; } public TopLevel TopLevel { get; private set; } - public RendererHost RendererControl { get; private set; } + public RendererHost RendererHostControl { get; private set; } public bool IsClosing { get; set; } public LibHacHorizonManager LibHacHorizonManager { get; internal set; } public IHostUiHandler UiHandler { get; internal set; } @@ -947,20 +950,18 @@ namespace Ryujinx.Ava.UI.ViewModels if (firmwareVersion == null) { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage], filename)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); return; } - string dialogTitle = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle], firmwareVersion.VersionString); + string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); + string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); - - string dialogMessage = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage], firmwareVersion.VersionString); - if (currentVersion != null) { - dialogMessage += string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage], currentVersion.VersionString); + dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); } dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; @@ -993,7 +994,7 @@ namespace Ryujinx.Ava.UI.ViewModels { waitingDialog.Close(); - string message = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage], firmwareVersion.VersionString); + string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); @@ -1063,7 +1064,7 @@ namespace Ryujinx.Ava.UI.ViewModels IsLoadingIndeterminate = false; break; case LoadState.Loaded: - LoadHeading = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName); + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName); IsLoadingIndeterminate = true; CacheLoadStatus = ""; break; @@ -1079,7 +1080,7 @@ namespace Ryujinx.Ava.UI.ViewModels IsLoadingIndeterminate = false; break; case ShaderCacheLoadingState.Loaded: - LoadHeading = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName); + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName); IsLoadingIndeterminate = true; CacheLoadStatus = ""; break; @@ -1091,35 +1092,27 @@ namespace Ryujinx.Ava.UI.ViewModels })); } - private void OpenSaveDirectory(in SaveDataFilter filter, ApplicationData data, ulong titleId) - { - ApplicationHelper.OpenSaveDir(in filter, titleId, data.ControlHolder, data.TitleName); - } - private async void ExtractLogo() { - var selection = SelectedApplication; - if (selection != null) + if (SelectedApplication != null) { - await ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path); + await ApplicationHelper.ExtractSection(NcaSectionType.Logo, SelectedApplication.Path, SelectedApplication.TitleName); } } private async void ExtractRomFs() { - var selection = SelectedApplication; - if (selection != null) + if (SelectedApplication != null) { - await ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path); + await ApplicationHelper.ExtractSection(NcaSectionType.Data, SelectedApplication.Path, SelectedApplication.TitleName); } } private async void ExtractExeFs() { - var selection = SelectedApplication; - if (selection != null) + if (SelectedApplication != null) { - await ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path); + await ApplicationHelper.ExtractSection(NcaSectionType.Code, SelectedApplication.Path, SelectedApplication.TitleName); } } @@ -1144,7 +1137,7 @@ namespace Ryujinx.Ava.UI.ViewModels private void InitializeGame() { - RendererControl.RendererInitialized += GlRenderer_Created; + RendererHostControl.WindowCreated += RendererHost_Created; AppHost.StatusUpdatedEvent += Update_StatusBar; AppHost.AppExit += AppHost_AppExit; @@ -1203,7 +1196,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - private void GlRenderer_Created(object sender, EventArgs e) + private void RendererHost_Created(object sender, EventArgs e) { ShowLoading(false); @@ -1333,10 +1326,15 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public void ChangeLanguage(object obj) + public void ChangeLanguage(object languageCode) { - LocaleManager.Instance.LoadDefaultLanguage(); - LocaleManager.Instance.LoadLanguage((string)obj); + LocaleManager.Instance.LoadLanguage((string)languageCode); + + if (Program.PreviewerDetached) + { + ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } } public async void ManageProfiles() @@ -1374,7 +1372,7 @@ namespace Ryujinx.Ava.UI.ViewModels // FIXME: Found a way to reproduce the bold effect on the title name (fork?). UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], - string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionMessage], selection.TitleName), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, selection.TitleName), LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); @@ -1401,7 +1399,7 @@ namespace Ryujinx.Ava.UI.ViewModels } catch (Exception e) { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionErrorMessage], file.Name, e)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, e)); } } } @@ -1438,7 +1436,7 @@ namespace Ryujinx.Ava.UI.ViewModels // FIXME: Found a way to reproduce the bold effect on the title name (fork?). UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], - string.Format(LocaleManager.Instance[LocaleKeys.DialogShaderDeletionMessage], selection.TitleName), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, selection.TitleName), LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); @@ -1463,7 +1461,7 @@ namespace Ryujinx.Ava.UI.ViewModels } catch (Exception e) { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionErrorMessage], directory.Name, e)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, e)); } } } @@ -1476,62 +1474,12 @@ namespace Ryujinx.Ava.UI.ViewModels } catch (Exception e) { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.ShaderCachePurgeError], file.Name, e)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, e)); } } } } - public void OpenDeviceSaveDirectory() - { - ApplicationData selection = SelectedApplication; - if (selection != null) - { - Task.Run(() => - { - if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) - { - async void Action() - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]); - } - - Dispatcher.UIThread.Post(Action); - - return; - } - - var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Device, userId: default, saveDataId: default, index: default); - OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber); - }); - } - } - - public void OpenBcatSaveDirectory() - { - ApplicationData selection = SelectedApplication; - if (selection != null) - { - Task.Run(() => - { - if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) - { - async void Action() - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]); - } - - Dispatcher.UIThread.Post(Action); - - return; - } - - var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Bcat, userId: default, saveDataId: default, index: default); - OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber); - }); - } - } - public void ToggleFavorite() { ApplicationData selection = SelectedApplication; @@ -1550,37 +1498,45 @@ namespace Ryujinx.Ava.UI.ViewModels public void OpenUserSaveDirectory() { - ApplicationData selection = SelectedApplication; - if (selection != null) + OpenSaveDirectory(SaveDataType.Account, userId: new UserId((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low)); + } + + public void OpenDeviceSaveDirectory() + { + OpenSaveDirectory(SaveDataType.Device, userId: default); + } + + public void OpenBcatSaveDirectory() + { + OpenSaveDirectory(SaveDataType.Bcat, userId: default); + } + + private void OpenSaveDirectory(SaveDataType saveDataType, UserId userId) + { + if (SelectedApplication != null) { - Task.Run(() => + if (!ulong.TryParse(SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) { - if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) + Dispatcher.UIThread.InvokeAsync(async () => { - async void Action() - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]); - } + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]); + }); - Dispatcher.UIThread.Post(Action); + return; + } - return; - } + var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default); - UserId userId = new((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low); - SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default); - OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber); - }); + ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, SelectedApplication.ControlHolder, SelectedApplication.TitleName); } } public void OpenModsDirectory() { - ApplicationData selection = SelectedApplication; - if (selection != null) + if (SelectedApplication != null) { string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath(); - string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId); + string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId); OpenHelper.OpenFolder(titleModsPath); } @@ -1588,12 +1544,10 @@ namespace Ryujinx.Ava.UI.ViewModels public void OpenSdModsDirectory() { - ApplicationData selection = SelectedApplication; - - if (selection != null) + if (SelectedApplication != null) { string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath(); - string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId); + string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId); OpenHelper.OpenFolder(titleModsPath); } @@ -1609,25 +1563,17 @@ namespace Ryujinx.Ava.UI.ViewModels public async void OpenDownloadableContentManager() { - ApplicationData selection = SelectedApplication; - if (selection != null) + if (SelectedApplication != null) { - if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName); - } + await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName); } } public async void OpenCheatManager() { - ApplicationData selection = SelectedApplication; - if (selection != null) + if (SelectedApplication != null) { - if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - await new CheatWindow(VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(desktop.MainWindow); - } + await new CheatWindow(VirtualFileSystem, SelectedApplication.TitleId, SelectedApplication.TitleName).ShowDialog(TopLevel as Window); } } @@ -1641,7 +1587,7 @@ namespace Ryujinx.Ava.UI.ViewModels StatusBarProgressMaximum = 0; StatusBarProgressValue = 0; - LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); }); ReloadGameList?.Invoke(); @@ -1731,18 +1677,10 @@ namespace Ryujinx.Ava.UI.ViewModels PrepareLoadScreen(); - RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel); - if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl) - { - RendererControl.CreateOpenGL(); - } - else - { - RendererControl.CreateVulkan(); - } + RendererHostControl = new RendererHost(); AppHost = new AppHost( - RendererControl, + RendererHostControl, InputManager, path, VirtualFileSystem, @@ -1763,8 +1701,14 @@ namespace Ryujinx.Ava.UI.ViewModels } CanUpdate = false; - LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], AppHost.Device.Application.TitleName) : titleName; - TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName; + + LoadHeading = TitleName = titleName; + + if (string.IsNullOrWhiteSpace(titleName)) + { + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName); + TitleName = AppHost.Device.Application.TitleName; + } SwitchToRenderer(startFullscreen); @@ -1783,9 +1727,9 @@ namespace Ryujinx.Ava.UI.ViewModels { SwitchToGameControl(startFullscreen); - SetMainContent(RendererControl); + SetMainContent(RendererHostControl); - RendererControl.Focus(); + RendererHostControl.Focus(); }); } @@ -1815,14 +1759,13 @@ namespace Ryujinx.Ava.UI.ViewModels if (version != null) { - LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, - version.VersionString); + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); hasApplet = version.Major > 3; } else { - LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); } IsAppletMenuActive = hasApplet; @@ -1853,8 +1796,8 @@ namespace Ryujinx.Ava.UI.ViewModels HandleRelaunch(); }); - RendererControl.RendererInitialized -= GlRenderer_Created; - RendererControl = null; + RendererHostControl.WindowCreated -= RendererHost_Created; + RendererHostControl = null; SelectedIcon = null; diff --git a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs index db83f90e3..3d0b20f7e 100644 --- a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs @@ -21,207 +21,230 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using SpanHelpers = LibHac.Common.SpanHelpers; +using System.Text; using Path = System.IO.Path; +using SpanHelpers = LibHac.Common.SpanHelpers; -namespace Ryujinx.Ava.UI.ViewModels +namespace Ryujinx.Ava.UI.ViewModels; + +public class TitleUpdateViewModel : BaseModel { - public class TitleUpdateViewModel : BaseModel + public TitleUpdateMetadata _titleUpdateWindowData; + public readonly string _titleUpdateJsonPath; + private VirtualFileSystem _virtualFileSystem { get; } + private ulong _titleId { get; } + private string _titleName { get; } + + private AvaloniaList _titleUpdates = new(); + private AvaloniaList _views = new(); + private object _selectedUpdate; + + public AvaloniaList TitleUpdates { - public TitleUpdateMetadata _titleUpdateWindowData; - public readonly string _titleUpdateJsonPath; - private VirtualFileSystem _virtualFileSystem { get; } - private ulong _titleId { get; } - private string _titleName { get; } - - private AvaloniaList _titleUpdates = new(); - private AvaloniaList _views = new(); - private object _selectedUpdate; - - public AvaloniaList TitleUpdates + get => _titleUpdates; + set { - get => _titleUpdates; - set + _titleUpdates = value; + OnPropertyChanged(); + } + } + + public AvaloniaList Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public object SelectedUpdate + { + get => _selectedUpdate; + set + { + _selectedUpdate = value; + OnPropertyChanged(); + } + } + + public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + { + _virtualFileSystem = virtualFileSystem; + + _titleId = titleId; + _titleName = titleName; + + _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); + + try + { + _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}"); + + _titleUpdateWindowData = new TitleUpdateMetadata { - _titleUpdates = value; - OnPropertyChanged(); - } + Selected = "", + Paths = new List() + }; + + Save(); } - public AvaloniaList Views + LoadUpdates(); + } + + private void LoadUpdates() + { + foreach (string path in _titleUpdateWindowData.Paths) { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } + AddUpdate(path); } - public object SelectedUpdate + // NOTE: Save the list again to remove leftovers. + Save(); + + TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null); + + SelectedUpdate = selected; + + SortUpdates(); + } + + public void SortUpdates() + { + var list = TitleUpdates.ToList(); + + list.Sort((first, second) => { - get => _selectedUpdate; - set + if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) { - _selectedUpdate = value; - OnPropertyChanged(); + return -1; } + else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) + { + return 1; + } + + return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; + }); + + Views.Clear(); + Views.Add(new BaseModel()); + Views.AddRange(list); + + if (SelectedUpdate == null) + { + SelectedUpdate = Views[0]; } - - public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + else if (!TitleUpdates.Contains(SelectedUpdate)) { - _virtualFileSystem = virtualFileSystem; - - _titleId = titleId; - _titleName = titleName; - - _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); - - try + if (Views.Count > 1) { - _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath); + SelectedUpdate = Views[1]; } - catch - { - Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}"); - - _titleUpdateWindowData = new TitleUpdateMetadata - { - Selected = "", - Paths = new List() - }; - } - - LoadUpdates(); - } - - private void LoadUpdates() - { - foreach (string path in _titleUpdateWindowData.Paths) - { - AddUpdate(path); - } - - TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null); - - SelectedUpdate = selected; - - SortUpdates(); - } - - public void SortUpdates() - { - var list = TitleUpdates.ToList(); - - list.Sort((first, second) => - { - if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) - { - return -1; - } - else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) - { - return 1; - } - - return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; - }); - - Views.Clear(); - Views.Add(new BaseModel()); - Views.AddRange(list); - - if (SelectedUpdate == null) + else { SelectedUpdate = Views[0]; } - else if (!TitleUpdates.Contains(SelectedUpdate)) + } + } + + private void AddUpdate(string path) + { + if (File.Exists(path) && TitleUpdates.All(x => x.Path != path)) + { + using FileStream file = new(path, FileMode.Open, FileAccess.Read); + + try { - if (Views.Count > 1) + (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); + + if (controlNca != null && patchNca != null) { - SelectedUpdate = Views[1]; + ApplicationControlProperty controlData = new(); + + using UniqueRef nacpFile = new(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); + + TitleUpdates.Add(new TitleUpdateModel(controlData, path)); } else - { - SelectedUpdate = Views[0]; - } - } - } - - private void AddUpdate(string path) - { - if (File.Exists(path) && TitleUpdates.All(x => x.Path != path)) - { - using FileStream file = new(path, FileMode.Open, FileAccess.Read); - - try - { - (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); - - if (controlNca != null && patchNca != null) - { - ApplicationControlProperty controlData = new(); - - using UniqueRef nacpFile = new(); - - controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); - - TitleUpdates.Add(new TitleUpdateModel(controlData, path)); - } - else - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]); - }); - } - } - catch (Exception ex) { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]); }); } } - } - - public void RemoveUpdate(TitleUpdateModel update) - { - TitleUpdates.Remove(update); - - SortUpdates(); - } - - public async void Add() - { - OpenFileDialog dialog = new() + catch (Exception ex) { - Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle], - AllowMultiple = true - }; - - dialog.Filters.Add(new FileDialogFilter - { - Name = "NSP", - Extensions = { "nsp" } - }); - - if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - string[] files = await dialog.ShowAsync(desktop.MainWindow); - - if (files != null) + Dispatcher.UIThread.Post(async () => { - foreach (string file in files) - { - AddUpdate(file); - } - } + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)); + }); } - - SortUpdates(); } } + + public void RemoveUpdate(TitleUpdateModel update) + { + TitleUpdates.Remove(update); + + SortUpdates(); + } + + public async void Add() + { + OpenFileDialog dialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle], + AllowMultiple = true + }; + + dialog.Filters.Add(new FileDialogFilter + { + Name = "NSP", + Extensions = { "nsp" } + }); + + if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + string[] files = await dialog.ShowAsync(desktop.MainWindow); + + if (files != null) + { + foreach (string file in files) + { + AddUpdate(file); + } + } + } + + SortUpdates(); + } + + public void Save() + { + _titleUpdateWindowData.Paths.Clear(); + _titleUpdateWindowData.Selected = ""; + + foreach (TitleUpdateModel update in TitleUpdates) + { + _titleUpdateWindowData.Paths.Add(update.Path); + + if (update == SelectedUpdate) + { + _titleUpdateWindowData.Selected = update.Path; + } + } + + File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); + } } \ No newline at end of file