Revert "Add support for multi game XCIs (#5638)" (#5914)

This reverts commit 5c3cfb84c0.
This commit is contained in:
gdkchan 2023-11-11 23:35:30 -03:00 committed by GitHub
parent 6228331fd1
commit 51065d9129
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 819 additions and 1171 deletions

View file

@ -1,61 +0,0 @@
using LibHac.Common.Keys;
using LibHac.Fs.Fsa;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.Ncm;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using System;
namespace Ryujinx.HLE.FileSystem
{
/// <summary>
/// Thin wrapper around <see cref="Cnmt"/>
/// </summary>
public class ContentCollection
{
private readonly IFileSystem _pfs;
private readonly Cnmt _cnmt;
public ulong Id => _cnmt.TitleId;
public TitleVersion Version => _cnmt.TitleVersion;
public ContentMetaType Type => _cnmt.Type;
public ulong ApplicationId => _cnmt.ApplicationTitleId;
public ulong PatchId => _cnmt.PatchTitleId;
public TitleVersion RequiredSystemVersion => _cnmt.MinimumSystemVersion;
public TitleVersion RequiredApplicationVersion => _cnmt.MinimumApplicationVersion;
public byte[] Digest => _cnmt.Hash;
public ulong ProgramBaseId => Id & ~0x1FFFUL;
public bool IsSystemTitle => _cnmt.Type < ContentMetaType.Application;
public ContentCollection(IFileSystem pfs, Cnmt cnmt)
{
_pfs = pfs;
_cnmt = cnmt;
}
public Nca GetNcaByType(KeySet keySet, ContentType type, int programIndex = 0)
{
// TODO: Replace this with a check for IdOffset as soon as LibHac supports it:
// && entry.IdOffset == programIndex
foreach (var entry in _cnmt.ContentEntries)
{
if (entry.Type != type)
{
continue;
}
string ncaId = BitConverter.ToString(entry.NcaId).Replace("-", null).ToLower();
Nca nca = _pfs.GetNca(keySet, $"/{ncaId}.nca");
if (nca.GetProgramIndex() == programIndex)
{
return nca;
}
}
return null;
}
}
}

View file

@ -2,31 +2,21 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Loader;
using LibHac.Ncm;
using LibHac.Ns;
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 Ryujinx.HLE.HOS;
using System.IO;
using System.Linq;
using ApplicationId = LibHac.Ncm.ApplicationId;
using ContentType = LibHac.Ncm.ContentType;
using Path = System.IO.Path;
namespace Ryujinx.HLE.Loaders.Processes.Extensions
{
public static class NcaExtensions
static class NcaExtensions
{
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
{
// Extract RomFs and ExeFs from NCA.
@ -57,7 +47,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
nacpData = controlNca.GetNacp(device);
}
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update.
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
// Load program 0 control NCA as we are going to need it for display version.
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
@ -96,11 +86,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return processResult;
}
public static ulong GetProgramIdBase(this Nca nca)
{
return nca.Header.TitleId & ~0x1FFFUL;
}
public static int GetProgramIndex(this Nca nca)
{
return (int)(nca.Header.TitleId & 0xF);
@ -111,11 +96,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nca.Header.ContentType == NcaContentType.Program;
}
public static bool IsMain(this Nca nca)
{
return nca.IsProgram() && !nca.IsPatch();
}
public static bool IsPatch(this Nca nca)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
@ -128,56 +108,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nca.Header.ContentType == NcaContentType.Control;
}
public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath)
{
updatePath = "(unknown)";
// Load Update NCAs.
Nca updatePatchNca = null;
Nca updateControlNca = null;
// Clear the program index part.
ulong titleIdBase = mainNca.GetProgramIdBase();
// Load update information if exists.
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json");
if (File.Exists(titleUpdateMetadataPath))
{
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
if (File.Exists(updatePath))
{
var updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
IFileSystem updatePartitionFileSystem;
if (Path.GetExtension(updatePath).ToLower() == ".xci")
{
updatePartitionFileSystem = new Xci(fileSystem.KeySet, updateFile.AsStorage()).OpenPartition(XciPartitionType.Secure);
}
else
{
PartitionFileSystem pfsTemp = new();
pfsTemp.Initialize(updateFile.AsStorage()).ThrowIfFailure();
updatePartitionFileSystem = pfsTemp;
}
foreach ((ulong updateTitleId, ContentCollection content) in updatePartitionFileSystem.GetUpdateData(fileSystem, checkLevel))
{
if ((updateTitleId & ~0x1FFFUL) != titleIdBase)
{
continue;
}
updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex);
updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex);
break;
}
}
}
return (updatePatchNca, updateControlNca);
}
public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
{
IFileSystem exeFs = null;
@ -242,31 +172,5 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nacpData;
}
public static Cnmt GetCnmt(this Nca cnmtNca, IntegrityCheckLevel checkLevel, ContentMetaType metaType)
{
string path = $"/{metaType}_{cnmtNca.Header.TitleId:x16}.cnmt";
using var cnmtFile = new UniqueRef<IFile>();
try
{
Result result = cnmtNca.OpenFileSystem(0, checkLevel)
.OpenFile(ref cnmtFile.Ref, path.ToU8Span(), OpenMode.Read);
if (result.IsSuccess())
{
return new Cnmt(cnmtFile.Release().AsStream());
}
}
catch (HorizonResultException ex)
{
if (!ResultFs.PathNotFound.Includes(ex.ResultValue))
{
Logger.Warning?.Print(LogClass.Application, $"Failed get cnmt for '{cnmtNca.Header.TitleId:x16}' from nca: {ex.Message}");
}
}
return null;
}
}
}

