Revert stream loader changes

"add stream based loaders" - Commit c16559f2

"Fix some DLCs crashing due to stream loader change" - Commit a62deb8c
This commit is contained in:
KeatonTheBot 2025-09-11 16:15:45 -05:00
parent fe7a30c747
commit a1834900be
6 changed files with 284 additions and 487 deletions

View file

@ -43,15 +43,11 @@ namespace Ryujinx.HLE.FileSystem
private readonly struct AocItem private readonly struct AocItem
{ {
public readonly string ContainerPath; public readonly string ContainerPath;
public readonly Stream ContainerStream;
public readonly string NcaPath; public readonly string NcaPath;
public readonly string Extension;
public AocItem(string containerPath, Stream containerStream, string ncaPath, string extension) public AocItem(string containerPath, string ncaPath)
{ {
ContainerPath = containerPath; ContainerPath = containerPath;
ContainerStream = containerStream;
Extension = extension;
NcaPath = ncaPath; NcaPath = ncaPath;
} }
} }
@ -191,10 +187,10 @@ namespace Ryujinx.HLE.FileSystem
} }
} }
public void AddAocItem(ulong titleId, string containerPath, Stream containerStream, string ncaPath, string extension, bool mergedToContainer = false) public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false)
{ {
// TODO: Check Aoc version. // TODO: Check Aoc version.
if (!AocData.TryAdd(titleId, new AocItem(containerPath, containerStream, ncaPath, extension))) if (!AocData.TryAdd(titleId, new AocItem(containerPath, ncaPath)))
{ {
Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}"); Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}");
} }
@ -204,20 +200,12 @@ namespace Ryujinx.HLE.FileSystem
if (!mergedToContainer) if (!mergedToContainer)
{ {
using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerStream, extension == ".xci", _virtualFileSystem); using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem);
} }
} }
} }
public void ClearAocData() public void ClearAocData() => AocData.Clear();
{
foreach (var aoc in AocData)
{
aoc.Value.ContainerStream?.Dispose();
}
AocData.Clear();
}
public int GetAocCount() => AocData.Count; public int GetAocCount() => AocData.Count;
@ -232,11 +220,11 @@ namespace Ryujinx.HLE.FileSystem
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read); var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
using var ncaFile = new UniqueRef<IFile>(); using var ncaFile = new UniqueRef<IFile>();
switch (aoc.Extension) switch (Path.GetExtension(aoc.ContainerPath))
{ {
case ".xci": case ".xci":
var xci = new Xci(_virtualFileSystem.KeySet, aoc.ContainerStream.AsStorage()).OpenPartition(XciPartitionType.Secure); var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read); xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
break; break;
case ".nsp": case ".nsp":
var pfs = new PartitionFileSystem(); var pfs = new PartitionFileSystem();
@ -488,27 +476,6 @@ namespace Ryujinx.HLE.FileSystem
FinishInstallation(temporaryDirectory, registeredDirectory); FinishInstallation(temporaryDirectory, registeredDirectory);
} }
public void InstallFirmware(Stream stream, bool isXci)
{
ContentPath.TryGetContentPath(StorageId.BuiltInSystem, out var contentPathString);
ContentPath.TryGetRealPath(contentPathString, out var contentDirectory);
string registeredDirectory = Path.Combine(contentDirectory, "registered");
string temporaryDirectory = Path.Combine(contentDirectory, "temp");
if (!isXci)
{
using ZipArchive archive = new ZipArchive(stream);
InstallFromZip(archive, temporaryDirectory);
}
else
{
Xci xci = new(_virtualFileSystem.KeySet, stream.AsStorage());
InstallFromCart(xci, temporaryDirectory);
}
FinishInstallation(temporaryDirectory, registeredDirectory);
}
public void InstallKeys(string keysSource, string installDirectory) public void InstallKeys(string keysSource, string installDirectory)
{ {
if (Directory.Exists(keysSource)) if (Directory.Exists(keysSource))
@ -695,16 +662,13 @@ namespace Ryujinx.HLE.FileSystem
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
} }
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
if (Directory.Exists(firmwarePackage)) if (Directory.Exists(firmwarePackage))
{ {
return VerifyAndGetVersionDirectory(firmwarePackage); return VerifyAndGetVersionDirectory(firmwarePackage);
} }
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
{
return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
}
if (!File.Exists(firmwarePackage)) if (!File.Exists(firmwarePackage))
{ {
throw new FileNotFoundException("Firmware file does not exist."); throw new FileNotFoundException("Firmware file does not exist.");
@ -712,144 +676,274 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(firmwarePackage); FileInfo info = new(firmwarePackage);
if (info.Extension == ".zip" || info.Extension == ".xci") using FileStream file = File.OpenRead(firmwarePackage);
switch (info.Extension)
{ {
using FileStream file = File.OpenRead(firmwarePackage); case ".zip":
using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
var isXci = info.Extension == ".xci";
return VerifyFirmwarePackage(file, isXci);
}
return null;
}
public SystemVersion VerifyFirmwarePackage(Stream file, bool isXci)
{
if (!isXci)
{
using ZipArchive archive = new ZipArchive(file, ZipArchiveMode.Read);
return VerifyAndGetVersionZip(archive);
}
else
{
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
if (xci.HasPartition(XciPartitionType.Update))
{
XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
return VerifyAndGetVersion(partition);
}
else
{
throw new InvalidFirmwarePackageException("Update not found in xci file.");
}
}
}
private SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
SystemVersion systemVersion = null;
foreach (var entry in archive.Entries)
{
if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00"))
{
using Stream ncaStream = GetZipStream(entry);
IStorage storage = ncaStream.AsStorage();
Nca nca = new(_virtualFileSystem.KeySet, storage);
if (updateNcas.TryGetValue(nca.Header.TitleId, out var updateNcasItem))
{ {
updateNcasItem.Add((nca.Header.ContentType, entry.FullName)); return VerifyAndGetVersionZip(archive);
} }
else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>())) case ".xci":
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
if (xci.HasPartition(XciPartitionType.Update))
{ {
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
return VerifyAndGetVersion(partition);
} }
} else
{
throw new InvalidFirmwarePackageException("Update not found in xci file.");
}
default:
break;
} }
if (updateNcas.TryGetValue(SystemUpdateTitleId, out var ncaEntry)) SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
{ {
string metaPath = ncaEntry.FirstOrDefault(x => x.type == NcaContentType.Meta).path; return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
}
CnmtContentMetaEntry[] metaEntries = null; SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
{
SystemVersion systemVersion = null;
var fileEntry = archive.GetEntry(metaPath); foreach (var entry in archive.Entries)
using (Stream ncaStream = GetZipStream(fileEntry))
{ {
Nca metaNca = new(_virtualFileSystem.KeySet, ncaStream.AsStorage()); if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00"))
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
using var metaFile = new UniqueRef<IFile>();
if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
{ {
var meta = new Cnmt(metaFile.Get.AsStream()); using Stream ncaStream = GetZipStream(entry);
IStorage storage = ncaStream.AsStorage();
if (meta.Type == ContentMetaType.SystemUpdate) Nca nca = new(_virtualFileSystem.KeySet, storage);
if (updateNcas.TryGetValue(nca.Header.TitleId, out var updateNcasItem))
{ {
metaEntries = meta.MetaEntries; updateNcasItem.Add((nca.Header.ContentType, entry.FullName));
}
updateNcas.Remove(SystemUpdateTitleId); else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName));
} }
} }
} }
if (metaEntries == null) if (updateNcas.TryGetValue(SystemUpdateTitleId, out var ncaEntry))
{ {
throw new FileNotFoundException("System update title was not found in the firmware package."); string metaPath = ncaEntry.FirstOrDefault(x => x.type == NcaContentType.Meta).path;
}
if (updateNcas.TryGetValue(SystemVersionTitleId, out var updateNcasItem)) CnmtContentMetaEntry[] metaEntries = null;
{
string versionEntry = updateNcasItem.FirstOrDefault(x => x.type != NcaContentType.Meta).path;
using Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry)); var fileEntry = archive.GetEntry(metaPath);
Nca nca = new(_virtualFileSystem.KeySet, ncaStream.AsStorage());
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); using (Stream ncaStream = GetZipStream(fileEntry))
using var systemVersionFile = new UniqueRef<IFile>();
if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
{ {
systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); Nca metaNca = new(_virtualFileSystem.KeySet, ncaStream.AsStorage());
}
} IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
foreach (CnmtContentMetaEntry metaEntry in metaEntries) string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
{
if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) using var metaFile = new UniqueRef<IFile>();
{
metaPath = ncaEntry.FirstOrDefault(x => x.type == NcaContentType.Meta).path; if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
{
string contentPath = ncaEntry.FirstOrDefault(x => x.type != NcaContentType.Meta).path; var meta = new Cnmt(metaFile.Get.AsStream());
// Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. if (meta.Type == ContentMetaType.SystemUpdate)
// This is a perfect valid case, so we should just ignore the missing content nca and continue. {
if (contentPath == null) metaEntries = meta.MetaEntries;
{
updateNcas.Remove(metaEntry.TitleId); updateNcas.Remove(SystemUpdateTitleId);
}
continue; }
} }
ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath); if (metaEntries == null)
ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath); {
throw new FileNotFoundException("System update title was not found in the firmware package.");
using Stream metaNcaStream = GetZipStream(metaZipEntry); }
using Stream contentNcaStream = GetZipStream(contentZipEntry);
Nca metaNca = new(_virtualFileSystem.KeySet, metaNcaStream.AsStorage()); if (updateNcas.TryGetValue(SystemVersionTitleId, out var updateNcasItem))
{
string versionEntry = updateNcasItem.FirstOrDefault(x => x.type != NcaContentType.Meta).path;
using Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry));
Nca nca = new(_virtualFileSystem.KeySet, ncaStream.AsStorage());
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
using var systemVersionFile = new UniqueRef<IFile>();
if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
{
systemVersion = new SystemVersion(systemVersionFile.Get.AsStream());
}
}
foreach (CnmtContentMetaEntry metaEntry in metaEntries)
{
if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry))
{
metaPath = ncaEntry.FirstOrDefault(x => x.type == NcaContentType.Meta).path;
string contentPath = ncaEntry.FirstOrDefault(x => x.type != NcaContentType.Meta).path;
// Nintendo in 9.0.0, removed PPC and only kept the meta nca of it.
// This is a perfect valid case, so we should just ignore the missing content nca and continue.
if (contentPath == null)
{
updateNcas.Remove(metaEntry.TitleId);
continue;
}
ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath);
ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath);
using Stream metaNcaStream = GetZipStream(metaZipEntry);
using Stream contentNcaStream = GetZipStream(contentZipEntry);
Nca metaNca = new(_virtualFileSystem.KeySet, metaNcaStream.AsStorage());
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
using var metaFile = new UniqueRef<IFile>();
if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
{
var meta = new Cnmt(metaFile.Get.AsStream());
IStorage contentStorage = contentNcaStream.AsStorage();
if (contentStorage.GetSize(out long size).IsSuccess())
{
byte[] contentData = new byte[size];
Span<byte> content = new(contentData);
contentStorage.Read(0, content);
Span<byte> hash = new(new byte[32]);
LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash);
if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash))
{
updateNcas.Remove(metaEntry.TitleId);
}
}
}
}
}
if (updateNcas.Count > 0)
{
StringBuilder extraNcas = new();
foreach (var entry in updateNcas)
{
foreach (var (type, path) in entry.Value)
{
extraNcas.AppendLine(path);
}
}
throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}");
}
}
else
{
throw new FileNotFoundException("System update title was not found in the firmware package.");
}
return systemVersion;
}
SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
{
SystemVersion systemVersion = null;
CnmtContentMetaEntry[] metaEntries = null;
foreach (var entry in filesystem.EnumerateEntries("/", "*.nca"))
{
IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage();
Nca nca = new(_virtualFileSystem.KeySet, ncaStorage);
if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta)
{
IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
using var metaFile = new UniqueRef<IFile>();
if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
{
var meta = new Cnmt(metaFile.Get.AsStream());
if (meta.Type == ContentMetaType.SystemUpdate)
{
metaEntries = meta.MetaEntries;
}
}
continue;
}
else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
{
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
using var systemVersionFile = new UniqueRef<IFile>();
if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
{
systemVersion = new SystemVersion(systemVersionFile.Get.AsStream());
}
}
if (updateNcas.TryGetValue(nca.Header.TitleId, out var updateNcasItem))
{
updateNcasItem.Add((nca.Header.ContentType, entry.FullPath));
}
else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath));
}
ncaStorage.Dispose();
}
if (metaEntries == null)
{
throw new FileNotFoundException("System update title was not found in the firmware package.");
}
foreach (CnmtContentMetaEntry metaEntry in metaEntries)
{
if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry))
{
string metaNcaPath = ncaEntry.FirstOrDefault(x => x.type == NcaContentType.Meta).path;
string contentPath = ncaEntry.FirstOrDefault(x => x.type != NcaContentType.Meta).path;
// Nintendo in 9.0.0, removed PPC and only kept the meta nca of it.
// This is a perfect valid case, so we should just ignore the missing content nca and continue.
if (contentPath == null)
{
updateNcas.Remove(metaEntry.TitleId);
continue;
}
IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaPath, OpenMode.Read).AsStorage();
IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage();
Nca metaNca = new(_virtualFileSystem.KeySet, metaStorage);
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
@ -861,7 +955,6 @@ namespace Ryujinx.HLE.FileSystem
{ {
var meta = new Cnmt(metaFile.Get.AsStream()); var meta = new Cnmt(metaFile.Get.AsStream());
IStorage contentStorage = contentNcaStream.AsStorage();
if (contentStorage.GetSize(out long size).IsSuccess()) if (contentStorage.GetSize(out long size).IsSuccess())
{ {
byte[] contentData = new byte[size]; byte[] contentData = new byte[size];
@ -897,146 +990,11 @@ namespace Ryujinx.HLE.FileSystem
throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}");
} }
}
else return systemVersion;
{
throw new FileNotFoundException("System update title was not found in the firmware package.");
} }
return systemVersion; return null;
}
private SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
{
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
SystemVersion systemVersion = null;
CnmtContentMetaEntry[] metaEntries = null;
foreach (var entry in filesystem.EnumerateEntries("/", "*.nca"))
{
IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage();
Nca nca = new(_virtualFileSystem.KeySet, ncaStorage);
if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta)
{
IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
using var metaFile = new UniqueRef<IFile>();
if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
{
var meta = new Cnmt(metaFile.Get.AsStream());
if (meta.Type == ContentMetaType.SystemUpdate)
{
metaEntries = meta.MetaEntries;
}
}
continue;
}
else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
{
var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
using var systemVersionFile = new UniqueRef<IFile>();
if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
{
systemVersion = new SystemVersion(systemVersionFile.Get.AsStream());
}
}
if (updateNcas.TryGetValue(nca.Header.TitleId, out var updateNcasItem))
{
updateNcasItem.Add((nca.Header.ContentType, entry.FullPath));
}
else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath));
}
ncaStorage.Dispose();
}
if (metaEntries == null)
{
throw new FileNotFoundException("System update title was not found in the firmware package.");
}
foreach (CnmtContentMetaEntry metaEntry in metaEntries)
{
if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry))
{
string metaNcaPath = ncaEntry.FirstOrDefault(x => x.type == NcaContentType.Meta).path;
string contentPath = ncaEntry.FirstOrDefault(x => x.type != NcaContentType.Meta).path;
// Nintendo in 9.0.0, removed PPC and only kept the meta nca of it.
// This is a perfect valid case, so we should just ignore the missing content nca and continue.
if (contentPath == null)
{
updateNcas.Remove(metaEntry.TitleId);
continue;
}
IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaPath, OpenMode.Read).AsStorage();
IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage();
Nca metaNca = new(_virtualFileSystem.KeySet, metaStorage);
IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
using var metaFile = new UniqueRef<IFile>();
if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
{
var meta = new Cnmt(metaFile.Get.AsStream());
if (contentStorage.GetSize(out long size).IsSuccess())
{
byte[] contentData = new byte[size];
Span<byte> content = new(contentData);
contentStorage.Read(0, content);
Span<byte> hash = new(new byte[32]);
LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash);
if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash))
{
updateNcas.Remove(metaEntry.TitleId);
}
}
}
}
}
if (updateNcas.Count > 0)
{
StringBuilder extraNcas = new();
foreach (var entry in updateNcas)
{
foreach (var (type, path) in entry.Value)
{
extraNcas.AppendLine(path);
}
}
throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}");
}
return systemVersion;
} }
public SystemVersion GetCurrentFirmwareVersion() public SystemVersion GetCurrentFirmwareVersion()

