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:
TSRBerry
2023-11-11 21:56:57 +01:00
committed by GitHub
parent 55557525b1
commit 5c3cfb84c0
33 changed files with 1168 additions and 816 deletions

View File

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