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}" Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDlcManager}" Command="{Binding OpenDownloadableContentManager}"
Header="{locale:Locale GameListContextMenuManageDlc}" Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem <MenuItem

View file

@ -37,7 +37,7 @@
Header="{locale:Locale GameListContextMenuManageTitleUpdates}" Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDlcManager}" Command="{Binding OpenDownloadableContentManager}"
Header="{locale:Locale GameListContextMenuManageDlc}" Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem <MenuItem

View file

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

View file

@ -22,7 +22,9 @@ namespace Ryujinx.Ava.Ui.Models
set set
{ {
_isEnabled = value; _isEnabled = value;
EnableToggled?.Invoke(this, _isEnabled); EnableToggled?.Invoke(this, _isEnabled);
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -30,6 +32,7 @@ namespace Ryujinx.Ava.Ui.Models
public string BuildId { get; } public string BuildId { get; }
public string BuildIdKey => $"{BuildId}-{Name}"; public string BuildIdKey => $"{BuildId}-{Name}";
public string Name { get; } public string Name { get; }
public string CleanName => Name.Substring(1, Name.Length - 8); public string CleanName => Name.Substring(1, Name.Length - 8);

View file

@ -11,23 +11,10 @@ namespace Ryujinx.Ava.Ui.Models
{ {
BuildId = buildId; BuildId = buildId;
Path = path; Path = path;
CollectionChanged += CheatsList_CollectionChanged; 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 BuildId { get; }
public string Path { get; } public string Path { get; }
@ -47,5 +34,18 @@ namespace Ryujinx.Ava.Ui.Models
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled))); 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(); 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; return amiiboJsonString;

View file

@ -1261,15 +1261,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
public async void OpenDlcManager() public async void OpenDownloadableContentManager()
{ {
var selection = SelectedApplication; var selection = SelectedApplication;
if (selection != null) 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.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
@ -27,9 +26,6 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
_ = DownloadPatronsJson(); _ = DownloadPatronsJson();
} }

View file

@ -1,6 +1,5 @@
using Avalonia; using Avalonia;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
@ -18,9 +17,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"];
} }
@ -31,9 +28,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["Amiibo"];

View file