View file

@ -127,7 +127,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return nca.Header.ContentType == NcaContentType.Control; return nca.Header.ContentType == NcaContentType.Control;
} }
public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath, Stream updateStream = null) public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath)
{ {
updatePath = null; updatePath = null;
@ -138,38 +138,26 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
// Clear the program index part. // Clear the program index part.
ulong titleIdBase = mainNca.GetProgramIdBase(); ulong titleIdBase = mainNca.GetProgramIdBase();
IFileSystem updatePartitionFileSystem = null; // Load update information if exists.
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
if (updateStream == null) if (File.Exists(titleUpdateMetadataPath))
{ {
// Load update information if exists. updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _applicationSerializerContext.TitleUpdateMetadata).Selected;
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); if (File.Exists(updatePath))
if (File.Exists(titleUpdateMetadataPath))
{ {
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _applicationSerializerContext.TitleUpdateMetadata).Selected; IFileSystem updatePartitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(updatePath, fileSystem);
if (File.Exists(updatePath))
{
updatePartitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(updatePath, fileSystem);
}
}
}
else
{
updatePartitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(updateStream, false, fileSystem);
}
if (updatePartitionFileSystem != null) foreach ((ulong applicationTitleId, ContentMetaData content) in updatePartitionFileSystem.GetContentData(ContentMetaType.Patch, fileSystem, checkLevel))
{
foreach ((ulong applicationTitleId, ContentMetaData content) in updatePartitionFileSystem.GetContentData(ContentMetaType.Patch, fileSystem, checkLevel))
{
if ((applicationTitleId & ~0x1FFFUL) != titleIdBase)
{ {
continue; if ((applicationTitleId & ~0x1FFFUL) != titleIdBase)
} {
continue;
}
updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex); updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex);
updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex); updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex);
break; break;
}
} }
} }

