Make dlc/updates records; use ApplicationLibrary for loading logic

This commit is contained in:
Jimmy Reichley 2024-08-15 23:41:12 -04:00
parent 8073b8d189
commit 48b7517284
No known key found for this signature in database
GPG key ID: 67715DC5A329803C
9 changed files with 77 additions and 125 deletions

View file

@ -514,7 +514,7 @@ namespace Ryujinx.UI.App.Common
if (nca.Header.ContentType == NcaContentType.PublicData)
{
titleUpdates.Add(new DownloadableContentModel(nca.Header.TitleId, filePath, fileEntry.FullPath, false));
titleUpdates.Add(new DownloadableContentModel(nca.Header.TitleId, filePath, fileEntry.FullPath));
}
}
@ -1294,7 +1294,7 @@ namespace Ryujinx.UI.App.Common
{
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex) { }
catch (Exception) { }
return null;
}

View file

@ -1,24 +1,11 @@
namespace Ryujinx.UI.Common.Models
{
public class DownloadableContentModel
public record DownloadableContentModel(ulong TitleId, string ContainerPath, string FullPath)
{
public ulong TitleId { get; }
public string ContainerPath { get; }
public string FullPath { get; }
public bool Enabled { get; }
public bool IsBundled { get; }
public bool IsBundled { get; } = System.IO.Path.GetExtension(ContainerPath)?.ToLower() == ".xci";
public string FileName => System.IO.Path.GetFileName(ContainerPath);
public string TitleIdStr => TitleId.ToString("X16");
public ulong TitleIdBase => TitleId & ~0x1FFFUL;
public DownloadableContentModel(ulong titleId, string containerPath, string fullPath, bool enabled)
{
TitleId = titleId;
ContainerPath = containerPath;
FullPath = fullPath;
Enabled = enabled;
IsBundled = System.IO.Path.GetExtension(containerPath)?.ToLower() == ".xci";
}
}
}

View file

@ -1,23 +1,10 @@
namespace Ryujinx.UI.Common.Models
{
public class TitleUpdateModel
public record TitleUpdateModel(ulong TitleId, ulong Version, string DisplayVersion, string Path)
{
public ulong TitleId { get; }
public ulong Version { get; }
public string DisplayVersion { get; }
public string Path { get; }
public bool IsBundled { get; }
public bool IsBundled { get; } = System.IO.Path.GetExtension(Path)?.ToLower() == ".xci";
public string TitleIdStr => TitleId.ToString("X16");
public ulong TitleIdBase => TitleId & ~0x1FFFUL;
public TitleUpdateModel(ulong titleId, ulong version, string displayVersion, string path)
{
TitleId = titleId;
Version = version;
DisplayVersion = displayVersion;
Path = path;
IsBundled = System.IO.Path.GetExtension(path)?.ToLower() == ".xci";
}
}
}

View file

@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.ApplicationLibrary, viewModel.SelectedApplication);
}
}
@ -96,7 +96,7 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.ApplicationLibrary, viewModel.SelectedApplication);
}
}

View file

