Add support for multi game XCIs (#5638)
* Add default values to ApplicationData directly * Refactor application loading It should now be possible to load multi game XCIs. Included updates won't be detected for now. Opening a game from the command line currently only opens the first one. * Only include program NCAs where at least one tuple item is not null * Get application data by title id and add programIndex check back * Refactor application loading again and remove duplicate code * Actually use patch ncas for updates * Fix number of applications found with multi game xcis * Don't load bundled updates from multi game xcis * Change ApplicationData.TitleId type to ulong & Add TitleIdString property * Use cnmt files and ContentCollection to load programs * Ava: Add updates and DLCs from gamecarts * Get the cnmt file from its NCA * Ava: Identify bundled updates in updater window * Fix the (hopefully) last few bugs * Add idOffset parameter to GetNcaByType * Handle missing file for dlc.json * Ava: Shorten error message for invalid files * Gtk: Add additional string for bundled updates in TitleUpdateWindow * Hopefully fix DLC issues * Apply formatting * Finally fix DLC issues * Adjust property names and fileSize field * Read the correct update file * Fix wrong casing for application id strings * Rename TitleId to ApplicationId * Address review comments * Fix formatting issues * Apply suggestions from code review Co-authored-by: gdkchan <gab.dark.100@gmail.com> * Gracefully fail when loading pfs for update and dlc window * Fix applications with multiple programs * Fix DLCWindow crash on GTK * Fix some GUI issues * Remove IsXci again --------- Co-authored-by: gdkchan <gab.dark.100@gmail.com>
This commit is contained in:
@@ -17,11 +17,12 @@ using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Application = Avalonia.Application;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
@@ -38,7 +39,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||
|
||||
private string _search;
|
||||
private readonly ulong _titleId;
|
||||
private readonly ApplicationData _applicationData;
|
||||
|
||||
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
@@ -92,18 +93,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public IStorageProvider StorageProvider;
|
||||
|
||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_applicationData = applicationData;
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
StorageProvider = desktop.MainWindow.StorageProvider;
|
||||
}
|
||||
|
||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "dlc.json");
|
||||
|
||||
if (!File.Exists(_downloadableContentJsonPath))
|
||||
{
|
||||
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -120,6 +128,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private void LoadDownloadableContents()
|
||||
{
|
||||
// NOTE: Try to load downloadable contents from PFS first.
|
||||
AddDownloadableContent(_applicationData.Path);
|
||||
|
||||
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
||||
{
|
||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||
@@ -127,7 +138,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
if (partitionFileSystem.Initialize(containerFile.AsStorage()).IsFailure())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
||||
@@ -220,22 +235,34 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
foreach (var file in result)
|
||||
{
|
||||
await AddDownloadableContent(file.Path.LocalPath);
|
||||
if (!AddDownloadableContent(file.Path.LocalPath))
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddDownloadableContent(string path)
|
||||
private bool AddDownloadableContent(string path)
|
||||
{
|
||||
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
using FileStream containerFile = File.OpenRead(path);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
bool containsDownloadableContent = false;
|
||||
IFileSystem partitionFileSystem;
|
||||
|
||||
if (Path.GetExtension(path).ToLower() == ".xci")
|
||||
{
|
||||
partitionFileSystem = new Xci(_virtualFileSystem.KeySet, containerFile.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
partitionFileSystem = pfsTemp;
|
||||
}
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
||||
@@ -253,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
||||
if (nca.GetProgramIdBase() != _applicationData.IdBase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -265,14 +292,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
OnPropertyChanged(nameof(UpdateCount));
|
||||
Sort();
|
||||
|
||||
containsDownloadableContent = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDownloadableContent)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Remove(DownloadableContentModel model)
|
||||
|
Reference in New Issue
Block a user