diff --git a/Ryujinx.Ava/Assets/Locales/en_US.json b/Ryujinx.Ava/Assets/Locales/en_US.json index e98988b7..86cb44c9 100644 --- a/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/Ryujinx.Ava/Assets/Locales/en_US.json @@ -564,10 +564,10 @@ "Writable": "Writable", "SelectDlcDialogTitle": "Select DLC files", "SelectUpdateDialogTitle": "Select update files", - "UserProfileWindowTitle": "Manage User Profiles", - "CheatWindowTitle": "Manage Game Cheats", - "DlcWindowTitle": "Manage Game DLC", - "UpdateWindowTitle": "Manage Game Updates", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Downloadable Content Manager", + "UpdateWindowTitle": "Title Update Manager", "CheatWindowHeading": "Cheats Available for {0} [{1}]", "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", "UserProfilesEditProfile": "Edit Selected", @@ -577,7 +577,7 @@ "UserProfilesSetProfileImage": "Set Profile Image", "UserProfileEmptyNameError": "Name is required", "UserProfileNoImageError": "Profile image must be set", - "GameUpdateWindowHeading": "Updates Available for {0} [{1}]", + "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", "UserProfilesName": "Name:", diff --git a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs index d5d3b760..cd437017 100644 --- a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs @@ -1283,7 +1283,7 @@ namespace Ryujinx.Ava.Ui.ViewModels ApplicationData selection = SelectedApplication; if (selection != null) { - await new TitleUpdateWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner); + await new TitleUpdateWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner); } } diff --git a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml index 0189c505..2e3fd05b 100644 --- a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml @@ -8,8 +8,10 @@ xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" Width="800" Height="500" - MinWidth="600" + MinWidth="800" MinHeight="500" + MaxWidth="800" + MaxHeight="500" SizeToContent="Height" WindowStartupLocation="CenterOwner" mc:Ignorable="d"> diff --git a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs index b1c86afc..e68214eb 100644 --- a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs @@ -8,7 +8,6 @@ using LibHac.FsSystem; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; -using LibHac.Tools.FsSystem.Save; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Models; @@ -17,8 +16,6 @@ using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.IO; using System.Linq; using System.Reactive.Linq; @@ -36,8 +33,8 @@ namespace Ryujinx.Ava.Ui.Windows private VirtualFileSystem _virtualFileSystem { get; } private AvaloniaList _downloadableContents { get; set; } - private ulong TitleId { get; } - private string TitleName { get; } + private ulong _titleId { get; } + private string _titleName { get; } public DownloadableContentManagerWindow() { @@ -45,15 +42,16 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); - Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {TitleName} ({TitleId:X16})"; + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})"; } public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) { _virtualFileSystem = virtualFileSystem; _downloadableContents = new AvaloniaList(); - TitleId = titleId; - TitleName = titleName; + + _titleId = titleId; + _titleName = titleName; _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); @@ -74,7 +72,7 @@ namespace Ryujinx.Ava.Ui.Windows DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged; - Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {TitleName} ({TitleId:X16})"; + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})"; LoadDownloadableContents(); PrintHeading(); @@ -87,7 +85,7 @@ namespace Ryujinx.Ava.Ui.Windows private void PrintHeading() { - Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, TitleName, TitleId.ToString("X16")); + Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, _titleName, _titleId.ToString("X16")); } private void LoadDownloadableContents() @@ -98,15 +96,15 @@ namespace Ryujinx.Ava.Ui.Windows { using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); - PartitionFileSystem pfs = new(containerFile.AsStorage()); + PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage()); - _virtualFileSystem.ImportTickets(pfs); + _virtualFileSystem.ImportTickets(partitionFileSystem); foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) { using UniqueRef ncaFile = new(); - pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); if (nca != null) @@ -169,7 +167,7 @@ namespace Ryujinx.Ava.Ui.Windows if (nca.Header.ContentType == NcaContentType.PublicData) { - if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId) { break; } diff --git a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml index 347c2cf5..dd690a55 100644 --- a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml @@ -3,13 +3,17 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" - SizeToContent="Height" - Width="600" MinHeight="500" Height="500" - WindowStartupLocation="CenterOwner" + Width="600" + Height="400" MinWidth="600" + MinHeight="400" + MaxWidth="600" + MaxHeight="400" + SizeToContent="Height" + WindowStartupLocation="CenterOwner" mc:Ignorable="d"> @@ -19,15 +23,15 @@ + TextAlignment="Center" + TextWrapping="Wrap" /> @@ -45,11 +47,19 @@ Margin="10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - Items="{Binding TitleUpdates}"> + Items="{Binding _titleUpdates}"> - - diff --git a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs index 751c7afc..1cc820d8 100644 --- a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs @@ -30,13 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows private readonly string _titleUpdateJsonPath; private TitleUpdateMetadata _titleUpdateWindowData; - public VirtualFileSystem VirtualFileSystem { get; } + private VirtualFileSystem _virtualFileSystem { get; } + private AvaloniaList _titleUpdates { get; set; } - internal AvaloniaList TitleUpdates { get; set; } = new AvaloniaList(); - public string TitleId { get; } - public string TitleName { get; } - - public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper()); + private ulong _titleId { get; } + private string _titleName { get; } public TitleUpdateWindow() { @@ -44,16 +42,18 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})"; } - public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) + public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) { - VirtualFileSystem = virtualFileSystem; - TitleId = titleId; - TitleName = titleName; + _virtualFileSystem = virtualFileSystem; + _titleUpdates = new AvaloniaList(); - _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); + _titleId = titleId; + _titleName = titleName; + + _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); try { @@ -64,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Windows _titleUpdateWindowData = new TitleUpdateMetadata { Selected = "", - Paths = new List() + Paths = new List() }; } @@ -72,14 +72,20 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; + Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})"; LoadUpdates(); + PrintHeading(); + } + + private void PrintHeading() + { + Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16")); } private void LoadUpdates() { - TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); + _titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); foreach (string path in _titleUpdateWindowData.Paths) { @@ -88,12 +94,12 @@ namespace Ryujinx.Ava.Ui.Windows if (_titleUpdateWindowData.Selected == "") { - TitleUpdates[0].IsEnabled = true; + _titleUpdates[0].IsEnabled = true; } else { - TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); - List enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); + TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); + List enabled = _titleUpdates.Where(x => x.IsEnabled).ToList(); foreach (TitleUpdateModel update in enabled) { @@ -111,50 +117,47 @@ namespace Ryujinx.Ava.Ui.Windows private void AddUpdate(string path) { - if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path)) + if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path)) { - using (FileStream file = new(path, FileMode.Open, FileAccess.Read)) + using FileStream file = new(path, FileMode.Open, FileAccess.Read); + + try { - PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); + (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); - try + if (controlNca != null && patchNca != null) { - (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0); + ApplicationControlProperty controlData = new(); - if (controlNca != null && patchNca != null) + 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)); + + foreach (var update in _titleUpdates) { - ApplicationControlProperty controlData = new ApplicationControlProperty(); - - using var nacpFile = new UniqueRef(); - - 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)); - - foreach (var update in TitleUpdates) - { - update.IsEnabled = false; - } - - TitleUpdates.Last().IsEnabled = true; - } - else - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]); - }); + update.IsEnabled = false; } + + _titleUpdates.Last().IsEnabled = true; } - catch (Exception ex) + else { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]); }); } } + catch (Exception ex) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path)); + }); + } } } @@ -162,16 +165,17 @@ namespace Ryujinx.Ava.Ui.Windows { if (removeSelectedOnly) { - TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList()); + _titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList()); } else { - TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList()); + _titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList()); } - TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true; + _titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true; SortUpdates(); + PrintHeading(); } public void RemoveSelected() @@ -186,7 +190,7 @@ namespace Ryujinx.Ava.Ui.Windows public async void Add() { - OpenFileDialog dialog = new OpenFileDialog() + OpenFileDialog dialog = new() { Title = LocaleManager.Instance["SelectUpdateDialogTitle"], AllowMultiple = true @@ -209,11 +213,12 @@ namespace Ryujinx.Ava.Ui.Windows } SortUpdates(); + PrintHeading(); } private void SortUpdates() { - var list = TitleUpdates.ToList(); + var list = _titleUpdates.ToList(); list.Sort((first, second) => { @@ -229,8 +234,8 @@ namespace Ryujinx.Ava.Ui.Windows return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; }); - TitleUpdates.Clear(); - TitleUpdates.AddRange(list); + _titleUpdates.Clear(); + _titleUpdates.AddRange(list); } public void Save() @@ -239,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows _titleUpdateWindowData.Selected = ""; - foreach (TitleUpdateModel update in TitleUpdates) + foreach (TitleUpdateModel update in _titleUpdates) { _titleUpdateWindowData.Paths.Add(update.Path);