Procházet zdrojové kódy

Refactor out Application details from Horizon (#1236)

* Initial Application refactor

* Misc typo and access modifier fixes

* Clean unused namespaces

* Address gdkchan's comments

* Move ticket reading to common method

* Change IParentalControlService to use ApplicationLoader.ControlData
mageven před 6 roky
rodič
revize
ba4830293e

+ 3 - 3
Ryujinx.HLE/FileSystem/Content/ContentManager.cs

@@ -580,7 +580,7 @@ namespace Ryujinx.HLE.FileSystem.Content
 
             SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
             {
-                IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
+                IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
 
                 SystemVersion systemVersion = null;
 
@@ -743,7 +743,7 @@ namespace Ryujinx.HLE.FileSystem.Content
 
             SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
             {
-                IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
+                IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
 
                 SystemVersion systemVersion = null;
 
@@ -874,7 +874,7 @@ namespace Ryujinx.HLE.FileSystem.Content
 
         public SystemVersion GetCurrentFirmwareVersion()
         {
-            IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
+            IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
 
             LoadEntries();
 

+ 18 - 1
Ryujinx.HLE/FileSystem/VirtualFileSystem.cs

@@ -1,7 +1,9 @@
 using LibHac;
+using LibHac.Common;
 using LibHac.Fs;
 using LibHac.FsService;
 using LibHac.FsSystem;
+using LibHac.Spl;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.HOS;
 using System;
@@ -261,6 +263,21 @@ namespace Ryujinx.HLE.FileSystem
             KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile);
         }
 
+        public void ImportTickets(IFileSystem fs)
+        {
+            foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik"))
+            {
+                Result result = fs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
+
+                if (result.IsSuccess())
+                {
+                    Ticket ticket = new Ticket(ticketFile.AsStream());
+
+                    KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
+                }
+            }
+        }
+
         public void Unload()
         {
             RomFs?.Dispose();
@@ -283,7 +300,7 @@ namespace Ryujinx.HLE.FileSystem
         {
             if (_isInitialized)
             {
-                throw new InvalidOperationException($"VirtualFileSystem can only be instanciated once!");
+                throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!");
             }
 
             _isInitialized = true;

+ 537 - 0
Ryujinx.HLE/HOS/ApplicationLoader.cs

@@ -0,0 +1,537 @@
+using LibHac;
+using LibHac.Account;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.FsSystem;
+using LibHac.FsSystem.NcaUtils;
+using LibHac.Ncm;
+using LibHac.Ns;
+using LibHac.Spl;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.FileSystem.Content;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Npdm;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+using static LibHac.Fs.ApplicationSaveDataManagement;
+
+namespace Ryujinx.HLE.HOS
+{
+    using JsonHelper = Common.Utilities.JsonHelper;
+
+    public class ApplicationLoader
+    {
+        private readonly Switch _device;
+        private readonly ContentManager _contentManager;
+        private readonly VirtualFileSystem _fileSystem;
+
+        public IntegrityCheckLevel FsIntegrityCheckLevel => _device.System.FsIntegrityCheckLevel;
+
+        public ulong TitleId { get; private set; }
+        public string TitleIdText => TitleId.ToString("x16");
+        public string TitleName { get; private set; }
+
+        public string TitleVersionString { get; private set; }
+
+        public bool TitleIs64Bit { get; private set; }
+
+        public BlitStruct<ApplicationControlProperty> ControlData { get; set; }
+
+        public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager)
+        {
+            _device = device;
+            _contentManager = contentManager;
+            _fileSystem = fileSystem;
+
+            ControlData = new BlitStruct<ApplicationControlProperty>(1);
+        }
+
+        public void LoadCart(string exeFsDir, string romFsFile = null)
+        {
+            if (romFsFile != null)
+            {
+                _fileSystem.LoadRomFs(romFsFile);
+            }
+
+            LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
+
+            LoadExeFs(codeFs, out _);
+
+            if (TitleId != 0)
+            {
+                EnsureSaveData(new TitleId(TitleId));
+            }
+        }
+
+        private (Nca Main, Nca Patch, Nca Control) GetGameData(PartitionFileSystem pfs)
+        {
+            Nca mainNca = null;
+            Nca patchNca = null;
+            Nca controlNca = null;
+
+            _fileSystem.ImportTickets(pfs);
+
+            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+            {
+                pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage());
+
+                if (nca.Header.ContentType == NcaContentType.Program)
+                {
+                    int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+                    if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+                    {
+                        patchNca = nca;
+                    }
+                    else
+                    {
+                        mainNca = nca;
+                    }
+                }
+                else if (nca.Header.ContentType == NcaContentType.Control)
+                {
+                    controlNca = nca;
+                }
+            }
+
+            return (mainNca, patchNca, controlNca);
+        }
+
+        public void LoadXci(string xciFile)
+        {
+            FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
+
+            Xci xci = new Xci(_fileSystem.KeySet, file.AsStorage());
+
+            if (!xci.HasPartition(XciPartitionType.Secure))
+            {
+                Logger.PrintError(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
+
+                return;
+            }
+
+            PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
+
+            Nca mainNca = null;
+            Nca patchNca = null;
+            Nca controlNca = null;
+
+            try
+            {
+                (mainNca, patchNca, controlNca) = GetGameData(securePartition);
+            }
+            catch (Exception e)
+            {
+                Logger.PrintError(LogClass.Loader, $"Unable to load XCI: {e.Message}");
+
+                return;
+            }
+
+            if (mainNca == null)
+            {
+                Logger.PrintError(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
+
+                return;
+            }
+
+            _contentManager.LoadEntries(_device);
+
+            LoadNca(mainNca, patchNca, controlNca);
+        }
+
+        public void LoadNsp(string nspFile)
+        {
+            FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
+
+            PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
+
+            Nca mainNca = null;
+            Nca patchNca = null;
+            Nca controlNca = null;
+
+            try
+            {
+                (mainNca, patchNca, controlNca) = GetGameData(nsp);
+            }
+            catch (Exception e)
+            {
+                Logger.PrintError(LogClass.Loader, $"Unable to load NSP: {e.Message}");
+
+                return;
+            }
+
+            if (mainNca == null)
+            {
+                Logger.PrintError(LogClass.Loader, "Unable to load NSP: Could not find Main NCA");
+
+                return;
+            }
+
+            if (mainNca != null)
+            {
+                LoadNca(mainNca, patchNca, controlNca);
+
+                return;
+            }
+
+            // This is not a normal NSP, it's actually a ExeFS as a NSP
+            LoadExeFs(nsp, out _);
+        }
+
+        public void LoadNca(string ncaFile)
+        {
+            FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
+
+            Nca nca = new Nca(_fileSystem.KeySet, file.AsStorage(false));
+
+            LoadNca(nca, null, null);
+        }
+
+        private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
+        {
+            if (mainNca.Header.ContentType != NcaContentType.Program)
+            {
+                Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
+
+                return;
+            }
+
+            IStorage dataStorage = null;
+            IFileSystem codeFs = null;
+
+            string titleUpdateMetadataPath = System.IO.Path.Combine(_fileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json");
+
+            if (File.Exists(titleUpdateMetadataPath))
+            {
+                string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
+
+                if (File.Exists(updatePath))
+                {
+                    FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
+                    PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
+
+                    _fileSystem.ImportTickets(nsp);
+
+                    foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
+                    {
+                        nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                        Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage());
+
+                        if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16"))
+                        {
+                            break;
+                        }
+
+                        if (nca.Header.ContentType == NcaContentType.Program)
+                        {
+                            patchNca = nca;
+                        }
+                        else if (nca.Header.ContentType == NcaContentType.Control)
+                        {
+                            controlNca = nca;
+                        }
+                    }
+                }
+            }
+
+            if (patchNca == null)
+            {
+                if (mainNca.CanOpenSection(NcaSectionType.Data))
+                {
+                    dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel);
+                }
+
+                if (mainNca.CanOpenSection(NcaSectionType.Code))
+                {
+                    codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel);
+                }
+            }
+            else
+            {
+                if (patchNca.CanOpenSection(NcaSectionType.Data))
+                {
+                    dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel);
+                }
+
+                if (patchNca.CanOpenSection(NcaSectionType.Code))
+                {
+                    codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel);
+                }
+            }
+
+            if (codeFs == null)
+            {
+                Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA");
+
+                return;
+            }
+
+            if (dataStorage == null)
+            {
+                Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
+            }
+            else
+            {
+                _fileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read));
+            }
+
+            LoadExeFs(codeFs, out Npdm metaData);
+
+            TitleId = metaData.Aci0.TitleId;
+            TitleIs64Bit = metaData.Is64Bit;
+
+            if (controlNca != null)
+            {
+                ReadControlData(controlNca);
+            }
+            else
+            {
+                ControlData.ByteSpan.Clear();
+            }
+
+            if (TitleId != 0)
+            {
+                EnsureSaveData(new TitleId(TitleId));
+            }
+
+            Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{TitleVersionString} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
+        }
+
+        public void ReadControlData(Nca controlNca)
+        {
+            IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel);
+
+            Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
+
+            if (result.IsSuccess())
+            {
+                result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None);
+
+                if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length)
+                {
+                    TitleName = ControlData.Value
+                        .Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
+
+                    if (string.IsNullOrWhiteSpace(TitleName))
+                    {
+                        TitleName = ControlData.Value.Titles.ToArray()
+                            .FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
+                    }
+
+                    TitleVersionString = ControlData.Value.DisplayVersion.ToString();
+                }
+            }
+            else
+            {
+                ControlData.ByteSpan.Clear();
+            }
+        }
+
+        private void LoadExeFs(IFileSystem codeFs, out Npdm metaData)
+        {
+            Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
+
+            if (ResultFs.PathNotFound.Includes(result))
+            {
+                Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!");
+
+                metaData = GetDefaultNpdm();
+            }
+            else
+            {
+                metaData = new Npdm(npdmFile.AsStream());
+            }
+
+            List<IExecutable> nsos = new List<IExecutable>();
+
+            void LoadNso(string filename)
+            {
+                foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*"))
+                {
+                    if (Path.GetExtension(file.Name) != string.Empty)
+                    {
+                        continue;
+                    }
+
+                    Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}...");
+
+                    codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                    NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage());
+
+                    nsos.Add(nso);
+                }
+            }
+
+            TitleId = metaData.Aci0.TitleId;
+            TitleIs64Bit = metaData.Is64Bit;
+
+            LoadNso("rtld");
+            LoadNso("main");
+            LoadNso("subsdk");
+            LoadNso("sdk");
+
+            _contentManager.LoadEntries(_device);
+
+            ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nsos.ToArray());
+        }
+
+        public void LoadProgram(string filePath)
+        {
+            Npdm metaData = GetDefaultNpdm();
+
+            bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
+
+            IExecutable nro;
+
+            if (isNro)
+            {
+                FileStream input = new FileStream(filePath, FileMode.Open);
+                NroExecutable obj = new NroExecutable(input);
+                nro = obj;
+
+                // homebrew NRO can actually have some data after the actual NRO
+                if (input.Length > obj.FileSize)
+                {
+                    input.Position = obj.FileSize;
+
+                    BinaryReader reader = new BinaryReader(input);
+
+                    uint asetMagic = reader.ReadUInt32();
+
+                    if (asetMagic == 0x54455341)
+                    {
+                        uint asetVersion = reader.ReadUInt32();
+                        if (asetVersion == 0)
+                        {
+                            ulong iconOffset = reader.ReadUInt64();
+                            ulong iconSize = reader.ReadUInt64();
+
+                            ulong nacpOffset = reader.ReadUInt64();
+                            ulong nacpSize = reader.ReadUInt64();
+
+                            ulong romfsOffset = reader.ReadUInt64();
+                            ulong romfsSize = reader.ReadUInt64();
+
+                            if (romfsSize != 0)
+                            {
+                                _fileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
+                            }
+
+                            if (nacpSize != 0)
+                            {
+                                input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
+
+                                reader.Read(ControlData.ByteSpan);
+
+                                ref ApplicationControlProperty nacp = ref ControlData.Value;
+
+                                metaData.TitleName = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
+
+                                if (string.IsNullOrWhiteSpace(metaData.TitleName))
+                                {
+                                    metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
+                                }
+
+                                if (nacp.PresenceGroupId != 0)
+                                {
+                                    metaData.Aci0.TitleId = nacp.PresenceGroupId;
+                                }
+                                else if (nacp.SaveDataOwnerId.Value != 0)
+                                {
+                                    metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
+                                }
+                                else if (nacp.AddOnContentBaseId != 0)
+                                {
+                                    metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
+                                }
+                                else
+                                {
+                                    metaData.Aci0.TitleId = 0000000000000000;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
+                        }
+                    }
+                }
+            }
+            else
+            {
+                nro = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read));
+            }
+
+            _contentManager.LoadEntries(_device);
+
+            TitleName = metaData.TitleName;
+            TitleId = metaData.Aci0.TitleId;
+            TitleIs64Bit = metaData.Is64Bit;
+
+            ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nro);
+        }
+
+        private Npdm GetDefaultNpdm()
+        {
+            Assembly asm = Assembly.GetCallingAssembly();
+
+            using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
+            {
+                return new Npdm(npdmStream);
+            }
+        }
+
+        private Result EnsureSaveData(TitleId titleId)
+        {
+            Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists.");
+
+            Uid user = _device.System.State.Account.LastOpenedUser.UserId.ToLibHacUid();
+
+            ref ApplicationControlProperty control = ref ControlData.Value;
+
+            if (Util.IsEmpty(ControlData.ByteSpan))
+            {
+                // If the current application doesn't have a loaded control property, create a dummy one
+                // and set the savedata sizes so a user savedata will be created.
+                control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
+
+                // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
+                control.UserAccountSaveDataSize = 0x4000;
+                control.UserAccountSaveDataJournalSize = 0x4000;
+
+                Logger.PrintWarning(LogClass.Application,
+                    "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
+            }
+
+            FileSystemClient fs = _fileSystem.FsClient;
+
+            Result rc = fs.EnsureApplicationCacheStorage(out _, titleId, ref control);
+
+            if (rc.IsFailure())
+            {
+                Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}");
+
+                return rc;
+            }
+
+            rc = EnsureApplicationSaveData(fs, out _, titleId, ref control, ref user);
+
+            if (rc.IsFailure())
+            {
+                Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}");
+            }
+
+            return rc;
+        }
+    }
+}

