diff options
author | TSRBerry <20988865+TSRBerry@users.noreply.github.com> | 2023-11-11 21:56:57 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-11 21:56:57 +0100 |
commit | 5c3cfb84c09b0566da677425915afa0b2d76da55 (patch) | |
tree | d53c683c3ed3e685bec5b16ca661755d8815f66e /src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs | |
parent | 55557525b16f8256d91f769e026874b5c70c3b2d (diff) |
Add support for multi game XCIs (#5638)1.1.1076
* 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>
Diffstat (limited to 'src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs')
-rw-r--r-- | src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs | 153 |
1 files changed, 79 insertions, 74 deletions
diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs index 50f7d585..5f45cd45 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -1,26 +1,87 @@ using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.Ncm; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; +using ContentType = LibHac.Ncm.ContentType; namespace Ryujinx.HLE.Loaders.Processes.Extensions { public static class PartitionFileSystemExtensions { private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage) + public static Dictionary<ulong, ContentCollection> GetApplicationData(this IFileSystem partitionFileSystem, + VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel) + { + fileSystem.ImportTickets(partitionFileSystem); + + var programs = new Dictionary<ulong, ContentCollection>(); + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca")) + { + Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, ContentMetaType.Application); + + if (cnmt == null) + { + continue; + } + + ContentCollection content = new(partitionFileSystem, cnmt); + + if (content.Type != ContentMetaType.Application) + { + continue; + } + + programs.TryAdd(content.ApplicationId, content); + } + + return programs; + } + + public static Dictionary<ulong, ContentCollection> GetUpdateData(this IFileSystem partitionFileSystem, + VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel) + { + fileSystem.ImportTickets(partitionFileSystem); + + var programs = new Dictionary<ulong, ContentCollection>(); + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca")) + { + Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, ContentMetaType.Patch); + + if (cnmt == null) + { + continue; + } + + ContentCollection content = new(partitionFileSystem, cnmt); + + if (content.Type != ContentMetaType.Patch) + { + continue; + } + + programs.TryAdd(content.ApplicationId, content); + } + + return programs; + } + + internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong titleId, out string errorMessage) where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new() where TFormat : IPartitionFileSystemFormat where THeader : unmanaged, IPartitionFileSystemHeader @@ -35,31 +96,22 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions try { - device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem); + Dictionary<ulong, ContentCollection> applications = partitionFileSystem.GetApplicationData(device.FileSystem, device.System.FsIntegrityCheckLevel); - // TODO: To support multi-games container, this should use CNMT NCA instead. - foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + if (titleId == 0) { - Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath); - - if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index) - { - continue; - } - - if (nca.IsPatch()) - { - patchNca = nca; - } - else if (nca.IsProgram()) + foreach ((ulong _, ContentCollection content) in applications) { - mainNca = nca; - } - else if (nca.IsControl()) - { - controlNca = nca; + mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); + controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); + break; } } + else if (applications.TryGetValue(titleId, out ContentCollection content)) + { + mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); + controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); + } ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure(); } @@ -79,54 +131,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions return (false, ProcessResult.Failed); } - // Load Update NCAs. - Nca updatePatchNca = null; - Nca updateControlNca = null; - - if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) - { - // Clear the program index part. - titleIdBase &= ~0xFUL; - - // Load update information if exists. - string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); - if (File.Exists(titleUpdateMetadataPath)) - { - string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected; - if (File.Exists(updatePath)) - { - PartitionFileSystem updatePartitionFileSystem = new(); - updatePartitionFileSystem.Initialize(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()).ThrowIfFailure(); - - device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem); - - // TODO: This should use CNMT NCA instead. - foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca")) - { - Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath); - - if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index) - { - continue; - } - - if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16")) - { - break; - } - - if (nca.IsProgram()) - { - updatePatchNca = nca; - } - else if (nca.IsControl()) - { - updateControlNca = nca; - } - } - } - } - } + (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _); if (updatePatchNca != null) { @@ -168,18 +173,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions return (true, mainNca.Load(device, patchNca, controlNca)); } - errorMessage = "Unable to load: Could not find Main NCA"; + errorMessage = $"Unable to load: Could not find Main NCA for title \"{titleId:X16}\""; return (false, ProcessResult.Failed); } - public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path) + public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path) { using var ncaFile = new UniqueRef<IFile>(); fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage()); + return new Nca(keySet, ncaFile.Release().AsStorage()); } } } |