@ -35,6 +35,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly string _downloadableContentJsonPath;
private readonly VirtualFileSystem _virtualFileSystem;
private readonly ApplicationLibrary _applicationLibrary;
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
private AvaloniaList<DownloadableContentModel> _views = new();
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
@ -93,9 +94,10 @@ namespace Ryujinx.Ava.UI.ViewModels
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
}
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
_virtualFileSystem = virtualFileSystem;
_applicationLibrary = applicationLibrary;
_applicationData = applicationData;
@ -145,12 +147,11 @@ namespace Ryujinx.Ava.UI.ViewModels
{
var content = new DownloadableContentModel(nca.Header.TitleId,
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled);
downloadableContentNca.FullPath);
DownloadableContents.Add(content);
if (content.Enabled)
if (downloadableContentNca.Enabled)
{
SelectedDownloadableContents.Add(content);
}
@ -240,34 +241,23 @@ namespace Ryujinx.Ava.UI.ViewModels
return true;
}
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
if (!_applicationLibrary.TryGetDownloadableContentFromFile(path, out var dlcs))
{
return false;
}
bool success = false;
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
foreach (var dlc in dlcs)
{
using var ncaFile = new UniqueRef<IFile>();
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
if (dlc.TitleIdBase != _applicationData.IdBase)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if (nca.GetProgramIdBase() != _applicationData.IdBase)
{
continue;
}
DownloadableContents.Add(dlc);
Dispatcher.UIThread.InvokeAsync(() => SelectedDownloadableContents.Add(dlc));
var content = new DownloadableContentModel(nca.Header.TitleId, path, fileEntry.FullPath, true);
DownloadableContents.Add(content);
Dispatcher.UIThread.InvokeAsync(() => SelectedDownloadableContents.Add(content));
success = true;
}
success = true;
}
if (success)
@ -282,6 +272,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Remove(DownloadableContentModel model)
{
DownloadableContents.Remove(model);
SelectedDownloadableContents.Remove(model);
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
@ -289,13 +280,15 @@ namespace Ryujinx.Ava.UI.ViewModels
public void RemoveAll()
{
DownloadableContents.Clear();
SelectedDownloadableContents.Clear();
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
public void EnableAll()
{
SelectedDownloadableContents = new(DownloadableContents);
SelectedDownloadableContents.Clear();
SelectedDownloadableContents.AddRange(DownloadableContents);
}
public void DisableAll()
@ -303,6 +296,16 @@ namespace Ryujinx.Ava.UI.ViewModels
SelectedDownloadableContents.Clear();
}
public void Enable(DownloadableContentModel model)
{
SelectedDownloadableContents.ReplaceOrAdd(model, model);
}
public void Disable(DownloadableContentModel model)
{
SelectedDownloadableContents.Remove(model);
}
public void Save()
{
_downloadableContentContainerList.Clear();
@ -327,7 +330,7 @@ namespace Ryujinx.Ava.UI.ViewModels
container.DownloadableContentNcaList.Add(new DownloadableContentNca
{
Enabled = downloadableContent.Enabled,
Enabled = SelectedDownloadableContents.Contains(downloadableContent),
TitleId = downloadableContent.TitleId,
FullPath = downloadableContent.FullPath,
});

View file

@ -38,6 +38,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public TitleUpdateMetadata TitleUpdateWindowData;
public readonly string TitleUpdateJsonPath;
private VirtualFileSystem VirtualFileSystem { get; }
private ApplicationLibrary ApplicationLibrary { get; }
private ApplicationData ApplicationData { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
@ -78,9 +79,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public IStorageProvider StorageProvider;
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
VirtualFileSystem = virtualFileSystem;
ApplicationLibrary = applicationLibrary;
ApplicationData = applicationData;
@ -162,53 +164,35 @@ namespace Ryujinx.Ava.UI.ViewModels
return;
}
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
try
{
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, VirtualFileSystem);
Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, VirtualFileSystem, checkLevel);
Nca patchNca = null;
Nca controlNca = null;
if (updates.TryGetValue(ApplicationData.Id, out ContentMetaData content))
{
patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program);
controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control);
}
if (controlNca != null && patchNca != null)
{
ApplicationControlProperty controlData = new();
using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
var displayVersion = controlData.DisplayVersionString.ToString();
var update = new TitleUpdateModel(content.ApplicationId, content.Version.Version, displayVersion, path);
TitleUpdates.Add(update);
if (selected)
{
Dispatcher.UIThread.InvokeAsync(() => SelectedUpdate = update);
}
}
else
if (!ApplicationLibrary.TryGetTitleUpdatesFromFile(path, out var titleUpdates))
{
if (!ignoreNotFound)
{
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
}
return;
}
foreach (var titleUpdate in titleUpdates)
{
if (titleUpdate.TitleIdBase != ApplicationData.Id)
{
continue;
}
TitleUpdates.Add(titleUpdate);
if (selected)
{
Dispatcher.UIThread.InvokeAsync(() => SelectedUpdate = titleUpdate);
}
}
}
catch (Exception ex)
} catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
}
@ -254,7 +238,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
TitleUpdateWindowData.Paths.Add(update.Path);
if (update == SelectedUpdate)
if (update == SelectedUpdate as TitleUpdateModel)
{
TitleUpdateWindowData.Selected = update.Path;
}

View file

@ -77,8 +77,9 @@
SelectionMode="Multiple, Toggle"
Background="Transparent"
SelectionChanged="OnSelectionChanged"
SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}"
SelectedItems="{Binding SelectedDownloadableContents}"
ItemsSource="{Binding Views}">
<!-- SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}" -->
<ListBox.DataTemplates>
<DataTemplate
DataType="models:DownloadableContentModel">

View file

@ -24,21 +24,21 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
}
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationData);
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationLibrary, applicationData);
InitializeComponent();
}
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData),
Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationLibrary, applicationData),
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdBaseString),
};
@ -89,12 +89,7 @@ namespace Ryujinx.Ava.UI.Windows
{
if (content is DownloadableContentModel model)
{
var index = ViewModel.DownloadableContents.IndexOf(model);
if (index != -1)
{
// ViewModel.DownloadableContents[index].Enabled = true;
}
ViewModel.Enable(model);
}
}
@ -102,12 +97,7 @@ namespace Ryujinx.Ava.UI.Windows
{
if (content is DownloadableContentModel model)
{
var index = ViewModel.DownloadableContents.IndexOf(model);
if (index != -1)
{
// ViewModel.DownloadableContents[index].Enabled = false;
}
ViewModel.Disable(model);
}
}
}

View file

@ -26,21 +26,21 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
}
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationData);
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationLibrary, applicationData);
InitializeComponent();
}
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationLibrary applicationLibrary, ApplicationData applicationData)
{
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
Content = new TitleUpdateWindow(virtualFileSystem, applicationData),
Content = new TitleUpdateWindow(virtualFileSystem, applicationLibrary, applicationData),
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdBaseString),
};