+ 7 - 557
Ryujinx.HLE/HOS/Horizon.cs

@@ -1,16 +1,8 @@
 using LibHac;
-using LibHac.Account;
 using LibHac.Bcat;
-using LibHac.Common;
 using LibHac.Fs;
 using LibHac.FsSystem;
-using LibHac.FsSystem.NcaUtils;
-using LibHac.Ncm;
-using LibHac.Ns;
-using LibHac.Spl;
 using Ryujinx.Common;
-using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Logging;
 using Ryujinx.Configuration;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.HOS.Font;
@@ -30,20 +22,14 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
 using Ryujinx.HLE.HOS.Services.Time.Clock;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Loaders.Executables;
-using Ryujinx.HLE.Loaders.Npdm;
 using Ryujinx.HLE.Utilities;
 using System;
-using System.Collections.Generic;
 using System.IO;
-using System.Linq;
-using System.Reflection;
 
-using static LibHac.Fs.ApplicationSaveDataManagement;
 
 namespace Ryujinx.HLE.HOS
 {
     using TimeServiceManager = Services.Time.TimeManager;
-    using JsonHelper         = Common.Utilities.JsonHelper;
 
     public class Horizon : IDisposable
     {
@@ -80,16 +66,6 @@ namespace Ryujinx.HLE.HOS
 #pragma warning restore CS0649
         private bool _isDisposed;
 
-        public BlitStruct<ApplicationControlProperty> ControlData { get; set; }
-
-        public string TitleName { get; private set; }
-
-        public ulong  TitleId { get; private set; }
-        public string TitleIdText => TitleId.ToString("x16");
-
-        public string TitleVersionString { get; private set; }
-
-        public bool TitleIs64Bit { get; private set; }
 
         public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
 
@@ -104,8 +80,6 @@ namespace Ryujinx.HLE.HOS
 
         public Horizon(Switch device, ContentManager contentManager)
         {
-            ControlData = new BlitStruct<ApplicationControlProperty>(1);
-
             KernelContext = new KernelContext(device, device.Memory);
 
             Device = device;
@@ -207,6 +181,13 @@ namespace Ryujinx.HLE.HOS
             InitLibHacHorizon();
         }
 
+        public void LoadKip(string kipFile)
+        {
+            using IStorage fs = new LocalStorage(kipFile, FileAccess.Read);
+
+            ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs));
+        }
+
         private void InitLibHacHorizon()
         {
             LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer);
