From 07caf2493642887cc4d391c8b4dbaf5d651ecf9e Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sun, 2 Jul 2023 16:49:14 +0000 Subject: [PATCH] add steam based loaders --- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 38 +++-- .../PartitionFileSystemExtensions.cs | 6 +- .../Loaders/Processes/ProcessLoader.cs | 130 +++++++++++++++++- src/Ryujinx.HLE/Switch.cs | 21 +++ 4 files changed, 172 insertions(+), 23 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index b27eb5ead..eaa94dc0b 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -39,12 +39,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; } } @@ -190,7 +192,7 @@ namespace Ryujinx.HLE.FileSystem } // fs must contain AOC nca files in its root - public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId, IntegrityCheckLevel integrityCheckLevel) + public void AddAocData(IFileSystem fs, Stream containerStream, ulong aocBaseId, IntegrityCheckLevel integrityCheckLevel, string extension) { _virtualFileSystem.ImportTickets(fs); @@ -220,14 +222,14 @@ namespace Ryujinx.HLE.FileSystem string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower(); - AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true); + AddAocItem(cnmt.TitleId, containerStream, $"/{ncaId}.nca", extension, true); } } - 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}"); } @@ -237,16 +239,23 @@ namespace Ryujinx.HLE.FileSystem if (!mergedToContainer) { - using FileStream fileStream = File.OpenRead(containerPath); using PartitionFileSystem partitionFileSystem = new(); - partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure(); + partitionFileSystem.Initialize(containerStream.AsStorage()).ThrowIfFailure(); _virtualFileSystem.ImportTickets(partitionFileSystem); } } } - public void ClearAocData() => AocData.Clear(); + public void ClearAocData() + { + foreach (var aoc in AocData) + { + aoc.Value.ContainerStream?.Dispose(); + } + + AocData.Clear(); + } public int GetAocCount() => AocData.Count; @@ -258,18 +267,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 e95b1b059..0d2d8c13f 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - internal static (bool, ProcessResult) TryLoad(this PartitionFileSystemCore partitionFileSystem, Switch device, string path, out string errorMessage) + internal static (bool, ProcessResult) TryLoad(this PartitionFileSystemCore partitionFileSystem, Switch device, , Stream stream, out string errorMessage, string extension) where TMetaData : PartitionFileSystemMetaCore, new() where TFormat : IPartitionFileSystemFormat where THeader : unmanaged, IPartitionFileSystemHeader @@ -141,7 +141,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions // Load contained DownloadableContents. // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here. device.Configuration.ContentManager.ClearAocData(); - device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel); + device.Configuration.ContentManager.AddAocData(partitionFileSystem, stream, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel, extension); // Load DownloadableContents. string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); @@ -155,7 +155,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions { if (File.Exists(downloadableContentContainer.ContainerPath) && 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 efeb9f613..2246b9994 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) { FileStream stream = new(path, FileMode.Open, FileAccess.Read); + + return LoadXci(stream); + } + + public bool LoadXci(Stream stream) + { 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, out string errorMessage); + (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, stream, out string errorMessage, "xci"); if (!success) { @@ -69,10 +75,16 @@ namespace Ryujinx.HLE.Loaders.Processes public bool LoadNsp(string path) { FileStream file = new(path, FileMode.Open, FileAccess.Read); - PartitionFileSystem partitionFileSystem = new(); - partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure(); + + return LoadNsp(file); + } - (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage); + public bool LoadNsp(Stream stream) + { + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(stream.AsStorage()).ThrowIfFailure(); + + (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, stream, 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); @@ -241,5 +259,107 @@ 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, + 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 ae063a47d..bcf66db9d 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 { @@ -92,6 +93,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();