@ -1,21 +1,24 @@
<window:StyleableWindow x:Class="Ryujinx.Ava.Ui.Windows.CheatWindow" <window:StyleableWindow
x:Class="Ryujinx.Ava.Ui.Windows.CheatWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="clr-namespace:Ryujinx.Ava.Ui.Models" xmlns:model="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
mc:Ignorable="d" Width="500"
Width="500" MinHeight="500" Height="500" Height="500"
MinWidth="500"
MinHeight="500"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
MinWidth="500"> mc:Ignorable="d">
<Window.Styles> <Window.Styles>
<Style Selector="TreeViewItem"> <Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="True" /> <Setter Property="IsExpanded" Value="True" />
</Style> </Style>
</Window.Styles> </Window.Styles>
<Grid Name="DlcGrid" Margin="15"> <Grid Name="CheatGrid" Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -24,14 +27,14 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20" Margin="20,15,20,20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18" LineHeight="18"
TextWrapping="Wrap"
Text="{Binding Heading}" Text="{Binding Heading}"
TextAlignment="Center" /> TextAlignment="Center"
TextWrapping="Wrap" />
<Border <Border
Grid.Row="2" Grid.Row="2"
Margin="5" Margin="5"
@ -39,32 +42,38 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="Gray" BorderBrush="Gray"
BorderThickness="1"> BorderThickness="1">
<TreeView Items="{Binding LoadedCheats}" <TreeView
Name="CheatsView"
MinHeight="300"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Name="CheatsView" Items="{Binding LoadedCheats}">
MinHeight="300">
<TreeView.Styles> <TreeView.Styles>
<Styles> <Styles>
<Style Selector="TreeViewItem:empty /template/ ItemsPresenter"> <Style Selector="TreeViewItem:empty /template/ ItemsPresenter">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False" />
</Style> </Style>
</Styles> </Styles>
</TreeView.Styles> </TreeView.Styles>
<TreeView.DataTemplates> <TreeView.DataTemplates>
<TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}"> <TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsEnabled}" MinWidth="20" /> <CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" />
<TextBlock Width="150" <TextBlock Width="150" Text="{Binding BuildId}" />
Text="{Binding BuildId}" /> <TextBlock Text="{Binding Path}" />
<TextBlock
Text="{Binding Path}" />
</StackPanel> </StackPanel>
</TreeDataTemplate> </TreeDataTemplate>
<DataTemplate x:DataType="model:CheatModel"> <DataTemplate x:DataType="model:CheatModel">
<StackPanel Orientation="Horizontal" Margin="0" HorizontalAlignment="Left"> <StackPanel
<CheckBox IsChecked="{Binding IsEnabled}" Padding="0" Margin="5,0" MinWidth="20" /> Margin="0"
<TextBlock Text="{Binding CleanName}" VerticalAlignment="Center" /> HorizontalAlignment="Left"
Orientation="Horizontal">
<CheckBox
MinWidth="20"
Margin="5,0"
Padding="0"
IsChecked="{Binding IsEnabled}" />
<TextBlock VerticalAlignment="Center" Text="{Binding CleanName}" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</TreeView.DataTemplates> </TreeView.DataTemplates>
@ -79,8 +88,8 @@
Name="SaveButton" Name="SaveButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
IsVisible="{Binding !NoCheatsFound}" Command="{Binding Save}"
Command="{Binding Save}"> IsVisible="{Binding !NoCheatsFound}">
<TextBlock Text="{locale:Locale SettingsButtonSave}" /> <TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button> </Button>
<Button <Button

View file