@@ -233,537 +214,6 @@ namespace Ryujinx.HLE.HOS
             }
         }
 
-        public void LoadCart(string exeFsDir, string romFsFile = null)
-        {
-            if (romFsFile != null)
-            {
-                Device.FileSystem.LoadRomFs(romFsFile);
-            }
-
-            LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
-
-            LoadExeFs(codeFs, out _);
-
-            if (TitleId != 0)
-            {
-                EnsureSaveData(new TitleId(TitleId));
-            }
-        }
-
-        public void LoadXci(string xciFile)
-        {
-            FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
-
-            Xci xci = new Xci(KeySet, file.AsStorage());
-
-            (Nca mainNca, Nca patchNca, Nca controlNca) = GetXciGameData(xci);
-
-            if (mainNca == null)
-            {
-                Logger.PrintError(LogClass.Loader, "Unable to load XCI");
-
-                return;
-            }
-
-            ContentManager.LoadEntries(Device);
-
-            LoadNca(mainNca, patchNca, controlNca);
-        }
-
-        public void LoadKip(string kipFile)
-        {
-            using (IStorage fs = new LocalStorage(kipFile, FileAccess.Read))
-            {
-                ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs));
-            }
-        }
-
-        private (Nca Main, Nca patch, Nca Control) GetXciGameData(Xci xci)
-        {
-            if (!xci.HasPartition(XciPartitionType.Secure))
-            {
-                throw new InvalidDataException("Could not find XCI secure partition");
-            }
-
-            Nca mainNca    = null;
-            Nca patchNca   = null;
-            Nca controlNca = null;
-
-            XciPartition securePartition = xci.OpenPartition(XciPartitionType.Secure);
-
-            foreach (DirectoryEntryEx ticketEntry in securePartition.EnumerateEntries("/", "*.tik"))
-            {
-                Result result = securePartition.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                if (result.IsSuccess())
-                {
-                    Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                    KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
-                }
-            }
-
-            foreach (DirectoryEntryEx fileEntry in securePartition.EnumerateEntries("/", "*.nca"))
-            {
-                Result result = securePartition.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read);
-                if (result.IsFailure())
-                {
-                    continue;
-                }
-
-                Nca nca = new Nca(KeySet, ncaFile.AsStorage());
-
-                if (nca.Header.ContentType == NcaContentType.Program)
-                {
-                    int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
-
-                    if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
-                    {
-                        patchNca = nca;
-                    }
-                    else
-                    {
-                        mainNca = nca;
-                    }
-                }
-                else if (nca.Header.ContentType == NcaContentType.Control)
-                {
-                    controlNca = nca;
-                }
-            }
-
-            if (mainNca == null)
-            {
-                Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file");
-            }
-
-            if (controlNca != null)
-            {
-                ReadControlData(controlNca);
-            }
-            else
-            {
-                ControlData.ByteSpan.Clear();
-            }
-
-            return (mainNca, patchNca, controlNca);
-        }
-
-        public void ReadControlData(Nca controlNca)
-        {
-            IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel);
-
-            Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
-
-            if (result.IsSuccess())
-            {
-                result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None);
-
-                if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length)
-                {
-                    TitleName = ControlData.Value
-                        .Titles[(int) State.DesiredTitleLanguage].Name.ToString();
-
-                    if (string.IsNullOrWhiteSpace(TitleName))
-                    {
-                        TitleName = ControlData.Value.Titles.ToArray()
-                            .FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
-                    }
-
-                    TitleVersionString = ControlData.Value.DisplayVersion.ToString();
-                }
-            }
-            else
-            {
-                ControlData.ByteSpan.Clear();
-            }
-        }
-
-        public void LoadNca(string ncaFile)
-        {
-            FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
-
-            Nca nca = new Nca(KeySet, file.AsStorage(false));
-
-            LoadNca(nca, null, null);
-        }
-
-        public void LoadNsp(string nspFile)
-        {
-            FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
-
-            PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
-
-            foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
-            {
-                Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                if (result.IsSuccess())
-                {
-                    Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                    KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
-                }
-            }
-
-            Nca mainNca    = null;
-            Nca patchNca   = null;
-            Nca controlNca = null;
-
-            foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
-            {
-                nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                Nca nca = new Nca(KeySet, ncaFile.AsStorage());
-
-                if (nca.Header.ContentType == NcaContentType.Program)
-                {
-                    int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
-
-                    if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
-                    {
-                        patchNca = nca;
-                    }
-                    else
-                    {
-                        mainNca = nca;
-                    }
-                }
-                else if (nca.Header.ContentType == NcaContentType.Control)
-                {
-                    controlNca = nca;
-                }
-            }
-
-            if (mainNca != null)
-            {
-                LoadNca(mainNca, patchNca, controlNca);
-
-                return;
-            }
-
-            // This is not a normal NSP, it's actually a ExeFS as a NSP
-            LoadExeFs(nsp, out _);
-        }
-
-        public void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
-        {
-            if (mainNca.Header.ContentType != NcaContentType.Program)
-            {
-                Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
-
-                return;
-            }
-
-            IStorage    dataStorage = null;
-            IFileSystem codeFs      = null;
-
-            string titleUpdateMetadataPath = System.IO.Path.Combine(Device.FileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json");
-
-            if (File.Exists(titleUpdateMetadataPath))
-            {
-                string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
-
-                if (File.Exists(updatePath))
-                {
-                    FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
-                    PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
-
-                    foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
-                    {
-                        Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                        if (result.IsSuccess())
-                        {
-                            Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                            KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
-                        }
-                    }
-
-                    foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
-                    {
-                        nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                        Nca nca = new Nca(KeySet, ncaFile.AsStorage());
-
-                        if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16"))
-                        {
-                            break;
-                        }
-
-                        if (nca.Header.ContentType == NcaContentType.Program)
-                        {
-                            patchNca = nca;
-                        }
-                        else if (nca.Header.ContentType == NcaContentType.Control)
-                        {
-                            controlNca = nca;
-                        }
-                    }
-                }
-            }
-
-            if (patchNca == null)
-            {
-                if (mainNca.CanOpenSection(NcaSectionType.Data))
-                {
-                    dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel);
-                }
-
-                if (mainNca.CanOpenSection(NcaSectionType.Code))
-                {
-                    codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel);
-                }
-            }
-            else
-            {
-                if (patchNca.CanOpenSection(NcaSectionType.Data))
-                {
-                    dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel);
-                }
-
-                if (patchNca.CanOpenSection(NcaSectionType.Code))
-                {
-                    codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel);
-                }
-            }
-
-            if (codeFs == null)
-            {
-                Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA");
-
-                return;
-            }
-
-            if (dataStorage == null)
-            {
-                Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
-            }
-            else
-            {
-                Device.FileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read));
-            }
-
-            LoadExeFs(codeFs, out Npdm metaData);
-            
-            TitleId      = metaData.Aci0.TitleId;
-            TitleIs64Bit = metaData.Is64Bit;
-
-            if (controlNca != null)
-            {
-                ReadControlData(controlNca);
-            }
-            else
-            {
-                ControlData.ByteSpan.Clear();
-            }
-
-            if (TitleId != 0)
-            {
-                EnsureSaveData(new TitleId(TitleId));
-            }
-
-            Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{TitleVersionString} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
-        }
-
-        private void LoadExeFs(IFileSystem codeFs, out Npdm metaData)
-        {
-            Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
-
-            if (ResultFs.PathNotFound.Includes(result))
-            {
-                Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!");
-
-                metaData = GetDefaultNpdm();
-            }
-            else
-            {
-                metaData = new Npdm(npdmFile.AsStream());
-            }
-
-            List<IExecutable> staticObjects = new List<IExecutable>();
-
-            void LoadNso(string filename)
-            {
-                foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*"))
-                {
-                    if (Path.GetExtension(file.Name) != string.Empty)
-                    {
-                        continue;
-                    }
-
-                    Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}...");
-
-                    codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();          
-                         
-                    NsoExecutable staticObject = new NsoExecutable(nsoFile.AsStorage());
-
-                    staticObjects.Add(staticObject);
-                }
-            }
-
-            TitleId      = metaData.Aci0.TitleId;
-            TitleIs64Bit = metaData.Is64Bit;
-
-            LoadNso("rtld");
-            LoadNso("main");
-            LoadNso("subsdk");
-            LoadNso("sdk");
-
-            ContentManager.LoadEntries(Device);
-
-            ProgramLoader.LoadNsos(KernelContext, metaData, staticObjects.ToArray());
-        }
-
-        public void LoadProgram(string filePath)
-        {
-            Npdm metaData = GetDefaultNpdm();
-
-            bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
-
-
-            IExecutable staticObject;
-
-            if (isNro)
-            {
-                FileStream input = new FileStream(filePath, FileMode.Open);
-                NroExecutable obj = new NroExecutable(input);
-                staticObject = obj;
-
-                // homebrew NRO can actually have some data after the actual NRO
-                if (input.Length > obj.FileSize)
-                {
-                    input.Position = obj.FileSize;
-
-                    BinaryReader reader = new BinaryReader(input);
-
-                    uint asetMagic = reader.ReadUInt32();
-
-                    if (asetMagic == 0x54455341)
-                    {
-                        uint asetVersion = reader.ReadUInt32();
-                        if (asetVersion == 0)
-                        {
-                            ulong iconOffset = reader.ReadUInt64();
-                            ulong iconSize   = reader.ReadUInt64();
-
-                            ulong nacpOffset = reader.ReadUInt64();
-                            ulong nacpSize   = reader.ReadUInt64();
-
-                            ulong romfsOffset = reader.ReadUInt64();
-                            ulong romfsSize   = reader.ReadUInt64();
-
-                            if (romfsSize != 0)
-                            {
-                                Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
-                            }
-
-                            if (nacpSize != 0)
-                            {
-                                input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
-
-                                reader.Read(ControlData.ByteSpan);
-
-                                ref ApplicationControlProperty nacp = ref ControlData.Value;
-
-                                metaData.TitleName = nacp.Titles[(int)State.DesiredTitleLanguage].Name.ToString();
-
-                                if (string.IsNullOrWhiteSpace(metaData.TitleName))
-                                {
-                                    metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
-                                }
-
-                                if (nacp.PresenceGroupId != 0)
-                                {
-                                    metaData.Aci0.TitleId = nacp.PresenceGroupId;
-                                }
-                                else if (nacp.SaveDataOwnerId.Value != 0)
-                                {
-                                    metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
-                                }
-                                else if (nacp.AddOnContentBaseId != 0)
-                                {
-                                    metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
-                                }
-                                else
-                                {
-                                    metaData.Aci0.TitleId = 0000000000000000;
-                                }
-                            }
-                        }
-                        else
-                        {
-                            Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
-                        }
-                    }
-                }
-            }
-            else
-            {
-                staticObject = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read));
-            }
-
-            ContentManager.LoadEntries(Device);
-
-            TitleName    = metaData.TitleName;
-            TitleId      = metaData.Aci0.TitleId;
-            TitleIs64Bit = metaData.Is64Bit;
-
-            ProgramLoader.LoadNsos(KernelContext, metaData, new IExecutable[] { staticObject });
-        }
-
-        private Npdm GetDefaultNpdm()
-        {
-            Assembly asm = Assembly.GetCallingAssembly();
-
-            using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
-            {
-                return new Npdm(npdmStream);
-            }
-        }
-
-        private Result EnsureSaveData(TitleId titleId)
-        {
-            Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists.");
-
-            Uid user = State.Account.LastOpenedUser.UserId.ToLibHacUid();
-
-            ref ApplicationControlProperty control = ref ControlData.Value;
-
-            if (LibHac.Util.IsEmpty(ControlData.ByteSpan))
-            {
-                // If the current application doesn't have a loaded control property, create a dummy one
-                // and set the savedata sizes so a user savedata will be created.
-                control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
-
-                // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
-                control.UserAccountSaveDataSize = 0x4000;
-                control.UserAccountSaveDataJournalSize = 0x4000;
-
-                Logger.PrintWarning(LogClass.Application,
-                    "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
-            }
-
-            FileSystemClient fs = Device.FileSystem.FsClient;
-
-            Result rc = fs.EnsureApplicationCacheStorage(out _, titleId, ref control);
-
-            if (rc.IsFailure())
-            {
-                Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}");
-            }
-
-            rc = EnsureApplicationSaveData(fs, out _, titleId, ref control, ref user);
-
-            if (rc.IsFailure())
-            {
-                Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}");
-            }
-
-            return rc;
-        }
-
         public void SignalDisplayResolutionChange()
         {
             DisplayResolutionChangeEvent.ReadableEvent.Signal();

+ 9 - 9
Ryujinx.HLE/HOS/ProgramLoader.cs

@@ -121,21 +121,21 @@ namespace Ryujinx.HLE.HOS
         }
 
         public static bool LoadNsos(
-            KernelContext context,
-            Npdm          metaData,
-            IExecutable[] nsos,
-            byte[]        arguments = null)
+                   KernelContext context,
+                   Npdm          metaData,
+                   byte[]        arguments = null,
+            params IExecutable[] executables)
         {
             ulong argsStart = 0;
             int   argsSize  = 0;
             ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL;
             int   codeSize  = 0;
 
-            ulong[] nsoBase = new ulong[nsos.Length];
+            ulong[] nsoBase = new ulong[executables.Length];
 
-            for (int index = 0; index < nsos.Length; index++)
+            for (int index = 0; index < executables.Length; index++)
             {
-                IExecutable staticObject = nsos[index];
+                IExecutable staticObject = executables[index];
 
                 int textEnd = staticObject.TextOffset + staticObject.Text.Length;
                 int roEnd   = staticObject.RoOffset   + staticObject.Ro.Length;
@@ -226,11 +226,11 @@ namespace Ryujinx.HLE.HOS
                 return false;
             }
 
-            for (int index = 0; index < nsos.Length; index++)
+            for (int index = 0; index < executables.Length; index++)
             {
                 Logger.PrintInfo(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
 
-                result = LoadIntoMemory(process, nsos[index], nsoBase[index]);
+                result = LoadIntoMemory(process, executables[index], nsoBase[index]);
 
                 if (result != KernelResult.Success)
                 {

+ 1 - 1
Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs

@@ -289,7 +289,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             // Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current PID and store the result (NACP File) internally.
             // But since we use LibHac and we load one Application at a time, it's not necessary.
 
-            context.ResponseData.Write(context.Device.System.ControlData.Value.UserAccountSwitchLock);
+            context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock);
 
             Logger.PrintStub(LogClass.ServiceAcc);
 

+ 1 - 1
Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs

@@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             Uid     userId  = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
             TitleId titleId = new TitleId(context.Process.TitleId);
 
-            BlitStruct<ApplicationControlProperty> controlHolder = context.Device.System.ControlData;
+            BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
 
             ref ApplicationControlProperty control = ref controlHolder.Value;
 

+ 1 - 1
Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs

@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
 
             return new ApplicationLaunchProperty
             {
-                TitleId             = context.Device.System.TitleId,
+                TitleId             = context.Device.Application.TitleId,
                 Version             = 0x00,
                 BaseGameStorageId   = (byte)StorageId.NandSystem,
                 UpdateGameStorageId = (byte)StorageId.None

+ 1 - 1
Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs

@@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
             launchProperty = new LibHac.Arp.ApplicationLaunchProperty();
 
             launchProperty.BaseStorageId = StorageId.BuiltInUser;
-            launchProperty.ApplicationId = new ApplicationId(System.TitleId);
+            launchProperty.ApplicationId = new ApplicationId(System.Device.Application.TitleId);
 
             return Result.Success;
         }

+ 1 - 1
Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs

@@ -14,7 +14,7 @@
 
             long position = context.Request.ReceiveBuff[0].Position;
 
-            byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray();
+            byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray();
 
             context.Memory.Write((ulong)position, nacpData);
 

+ 1 - 1
Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs

@@ -13,7 +13,7 @@
 
             long position = context.Request.ReceiveBuff[0].Position;
 
-            byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray();
+            byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray();
 
             context.Memory.Write((ulong)position, nacpData);
 

+ 2 - 2
Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs

@@ -43,8 +43,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
                         _titleId = titleId;
 
                         // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
-                        _ratingAge                = Array.ConvertAll(context.Device.System.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
-                        _freeCommunicationEnabled = context.Device.System.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication;
+                        _ratingAge                = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
+                        _freeCommunicationEnabled = context.Device.Application.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication;
                     }
                 }
 

+ 2 - 2
Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs

@@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
                 }
             }
 
