From 6919b123b76f3abf28fe864a15850b97a2610394 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sun, 2 Jul 2023 16:49:14 +0000 Subject: [PATCH] add stream based loaders (cherry picked from commit e86dec9112b5e1b58a8241aadcf37bf2e3acd14c) --- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 33 +++-- .../PartitionFileSystemExtensions.cs | 4 +- .../Loaders/Processes/ProcessLoader.cs | 131 +++++++++++++++++- src/Ryujinx.HLE/Switch.cs | 21 +++ .../Utilities/PartitionFileSystemUtils.cs | 11 +- 5 files changed, 178 insertions(+), 22 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index e6c0fce08..f0e224472 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -40,12 +40,14 @@ namespace Ryujinx.HLE.FileSystem private readonly struct AocItem { - public readonly string ContainerPath; + public readonly Stream ContainerStream; public readonly string NcaPath; + public readonly string Extension; - public AocItem(string containerPath, string ncaPath) + public AocItem(Stream containerStream, string ncaPath, string extension) { - ContainerPath = containerPath; + ContainerStream = containerStream; + Extension = extension; NcaPath = ncaPath; } } @@ -185,10 +187,10 @@ namespace Ryujinx.HLE.FileSystem } } - public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false) + public void AddAocItem(ulong titleId, Stream containerStream, string ncaPath, string extension, bool mergedToContainer = false) { // TODO: Check Aoc version. - if (!AocData.TryAdd(titleId, new AocItem(containerPath, ncaPath))) + if (!AocData.TryAdd(titleId, new AocItem(containerStream, ncaPath, extension))) { Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}"); } @@ -198,12 +200,20 @@ namespace Ryujinx.HLE.FileSystem if (!mergedToContainer) { - using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem); + using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerStream, extension == ".xci", _virtualFileSystem); } } } - public void ClearAocData() => AocData.Clear(); + public void ClearAocData() + { + foreach (var aoc in AocData) + { + aoc.Value.ContainerStream?.Dispose(); + } + + AocData.Clear(); + } public int GetAocCount() => AocData.Count; @@ -215,18 +225,17 @@ namespace Ryujinx.HLE.FileSystem if (AocData.TryGetValue(aocTitleId, out AocItem aoc)) { - var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read); using var ncaFile = new UniqueRef(); - switch (Path.GetExtension(aoc.ContainerPath)) + switch (aoc.Extension) { case ".xci": - var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); - xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + var xci = new Xci(_virtualFileSystem.KeySet, aoc.ContainerStream.AsStorage()).OpenPartition(XciPartitionType.Secure); + xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read); break; case ".nsp": var pfs = new PartitionFileSystem(); - pfs.Initialize(file.AsStorage()); + pfs.Initialize(aoc.ContainerStream.AsStorage()); pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); break; default: diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs index b3590d9bd..a6dde3a68 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -52,7 +52,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions return programs; } - internal static (bool, ProcessResult) TryLoad(this PartitionFileSystemCore partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage) + internal static (bool, ProcessResult) TryLoad(this PartitionFileSystemCore partitionFileSystem, Switch device, Stream stream, ulong applicationId, out string errorMessage, string extension) where TMetaData : PartitionFileSystemMetaCore, new() where TFormat : IPartitionFileSystemFormat where THeader : unmanaged, IPartitionFileSystemHeader @@ -131,7 +131,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions { if (downloadableContentNca.Enabled) { - device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); + device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, stream, downloadableContentNca.FullPath, System.IO.Path.GetExtension(downloadableContentContainer.ContainerPath)); } } else diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index 12d9c8bd9..cef1667e1 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -35,6 +35,12 @@ namespace Ryujinx.HLE.Loaders.Processes public bool LoadXci(string path, ulong applicationId) { FileStream stream = new(path, FileMode.Open, FileAccess.Read); + + return LoadXci(stream, applicationId); + } + + public bool LoadXci(Stream stream, ulong applicationId) + { Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage()); if (!xci.HasPartition(XciPartitionType.Secure)) @@ -44,7 +50,7 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } - (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage); + (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, stream, applicationId, out string errorMessage, "xci"); if (!success) { @@ -69,10 +75,16 @@ namespace Ryujinx.HLE.Loaders.Processes public bool LoadNsp(string path, ulong applicationId) { FileStream file = new(path, FileMode.Open, FileAccess.Read); - PartitionFileSystem partitionFileSystem = new(); - partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure(); - (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage); + return LoadNsp(file, applicationId); + } + + public bool LoadNsp(Stream stream, ulong applicationId) + { + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(stream.AsStorage()).ThrowIfFailure(); + + (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, stream, applicationId, out string errorMessage, "nsp"); if (processResult.ProcessId == 0) { @@ -101,7 +113,13 @@ namespace Ryujinx.HLE.Loaders.Processes public bool LoadNca(string path) { 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); @@ -242,5 +260,108 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } + + public bool LoadNxo(Stream stream, bool isNro, string name) + { + var nacpData = new BlitStruct(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.ItemsRo.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, + 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; + } + + } } diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 9dfc69892..58fc7ac74 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -10,6 +10,7 @@ using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.UI; using Ryujinx.Memory; using System; +using System.IO; namespace Ryujinx.HLE { @@ -93,6 +94,26 @@ namespace Ryujinx.HLE return Processes.LoadNxo(fileName); } + public bool LoadXci(Stream xciStream) + { + return Processes.LoadXci(xciStream); + } + + public bool LoadNca(Stream ncaStream) + { + return Processes.LoadNca(ncaStream); + } + + public bool LoadNsp(Stream nspStream) + { + return Processes.LoadNsp(nspStream); + } + + public bool LoadProgram(Stream stream, bool isNro, string name) + { + return Processes.LoadNxo(stream, isNro, name); + } + public bool WaitFifo() { return Gpu.GPFifo.WaitForCommands(); diff --git a/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs b/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs index 3c4ce0850..e2a214da1 100644 --- a/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs +++ b/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs @@ -14,16 +14,21 @@ namespace Ryujinx.HLE.Utilities { 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; - if (Path.GetExtension(path).ToLower() == ".xci") + if (isXci) { - partitionFileSystem = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); + partitionFileSystem = new Xci(fileSystem.KeySet, stream.AsStorage()).OpenPartition(XciPartitionType.Secure); } else { var pfsTemp = new PartitionFileSystem(); - Result initResult = pfsTemp.Initialize(file.AsStorage()); + Result initResult = pfsTemp.Initialize(stream.AsStorage()); if (throwOnFailure) {