@ -1,6 +1,5 @@
using Avalonia; using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
@ -26,7 +25,6 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"]; 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()); Heading = string.Format(LocaleManager.Instance["CheatWindowHeading"], titleName, titleId.ToUpper());
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath(); string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId); string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
@ -96,12 +91,6 @@ namespace Ryujinx.Ava.Ui.Windows
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["CheatWindowTitle"];
} }
[Conditional("DEBUG")]
private void AttachDebugDevTools()
{
this.AttachDevTools();
}
public void Save() public void Save()
{ {
if (NoCheatsFound) if (NoCheatsFound)

View file

@ -3,24 +3,14 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using Ryujinx.Ui.Common.Configuration;
using System; 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 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 <window:StyleableWindow
x:Class="Ryujinx.Ava.Ui.Windows.DlcManagerWindow" x:Class="Ryujinx.Ava.Ui.Windows.DownloadableContentManagerWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -11,7 +11,7 @@
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
MinWidth="600" MinWidth="600"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Name="DlcGrid" Margin="15"> <Grid Name="DownloadableContentGrid" Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -40,7 +40,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
Items="{Binding Dlcs}" Items="{Binding DownloadableContents}"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn Width="90"> <DataGridTemplateColumn Width="90">
@ -50,7 +50,7 @@
Width="50" Width="50"
MinWidth="40" MinWidth="40"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsChecked="{Binding IsEnabled}" /> IsChecked="{Binding Enabled}" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header> <DataGridTemplateColumn.Header>
@ -116,7 +116,7 @@
Name="SaveButton" Name="SaveButton"
MinWidth="90" MinWidth="90"
Margin="5" Margin="5"
Command="{Binding Save}"> Command="{Binding SaveAndClose}">
<TextBlock Text="{locale:Locale SettingsButtonSave}" /> <TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button> </Button>
<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> </DockPanel>
</StackPanel> </StackPanel>
<ContentControl <ContentControl
Name="Content" Name="MainContent"
Grid.Row="1" Grid.Row="1"
Padding="0" Padding="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View file

@ -2,10 +2,8 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Win32;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -33,7 +31,7 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
using ProgressBar = Avalonia.Controls.ProgressBar;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public partial class MainWindow : StyleableWindow public partial class MainWindow : StyleableWindow
@ -87,7 +85,6 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Load(); Load();
AttachDebugDevTools();
UiHandler = new AvaHostUiHandler(this); UiHandler = new AvaHostUiHandler(this);
@ -110,12 +107,6 @@ namespace Ryujinx.Ava.Ui.Windows
_rendererWaitEvent = new AutoResetEvent(false); _rendererWaitEvent = new AutoResetEvent(false);
} }
[Conditional("DEBUG")]
private void AttachDebugDevTools()
{
this.AttachDevTools();
}
public void LoadGameList() public void LoadGameList()
{ {
if (_isLoading) if (_isLoading)
@ -244,7 +235,7 @@ namespace Ryujinx.Ava.Ui.Windows
PrepareLoadScreen(); PrepareLoadScreen();
_mainViewContent = Content.Content as Control; _mainViewContent = MainContent.Content as Control;
GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel); GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this); AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
@ -311,7 +302,7 @@ namespace Ryujinx.Ava.Ui.Windows
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
Content.Content = GlRenderer; MainContent.Content = GlRenderer;
if (startFullscreen && WindowState != WindowState.FullScreen) if (startFullscreen && WindowState != WindowState.FullScreen)
{ {
@ -355,9 +346,9 @@ namespace Ryujinx.Ava.Ui.Windows
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
if (Content.Content != _mainViewContent) if (MainContent.Content != _mainViewContent)
{ {
Content.Content = _mainViewContent; MainContent.Content = _mainViewContent;
} }
ViewModel.ShowMenuAndStatusBar = true; ViewModel.ShowMenuAndStatusBar = true;

View file

@ -1,5 +1,4 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;

View file

@ -1,5 +1,4 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;

View file

@ -1,19 +1,14 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels; using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Input; using Ryujinx.Input;
@ -23,8 +18,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone; using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
@ -44,7 +37,6 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Load(); Load();
AttachDebugDevTools();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray())); FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()));
MultiBinding tzMultiBinding = new() { Converter = converter }; MultiBinding tzMultiBinding = new() { Converter = converter };
@ -62,13 +54,6 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Load(); Load();
AttachDebugDevTools();
}
[Conditional("DEBUG")]
private void AttachDebugDevTools()
{
this.AttachDevTools();
} }
private void Load() private void Load()

View file

@ -2,7 +2,6 @@
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using FluentAvalonia.UI.Controls;
using System; using System;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;

View file

@ -1,13 +1,14 @@
using Avalonia; using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Threading;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Ns; using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;
@ -23,14 +24,12 @@ using System.Linq;
using System.Text; using System.Text;
using Path = System.IO.Path; using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers; using SpanHelpers = LibHac.Common.SpanHelpers;
using LibHac.Tools.FsSystem;
using Avalonia.Threading;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
{ {
public partial class TitleUpdateWindow : StyleableWindow public partial class TitleUpdateWindow : StyleableWindow
{ {
private readonly string _updateJsonPath; private readonly string _titleUpdateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; } public VirtualFileSystem VirtualFileSystem { get; }
@ -46,7 +45,6 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
} }
@ -57,33 +55,30 @@ namespace Ryujinx.Ava.Ui.Windows
TitleId = titleId; TitleId = titleId;
TitleName = titleName; TitleName = titleName;
_updateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
try try
{ {
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath); _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
} }
catch catch
{ {
_titleUpdateWindowData = new TitleUpdateMetadata {Selected = "", Paths = new List<string>()}; _titleUpdateWindowData = new TitleUpdateMetadata
{
Selected = "",
Paths = new List<string>()
};
} }
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
LoadUpdates(); LoadUpdates();
} }
[Conditional("DEBUG")]
private void AttachDebugDevTools()
{
this.AttachDevTools();
}
private void LoadUpdates() private void LoadUpdates()
{ {
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
@ -126,8 +121,7 @@ namespace Ryujinx.Ava.Ui.Windows
try try
{ {
(Nca patchNca, Nca controlNca) = (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0);
ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0);
if (controlNca != null && patchNca != null) if (controlNca != null && patchNca != null)
{ {
@ -135,11 +129,8 @@ namespace Ryujinx.Ava.Ui.Windows
using var nacpFile = new UniqueRef<IFile>(); using var nacpFile = new UniqueRef<IFile>();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None) controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
.OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read) nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
.ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None)
.ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path)); TitleUpdates.Add(new TitleUpdateModel(controlData, path));
} }
@ -190,9 +181,17 @@ namespace Ryujinx.Ava.Ui.Windows
public async void Add() 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); string[] files = await dialog.ShowAsync(this);
@ -222,12 +221,10 @@ namespace Ryujinx.Ava.Ui.Windows
return 1; return 1;
} }
return Version.Parse(first.Control.DisplayVersionString.ToString()) return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
.CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
}); });
TitleUpdates.Clear(); TitleUpdates.Clear();
TitleUpdates.AddRange(list); 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) if (Owner is MainWindow window)