-            PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.System.ControlData.Value.PlayLogQueryCapability;
+            PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability;
 
             List<ulong> titleIds = new List<ulong>();
 
@@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
                 // Check if input title ids are in the whitelist.
                 foreach (ulong titleId in titleIds)
                 {
-                    if (!context.Device.System.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
+                    if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
                     {
                         return (ResultCode)Am.ResultCode.ObjectInvalid;
                     }

+ 11 - 7
Ryujinx.HLE/Switch.cs

@@ -27,6 +27,8 @@ namespace Ryujinx.HLE
 
         public Horizon System { get; private set; }
 
+        public ApplicationLoader Application { get; }
+
         public PerformanceStatistics Statistics { get; private set; }
 
         public Hid Hid { get; private set; }
@@ -59,6 +61,8 @@ namespace Ryujinx.HLE
 
             Hid = new Hid(this, System.HidBaseAddress);
             Hid.InitDevices();
+
+            Application = new ApplicationLoader(this, fileSystem, contentManager);
         }
 
         public void Initialize()
@@ -76,14 +80,14 @@ namespace Ryujinx.HLE
                 System.EnableMultiCoreScheduling();
             }
 
-            System.FsIntegrityCheckLevel = GetIntigrityCheckLevel();
+            System.FsIntegrityCheckLevel = GetIntegrityCheckLevel();
 
             System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
 
             ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
         }
 
