From 07caf2493642887cc4d391c8b4dbaf5d651ecf9e Mon Sep 17 00:00:00 2001
From: Emmanuel Hansen <emmausssss@gmail.com>
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<IFile>();
 
-                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<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
+        internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, , Stream stream, out string errorMessage, string extension)
             where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, 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<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.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();