From 46c8129bf57a3df3e0a2db27e6611ca4210614be Mon Sep 17 00:00:00 2001 From: Ac_K <Acoustik666@gmail.com> Date: Fri, 29 Jul 2022 00:41:34 +0200 Subject: [PATCH] Avalonia: Another Cleanup (#3494) * Avalonia: Another Cleanup This PR is a cleanup to the avalonia code recently added: - Some XAML file are autoformatted like a previous PR. - Dlc is renamed to DownloadableContent (Locale exclude). - DownloadableContentManagerWindow is a bit improved (Fixes #3491). - Some nits here and there. * Fix GTK * Remove AttachDebugDevTools * Fix last warning * Fix JSON fields --- Ryujinx.Ava/Ui/Controls/GameGridView.axaml | 2 +- Ryujinx.Ava/Ui/Controls/GameListView.axaml | 2 +- .../Ui/Controls/UpdateWaitWindow.axaml.cs | 3 - Ryujinx.Ava/Ui/Models/CheatModel.cs | 7 +- Ryujinx.Ava/Ui/Models/CheatsList.cs | 32 +-- Ryujinx.Ava/Ui/Models/DlcModel.cs | 18 -- .../Ui/Models/DownloadableContentModel.cs | 18 ++ .../Ui/ViewModels/AmiiboWindowViewModel.cs | 4 +- .../Ui/ViewModels/MainWindowViewModel.cs | 6 +- Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs | 4 - Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs | 9 +- Ryujinx.Ava/Ui/Windows/CheatWindow.axaml | 73 ++--- Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs | 13 +- .../Windows/ControllerSettingsWindow.axaml.cs | 10 - .../Ui/Windows/DlcManagerWindow.axaml.cs | 254 ----------------- ...=> DownloadableContentManagerWindow.axaml} | 10 +- .../DownloadableContentManagerWindow.axaml.cs | 266 ++++++++++++++++++ Ryujinx.Ava/Ui/Windows/MainWindow.axaml | 2 +- Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 19 +- .../Ui/Windows/MotionSettingsWindow.axaml.cs | 1 - .../Ui/Windows/RumbleSettingsWindow.axaml.cs | 1 - .../Ui/Windows/SettingsWindow.axaml.cs | 15 - Ryujinx.Ava/Ui/Windows/StyleableWindow.cs | 1 - .../Ui/Windows/TitleUpdateWindow.axaml.cs | 67 +++-- Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs | 5 +- Ryujinx.Common/Configuration/DlcContainer.cs | 10 - Ryujinx.Common/Configuration/DlcNca.cs | 9 - .../DownloadableContentContainer.cs | 13 + .../Configuration/DownloadableContentNca.cs | 14 + Ryujinx.HLE/HOS/ApplicationLoader.cs | 12 +- Ryujinx/Ui/Windows/DlcWindow.cs | 46 +-- 31 files changed, 456 insertions(+), 490 deletions(-) delete mode 100644 Ryujinx.Ava/Ui/Models/DlcModel.cs create mode 100644 Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs delete mode 100644 Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs rename Ryujinx.Ava/Ui/Windows/{DlcManagerWindow.axaml => DownloadableContentManagerWindow.axaml} (94%) create mode 100644 Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs delete mode 100644 Ryujinx.Common/Configuration/DlcContainer.cs delete mode 100644 Ryujinx.Common/Configuration/DlcNca.cs create mode 100644 Ryujinx.Common/Configuration/DownloadableContentContainer.cs create mode 100644 Ryujinx.Common/Configuration/DownloadableContentNca.cs diff --git a/Ryujinx.Ava/Ui/Controls/GameGridView.axaml b/Ryujinx.Ava/Ui/Controls/GameGridView.axaml index 431bcf00f1..bcf38e7a0e 100644 --- a/Ryujinx.Ava/Ui/Controls/GameGridView.axaml +++ b/Ryujinx.Ava/Ui/Controls/GameGridView.axaml @@ -37,7 +37,7 @@ Header="{locale:Locale GameListContextMenuManageTitleUpdates}" ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> <MenuItem - Command="{Binding OpenDlcManager}" + Command="{Binding OpenDownloadableContentManager}" Header="{locale:Locale GameListContextMenuManageDlc}" ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> <MenuItem diff --git a/Ryujinx.Ava/Ui/Controls/GameListView.axaml b/Ryujinx.Ava/Ui/Controls/GameListView.axaml index 6fe0b1620a..2c6022bded 100644 --- a/Ryujinx.Ava/Ui/Controls/GameListView.axaml +++ b/Ryujinx.Ava/Ui/Controls/GameListView.axaml @@ -37,7 +37,7 @@ Header="{locale:Locale GameListContextMenuManageTitleUpdates}" ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> <MenuItem - Command="{Binding OpenDlcManager}" + Command="{Binding OpenDownloadableContentManager}" Header="{locale:Locale GameListContextMenuManageDlc}" ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> <MenuItem diff --git a/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs b/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs index eff15c7ba3..19d6f98b50 100644 --- a/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Controls/UpdateWaitWindow.axaml.cs @@ -17,9 +17,6 @@ namespace Ryujinx.Ava.Ui.Controls public UpdateWaitWindow() { InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif } } } \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Models/CheatModel.cs b/Ryujinx.Ava/Ui/Models/CheatModel.cs index cdab27cd84..5011d3981b 100644 --- a/Ryujinx.Ava/Ui/Models/CheatModel.cs +++ b/Ryujinx.Ava/Ui/Models/CheatModel.cs @@ -11,8 +11,8 @@ namespace Ryujinx.Ava.Ui.Models public CheatModel(string name, string buildId, bool isEnabled) { - Name = name; - BuildId = buildId; + Name = name; + BuildId = buildId; IsEnabled = isEnabled; } @@ -22,7 +22,9 @@ namespace Ryujinx.Ava.Ui.Models set { _isEnabled = value; + EnableToggled?.Invoke(this, _isEnabled); + OnPropertyChanged(); } } @@ -30,6 +32,7 @@ namespace Ryujinx.Ava.Ui.Models public string BuildId { get; } public string BuildIdKey => $"{BuildId}-{Name}"; + public string Name { get; } public string CleanName => Name.Substring(1, Name.Length - 8); diff --git a/Ryujinx.Ava/Ui/Models/CheatsList.cs b/Ryujinx.Ava/Ui/Models/CheatsList.cs index f2b0592e97..8231e5211e 100644 --- a/Ryujinx.Ava/Ui/Models/CheatsList.cs +++ b/Ryujinx.Ava/Ui/Models/CheatsList.cs @@ -10,26 +10,13 @@ namespace Ryujinx.Ava.Ui.Models public CheatsList(string buildId, string path) { BuildId = buildId; - Path = path; + Path = path; + CollectionChanged += CheatsList_CollectionChanged; } - private void CheatsList_CollectionChanged(object sender, - NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Add) - { - (e.NewItems[0] as CheatModel).EnableToggled += Item_EnableToggled; - } - } - - private void Item_EnableToggled(object sender, bool e) - { - OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled))); - } - public string BuildId { get; } - public string Path { get; } + public string Path { get; } public bool IsEnabled { @@ -47,5 +34,18 @@ namespace Ryujinx.Ava.Ui.Models OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled))); } } + + private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + (e.NewItems[0] as CheatModel).EnableToggled += Item_EnableToggled; + } + } + + private void Item_EnableToggled(object sender, bool e) + { + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled))); + } } } \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Models/DlcModel.cs b/Ryujinx.Ava/Ui/Models/DlcModel.cs deleted file mode 100644 index 7e5f4a62fc..0000000000 --- a/Ryujinx.Ava/Ui/Models/DlcModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ryujinx.Ava.Ui.Models -{ - public class DlcModel - { - public bool IsEnabled { get; set; } - public string TitleId { get; } - public string ContainerPath { get; } - public string FullPath { get; } - - public DlcModel(string titleId, string containerPath, string fullPath, bool isEnabled) - { - TitleId = titleId; - ContainerPath = containerPath; - FullPath = fullPath; - IsEnabled = isEnabled; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs b/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs new file mode 100644 index 0000000000..67530f62c4 --- /dev/null +++ b/Ryujinx.Ava/Ui/Models/DownloadableContentModel.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Ava.Ui.Models +{ + public class DownloadableContentModel + { + public bool Enabled { get; set; } + public string TitleId { get; } + public string ContainerPath { get; } + public string FullPath { get; } + + public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled) + { + TitleId = titleId; + ContainerPath = containerPath; + FullPath = fullPath; + Enabled = enabled; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs index 7015df44e6..fc809e52ba 100644 --- a/Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs +++ b/Ryujinx.Ava/Ui/ViewModels/AmiiboWindowViewModel.cs @@ -382,9 +382,9 @@ namespace Ryujinx.Ava.Ui.ViewModels { string amiiboJsonString = await response.Content.ReadAsStringAsync(); - using (FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough)) + using (FileStream amiiboJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough)) { - dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + amiiboJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); } return amiiboJsonString; diff --git a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs index 7e6452f989..bd9242e7ca 100644 --- a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs @@ -1261,15 +1261,15 @@ namespace Ryujinx.Ava.Ui.ViewModels } } - public async void OpenDlcManager() + public async void OpenDownloadableContentManager() { var selection = SelectedApplication; if (selection != null) { - DlcManagerWindow dlcManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName); + DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName); - await dlcManager.ShowDialog(_owner); + await downloadableContentManager.ShowDialog(_owner); } } diff --git a/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs index 75e9901b02..90b870aafa 100644 --- a/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/AboutWindow.axaml.cs @@ -2,7 +2,6 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; using Avalonia.Threading; using Ryujinx.Ava.Common.Locale; using Ryujinx.Common.Utilities; @@ -27,9 +26,6 @@ namespace Ryujinx.Ava.Ui.Windows DataContext = this; InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif _ = DownloadPatronsJson(); } diff --git a/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs index 5ed845710d..9333a40e28 100644 --- a/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/AmiiboWindow.axaml.cs @@ -1,6 +1,5 @@ using Avalonia; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.ViewModels; @@ -18,9 +17,7 @@ namespace Ryujinx.Ava.Ui.Windows DataContext = ViewModel; InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"]; } @@ -31,9 +28,7 @@ namespace Ryujinx.Ava.Ui.Windows DataContext = ViewModel; InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif + if (Program.PreviewerDetached) { Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"]; diff --git a/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml index a5a4b0eeb1..8d91161161 100644 --- a/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml @@ -1,21 +1,24 @@ -<window:StyleableWindow x:Class="Ryujinx.Ava.Ui.Windows.CheatWindow" - 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:model="clr-namespace:Ryujinx.Ava.Ui.Models" - xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" - mc:Ignorable="d" - Width="500" MinHeight="500" Height="500" - WindowStartupLocation="CenterOwner" - MinWidth="500"> +<window:StyleableWindow + x:Class="Ryujinx.Ava.Ui.Windows.CheatWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:model="clr-namespace:Ryujinx.Ava.Ui.Models" + xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + Width="500" + Height="500" + MinWidth="500" + MinHeight="500" + WindowStartupLocation="CenterOwner" + mc:Ignorable="d"> <Window.Styles> <Style Selector="TreeViewItem"> <Setter Property="IsExpanded" Value="True" /> </Style> </Window.Styles> - <Grid Name="DlcGrid" Margin="15"> + <Grid Name="CheatGrid" Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> @@ -24,14 +27,14 @@ </Grid.RowDefinitions> <TextBlock Grid.Row="1" + MaxWidth="500" Margin="20,15,20,20" HorizontalAlignment="Center" VerticalAlignment="Center" - MaxWidth="500" LineHeight="18" - TextWrapping="Wrap" Text="{Binding Heading}" - TextAlignment="Center" /> + TextAlignment="Center" + TextWrapping="Wrap" /> <Border Grid.Row="2" Margin="5" @@ -39,32 +42,38 @@ VerticalAlignment="Stretch" BorderBrush="Gray" BorderThickness="1"> - <TreeView Items="{Binding LoadedCheats}" - HorizontalAlignment="Stretch" - VerticalAlignment="Stretch" - Name="CheatsView" - MinHeight="300"> + <TreeView + Name="CheatsView" + MinHeight="300" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Items="{Binding LoadedCheats}"> <TreeView.Styles> <Styles> <Style Selector="TreeViewItem:empty /template/ ItemsPresenter"> - <Setter Property="IsVisible" Value="False"/> + <Setter Property="IsVisible" Value="False" /> </Style> </Styles> </TreeView.Styles> <TreeView.DataTemplates> <TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}"> <StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> - <CheckBox IsChecked="{Binding IsEnabled}" MinWidth="20" /> - <TextBlock Width="150" - Text="{Binding BuildId}" /> - <TextBlock - Text="{Binding Path}" /> + <CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" /> + <TextBlock Width="150" Text="{Binding BuildId}" /> + <TextBlock Text="{Binding Path}" /> </StackPanel> </TreeDataTemplate> <DataTemplate x:DataType="model:CheatModel"> - <StackPanel Orientation="Horizontal" Margin="0" HorizontalAlignment="Left"> - <CheckBox IsChecked="{Binding IsEnabled}" Padding="0" Margin="5,0" MinWidth="20" /> - <TextBlock Text="{Binding CleanName}" VerticalAlignment="Center" /> + <StackPanel + Margin="0" + HorizontalAlignment="Left" + Orientation="Horizontal"> + <CheckBox + MinWidth="20" + Margin="5,0" + Padding="0" + IsChecked="{Binding IsEnabled}" /> + <TextBlock VerticalAlignment="Center" Text="{Binding CleanName}" /> </StackPanel> </DataTemplate> </TreeView.DataTemplates> @@ -79,8 +88,8 @@ Name="SaveButton" MinWidth="90" Margin="5" - IsVisible="{Binding !NoCheatsFound}" - Command="{Binding Save}"> + Command="{Binding Save}" + IsVisible="{Binding !NoCheatsFound}"> <TextBlock Text="{locale:Locale SettingsButtonSave}" /> </Button> <Button diff --git a/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs index e3cccdd3bc..13c2f20a8b 100644 --- a/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/CheatWindow.axaml.cs @@ -1,6 +1,5 @@ using Avalonia; using Avalonia.Collections; -using Avalonia.Markup.Xaml; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Models; using Ryujinx.HLE.FileSystem; @@ -26,8 +25,7 @@ namespace Ryujinx.Ava.Ui.Windows DataContext = this; InitializeComponent(); - AttachDebugDevTools(); - + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"]; } @@ -38,9 +36,6 @@ namespace Ryujinx.Ava.Ui.Windows Heading = string.Format(LocaleManager.Instance["CheatWindowHeading"], titleName, titleId.ToUpper()); InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath(); string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId); @@ -96,12 +91,6 @@ namespace Ryujinx.Ava.Ui.Windows Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"]; } - [Conditional("DEBUG")] - private void AttachDebugDevTools() - { - this.AttachDevTools(); - } - public void Save() { if (NoCheatsFound) diff --git a/Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs index 45262bcd5d..82ef75ca55 100644 --- a/Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/ControllerSettingsWindow.axaml.cs @@ -3,24 +3,14 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Markup.Xaml; -using Avalonia.Threading; -using Avalonia.VisualTree; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.ViewModels; -using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Input; using Ryujinx.Input.Assigner; -using Ryujinx.Ui.Common.Configuration; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Ui.Windows { diff --git a/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs deleted file mode 100644 index 09b221e3fc..0000000000 --- a/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml.cs +++ /dev/null @@ -1,254 +0,0 @@ -using Avalonia; -using Avalonia.Collections; -using Avalonia.Controls; -using Avalonia.Threading; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Ava.Common.Locale; -using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Models; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Utilities; -using Ryujinx.HLE.FileSystem; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Path = System.IO.Path; - -namespace Ryujinx.Ava.Ui.Windows -{ - public partial class DlcManagerWindow : StyleableWindow - { - private readonly List<DlcContainer> _dlcContainerList; - private readonly string _dlcJsonPath; - - public VirtualFileSystem VirtualFileSystem { get; } - - public AvaloniaList<DlcModel> Dlcs { get; set; } - public ulong TitleId { get; } - public string TitleName { get; } - - public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16")); - - public DlcManagerWindow() - { - DataContext = this; - - InitializeComponent(); - AttachDebugDevTools(); - - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; - } - - public DlcManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) - { - VirtualFileSystem = virtualFileSystem; - TitleId = titleId; - TitleName = titleName; - - _dlcJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); - - try - { - _dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath); - } - catch - { - _dlcContainerList = new List<DlcContainer>(); - } - - DataContext = this; - - InitializeComponent(); - AttachDebugDevTools(); - - Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; - - LoadDlcs(); - } - - [Conditional("DEBUG")] - private void AttachDebugDevTools() - { - this.AttachDevTools(); - } - - private void LoadDlcs() - { - foreach (DlcContainer dlcContainer in _dlcContainerList) - { - using FileStream containerFile = File.OpenRead(dlcContainer.Path); - - PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); - - VirtualFileSystem.ImportTickets(pfs); - - foreach (DlcNca dlcNca in dlcContainer.DlcNcaList) - { - using var ncaFile = new UniqueRef<IFile>(); - pfs.OpenFile(ref ncaFile.Ref(), dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.Path); - - if (nca != null) - { - Dlcs.Add(new DlcModel(nca.Header.TitleId.ToString("X16"), dlcContainer.Path, dlcNca.Path, - dlcNca.Enabled)); - } - } - } - } - - private Nca TryCreateNca(IStorage ncaStorage, string containerPath) - { - try - { - return new Nca(VirtualFileSystem.KeySet, ncaStorage); - } - catch (Exception ex) - { - Dispatcher.UIThread.InvokeAsync(async () => - { - await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[ - "DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath)); - }); - } - - return null; - } - - private async Task AddDlc(string path) - { - if (!File.Exists(path) || Dlcs.FirstOrDefault(x => x.ContainerPath == path) != null) - { - return; - } - - using (FileStream containerFile = File.OpenRead(path)) - { - PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); - bool containsDlc = false; - - VirtualFileSystem.ImportTickets(pfs); - - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path); - - if (nca == null) - { - continue; - } - - if (nca.Header.ContentType == NcaContentType.PublicData) - { - if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) - { - break; - } - - Dlcs.Add(new DlcModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true)); - - containsDlc = true; - } - } - - if (!containsDlc) - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); - } - } - } - - private void RemoveDlcs(bool removeSelectedOnly = false) - { - if (removeSelectedOnly) - { - Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList()); - } - else - { - Dlcs.Clear(); - } - } - - public void RemoveSelected() - { - RemoveDlcs(true); - } - - public void RemoveAll() - { - RemoveDlcs(); - } - - public async void Add() - { - OpenFileDialog dialog = new OpenFileDialog() { Title = LocaleManager.Instance["SelectDlcDialogTitle"], AllowMultiple = true }; - - dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); - - string[] files = await dialog.ShowAsync(this); - - if (files != null) - { - foreach (string file in files) - { - await AddDlc(file); - } - } - } - - public void Save() - { - _dlcContainerList.Clear(); - - DlcContainer container = default; - - foreach (DlcModel dlc in Dlcs) - { - if (container.Path != dlc.ContainerPath) - { - if (!string.IsNullOrWhiteSpace(container.Path)) - { - _dlcContainerList.Add(container); - } - - container = new DlcContainer { Path = dlc.ContainerPath, DlcNcaList = new List<DlcNca>() }; - } - - container.DlcNcaList.Add(new DlcNca - { - Enabled = dlc.IsEnabled, - TitleId = Convert.ToUInt64(dlc.TitleId, 16), - Path = dlc.FullPath - }); - } - - if (!string.IsNullOrWhiteSpace(container.Path)) - { - _dlcContainerList.Add(container); - } - - using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough)) - { - dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true))); - } - - Close(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml similarity index 94% rename from Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml rename to Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml index 94b3895ef4..068ea826a8 100644 --- a/Ryujinx.Ava/Ui/Windows/DlcManagerWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml @@ -1,5 +1,5 @@ <window:StyleableWindow - x:Class="Ryujinx.Ava.Ui.Windows.DlcManagerWindow" + x:Class="Ryujinx.Ava.Ui.Windows.DownloadableContentManagerWindow" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" @@ -11,7 +11,7 @@ WindowStartupLocation="CenterOwner" MinWidth="600" mc:Ignorable="d"> - <Grid Name="DlcGrid" Margin="15"> + <Grid Name="DownloadableContentGrid" Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> @@ -40,7 +40,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalScrollBarVisibility="Auto" - Items="{Binding Dlcs}" + Items="{Binding DownloadableContents}" VerticalScrollBarVisibility="Auto"> <DataGrid.Columns> <DataGridTemplateColumn Width="90"> @@ -50,7 +50,7 @@ Width="50" MinWidth="40" HorizontalAlignment="Right" - IsChecked="{Binding IsEnabled}" /> + IsChecked="{Binding Enabled}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.Header> @@ -116,7 +116,7 @@ Name="SaveButton" MinWidth="90" Margin="5" - Command="{Binding Save}"> + Command="{Binding SaveAndClose}"> <TextBlock Text="{locale:Locale SettingsButtonSave}" /> </Button> <Button diff --git a/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs new file mode 100644 index 0000000000..972ffbc3e4 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/DownloadableContentManagerWindow.axaml.cs @@ -0,0 +1,266 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Threading; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Controls; +using Ryujinx.Ava.Ui.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.Ui.Windows +{ + public partial class DownloadableContentManagerWindow : StyleableWindow + { + private readonly List<DownloadableContentContainer> _downloadableContentContainerList; + private readonly string _downloadableContentJsonPath; + + public VirtualFileSystem VirtualFileSystem { get; } + public AvaloniaList<DownloadableContentModel> DownloadableContents { get; set; } = new AvaloniaList<DownloadableContentModel>(); + public ulong TitleId { get; } + public string TitleName { get; } + + public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16")); + + public DownloadableContentManagerWindow() + { + DataContext = this; + + InitializeComponent(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; + } + + public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + { + VirtualFileSystem = virtualFileSystem; + TitleId = titleId; + TitleName = titleName; + + _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); + + try + { + _downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath); + } + catch + { + _downloadableContentContainerList = new List<DownloadableContentContainer>(); + } + + DataContext = this; + + InitializeComponent(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; + + LoadDownloadableContents(); + } + + private void LoadDownloadableContents() + { + foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList) + { + if (File.Exists(downloadableContentContainer.ContainerPath)) + { + using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); + + PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); + + VirtualFileSystem.ImportTickets(pfs); + + foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) + { + using var ncaFile = new UniqueRef<IFile>(); + + pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); + if (nca != null) + { + DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), + downloadableContentContainer.ContainerPath, + downloadableContentNca.FullPath, + downloadableContentNca.Enabled)); + } + } + } + } + + // NOTE: Save the list again to remove leftovers. + Save(); + } + + private Nca TryCreateNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(VirtualFileSystem.KeySet, ncaStorage); + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath)); + }); + } + + return null; + } + + private async Task AddDownloadableContent(string path) + { + if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) + { + return; + } + + using (FileStream containerFile = File.OpenRead(path)) + { + PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); + bool containsDownloadableContent = false; + + VirtualFileSystem.ImportTickets(pfs); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef<IFile>(); + + pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path); + + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) + { + break; + } + + DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true)); + + containsDownloadableContent = true; + } + } + + if (!containsDownloadableContent) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); + } + } + } + + private void RemoveDownloadableContents(bool removeSelectedOnly = false) + { + if (removeSelectedOnly) + { + DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList()); + } + else + { + DownloadableContents.Clear(); + } + } + + public void RemoveSelected() + { + RemoveDownloadableContents(true); + } + + public void RemoveAll() + { + RemoveDownloadableContents(); + } + + public async void Add() + { + OpenFileDialog dialog = new OpenFileDialog() + { + Title = LocaleManager.Instance["SelectDlcDialogTitle"], + AllowMultiple = true + }; + + dialog.Filters.Add(new FileDialogFilter + { + Name = "NSP", + Extensions = { "nsp" } + }); + + string[] files = await dialog.ShowAsync(this); + + if (files != null) + { + foreach (string file in files) + { + await AddDownloadableContent(file); + } + } + } + + public void Save() + { + _downloadableContentContainerList.Clear(); + + DownloadableContentContainer container = default; + + foreach (DownloadableContentModel downloadableContent in DownloadableContents) + { + if (container.ContainerPath != downloadableContent.ContainerPath) + { + if (!string.IsNullOrWhiteSpace(container.ContainerPath)) + { + _downloadableContentContainerList.Add(container); + } + + container = new DownloadableContentContainer + { + ContainerPath = downloadableContent.ContainerPath, + DownloadableContentNcaList = new List<DownloadableContentNca>() + }; + } + + container.DownloadableContentNcaList.Add(new DownloadableContentNca + { + Enabled = downloadableContent.Enabled, + TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16), + FullPath = downloadableContent.FullPath + }); + } + + if (!string.IsNullOrWhiteSpace(container.ContainerPath)) + { + _downloadableContentContainerList.Add(container); + } + + using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough)) + { + downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true))); + } + } + + public void SaveAndClose() + { + Save(); + Close(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml index ab4d76de41..a08ca91058 100644 --- a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml @@ -257,7 +257,7 @@ </DockPanel> </StackPanel> <ContentControl - Name="Content" + Name="MainContent" Grid.Row="1" Padding="0" HorizontalAlignment="Stretch" diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs index d55dd41325..a5e3c06a0d 100644 --- a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs @@ -2,10 +2,8 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Threading; -using Avalonia.Win32; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; @@ -33,7 +31,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using InputManager = Ryujinx.Input.HLE.InputManager; -using ProgressBar = Avalonia.Controls.ProgressBar; + namespace Ryujinx.Ava.Ui.Windows { public partial class MainWindow : StyleableWindow @@ -87,7 +85,6 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); Load(); - AttachDebugDevTools(); UiHandler = new AvaHostUiHandler(this); @@ -110,12 +107,6 @@ namespace Ryujinx.Ava.Ui.Windows _rendererWaitEvent = new AutoResetEvent(false); } - [Conditional("DEBUG")] - private void AttachDebugDevTools() - { - this.AttachDevTools(); - } - public void LoadGameList() { if (_isLoading) @@ -244,7 +235,7 @@ namespace Ryujinx.Ava.Ui.Windows PrepareLoadScreen(); - _mainViewContent = Content.Content as Control; + _mainViewContent = MainContent.Content as Control; GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel); AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this); @@ -311,7 +302,7 @@ namespace Ryujinx.Ava.Ui.Windows Dispatcher.UIThread.InvokeAsync(() => { - Content.Content = GlRenderer; + MainContent.Content = GlRenderer; if (startFullscreen && WindowState != WindowState.FullScreen) { @@ -355,9 +346,9 @@ namespace Ryujinx.Ava.Ui.Windows Dispatcher.UIThread.InvokeAsync(() => { - if (Content.Content != _mainViewContent) + if (MainContent.Content != _mainViewContent) { - Content.Content = _mainViewContent; + MainContent.Content = _mainViewContent; } ViewModel.ShowMenuAndStatusBar = true; diff --git a/Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs index ec9195302b..b478198416 100644 --- a/Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/MotionSettingsWindow.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Markup.Xaml; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Models; diff --git a/Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs index bc8b04e4d9..afb5a33ac0 100644 --- a/Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/RumbleSettingsWindow.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Markup.Xaml; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Models; diff --git a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs index 371d6b6435..cdac6ef680 100644 --- a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs @@ -1,19 +1,14 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.LogicalTree; -using Avalonia.Markup.Xaml; -using Avalonia.Threading; using FluentAvalonia.Core; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.HLE.FileSystem; using Ryujinx.Input; @@ -23,8 +18,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone; namespace Ryujinx.Ava.Ui.Windows @@ -44,7 +37,6 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); Load(); - AttachDebugDevTools(); FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray())); MultiBinding tzMultiBinding = new() { Converter = converter }; @@ -62,13 +54,6 @@ namespace Ryujinx.Ava.Ui.Windows InitializeComponent(); Load(); - AttachDebugDevTools(); - } - - [Conditional("DEBUG")] - private void AttachDebugDevTools() - { - this.AttachDevTools(); } private void Load() diff --git a/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs b/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs index f427adc3e9..5f537ed124 100644 --- a/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs +++ b/Ryujinx.Ava/Ui/Windows/StyleableWindow.cs @@ -2,7 +2,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Media.Imaging; using Avalonia.Platform; -using FluentAvalonia.UI.Controls; using System; using System.IO; using System.Reflection; diff --git a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs index 4e26ee7363..f1f7850e2d 100644 --- a/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/TitleUpdateWindow.axaml.cs @@ -1,13 +1,14 @@ using Avalonia; using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.Threading; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Models; @@ -23,14 +24,12 @@ using System.Linq; using System.Text; using Path = System.IO.Path; using SpanHelpers = LibHac.Common.SpanHelpers; -using LibHac.Tools.FsSystem; -using Avalonia.Threading; namespace Ryujinx.Ava.Ui.Windows { public partial class TitleUpdateWindow : StyleableWindow { - private readonly string _updateJsonPath; + private readonly string _titleUpdateJsonPath; private TitleUpdateMetadata _titleUpdateWindowData; public VirtualFileSystem VirtualFileSystem { get; } @@ -46,7 +45,6 @@ namespace Ryujinx.Ava.Ui.Windows DataContext = this; InitializeComponent(); - AttachDebugDevTools(); Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; } @@ -54,36 +52,33 @@ namespace Ryujinx.Ava.Ui.Windows public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) { VirtualFileSystem = virtualFileSystem; - TitleId = titleId; - TitleName = titleName; + TitleId = titleId; + TitleName = titleName; - _updateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); + _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); try { - _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath); + _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath); } catch { - _titleUpdateWindowData = new TitleUpdateMetadata {Selected = "", Paths = new List<string>()}; + _titleUpdateWindowData = new TitleUpdateMetadata + { + Selected = "", + Paths = new List<string>() + }; } DataContext = this; InitializeComponent(); - AttachDebugDevTools(); - + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; LoadUpdates(); } - [Conditional("DEBUG")] - private void AttachDebugDevTools() - { - this.AttachDevTools(); - } - private void LoadUpdates() { TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); @@ -99,8 +94,8 @@ namespace Ryujinx.Ava.Ui.Windows } else { - TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); - List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); + TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); + List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); foreach (TitleUpdateModel update in enabled) { @@ -126,8 +121,7 @@ namespace Ryujinx.Ava.Ui.Windows try { - (Nca patchNca, Nca controlNca) = - ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0); + (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0); if (controlNca != null && patchNca != null) { @@ -135,11 +129,8 @@ namespace Ryujinx.Ava.Ui.Windows 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(); + 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)); } @@ -190,9 +181,17 @@ namespace Ryujinx.Ava.Ui.Windows public async void Add() { - OpenFileDialog dialog = new OpenFileDialog() { Title = LocaleManager.Instance["SelectUpdateDialogTitle"], AllowMultiple = true }; + OpenFileDialog dialog = new OpenFileDialog() + { + Title = LocaleManager.Instance["SelectUpdateDialogTitle"], + AllowMultiple = true + }; - dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); + dialog.Filters.Add(new FileDialogFilter + { + Name = "NSP", + Extensions = { "nsp" } + }); string[] files = await dialog.ShowAsync(this); @@ -222,12 +221,10 @@ namespace Ryujinx.Ava.Ui.Windows return 1; } - 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.AddRange(list); } @@ -247,9 +244,9 @@ namespace Ryujinx.Ava.Ui.Windows } } - using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough)) + using (FileStream titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough)) { - dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); + titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); } if (Owner is MainWindow window) diff --git a/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs index ff94747754..51c7bff0f1 100644 --- a/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/UpdaterWindow.axaml.cs @@ -1,6 +1,5 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Ryujinx.Ava.Common.Locale; using Ryujinx.Modules; using System; @@ -23,9 +22,7 @@ namespace Ryujinx.Ava.Ui.Windows DataContext = this; InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif + Title = LocaleManager.Instance["RyujinxUpdater"]; } diff --git a/Ryujinx.Common/Configuration/DlcContainer.cs b/Ryujinx.Common/Configuration/DlcContainer.cs deleted file mode 100644 index c056ac1e81..0000000000 --- a/Ryujinx.Common/Configuration/DlcContainer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Common.Configuration -{ - public struct DlcContainer - { - public string Path { get; set; } - public List<DlcNca> DlcNcaList { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/DlcNca.cs b/Ryujinx.Common/Configuration/DlcNca.cs deleted file mode 100644 index df6a453252..0000000000 --- a/Ryujinx.Common/Configuration/DlcNca.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Common.Configuration -{ - public struct DlcNca - { - public string Path { get; set; } - public ulong TitleId { get; set; } - public bool Enabled { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/DownloadableContentContainer.cs b/Ryujinx.Common/Configuration/DownloadableContentContainer.cs new file mode 100644 index 0000000000..b6ae2f3fb9 --- /dev/null +++ b/Ryujinx.Common/Configuration/DownloadableContentContainer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + public struct DownloadableContentContainer + { + [JsonPropertyName("path")] + public string ContainerPath { get; set; } + [JsonPropertyName("dlc_nca_list")] + public List<DownloadableContentNca> DownloadableContentNcaList { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/DownloadableContentNca.cs b/Ryujinx.Common/Configuration/DownloadableContentNca.cs new file mode 100644 index 0000000000..80b67300e4 --- /dev/null +++ b/Ryujinx.Common/Configuration/DownloadableContentNca.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + public struct DownloadableContentNca + { + [JsonPropertyName("path")] + public string FullPath { get; set; } + [JsonPropertyName("title_id")] + public ulong TitleId { get; set; } + [JsonPropertyName("is_enabled")] + public bool Enabled { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index 893a0c97df..ea05d2bbed 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -422,19 +422,19 @@ namespace Ryujinx.HLE.HOS if (File.Exists(titleAocMetadataPath)) { - List<DlcContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(titleAocMetadataPath); + List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath); - foreach (DlcContainer dlcContainer in dlcContainerList) + foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) { - foreach (DlcNca dlcNca in dlcContainer.DlcNcaList) + foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) { - if (File.Exists(dlcContainer.Path)) + if (File.Exists(downloadableContentContainer.ContainerPath)) { - _device.Configuration.ContentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled); + _device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath, downloadableContentNca.Enabled); } else { - Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {dlcContainer.Path}. It may have been moved or renamed."); + Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); } } } diff --git a/Ryujinx/Ui/Windows/DlcWindow.cs b/Ryujinx/Ui/Windows/DlcWindow.cs index 76be60ef0d..1a47ae4148 100644 --- a/Ryujinx/Ui/Windows/DlcWindow.cs +++ b/Ryujinx/Ui/Windows/DlcWindow.cs @@ -21,10 +21,10 @@ namespace Ryujinx.Ui.Windows { public class DlcWindow : Window { - private readonly VirtualFileSystem _virtualFileSystem; - private readonly string _titleId; - private readonly string _dlcJsonPath; - private readonly List<DlcContainer> _dlcContainerList; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly string _titleId; + private readonly string _dlcJsonPath; + private readonly List<DownloadableContentContainer> _dlcContainerList; #pragma warning disable CS0649, IDE0044 [GUI] Label _baseTitleInfoLabel; @@ -45,11 +45,11 @@ namespace Ryujinx.Ui.Windows try { - _dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath); + _dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_dlcJsonPath); } catch { - _dlcContainerList = new List<DlcContainer>(); + _dlcContainerList = new List<DownloadableContentContainer>(); } _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string)); @@ -75,37 +75,37 @@ namespace Ryujinx.Ui.Windows _dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1); _dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); - foreach (DlcContainer dlcContainer in _dlcContainerList) + foreach (DownloadableContentContainer dlcContainer in _dlcContainerList) { - if (File.Exists(dlcContainer.Path)) + if (File.Exists(dlcContainer.ContainerPath)) { // The parent tree item has its own "enabled" check box, but it's the actual // nca entries that store the enabled / disabled state. A bit of a UI inconsistency. // Maybe a tri-state check box would be better, but for now we check the parent // "enabled" box if all child NCAs are enabled. Usually fine since each nsp has only one nca. - bool areAllContentPacksEnabled = dlcContainer.DlcNcaList.TrueForAll((nca) => nca.Enabled); - TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.Path); - using FileStream containerFile = File.OpenRead(dlcContainer.Path); + bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled); + TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath); + using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath); PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); _virtualFileSystem.ImportTickets(pfs); - foreach (DlcNca dlcNca in dlcContainer.DlcNcaList) + foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList) { using var ncaFile = new UniqueRef<IFile>(); - pfs.OpenFile(ref ncaFile.Ref(), dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.Path); + pfs.OpenFile(ref ncaFile.Ref(), dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath); if (nca != null) { - ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.Path); + ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.FullPath); } } } else { // DLC file moved or renamed. Allow the user to remove it without crashing the whole dialog. - TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.Path}"); + TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}"); } } } @@ -237,19 +237,19 @@ namespace Ryujinx.Ui.Windows { if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) { - DlcContainer dlcContainer = new DlcContainer + DownloadableContentContainer dlcContainer = new DownloadableContentContainer { - Path = (string)_dlcTreeView.Model.GetValue(parentIter, 2), - DlcNcaList = new List<DlcNca>() + ContainerPath = (string)_dlcTreeView.Model.GetValue(parentIter, 2), + DownloadableContentNcaList = new List<DownloadableContentNca>() }; do { - dlcContainer.DlcNcaList.Add(new DlcNca + dlcContainer.DownloadableContentNcaList.Add(new DownloadableContentNca { - Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0), - TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16), - Path = (string)_dlcTreeView.Model.GetValue(childIter, 2) + Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0), + TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16), + FullPath = (string)_dlcTreeView.Model.GetValue(childIter, 2) }); } while (_dlcTreeView.Model.IterNext(ref childIter));