ava: Refactor Title Update Manager window (#3898)

* ava: Refactor TitleUpdate Manager window

* Update locale
This commit is contained in:
Ac_K 2022-11-25 17:55:08 +01:00 committed by GitHub
parent 476b4683cf
commit 5d3ef7761b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 90 deletions

View file

@ -564,10 +564,10 @@
"Writable": "Writable", "Writable": "Writable",
"SelectDlcDialogTitle": "Select DLC files", "SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files", "SelectUpdateDialogTitle": "Select update files",
"UserProfileWindowTitle": "Manage User Profiles", "UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Manage Game Cheats", "CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Manage Game DLC", "DlcWindowTitle": "Downloadable Content Manager",
"UpdateWindowTitle": "Manage Game Updates", "UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
@ -577,7 +577,7 @@
"UserProfilesSetProfileImage": "Set Profile Image", "UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required", "UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set", "UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]", "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
"UserProfilesName": "Name:", "UserProfilesName": "Name:",

View file

@ -1283,7 +1283,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
ApplicationData selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) 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);
} }
} }

View file

@ -8,8 +8,10 @@
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="800" Width="800"
Height="500" Height="500"
MinWidth="600" MinWidth="800"
MinHeight="500" MinHeight="500"
MaxWidth="800"
MaxHeight="500"
SizeToContent="Height" SizeToContent="Height"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
mc:Ignorable="d"> mc:Ignorable="d">

View file

@ -8,7 +8,6 @@ using LibHac.FsSystem;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.FsSystem.Save;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;
@ -17,8 +16,6 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
@ -36,8 +33,8 @@ namespace Ryujinx.Ava.Ui.Windows
private VirtualFileSystem _virtualFileSystem { get; } private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; } private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
private ulong TitleId { get; } private ulong _titleId { get; }
private string TitleName { get; } private string _titleName { get; }
public DownloadableContentManagerWindow() public DownloadableContentManagerWindow()
{ {
@ -45,15 +42,16 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); 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) public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_downloadableContents = new AvaloniaList<DownloadableContentModel>(); _downloadableContents = new AvaloniaList<DownloadableContentModel>();
TitleId = titleId;
TitleName = titleName; _titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
@ -74,7 +72,7 @@ namespace Ryujinx.Ava.Ui.Windows
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged; 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(); LoadDownloadableContents();
PrintHeading(); PrintHeading();
@ -87,7 +85,7 @@ namespace Ryujinx.Ava.Ui.Windows
private void PrintHeading() 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() private void LoadDownloadableContents()
@ -98,15 +96,15 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); 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) foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{ {
using UniqueRef<IFile> ncaFile = new(); using UniqueRef<IFile> 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); Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null) if (nca != null)
@ -169,7 +167,7 @@ namespace Ryujinx.Ava.Ui.Windows
if (nca.Header.ContentType == NcaContentType.PublicData) if (nca.Header.ContentType == NcaContentType.PublicData)
{ {
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{ {
break; break;
} }

View file

@ -3,13 +3,17 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
SizeToContent="Height" Width="600"
Width="600" MinHeight="500" Height="500" Height="400"
WindowStartupLocation="CenterOwner"
MinWidth="600" MinWidth="600"
MinHeight="400"
MaxWidth="600"
MaxHeight="400"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Margin="15"> <Grid Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -19,15 +23,15 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
Name="Heading"
Grid.Row="1" Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20" Margin="20,15,20,20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18" LineHeight="18"
TextWrapping="Wrap" TextAlignment="Center"
Text="{Binding Heading}" TextWrapping="Wrap" />
TextAlignment="Center" />
<Border <Border
Grid.Row="2" Grid.Row="2"
Margin="5" Margin="5"
@ -36,8 +40,6 @@
BorderBrush="Gray" BorderBrush="Gray"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer <ScrollViewer
Width="550"
MinHeight="200"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
@ -45,11 +47,19 @@
Margin="10" Margin="10"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Items="{Binding TitleUpdates}"> Items="{Binding _titleUpdates}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}"> <RadioButton
<Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" /> Padding="8,0"
VerticalContentAlignment="Center"
GroupName="Update"
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
<Label
Margin="0"
VerticalAlignment="Center"
Content="{Binding Label}"
FontSize="12" />
</RadioButton> </RadioButton>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View file

@ -30,13 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows
private readonly string _titleUpdateJsonPath; private readonly string _titleUpdateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; } private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>(); private ulong _titleId { get; }
public string TitleId { get; } private string _titleName { get; }
public string TitleName { get; }
public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper());
public TitleUpdateWindow() public TitleUpdateWindow()
{ {
@ -44,16 +42,18 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); 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; _virtualFileSystem = virtualFileSystem;
TitleId = titleId; _titleUpdates = new AvaloniaList<TitleUpdateModel>();
TitleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); _titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try try
{ {
@ -64,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData = new TitleUpdateMetadata _titleUpdateWindowData = new TitleUpdateMetadata
{ {
Selected = "", Selected = "",
Paths = new List<string>() Paths = new List<string>()
}; };
} }
@ -72,14 +72,20 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadUpdates(); LoadUpdates();
PrintHeading();
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
} }
private void LoadUpdates() 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) foreach (string path in _titleUpdateWindowData.Paths)
{ {
@ -88,12 +94,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (_titleUpdateWindowData.Selected == "") if (_titleUpdateWindowData.Selected == "")
{ {
TitleUpdates[0].IsEnabled = true; _titleUpdates[0].IsEnabled = true;
} }
else else
{ {
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
foreach (TitleUpdateModel update in enabled) foreach (TitleUpdateModel update in enabled)
{ {
@ -111,50 +117,47 @@ namespace Ryujinx.Ava.Ui.Windows
private void AddUpdate(string path) 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<IFile> 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(); update.IsEnabled = false;
using var nacpFile = new UniqueRef<IFile>();
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"]);
});
} }
_titleUpdates.Last().IsEnabled = true;
} }
catch (Exception ex) else
{ {
Dispatcher.UIThread.Post(async () => 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) if (removeSelectedOnly)
{ {
TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList()); _titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
} }
else 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(); SortUpdates();
PrintHeading();
} }
public void RemoveSelected() public void RemoveSelected()
@ -186,7 +190,7 @@ namespace Ryujinx.Ava.Ui.Windows
public async void Add() public async void Add()
{ {
OpenFileDialog dialog = new OpenFileDialog() OpenFileDialog dialog = new()
{ {
Title = LocaleManager.Instance["SelectUpdateDialogTitle"], Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
AllowMultiple = true AllowMultiple = true
@ -209,11 +213,12 @@ namespace Ryujinx.Ava.Ui.Windows
} }
SortUpdates(); SortUpdates();
PrintHeading();
} }
private void SortUpdates() private void SortUpdates()
{ {
var list = TitleUpdates.ToList(); var list = _titleUpdates.ToList();
list.Sort((first, second) => 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; return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
}); });
TitleUpdates.Clear(); _titleUpdates.Clear();
TitleUpdates.AddRange(list); _titleUpdates.AddRange(list);
} }
public void Save() public void Save()
@ -239,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData.Selected = ""; _titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates) foreach (TitleUpdateModel update in _titleUpdates)
{ {
_titleUpdateWindowData.Paths.Add(update.Path); _titleUpdateWindowData.Paths.Add(update.Path);