View file

@ -1,6 +1,5 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Modules; using Ryujinx.Modules;
using System; using System;
@ -23,9 +22,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = this; DataContext = this;
InitializeComponent(); InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Title = LocaleManager.Instance["RyujinxUpdater"]; 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)) 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 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

@ -24,7 +24,7 @@ namespace Ryujinx.Ui.Windows
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly string _titleId; private readonly string _titleId;
private readonly string _dlcJsonPath; private readonly string _dlcJsonPath;
private readonly List<DlcContainer> _dlcContainerList; private readonly List<DownloadableContentContainer> _dlcContainerList;
#pragma warning disable CS0649, IDE0044 #pragma warning disable CS0649, IDE0044
[GUI] Label _baseTitleInfoLabel; [GUI] Label _baseTitleInfoLabel;
@ -45,11 +45,11 @@ namespace Ryujinx.Ui.Windows
try try
{ {
_dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath); _dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_dlcJsonPath);
} }
catch catch
{ {
_dlcContainerList = new List<DlcContainer>(); _dlcContainerList = new List<DownloadableContentContainer>();
} }
_dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string)); _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("TitleId", new CellRendererText(), "text", 1);
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); _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 // 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. // 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 // 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. // "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); bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.Path); TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
using FileStream containerFile = File.OpenRead(dlcContainer.Path); using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
_virtualFileSystem.ImportTickets(pfs); _virtualFileSystem.ImportTickets(pfs);
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList) foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
{ {
using var ncaFile = new UniqueRef<IFile>(); using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref(), dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); pfs.OpenFile(ref ncaFile.Ref(), dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.Path); Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
if (nca != null) 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 else
{ {
// DLC file moved or renamed. Allow the user to remove it without crashing the whole dialog. // 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)) if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter))
{ {
DlcContainer dlcContainer = new DlcContainer DownloadableContentContainer dlcContainer = new DownloadableContentContainer
{ {
Path = (string)_dlcTreeView.Model.GetValue(parentIter, 2), ContainerPath = (string)_dlcTreeView.Model.GetValue(parentIter, 2),
DlcNcaList = new List<DlcNca>() DownloadableContentNcaList = new List<DownloadableContentNca>()
}; };
do do
{ {
dlcContainer.DlcNcaList.Add(new DlcNca dlcContainer.DownloadableContentNcaList.Add(new DownloadableContentNca
{ {
Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0), Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0),
TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16), TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16),
Path = (string)_dlcTreeView.Model.GetValue(childIter, 2) FullPath = (string)_dlcTreeView.Model.GetValue(childIter, 2)
}); });
} }
while (_dlcTreeView.Model.IterNext(ref childIter)); while (_dlcTreeView.Model.IterNext(ref childIter));