View file

@ -1,87 +1,26 @@
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());
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)
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
where TFormat : IPartitionFileSystemFormat
where THeader : unmanaged, IPartitionFileSystemHeader
@ -96,21 +35,30 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
try
{
Dictionary<ulong, ContentCollection> applications = partitionFileSystem.GetApplicationData(device.FileSystem, device.System.FsIntegrityCheckLevel);
device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
if (titleId == 0)
// TODO: To support multi-games container, this should use CNMT NCA instead.
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
foreach ((ulong _, ContentCollection content) in applications)
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
{
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;
continue;
}
if (nca.IsPatch())
{
patchNca = nca;
}
else if (nca.IsProgram())
{
mainNca = nca;
}
else if (nca.IsControl())
{
controlNca = nca;
}
}
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();
@ -131,7 +79,54 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (false, ProcessResult.Failed);
}
(Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
// 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;
}
}
}
}
}
if (updatePatchNca != null)
{
@ -173,18 +168,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (true, mainNca.Load(device, patchNca, controlNca));
}
errorMessage = $"Unable to load: Could not find Main NCA for title \"{titleId:X16}\"";
errorMessage = "Unable to load: Could not find Main NCA";
return (false, ProcessResult.Failed);
}
public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path)
public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
{
using var ncaFile = new UniqueRef<IFile>();
fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
return new Nca(keySet, ncaFile.Release().AsStorage());
return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
}
}
}

View file

@ -32,7 +32,7 @@ namespace Ryujinx.HLE.Loaders.Processes
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
}
public bool LoadXci(string path, ulong titleId)
public bool LoadXci(string path)
{
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
@ -44,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, titleId, out string errorMessage);
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage);
if (!success)
{
@ -66,13 +66,13 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
public bool LoadNsp(string path, ulong titleId)
public bool LoadNsp(string path)
{
FileStream file = new(path, FileMode.Open, FileAccess.Read);
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, titleId, out string errorMessage);
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
if (processResult.ProcessId == 0)
{

View file

@ -42,14 +42,15 @@ namespace Ryujinx.HLE.Loaders.Processes
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
Nca nca = partitionFileSystem.GetNca(device.FileSystem.KeySet, fileEntry.FullPath);
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
if (!nca.IsProgram())
if (!nca.IsProgram() && nca.IsPatch())
{
continue;
}
ulong currentMainProgramId = nca.GetProgramIdBase();
ulong currentProgramId = nca.Header.TitleId;
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
if (applicationId == 0 && currentMainProgramId != 0)
{
@ -66,7 +67,7 @@ namespace Ryujinx.HLE.Loaders.Processes
break;
}
hasIndex[nca.GetProgramIndex()] = true;
hasIndex[(int)(currentProgramId & 0xF)] = true;
}
if (programCount == 0)

View file

@ -72,9 +72,9 @@ namespace Ryujinx.HLE
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
}
public bool LoadXci(string xciFile, ulong titleId = 0)
public bool LoadXci(string xciFile)
{
return Processes.LoadXci(xciFile, titleId);
return Processes.LoadXci(xciFile);
}
public bool LoadNca(string ncaFile)
@ -82,9 +82,9 @@ namespace Ryujinx.HLE
return Processes.LoadNca(ncaFile);
}
public bool LoadNsp(string nspFile, ulong titleId = 0)
public bool LoadNsp(string nspFile)
{
return Processes.LoadNsp(nspFile, titleId);
return Processes.LoadNsp(nspFile);
}
public bool LoadProgram(string fileName)