View file

@ -52,7 +52,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return programs; return programs;
} }
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, Stream stream, ulong applicationId, out string errorMessage, string extension, Stream updateStream = null) internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage)
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new() where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
where TFormat : IPartitionFileSystemFormat where TFormat : IPartitionFileSystemFormat
where THeader : unmanaged, IPartitionFileSystemHeader where THeader : unmanaged, IPartitionFileSystemHeader
@ -102,7 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
return (false, ProcessResult.Failed); return (false, ProcessResult.Failed);
} }
(Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _, updateStream); (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
if (updatePatchNca != null) if (updatePatchNca != null)
{ {
@ -131,7 +131,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
{ {
if (downloadableContentNca.Enabled) if (downloadableContentNca.Enabled)
{ {
device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, stream, downloadableContentNca.FullPath, System.IO.Path.GetExtension(downloadableContentContainer.ContainerPath)); device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
} }
} }
else else

View file

@ -35,12 +35,6 @@ namespace Ryujinx.HLE.Loaders.Processes
public bool LoadXci(string path, ulong applicationId) public bool LoadXci(string path, ulong applicationId)
{ {
FileStream stream = new(path, FileMode.Open, FileAccess.Read); FileStream stream = new(path, FileMode.Open, FileAccess.Read);
return LoadXci(stream, applicationId);
}
public bool LoadXci(Stream stream, ulong applicationId, Stream updateStream = null)
{
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage()); Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
if (!xci.HasPartition(XciPartitionType.Secure)) if (!xci.HasPartition(XciPartitionType.Secure))
@ -50,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
return false; return false;
} }
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, stream, applicationId, out string errorMessage, "xci", updateStream); (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage);
if (!success) if (!success)
{ {
@ -75,16 +69,10 @@ namespace Ryujinx.HLE.Loaders.Processes
public bool LoadNsp(string path, ulong applicationId) public bool LoadNsp(string path, ulong applicationId)
{ {
FileStream file = new(path, FileMode.Open, FileAccess.Read); FileStream file = new(path, FileMode.Open, FileAccess.Read);
return LoadNsp(file, applicationId);
}
public bool LoadNsp(Stream stream, ulong applicationId, Stream updateStream = null)
{
PartitionFileSystem partitionFileSystem = new(); PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(stream.AsStorage()).ThrowIfFailure(); partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, stream, applicationId, out string errorMessage, "nsp", updateStream); (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage);
if (processResult.ProcessId == 0) if (processResult.ProcessId == 0)
{ {
@ -113,13 +101,7 @@ namespace Ryujinx.HLE.Loaders.Processes
public bool LoadNca(string path) public bool LoadNca(string path)
{ {
FileStream file = new(path, FileMode.Open, FileAccess.Read); FileStream file = new(path, FileMode.Open, FileAccess.Read);
Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
return LoadNca(file);
}
public bool LoadNca(Stream ncaStream)
{
Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, ncaStream.AsStorage(false));
ProcessResult processResult = nca.Load(_device, null, null); ProcessResult processResult = nca.Load(_device, null, null);
@ -267,109 +249,5 @@ namespace Ryujinx.HLE.Loaders.Processes
return false; return false;
} }
public bool LoadNxo(Stream stream, bool isNro, string name)
{
var nacpData = new BlitStruct<ApplicationControlProperty>(1);
IFileSystem dummyExeFs = null;
Stream romfsStream = null;
string programName = "";
ulong programId = 0000000000000000;
// Load executable.
IExecutable executable;
if (isNro)
{
NroExecutable nro = new(stream.AsStorage());
executable = nro;
// Open RomFS if exists.
IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false);
romFsStorage.GetSize(out long romFsSize).ThrowIfFailure();
if (romFsSize != 0)
{
romfsStream = romFsStorage.AsStream();
}
// Load Nacp if exists.
IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false);
nacpStorage.GetSize(out long nacpSize).ThrowIfFailure();
if (nacpSize != 0)
{
nacpStorage.Read(0, nacpData.ByteSpan);
programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
if (string.IsNullOrWhiteSpace(programName))
{
programName = Array.Find(nacpData.Value.Title.AsReadOnlySpan().ToArray(), x => x.Name[0] != 0).NameString.ToString();
}
if (nacpData.Value.PresenceGroupId != 0)
{
programId = nacpData.Value.PresenceGroupId;
}
else if (nacpData.Value.SaveDataOwnerId != 0)
{
programId = nacpData.Value.SaveDataOwnerId;
}
else if (nacpData.Value.AddOnContentBaseId != 0)
{
programId = nacpData.Value.AddOnContentBaseId - 0x1000;
}
}
// TODO: Add icon maybe ?
}
else
{
executable = new NsoExecutable(new LocalStorage(name, FileAccess.Read), programName);
}
// Explicitly null TitleId to disable the shader cache.
Graphics.Gpu.GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set();
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
_device.System.KernelContext,
dummyExeFs.GetNpdm(),
nacpData,
diskCacheEnabled: false,
diskCacheSelector: null,
allowCodeMemoryForJit: true,
programName,
programId,
0,
null,
executable);
// Make sure the process id is valid.
if (processResult.ProcessId != 0)
{
// Load RomFS.
if (romfsStream != null)
{
_device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream);
}
// Start process.
if (_processesByPid.TryAdd(processResult.ProcessId, processResult))
{
if (processResult.Start(_device))
{
_latestPid = processResult.ProcessId;
return true;
}
}
}
return false;
}
} }
} }