-        public static IntegrityCheckLevel GetIntigrityCheckLevel()
+        public static IntegrityCheckLevel GetIntegrityCheckLevel()
         {
             return ConfigurationState.Instance.System.EnableFsIntegrityChecks
                 ? IntegrityCheckLevel.ErrorOnInvalid
@@ -92,27 +96,27 @@ namespace Ryujinx.HLE
 
         public void LoadCart(string exeFsDir, string romFsFile = null)
         {
-            System.LoadCart(exeFsDir, romFsFile);
+            Application.LoadCart(exeFsDir, romFsFile);
         }
 
         public void LoadXci(string xciFile)
         {
-            System.LoadXci(xciFile);
+            Application.LoadXci(xciFile);
         }
 
         public void LoadNca(string ncaFile)
         {
-            System.LoadNca(ncaFile);
+            Application.LoadNca(ncaFile);
         }
 
         public void LoadNsp(string nspFile)
         {
-            System.LoadNsp(nspFile);
+            Application.LoadNsp(nspFile);
         }
 
         public void LoadProgram(string fileName)
         {
-            System.LoadProgram(fileName);
+            Application.LoadProgram(fileName);
         }
 
         public bool WaitFifo()

+ 2 - 22
Ryujinx/Ui/ApplicationLibrary.cs

@@ -474,17 +474,7 @@ namespace Ryujinx.Ui
             Nca controlNca = null;
 
             // Add keys to key set if needed
-            foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik"))
-            {
-                Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                if (result.IsSuccess())
-                {
-                    Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                    _virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
-                }
-            }
+            _virtualFileSystem.ImportTickets(pfs);
 
             // Find the Control NCA and store it in variable called controlNca
             foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
@@ -661,17 +651,7 @@ namespace Ryujinx.Ui
                 {
                     PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
 
-                    foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
-                    {
-                        Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                        if (result.IsSuccess())
-                        {
-                            Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                            _virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
-                        }
-                    }
+                    _virtualFileSystem.ImportTickets(nsp);
 
                     foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
                     {

+ 7 - 7
Ryujinx/Ui/GLRenderer.cs

@@ -180,16 +180,16 @@ namespace Ryujinx.Ui
             {
                 parent.Present();
 
-                string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
-                    : $" - {_device.System.TitleName}";
+                string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty
+                    : $" - {_device.Application.TitleName}";
 
-                string titleVersionSection = string.IsNullOrWhiteSpace(_device.System.TitleVersionString) ? string.Empty
-                    : $" v{_device.System.TitleVersionString}";
+                string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.TitleVersionString) ? string.Empty
+                    : $" v{_device.Application.TitleVersionString}";
 
-                string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty
-                    : $" ({_device.System.TitleIdText.ToUpper()})";
+                string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty
+                    : $" ({_device.Application.TitleIdText.ToUpper()})";
 
-                string titleArchSection = _device.System.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
+                string titleArchSection = _device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
 
                 parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
             });

+ 1 - 11
Ryujinx/Ui/GameTableContextMenu.cs

@@ -280,17 +280,7 @@ namespace Ryujinx.Ui
                                 FileStream updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
                                 PartitionFileSystem nsp = new PartitionFileSystem(updateFile.AsStorage());
 
-                                foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
-                                {
-                                    Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                                    if (result.IsSuccess())
-                                    {
-                                        Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                                        _virtualFileSystem.KeySet.ExternalKeySet.Add(new LibHac.Fs.RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
-                                    }
-                                }
+                                _virtualFileSystem.ImportTickets(nsp);
 
                                 foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
                                 {

+ 3 - 3
Ryujinx/Ui/MainWindow.cs

@@ -435,9 +435,9 @@ namespace Ryujinx.Ui
                 _firmwareInstallFile.Sensitive      = false;
                 _firmwareInstallDirectory.Sensitive = false;
 
-                DiscordIntegrationModule.SwitchToPlayingState(device.System.TitleIdText, device.System.TitleName);
+                DiscordIntegrationModule.SwitchToPlayingState(device.Application.TitleIdText, device.Application.TitleName);
 
-                ApplicationLibrary.LoadAndSaveMetaData(device.System.TitleIdText, appMetadata =>
+                ApplicationLibrary.LoadAndSaveMetaData(device.Application.TitleIdText, appMetadata =>
                 {
                     appMetadata.LastPlayed = DateTime.UtcNow.ToString();
                 });
@@ -580,7 +580,7 @@ namespace Ryujinx.Ui
 
             if (device != null)
             {
-                UpdateGameMetadata(device.System.TitleIdText);
+                UpdateGameMetadata(device.Application.TitleIdText);
 
                 if (_glWidget != null)
                 {

+ 1 - 11
Ryujinx/Ui/TitleUpdateWindow.cs

@@ -81,17 +81,7 @@ namespace Ryujinx.Ui
                 {
                     PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
 
-                    foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
-                    {
-                        Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
-
-                        if (result.IsSuccess())
-                        {
-                            Ticket ticket = new Ticket(ticketFile.AsStream());
-
-                            _virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
-                        }
-                    }
+                    _virtualFileSystem.ImportTickets(nsp);
 
                     foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
                     {