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
This commit is contained in:
Ac_K 2022-07-29 00:41:34 +02:00 committed by GitHub
parent 8cfec5de4b
commit 46c8129bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 456 additions and 490 deletions

View File

@ -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

View File

@ -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

View File

@ -17,9 +17,6 @@ namespace Ryujinx.Ava.Ui.Controls
public UpdateWaitWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
}
}

View File

@ -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);

View File

@ -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)));
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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"];

View File

@ -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

View File

@ -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,7 +25,6 @@ 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)

View File

@ -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
{

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -257,7 +257,7 @@
</DockPanel>
</StackPanel>
<ContentControl
Name="Content"
Name="MainContent"
Grid.Row="1"
Padding="0"
HorizontalAlignment="Stretch"

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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()

View File

@ -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;

View File

@ -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)

View File

@ -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"];
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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.");
}
}
}

View File

@ -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));