View file

@ -11,8 +11,6 @@ using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.IO;
using System.Threading;
namespace Ryujinx.HLE namespace Ryujinx.HLE
{ {
@ -119,26 +117,6 @@ namespace Ryujinx.HLE
return Processes.LoadNxo(fileName); return Processes.LoadNxo(fileName);
} }
public bool LoadXci(Stream xciStream, ulong applicationId = 0, Stream updateStream = null)
{
return Processes.LoadXci(xciStream, applicationId, updateStream);
}
public bool LoadNca(Stream ncaStream)
{
return Processes.LoadNca(ncaStream);
}
public bool LoadNsp(Stream nspStream, ulong applicationId = 0, Stream updateStream = null)
{
return Processes.LoadNsp(nspStream, applicationId, updateStream);
}
public bool LoadProgram(Stream stream, bool isNro, string name)
{
return Processes.LoadNxo(stream, isNro, name);
}
public bool WaitFifo() public bool WaitFifo()
{ {
return Gpu.GPFifo.WaitForCommands(); return Gpu.GPFifo.WaitForCommands();

View file

@ -14,21 +14,16 @@ namespace Ryujinx.HLE.Utilities
{ {
FileStream file = File.OpenRead(path); FileStream file = File.OpenRead(path);
return OpenApplicationFileSystem(file, Path.GetExtension(path).ToLower() == ".xci", fileSystem, throwOnFailure);
}
public static IFileSystem OpenApplicationFileSystem(Stream stream, bool isXci, VirtualFileSystem fileSystem, bool throwOnFailure = true)
{
IFileSystem partitionFileSystem; IFileSystem partitionFileSystem;
if (isXci) if (Path.GetExtension(path).ToLower() == ".xci")
{ {
partitionFileSystem = new Xci(fileSystem.KeySet, stream.AsStorage()).OpenPartition(XciPartitionType.Secure); partitionFileSystem = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
} }
else else
{ {
var pfsTemp = new PartitionFileSystem(); var pfsTemp = new PartitionFileSystem();
Result initResult = pfsTemp.Initialize(stream.AsStorage()); Result initResult = pfsTemp.Initialize(file.AsStorage());
if (throwOnFailure) if (throwOnFailure)
{ {