diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs
index eb22b39e9b..3cdb3906ff 100644
--- a/Ryujinx.Ava/AppHost.cs
+++ b/Ryujinx.Ava/AppHost.cs
@@ -320,10 +320,14 @@ namespace Ryujinx.Ava
 
             _viewModel.IsGameRunning = true;
 
-            string titleNameSection    = string.IsNullOrWhiteSpace(Device.Application.TitleName)      ? string.Empty : $" - {Device.Application.TitleName}";
-            string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
-            string titleIdSection      = string.IsNullOrWhiteSpace(Device.Application.TitleIdText)    ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
-            string titleArchSection    = Device.Application.TitleIs64Bit                              ? " (64-bit)"  : " (32-bit)";
+            var activeProcess   = Device.Processes.ActiveApplication;
+            var nacp            = activeProcess.ApplicationControlProperties;
+            int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
+
+            string titleNameSection    = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
+            string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString())              ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
+            string titleIdSection      = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText)                       ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
+            string titleArchSection    = activeProcess.Is64Bit                                                        ? " (64-bit)"  : " (32-bit)";
 
             Dispatcher.UIThread.InvokeAsync(() =>
             {
@@ -423,9 +427,9 @@ namespace Ryujinx.Ava
 
         private void Dispose()
         {
-            if (Device.Application != null)
+            if (Device.Processes != null)
             {
-                _viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
+                _viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
             }
 
             ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
@@ -539,7 +543,12 @@ namespace Ryujinx.Ava
             {
                 Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
 
-                Device.LoadNca(ApplicationPath);
+                if (!Device.LoadNca(ApplicationPath))
+                {
+                    Device.Dispose();
+
+                    return false;
+                }
             }
             else if (Directory.Exists(ApplicationPath))
             {
@@ -554,13 +563,23 @@ namespace Ryujinx.Ava
                 {
                     Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
 
-                    Device.LoadCart(ApplicationPath, romFsFiles[0]);
+                    if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
+                    {
+                        Device.Dispose();
+
+                        return false;
+                    }
                 }
                 else
                 {
                     Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
 
-                    Device.LoadCart(ApplicationPath);
+                    if (!Device.LoadCart(ApplicationPath))
+                    {
+                        Device.Dispose();
+
+                        return false;
+                    }
                 }
             }
             else if (File.Exists(ApplicationPath))
@@ -571,7 +590,12 @@ namespace Ryujinx.Ava
                         {
                             Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
 
-                            Device.LoadXci(ApplicationPath);
+                            if (!Device.LoadXci(ApplicationPath))
+                            {
+                                Device.Dispose();
+
+                                return false;
+                            }
 
                             break;
                         }
@@ -579,7 +603,12 @@ namespace Ryujinx.Ava
                         {
                             Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
 
-                            Device.LoadNca(ApplicationPath);
+                            if (!Device.LoadNca(ApplicationPath))
+                            {
+                                Device.Dispose();
+
+                                return false;
+                            }
 
                             break;
                         }
@@ -588,7 +617,12 @@ namespace Ryujinx.Ava
                         {
                             Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
 
-                            Device.LoadNsp(ApplicationPath);
+                            if (!Device.LoadNsp(ApplicationPath))
+                            {
+                                Device.Dispose();
+
+                                return false;
+                            }
 
                             break;
                         }
@@ -598,13 +632,18 @@ namespace Ryujinx.Ava
 
                             try
                             {
-                                Device.LoadProgram(ApplicationPath);
+                                if (!Device.LoadProgram(ApplicationPath))
+                                {
+                                    Device.Dispose();
+
+                                    return false;
+                                }
                             }
                             catch (ArgumentOutOfRangeException)
                             {
                                 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
 
-                                Dispose();
+                                Device.Dispose();
 
                                 return false;
                             }
@@ -617,14 +656,14 @@ namespace Ryujinx.Ava
             {
                 Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
 
-                Dispose();
+                Device.Dispose();
 
                 return false;
             }
 
-            DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
+            DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
 
-            _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
+            _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
             {
                 appMetadata.LastPlayed = DateTime.UtcNow.ToString();
             });
@@ -950,7 +989,7 @@ namespace Ryujinx.Ava
                 {
                     if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
                     {
-                        Device.Application.DiskCacheLoadState?.Cancel();
+                        Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
                     }
                 });
 
@@ -1088,4 +1127,4 @@ namespace Ryujinx.Ava
             return state;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.Ava/Common/ApplicationHelper.cs b/Ryujinx.Ava/Common/ApplicationHelper.cs
index 276d18745d..161ef8596c 100644
--- a/Ryujinx.Ava/Common/ApplicationHelper.cs
+++ b/Ryujinx.Ava/Common/ApplicationHelper.cs
@@ -19,6 +19,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.Ui.App.Common;
 using Ryujinx.Ui.Common.Helper;
 using System;
 using System.Buffers;
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
                         return;
                     }
 
-                    (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
+                    (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
                     if (updatePatchNca != null)
                     {
                         patchNca = updatePatchNca;
diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index a3663af3e1..d5ff785458 100644
--- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
 
         public void SetUIProgressHandlers(Switch emulationContext)
         {
-            if (emulationContext.Application.DiskCacheLoadState != null)
+            if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
             {
-                emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
-                emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
+                emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
+                emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
             }
 
             emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -1705,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels
 
                 if (string.IsNullOrWhiteSpace(titleName))
                 {
-                    LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
-                    TitleName   = AppHost.Device.Application.TitleName;
+                    LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
+                    TitleName   = AppHost.Device.Processes.ActiveApplication.Name;
                 }
 
                 SwitchToRenderer(startFullscreen);
diff --git a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
index dd9e1b9616..0798502cdc 100644
--- a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
+++ b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
@@ -17,6 +17,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Common.Utilities;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
+using Ryujinx.Ui.App.Common;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -162,7 +163,7 @@ public class TitleUpdateViewModel : BaseModel
 
             try
             {
-                (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
+                (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
 
                 if (controlNca != null && patchNca != null)
                 {
diff --git a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
index 1c6f4265c6..30d4115085 100644
--- a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
+++ b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main
 
             if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
             {
-                string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
+                string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
                 AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
 
                 await window.ShowDialog(Window);
@@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main
                 return;
             }
 
-            ApplicationLoader application = ViewModel.AppHost.Device.Application;
-            if (application != null)
-            {
-                await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
+            string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
 
-                ViewModel.AppHost.Device.EnableCheats();
-            }
+            await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
+
+            ViewModel.AppHost.Device.EnableCheats();
         }
 
         private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
index 3f94ce61be..1b3968eabf 100644
--- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
+++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
@@ -20,7 +20,6 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
 using System.Runtime.CompilerServices;
-
 using Path = System.IO.Path;
 using RightsId = LibHac.Fs.RightsId;
 
@@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem
 
                 return $"{basePath}:/{fileName}";
             }
+
             return null;
         }
 
@@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem
             fsServerClient = horizon.CreatePrivilegedHorizonClient();
             var fsServer = new FileSystemServer(fsServerClient);
 
-            RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
+            RandomDataGenerator randomGenerator = Random.Shared.NextBytes;
 
             DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
 
@@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem
 
                 if (result.IsSuccess())
                 {
-                    Ticket ticket = new Ticket(ticketFile.Get.AsStream());
+                    Ticket ticket = new(ticketFile.Get.AsStream());
                     var titleKey = ticket.GetTitleKey(KeySet);
 
                     if (titleKey != null)
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
deleted file mode 100644
index 82bd9b312b..0000000000
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ /dev/null
@@ -1,908 +0,0 @@
-using LibHac;
-using LibHac.Account;
-using LibHac.Common;
-using LibHac.Fs;
-using LibHac.Fs.Fsa;
-using LibHac.Fs.Shim;
-using LibHac.FsSystem;
-using LibHac.Loader;
-using LibHac.Ncm;
-using LibHac.Ns;
-using LibHac.Tools.Fs;
-using LibHac.Tools.FsSystem;
-using LibHac.Tools.FsSystem.NcaUtils;
-using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Logging;
-using Ryujinx.Cpu;
-using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.Loaders.Executables;
-using Ryujinx.Memory;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using static Ryujinx.HLE.HOS.ModLoader;
-using ApplicationId = LibHac.Ncm.ApplicationId;
-using Path = System.IO.Path;
-
-namespace Ryujinx.HLE.HOS
-{
-    using JsonHelper = Common.Utilities.JsonHelper;
-
-    public class ApplicationLoader
-    {
-        // Binaries from exefs are loaded into mem in this order. Do not change.
-        internal static readonly string[] ExeFsPrefixes =
-        {
-            "rtld",
-            "main",
-            "subsdk0",
-            "subsdk1",
-            "subsdk2",
-            "subsdk3",
-            "subsdk4",
-            "subsdk5",
-            "subsdk6",
-            "subsdk7",
-            "subsdk8",
-            "subsdk9",
-            "sdk"
-        };
-
-        private readonly Switch _device;
-        private string _titleName;
-        private string _displayVersion;
-        private BlitStruct<ApplicationControlProperty> _controlData;
-
-        public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
-        public string TitleName => _titleName;
-        public string DisplayVersion => _displayVersion;
-
-        public ulong TitleId { get; private set; }
-        public bool TitleIs64Bit { get; private set; }
-
-        public string TitleIdText => TitleId.ToString("x16");
-
-        public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
-
-        public ApplicationLoader(Switch device)
-        {
-            _device = device;
-            _controlData = new BlitStruct<ApplicationControlProperty>(1);
-        }
-
-        public void LoadCart(string exeFsDir, string romFsFile = null)
-        {
-            LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
-
-            MetaLoader metaData = ReadNpdm(codeFs);
-
-            _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
-                new[] { TitleId },
-                _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
-                _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
-
-            if (TitleId != 0)
-            {
-                EnsureSaveData(new ApplicationId(TitleId));
-            }
-
-            ulong pid = LoadExeFs(codeFs, string.Empty, metaData);
-
-            if (romFsFile != null)
-            {
-                _device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile);
-            }
-        }
-
-        public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
-        {
-            Nca mainNca = null;
-            Nca patchNca = null;
-            Nca controlNca = null;
-
-            fileSystem.ImportTickets(pfs);
-
-            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
-            {
-                using var ncaFile = new UniqueRef<IFile>();
-
-                pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
-
-                int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
-
-                if (ncaProgramIndex != programIndex)
-                {
-                    continue;
-                }
-
-                if (nca.Header.ContentType == NcaContentType.Program)
-                {
-                    int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
-
-                    if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
-                    {
-                        patchNca = nca;
-                    }
-                    else
-                    {
-                        mainNca = nca;
-                    }
-                }
-                else if (nca.Header.ContentType == NcaContentType.Control)
-                {
-                    controlNca = nca;
-                }
-            }
-
-            return (mainNca, patchNca, controlNca);
-        }
-
-        public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
-        {
-            Nca patchNca = null;
-            Nca controlNca = null;
-
-            fileSystem.ImportTickets(pfs);
-
-            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
-            {
-                using var ncaFile = new UniqueRef<IFile>();
-
-                pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
-
-                int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
-
-                if (ncaProgramIndex != programIndex)
-                {
-                    continue;
-                }
-
-                if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
-                {
-                    break;
-                }
-
-                if (nca.Header.ContentType == NcaContentType.Program)
-                {
-                    patchNca = nca;
-                }
-                else if (nca.Header.ContentType == NcaContentType.Control)
-                {
-                    controlNca = nca;
-                }
-            }
-
-            return (patchNca, controlNca);
-        }
-
-        public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
-        {
-            updatePath = null;
-
-            if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
-            {
-                // Clear the program index part.
-                titleIdBase &= 0xFFFFFFFFFFFFFFF0;
-
-                // Load update informations if existing.
-                string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
-
-                if (File.Exists(titleUpdateMetadataPath))
-                {
-                    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());
-
-                        return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
-                    }
-                }
-            }
-
-            return (null, null);
-        }
-
-        public void LoadXci(string xciFile)
-        {
-            FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
-            Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage());
-
-            if (!xci.HasPartition(XciPartitionType.Secure))
-            {
-                Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
-
-                return;
-            }
-
-            PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
-
-            Nca mainNca;
-            Nca patchNca;
-            Nca controlNca;
-
-            try
-            {
-                (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
-
-                RegisterProgramMapInfo(securePartition).ThrowIfFailure();
-            }
-            catch (Exception e)
-            {
-                Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}");
-
-                return;
-            }
-
-            if (mainNca == null)
-            {
-                Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
-
-                return;
-            }
-
-            _device.Configuration.ContentManager.LoadEntries(_device);
-            _device.Configuration.ContentManager.ClearAocData();
-            _device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
-
-            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;
-            Nca patchNca;
-            Nca controlNca;
-
-            try
-            {
-                (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
-
-                RegisterProgramMapInfo(nsp).ThrowIfFailure();
-            }
-            catch (Exception e)
-            {
-                Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}");
-
-                return;
-            }
-
-            if (mainNca != null)
-            {
-                _device.Configuration.ContentManager.ClearAocData();
-                _device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
-
-                LoadNca(mainNca, patchNca, controlNca);
-
-                return;
-            }
-
-            // This is not a normal NSP, it's actually a ExeFS as a NSP
-            LoadExeFs(nsp, null, isHomebrew: true);
-        }
-
-        public void LoadNca(string ncaFile)
-        {
-            FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
-            Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
-
-            LoadNca(nca, null, null);
-        }
-
-        public void LoadServiceNca(string ncaFile)
-        {
-            FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
-            Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
-
-            if (mainNca.Header.ContentType != NcaContentType.Program)
-            {
-                Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
-
-                return;
-            }
-
-            IFileSystem codeFs = null;
-
-            if (mainNca.CanOpenSection(NcaSectionType.Code))
-            {
-                codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
-            }
-
-            if (codeFs == null)
-            {
-                Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
-
-                return;
-            }
-
-            using var npdmFile = new UniqueRef<IFile>();
-
-            Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
-
-            MetaLoader metaData;
-
-            npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
-
-            var npdmBuffer = new byte[fileSize];
-            npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
-
-            metaData = new MetaLoader();
-            metaData.Load(npdmBuffer).ThrowIfFailure();
-
-            NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
-
-            for (int i = 0; i < nsos.Length; i++)
-            {
-                string name = ExeFsPrefixes[i];
-
-                if (!codeFs.FileExists($"/{name}"))
-                {
-                    continue; // File doesn't exist, skip.
-                }
-
-                Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
-
-                using var nsoFile = new UniqueRef<IFile>();
-
-                codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
-            }
-
-            // Collect the nsos, ignoring ones that aren't used.
-            NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
-
-            string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
-            bool usePtc = _device.System.EnablePtc;
-
-            metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
-            ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false);
-            ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
-
-            string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16");
-            bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0;
-
-            string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0');
-
-            Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
-        }
-
-        private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
-        {
-            if (mainNca.Header.ContentType != NcaContentType.Program)
-            {
-                Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
-
-                return;
-            }
-
-            IStorage dataStorage = null;
-            IFileSystem codeFs = null;
-
-            (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _);
-
-            if (updatePatchNca != null)
-            {
-                patchNca = updatePatchNca;
-            }
-
-            if (updateControlNca != null)
-            {
-                controlNca = updateControlNca;
-            }
-
-            // Load program 0 control NCA as we are going to need it for display version.
-            (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
-
-            // Load Aoc
-            string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
-
-            if (File.Exists(titleAocMetadataPath))
-            {
-                List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
-
-                foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
-                {
-                    foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
-                    {
-                        if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
-                        {
-                            _device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
-                        }
-                        else
-                        {
-                            Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
-                        }
-                    }
-                }
-            }
-
-            if (patchNca == null)
-            {
-                if (mainNca.CanOpenSection(NcaSectionType.Data))
-                {
-                    dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
-                }
-
-                if (mainNca.CanOpenSection(NcaSectionType.Code))
-                {
-                    codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
-                }
-            }
-            else
-            {
-                if (patchNca.CanOpenSection(NcaSectionType.Data))
-                {
-                    dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
-                }
-
-                if (patchNca.CanOpenSection(NcaSectionType.Code))
-                {
-                    codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
-                }
-            }
-
-            if (codeFs == null)
-            {
-                Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
-
-                return;
-            }
-
-            MetaLoader metaData = ReadNpdm(codeFs);
-
-            _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
-                _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
-                _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
-                _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
-
-            string displayVersion = string.Empty;
-
-            if (controlNca != null)
-            {
-                ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion);
-            }
-            else
-            {
-                ControlData.ByteSpan.Clear();
-            }
-
-            // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
-            // BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
-            if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
-            {
-                string dummyTitleName = "";
-                BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
-
-                ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion);
-            }
-
-            _displayVersion = displayVersion;
-
-            ulong pid = LoadExeFs(codeFs, displayVersion, metaData);
-
-            if (dataStorage == null)
-            {
-                Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
-            }
-            else
-            {
-                IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
-
-                _device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read));
-            }
-
-            // Don't create save data for system programs.
-            if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
-            {
-                // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
-                // We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
-                EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
-            }
-
-            Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
-        }
-
-        // Sets TitleId, so be sure to call before using it
-        private MetaLoader ReadNpdm(IFileSystem fs)
-        {
-            using var npdmFile = new UniqueRef<IFile>();
-
-            Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
-
-            MetaLoader metaData;
-
-            if (ResultFs.PathNotFound.Includes(result))
-            {
-                Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
-
-                metaData = GetDefaultNpdm();
-            }
-            else
-            {
-                npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
-
-                var npdmBuffer = new byte[fileSize];
-                npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
-
-                metaData = new MetaLoader();
-                metaData.Load(npdmBuffer).ThrowIfFailure();
-            }
-
-            metaData.GetNpdm(out var npdm).ThrowIfFailure();
-
-            TitleId = npdm.Aci.ProgramId.Value;
-            TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
-            _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
-
-            return metaData;
-        }
-
-        private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
-        {
-            using var controlFile = new UniqueRef<IFile>();
-
-            IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
-            Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
-
-            if (result.IsSuccess())
-            {
-                result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
-
-                if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
-                {
-                    titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
-
-                    if (string.IsNullOrWhiteSpace(titleName))
-                    {
-                        titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
-                    }
-
-                    displayVersion = controlData.Value.DisplayVersionString.ToString();
-                }
-            }
-            else
-            {
-                controlData.ByteSpan.Clear();
-            }
-        }
-
-        private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
-        {
-            if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
-            {
-                metaData = null; // TODO: Check if we should retain old npdm.
-            }
-
-            metaData ??= ReadNpdm(codeFs);
-
-            NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
-
-            for (int i = 0; i < nsos.Length; i++)
-            {
-                string name = ExeFsPrefixes[i];
-
-                if (!codeFs.FileExists($"/{name}"))
-                {
-                    continue; // File doesn't exist, skip.
-                }
-
-                Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
-
-                using var nsoFile = new UniqueRef<IFile>();
-
-                codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
-            }
-
-            // ExeFs file replacements.
-            ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
-
-            // Collect the nsos, ignoring ones that aren't used.
-            NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
-
-            // Take the npdm from mods if present.
-            if (modLoadResult.Npdm != null)
-            {
-                metaData = modLoadResult.Npdm;
-            }
-
-            _device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
-
-            _device.Configuration.ContentManager.LoadEntries(_device);
-
-            bool usePtc = _device.System.EnablePtc;
-
-            // Don't use PPTC if ExeFs files have been replaced.
-            usePtc &= !modLoadResult.Modified;
-
-            if (_device.System.EnablePtc && !usePtc)
-            {
-                Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
-            }
-
-            Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
-            _device.Gpu.HostInitalized.Set();
-
-            MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
-
-            if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
-            {
-                memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
-            }
-
-            // We allow it for nx-hbloader because it can be used to launch homebrew.
-            bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
-
-            metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
-            ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit);
-            ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
-
-            DiskCacheLoadState = result.DiskCacheLoadState;
-
-            _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
-
-            return result.ProcessId;
-        }
-
-        public void LoadProgram(string filePath)
-        {
-            MetaLoader metaData = GetDefaultNpdm();
-            metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
-            ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true);
-
-            bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
-
-            IExecutable executable;
-            Stream romfsStream = null;
-
-            if (isNro)
-            {
-                FileStream input = new FileStream(filePath, FileMode.Open);
-                NroExecutable obj = new NroExecutable(input.AsStorage());
-
-                executable = 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)
-                            {
-                                romfsStream = 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;
-
-                                programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
-
-                                if (string.IsNullOrWhiteSpace(programInfo.Name))
-                                {
-                                    programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
-                                }
-
-                                if (nacp.PresenceGroupId != 0)
-                                {
-                                    programInfo.ProgramId = nacp.PresenceGroupId;
-                                }
-                                else if (nacp.SaveDataOwnerId != 0)
-                                {
-                                    programInfo.ProgramId = nacp.SaveDataOwnerId;
-                                }
-                                else if (nacp.AddOnContentBaseId != 0)
-                                {
-                                    programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
-                                }
-                                else
-                                {
-                                    programInfo.ProgramId = 0000000000000000;
-                                }
-                            }
-                        }
-                        else
-                        {
-                            Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
-                        }
-                    }
-                }
-            }
-            else
-            {
-                executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
-            }
-
-            _device.Configuration.ContentManager.LoadEntries(_device);
-
-            _titleName = programInfo.Name;
-            TitleId = programInfo.ProgramId;
-            TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
-            _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
-
-            // Explicitly null titleid to disable the shader cache.
-            Graphics.Gpu.GraphicsConfig.TitleId = null;
-            _device.Gpu.HostInitalized.Set();
-
-            ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
-
-            if (romfsStream != null)
-            {
-                _device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream);
-            }
-
-            DiskCacheLoadState = result.DiskCacheLoadState;
-
-            _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
-        }
-
-        private MetaLoader GetDefaultNpdm()
-        {
-            Assembly asm = Assembly.GetCallingAssembly();
-
-            using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
-            {
-                var npdmBuffer = new byte[npdmStream.Length];
-                npdmStream.Read(npdmBuffer);
-
-                var metaLoader = new MetaLoader();
-                metaLoader.Load(npdmBuffer).ThrowIfFailure();
-
-                return metaLoader;
-            }
-        }
-
-        private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
-        {
-            ulong mainProgramId = 0;
-            Span<bool> hasIndex = stackalloc bool[0x10];
-
-            fileSystem.ImportTickets(pfs);
-
-            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
-            {
-                using var ncaFile = new UniqueRef<IFile>();
-
-                pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
-                Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
-
-                if (nca.Header.ContentType != NcaContentType.Program)
-                {
-                    continue;
-                }
-
-                int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
-
-                if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
-                {
-                    continue;
-                }
-
-                ulong currentProgramId = nca.Header.TitleId;
-                ulong currentMainProgramId = currentProgramId & ~0xFFFul;
-
-                if (mainProgramId == 0 && currentMainProgramId != 0)
-                {
-                    mainProgramId = currentMainProgramId;
-                }
-
-                if (mainProgramId != currentMainProgramId)
-                {
-                    // As far as I know there aren't any multi-application game cards containing multi-program applications,
-                    // so because multi-application game cards are the only way we should run into multiple applications
-                    // we'll just return that there's a single program.
-                    return (mainProgramId, 1);
-                }
-
-                hasIndex[(int)(currentProgramId & 0xF)] = true;
-            }
-
-            int programCount = 0;
-
-            for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
-            {
-                programCount++;
-            }
-
-            return (mainProgramId, programCount);
-        }
-
-        private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
-        {
-            (ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
-
-            if (programCount <= 0)
-                return Result.Success;
-
-            Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
-
-            for (int i = 0; i < programCount; i++)
-            {
-                mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
-                mapInfo[i].MainProgramId = new ApplicationId(applicationId);
-                mapInfo[i].ProgramIndex = (byte)i;
-            }
-
-            return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
-        }
-
-        private Result EnsureSaveData(ApplicationId applicationId)
-        {
-            Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
-
-            Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
-
-            ref ApplicationControlProperty control = ref ControlData.Value;
-
-            if (LibHac.Common.Utilities.IsZeros(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;
-                control.SaveDataOwnerId = applicationId.Value;
-
-                Logger.Warning?.Print(LogClass.Application,
-                    "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
-            }
-
-            HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
-            Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
-
-            if (resultCode.IsFailure())
-            {
-                Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
-
-                return resultCode;
-            }
-
-            resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
-
-            if (resultCode.IsFailure())
-            {
-                Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
-            }
-
-            return resultCode;
-        }
-    }
-}
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 2b77a7c2e6..1639532ed8 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -35,6 +35,7 @@ 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.Processes;
 using Ryujinx.Horizon;
 using System;
 using System.Collections.Generic;
@@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS
             }
         }
 
-        public void LoadKip(string kipPath)
+        public bool LoadKip(string kipPath)
         {
             using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
 
-            ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile));
+            return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
         }
 
         public void ChangeDockedModeState(bool newState)
diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
index 556703cf68..4cf67172d0 100644
--- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
@@ -2,7 +2,7 @@ using System.Collections.Generic;
 
 namespace Ryujinx.HLE.HOS.Kernel.Process
 {
-    internal class ProcessTamperInfo
+    class ProcessTamperInfo
     {
         public KProcess Process { get; }
         public IEnumerable<string> BuildIds { get; }
diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs
index a6dc901358..1651254144 100644
--- a/Ryujinx.HLE/HOS/ModLoader.cs
+++ b/Ryujinx.HLE/HOS/ModLoader.cs
@@ -10,6 +10,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.HLE.Loaders.Mods;
+using Ryujinx.HLE.Loaders.Processes;
 using System;
 using System.Collections.Generic;
 using System.Collections.Specialized;
@@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS
                 return modLoadResult;
             }
 
-            if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length)
+            if (nsos.Length != ProcessConst.ExeFsPrefixes.Length)
             {
                 throw new ArgumentOutOfRangeException("NSO Count is incorrect");
             }
@@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS
 
             foreach (var mod in exeMods)
             {
-                for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i)
+                for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i)
                 {
-                    var nsoName = ApplicationLoader.ExeFsPrefixes[i];
+                    var nsoName = ProcessConst.ExeFsPrefixes[i];
 
                     FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
                     if (nsoFile.Exists)
@@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS
                 }
             }
 
-            for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i)
+            for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i)
             {
                 if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
                 {
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
index 1b412d74e7..413bedcedc 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             // TODO: 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((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
+            context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
 
             Logger.Stub?.PrintStub(LogClass.ServiceAcc);
 
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs
index 00081e1b15..7204971410 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs
@@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
 
         public ILibraryAppletSelfAccessor(ServiceCtx context)
         {
-            if (context.Device.Application.TitleId == 0x0100000000001009)
+            if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009)
             {
                 // Create MiiEdit data.
                 _appletStandalone = new AppletStandalone()
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
             }
             else
             {
-                throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented.");
+                throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
             }
         }
 
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
index f8f88a1cbd..924f542982 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
@@ -115,28 +115,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
 
             // Mask out the low nibble of the program ID to get the application ID
-            ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
+            ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
 
-            BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
-
-            ref ApplicationControlProperty control = ref controlHolder.Value;
-
-            if (LibHac.Common.Utilities.IsZeros(controlHolder.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.Warning?.Print(LogClass.ServiceAm,
-                    "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
-            }
+            ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
 
             LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
-            LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
+            LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId);
 
             context.ResponseData.Write(requiredSize);
 
@@ -153,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             // TODO: When above calls are implemented, switch to using ns:am
 
             long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
-            int  supportedLanguages  = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
+            int  supportedLanguages  = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag;
             int  firstSupported      = BitOperations.TrailingZeroCount(supportedLanguages);
 
             if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
@@ -196,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
         public ResultCode GetDisplayVersion(ServiceCtx context)
         {
             // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
-            context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
+            context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
 
             return ResultCode.Success;
         }
@@ -251,13 +235,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             long journalSize = context.RequestData.ReadInt64();
 
             // Mask out the low nibble of the program ID to get the application ID
-            ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
+            ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
 
-            BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
+            ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
 
             LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
-                out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
-                journalSize);
+                out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize);
 
             if (result.IsFailure()) return (ResultCode)result.Value;
 
@@ -677,7 +660,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
                     throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
                 }
 
-                context.Device.Application.LoadServiceNca(filePath);
+                context.Device.LoadNca(filePath);
 
                 // FIXME: Most likely not how this should be done?
                 while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs
index c985092b83..3e4eca0ac9 100644
--- a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs
+++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs
@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
 
             return new ApplicationLaunchProperty
             {
-                TitleId             = context.Device.Application.TitleId,
+                TitleId             = context.Device.Processes.ActiveApplication.ProgramId,
                 Version             = 0x00,
                 BaseGameStorageId   = (byte)StorageId.BuiltInSystem,
                 UpdateGameStorageId = (byte)StorageId.None
diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
index 1789122e4f..e0c65f440f 100644
--- a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
+++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
@@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
 
             byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
 
-            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
+            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
 
             context.ResponseData.WriteStruct(applicationAlbumEntry);
 
@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
 
             byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
 
-            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
+            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
 
             context.ResponseData.WriteStruct(applicationAlbumEntry);
 
@@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
 
             byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
 
-            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
+            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
 
             context.ResponseData.WriteStruct(applicationAlbumEntry);
 
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
index 6d663a4dec..c884e88032 100644
--- a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
+++ b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
@@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
             errorReport.AppendLine();
             errorReport.AppendLine("ErrorReport log:");
 
-            errorReport.AppendLine($"\tTitleId: {context.Device.Application.TitleId:x16}");
+            errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}");
             errorReport.AppendLine($"\tPid: {pid}");
             errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
             errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
@@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
             {
                 errorReport.AppendLine("CPU Context:");
 
-                if (context.Device.Application.TitleIs64Bit)
+                if (context.Device.Processes.ActiveApplication.Is64Bit)
                 {
                     CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0];
 
diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
index 17a33b7995..4317c8f615 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
@@ -334,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
             }
 
             // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
-            ApplicationControlProperty controlProperty = context.Device.Application.ControlData.Value;
+            ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
 
             /*
 
diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs
index 37143a5aae..1b63f362e8 100644
--- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs
+++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs
@@ -808,7 +808,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
         {
             byte programIndex = context.RequestData.ReadByte();
 
-            if ((context.Device.Application.TitleId & 0xf) != programIndex)
+            if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex)
             {
                 throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
             }
diff --git a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs
index 0d5520038b..b8f9e3b979 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs
@@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
 
             // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
 
-            return CountAddOnContentImpl(context, context.Device.Application.TitleId);
+            return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
         }
 
         [CommandHipc(3)]
@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
 
             // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
 
-            return ListAddContentImpl(context, context.Device.Application.TitleId);
+            return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
         }
 
         [CommandHipc(4)] // 1.0.0-6.2.0
@@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
 
             // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
 
-            return GetAddOnContentBaseIdImpl(context, context.Device.Application.TitleId);
+            return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
         }
 
         [CommandHipc(6)] // 1.0.0-6.2.0
@@ -99,7 +99,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
 
             // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
 
-            return PrepareAddOnContentImpl(context, context.Device.Application.TitleId);
+            return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
         }
 
         [CommandHipc(8)] // 4.0.0+
@@ -128,7 +128,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
             // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
 
             // TODO: Found where stored value is used.
-            ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId);
+            ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId);
 
             if (resultCode != ResultCode.Success)
             {
@@ -294,7 +294,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
             // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId,
             //       If the call fails, it returns ResultCode.InvalidPid.
 
-            _addOnContentBaseId = context.Device.Application.ControlData.Value.AddOnContentBaseId;
+            _addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId;
 
             if (_addOnContentBaseId == 0)
             {
@@ -308,7 +308,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
         {
             uint index = context.RequestData.ReadUInt32();
 
-            ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId);
+            ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId);
 
             if (resultCode != ResultCode.Success)
             {
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
index d3a8917824..249343d79f 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
@@ -1,4 +1,8 @@
-namespace Ryujinx.HLE.HOS.Services.Ns
+using LibHac.Ns;
+using Ryujinx.Common.Utilities;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
 {
     [Service("ns:am")]
     class IApplicationManagerInterface : IpcService
@@ -14,9 +18,9 @@
 
             ulong position = context.Request.ReceiveBuff[0].Position;
 
-            byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray();
+            ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
 
-            context.Memory.Write(position, nacpData);
+            context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
 
             return ResultCode.Success;
         }
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
index 3b6965d0a0..8f6acc1c61 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
@@ -1,4 +1,7 @@
-namespace Ryujinx.HLE.HOS.Services.Ns
+using LibHac.Common;
+using LibHac.Ns;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
 {
     class IReadOnlyApplicationControlDataInterface : IpcService
     {
@@ -13,9 +16,9 @@
 
             ulong position = context.Request.ReceiveBuff[0].Position;
 
-            byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray();
+            ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
 
-            context.Memory.Write(position, nacpData);
+            context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
 
             return ResultCode.Success;
         }
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
index e0017808d9..02964749cf 100644
--- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
+++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
@@ -56,8 +56,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.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
-                        _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag;
+                        _ratingAge           = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
+                        _parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag;
                     }
                 }
 
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
index 1d6cc118e5..52a07d4665 100644
--- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
@@ -6,7 +6,6 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
 {
@@ -16,8 +15,6 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
 
         internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
         {
-            ref readonly var controlProperty = ref context.Device.Application.ControlData.Value;
-
             ulong inputPosition = context.Request.SendBuff[0].Position;
             ulong inputSize     = context.Request.SendBuff[0].Size;
 
@@ -34,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
                 }
             }
 
-            PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability;
+            PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability;
 
             List<ulong> titleIds = new List<ulong>();
 
@@ -48,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 (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
+                    if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
                     {
                         return (ResultCode)Am.ResultCode.ObjectInvalid;
                     }
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs
new file mode 100644
index 0000000000..58759ddb17
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs
@@ -0,0 +1,133 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.Loader;
+using LibHac.Ns;
+using LibHac.Tools.FsSystem;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.Memory;
+using System.Linq;
+using static Ryujinx.HLE.HOS.ModLoader;
+
+namespace Ryujinx.HLE.Loaders.Processes.Extensions
+{
+    static class FileSystemExtensions
+    {
+        public static MetaLoader GetNpdm(this IFileSystem fileSystem)
+        {
+            MetaLoader metaLoader = new();
+
+            if (fileSystem == null || !fileSystem.FileExists(ProcessConst.MainNpdmPath))
+            {
+                Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
+
+                metaLoader.LoadDefault();
+            }
+            else
+            {
+                metaLoader.LoadFromFile(fileSystem);
+            }
+
+            return metaLoader;
+        }
+
+        public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct<ApplicationControlProperty> nacpData, MetaLoader metaLoader, bool isHomebrew = false)
+        {
+            ulong programId = metaLoader.GetProgramId();
+
+            // Replace the whole ExeFs partition by the modded one.
+            if (device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(programId, ref exeFs))
+            {
+                metaLoader = null;
+            }
+
+            // Reload the MetaLoader in case of ExeFs partition replacement.
+            metaLoader ??= exeFs.GetNpdm();
+
+            NsoExecutable[] nsoExecutables = new NsoExecutable[ProcessConst.ExeFsPrefixes.Length];
+
+            for (int i = 0; i < nsoExecutables.Length; i++)
+            {
+                string name = ProcessConst.ExeFsPrefixes[i];
+
+                if (!exeFs.FileExists($"/{name}"))
+                {
+                    continue; // File doesn't exist, skip.
+                }
+
+                Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
+
+                using var nsoFile = new UniqueRef<IFile>();
+
+                exeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                nsoExecutables[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
+            }
+
+            // ExeFs file replacements.
+            ModLoadResult modLoadResult = device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(programId, nsoExecutables);
+
+            // Take the Npdm from mods if present.
+            if (modLoadResult.Npdm != null)
+            {
+                metaLoader = modLoadResult.Npdm;
+            }
+
+            // Collect the Nsos, ignoring ones that aren't used.
+            nsoExecutables = nsoExecutables.Where(x => x != null).ToArray();
+
+            // Apply Nsos patches.
+            device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables);
+
+            // Don't use PTC if ExeFS files have been replaced.
+            bool enablePtc = device.System.EnablePtc && !modLoadResult.Modified;
+            if (!enablePtc)
+            {
+                Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PTC disabled.");
+            }
+
+            // We allow it for nx-hbloader because it can be used to launch homebrew.
+            bool allowCodeMemoryForJit = programId == 0x010000000000100DUL || isHomebrew;
+
+            string programName = "";
+
+            if (!isHomebrew && programId > 0x010000000000FFFF)
+            {
+                programName = nacpData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
+
+                if (string.IsNullOrWhiteSpace(programName))
+                {
+                    programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
+                }
+            }
+
+            // Initialize GPU.
+            Graphics.Gpu.GraphicsConfig.TitleId = $"{programId:x16}";
+            device.Gpu.HostInitalized.Set();
+
+            if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
+            {
+                device.Configuration.MemoryManagerMode = MemoryManagerMode.SoftwarePageTable;
+            }
+
+            ProcessResult processResult = ProcessLoaderHelper.LoadNsos(
+                device,
+                device.System.KernelContext,
+                metaLoader,
+                nacpData.Value,
+                enablePtc,
+                allowCodeMemoryForJit,
+                programName,
+                metaLoader.GetProgramId(),
+                null,
+                nsoExecutables);
+
+            // TODO: This should be stored using ProcessId instead.
+            device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(metaLoader.GetProgramId());
+
+            return processResult;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs
new file mode 100644
index 0000000000..28d9078512
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs
@@ -0,0 +1,39 @@
+using LibHac.Common;
+using LibHac.FsSystem;
+using LibHac.Loader;
+using LibHac.Ns;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
+using ApplicationId = LibHac.Ncm.ApplicationId;
+
+namespace Ryujinx.HLE.Loaders.Processes
+{
+    static class LocalFileSystemExtensions
+    {
+        public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, string romFsPath = "")
+        {
+            MetaLoader metaLoader = exeFs.GetNpdm();
+            var        nacpData   = new BlitStruct<ApplicationControlProperty>(1);
+            ulong      programId  = metaLoader.GetProgramId();
+
+            device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
+                new[] { programId },
+                device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
+                device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
+
+            if (programId != 0)
+            {
+                ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData);
+            }
+
+            ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader);
+
+            // Load RomFS.
+            if (!string.IsNullOrEmpty(romFsPath))
+            {
+                device.Configuration.VirtualFileSystem.LoadRomFs(processResult.ProcessId, romFsPath);
+            }
+
+            return processResult;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs
new file mode 100644
index 0000000000..c639ee5246
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs
@@ -0,0 +1,61 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.Loader;
+using LibHac.Util;
+using Ryujinx.Common;
+using System;
+
+namespace Ryujinx.HLE.Loaders.Processes.Extensions
+{
+    public static class MetaLoaderExtensions
+    {
+        public static ulong GetProgramId(this MetaLoader metaLoader)
+        {
+            metaLoader.GetNpdm(out var npdm).ThrowIfFailure();
+
+            return npdm.Aci.ProgramId.Value;
+        }
+
+        public static string GetProgramName(this MetaLoader metaLoader)
+        {
+            metaLoader.GetNpdm(out var npdm).ThrowIfFailure();
+
+            return StringUtils.Utf8ZToString(npdm.Meta.ProgramName);
+        }
+
+        public static bool IsProgram64Bit(this MetaLoader metaLoader)
+        {
+            metaLoader.GetNpdm(out var npdm).ThrowIfFailure();
+
+            return (npdm.Meta.Flags & 1) != 0;
+        }
+
+        public static void LoadDefault(this MetaLoader metaLoader)
+        {
+            byte[] npdmBuffer = EmbeddedResources.Read("Ryujinx.HLE/Homebrew.npdm");
+
+            metaLoader.Load(npdmBuffer).ThrowIfFailure();
+        }
+
+        public static void LoadFromFile(this MetaLoader metaLoader, IFileSystem fileSystem, string path = "")
+        {
+            if (string.IsNullOrEmpty(path))
+            {
+                path = ProcessConst.MainNpdmPath;
+            }
+
+            using var npdmFile = new UniqueRef<IFile>();
+
+            fileSystem.OpenFile(ref npdmFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+            npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
+
+            Span<byte> npdmBuffer = new byte[fileSize];
+
+            npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
+
+            metaLoader.Load(npdmBuffer).ThrowIfFailure();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs
new file mode 100644
index 0000000000..473f374db9
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs
@@ -0,0 +1,175 @@
+using LibHac;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.Loader;
+using LibHac.Ncm;
+using LibHac.Ns;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common.Logging;
+using System.IO;
+using System.Linq;
+using ApplicationId = LibHac.Ncm.ApplicationId;
+
+namespace Ryujinx.HLE.Loaders.Processes.Extensions
+{
+    static class NcaExtensions
+    {
+        public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
+        {
+            // Extract RomFs and ExeFs from NCA.
+            IStorage    romFs = nca.GetRomFs(device, patchNca);
+            IFileSystem exeFs = nca.GetExeFs(device, patchNca);
+
+            if (exeFs == null)
+            {
+                Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
+
+                return ProcessResult.Failed;
+            }
+
+            // Load Npdm file.
+            MetaLoader metaLoader = exeFs.GetNpdm();
+
+            // Collecting mods related to AocTitleIds and ProgramId.
+            device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
+                device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()),
+                device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
+                device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
+
+            // Load Nacp file.
+            var nacpData = new BlitStruct<ApplicationControlProperty>(1);
+
+            if (controlNca != null)
+            {
+                nacpData = controlNca.GetNacp(device);
+            }
+
+            /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
+
+            // Load program 0 control NCA as we are going to need it for display version.
+            (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
+
+            // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
+            //       As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program.
+            if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
+            {
+                nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion;
+            }
+
+            */
+
+            ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader);
+
+            // Load RomFS.
+            if (romFs == null)
+            {
+                Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
+            }
+            else
+            {
+                romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs);
+
+                device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read));
+            }
+
+            // Don't create save data for system programs.
+            if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value))
+            {
+                // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
+                // We'll know if this changes in the future because applications will get errors when trying to mount the correct save.
+                ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData);
+            }
+
+            return processResult;
+        }
+
+        public static int GetProgramIndex(this Nca nca)
+        {
+            return (int)(nca.Header.TitleId & 0xF);
+        }
+
+        public static bool IsProgram(this Nca nca)
+        {
+            return nca.Header.ContentType == NcaContentType.Program;
+        }
+
+        public static bool IsPatch(this Nca nca)
+        {
+            int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+            return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection();
+        }
+
+        public static bool IsControl(this Nca nca)
+        {
+            return nca.Header.ContentType == NcaContentType.Control;
+        }
+
+        public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
+        {
+            IFileSystem exeFs = null;
+
+            if (patchNca == null)
+            {
+                if (nca.CanOpenSection(NcaSectionType.Code))
+                {
+                    exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel);
+                }
+            }
+            else
+            {
+                if (patchNca.CanOpenSection(NcaSectionType.Code))
+                {
+                    exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel);
+                }
+            }
+
+            return exeFs;
+        }
+
+        public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null)
+        {
+            IStorage romFs = null;
+
+            if (patchNca == null)
+            {
+                if (nca.CanOpenSection(NcaSectionType.Data))
+                {
+                    romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
+                }
+            }
+            else
+            {
+                if (patchNca.CanOpenSection(NcaSectionType.Data))
+                {
+                    romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
+                }
+            }
+
+            return romFs;
+        }
+
+        public static BlitStruct<ApplicationControlProperty> GetNacp(this Nca controlNca, Switch device)
+        {
+            var nacpData = new BlitStruct<ApplicationControlProperty>(1);
+
+            using var controlFile = new UniqueRef<IFile>();
+
+            Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel)
+                                      .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
+
+            if (result.IsSuccess())
+            {
+                result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None);
+            }
+            else
+            {
+                nacpData.ByteSpan.Clear();
+            }
+
+            return nacpData;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
new file mode 100644
index 0000000000..5147f5c3a6
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
@@ -0,0 +1,177 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Tools.Fs;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+
+namespace Ryujinx.HLE.Loaders.Processes.Extensions
+{
+    public static class PartitionFileSystemExtensions
+    {
+        internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage)
+        {
+            errorMessage = null;
+
+            // Load required NCAs.
+            Nca mainNca    = null;
+            Nca patchNca   = null;
+            Nca controlNca = null;
+
+            try
+            {
+                device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
+
+                // TODO: To support multi-games container, this should use CNMT NCA instead.
+                foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
+                {
+                    Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
+
+                    if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
+                    {
+                        continue;
+                    }
+
+                    if (nca.IsPatch())
+                    {
+                        patchNca = nca;
+                    }
+                    else if (nca.IsProgram())
+                    {
+                        mainNca = nca;
+                    }
+                    else if (nca.IsControl())
+                    {
+                        controlNca = nca;
+                    }
+                }
+
+                ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
+            }
+            catch (Exception ex)
+            {
+                errorMessage = $"Unable to load: {ex.Message}";
+
+                return (false, ProcessResult.Failed);
+            }
+
+            if (mainNca != null)
+            {
+                if (mainNca.Header.ContentType != NcaContentType.Program)
+                {
+                    errorMessage = "Selected NCA file is not a \"Program\" NCA";
+
+                    return (false, ProcessResult.Failed);
+                }
+
+                // Load Update NCAs.
+                Nca updatePatchNca = null;
+                Nca updateControlNca = null;
+
+                if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+                {
+                    // Clear the program index part.
+                    titleIdBase &= ~0xFUL;
+
+                    // Load update information if exists.
+                    string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+                    if (File.Exists(titleUpdateMetadataPath))
+                    {
+                        string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
+                        if (File.Exists(updatePath))
+                        {
+                            PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage());
+
+                            device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
+
+                            // TODO: This should use CNMT NCA instead.
+                            foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca"))
+                            {
+                                Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath);
+
+                                if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
+                                {
+                                    continue;
+                                }
+
+                                if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16"))
+                                {
+                                    break;
+                                }
+
+                                if (nca.IsProgram())
+                                {
+                                    updatePatchNca = nca;
+                                }
+                                else if (nca.IsControl())
+                                {
+                                    updateControlNca = nca;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (updatePatchNca != null)
+                {
+                    patchNca = updatePatchNca;
+                }
+
+                if (updateControlNca != null)
+                {
+                    controlNca = updateControlNca;
+                }
+
+                // 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);
+
+                // Load DownloadableContents.
+                string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
+                if (File.Exists(addOnContentMetadataPath))
+                {
+                    List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(addOnContentMetadataPath);
+
+                    foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
+                    {
+                        foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
+                        {
+                            if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
+                            {
+                                device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
+                            }
+                            else
+                            {
+                                Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
+                            }
+                        }
+                    }
+                }
+
+                return (true, mainNca.Load(device, patchNca, controlNca));
+            }
+
+            errorMessage = "Unable to load: Could not find Main NCA";
+
+            return (false, ProcessResult.Failed);
+        }
+
+        public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
+        {
+            using var ncaFile = new UniqueRef<IFile>();
+
+            fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+            return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs
new file mode 100644
index 0000000000..42ae2e89bd
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.HLE.Loaders.Processes
+{
+    static class ProcessConst
+    {
+        // Binaries from exefs are loaded into mem in this order. Do not change.
+        public static readonly string[] ExeFsPrefixes =
+        {
+            "rtld",
+            "main",
+            "subsdk0",
+            "subsdk1",
+            "subsdk2",
+            "subsdk3",
+            "subsdk4",
+            "subsdk5",
+            "subsdk6",
+            "subsdk7",
+            "subsdk8",
+            "subsdk9",
+            "sdk"
+        };
+
+        public static readonly string MainNpdmPath = "/main.npdm";
+
+        public const int NroAsetMagic = ('A' << 0) | ('S' << 8) | ('E' << 16) | ('T' << 24);
+
+        public const bool AslrEnabled = true;
+
+        public const int NsoArgsHeaderSize = 8;
+        public const int NsoArgsDataSize   = 0x9000;
+        public const int NsoArgsTotalSize  = NsoArgsHeaderSize + NsoArgsDataSize;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
new file mode 100644
index 0000000000..785db0e50b
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
@@ -0,0 +1,244 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Ns;
+using LibHac.Tools.Fs;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using Path = System.IO.Path;
+
+namespace Ryujinx.HLE.Loaders.Processes
+{
+    public class ProcessLoader
+    {
+        private readonly Switch _device;
+
+        private readonly ConcurrentDictionary<ulong, ProcessResult> _processesByPid;
+
+        private ulong _latestPid;
+
+        public ProcessResult ActiveApplication => _processesByPid[_latestPid];
+
+        public ProcessLoader(Switch device)
+        {
+            _device         = device;
+            _processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
+        }
+
+        public bool LoadXci(string path)
+        {
+            FileStream stream = new(path, FileMode.Open, FileAccess.Read);
+            Xci        xci    = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
+
+            if (!xci.HasPartition(XciPartitionType.Secure))
+            {
+                Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition");
+
+                return false;
+            }
+
+            (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage);
+
+            if (!success)
+            {
+                Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad));
+
+                return false;
+            }
+
+            if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
+            {
+                if (processResult.Start(_device))
+                {
+                    _latestPid = processResult.ProcessId;
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public bool LoadNsp(string path)
+        {
+            FileStream          file                = new(path, FileMode.Open, FileAccess.Read);
+            PartitionFileSystem partitionFileSystem = new(file.AsStorage());
+
+            (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
+
+            if (processResult.ProcessId == 0)
+            {
+                // This is not a normal NSP, it's actually a ExeFS as a NSP
+                processResult = partitionFileSystem.Load(_device, new BlitStruct<ApplicationControlProperty>(1), partitionFileSystem.GetNpdm(), true);
+            }
+
+            if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
+            {
+                if (processResult.Start(_device))
+                {
+                    _latestPid = processResult.ProcessId;
+
+                    return true;
+                }
+            }
+
+            if (!success)
+            {
+                Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad));
+            }
+
+            return false;
+        }
+
+        public bool LoadNca(string path)
+        {
+            FileStream file = new(path, FileMode.Open, FileAccess.Read);
+            Nca        nca  = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
+
+            ProcessResult processResult = nca.Load(_device, null, null);
+
+            if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
+            {
+                if (processResult.Start(_device))
+                {
+                    // NOTE: Check if process is SystemApplicationId or ApplicationId
+                    if (processResult.ProgramId > 0x01000000000007FF)
+                    {
+                        _latestPid = processResult.ProcessId;
+                    }
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null)
+        {
+            ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
+
+            if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
+            {
+                if (processResult.Start(_device))
+                {
+                    _latestPid = processResult.ProcessId;
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public bool LoadNxo(string path)
+        {
+            var         nacpData    = new BlitStruct<ApplicationControlProperty>(1);
+            IFileSystem dummyExeFs  = null;
+            Stream      romfsStream = null;
+
+            string programName = "";
+            ulong  programId   = 0000000000000000;
+
+            // Load executable.
+            IExecutable executable;
+
+            if (Path.GetExtension(path).ToLower() == ".nro")
+            {
+                FileStream    input = new(path, FileMode.Open);
+                NroExecutable nro   = new(input.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 = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(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
+            {
+                programName = System.IO.Path.GetFileNameWithoutExtension(path);
+
+                executable = new NsoExecutable(new LocalStorage(path, 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.Value,
+                                                                       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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
similarity index 54%
rename from Ryujinx.HLE/HOS/ProgramLoader.cs
rename to Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
index 4ebcb7e7dc..7176445e58 100644
--- a/Ryujinx.HLE/HOS/ProgramLoader.cs
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
@@ -1,69 +1,132 @@
+using LibHac.Account;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Shim;
+using LibHac.FsSystem;
 using LibHac.Loader;
 using LibHac.Ncm;
-using LibHac.Util;
+using LibHac.Ns;
+using LibHac.Tools.Fs;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
 using Ryujinx.Common;
 using Ryujinx.Common.Logging;
-using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Kernel;
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
 using Ryujinx.Horizon.Common;
 using System;
 using System.Linq;
 using System.Runtime.InteropServices;
-using Npdm = LibHac.Loader.Npdm;
+using ApplicationId = LibHac.Ncm.ApplicationId;
 
-namespace Ryujinx.HLE.HOS
+namespace Ryujinx.HLE.Loaders.Processes
 {
-    struct ProgramInfo
+    static class ProcessLoaderHelper
     {
-        public string Name;
-        public ulong ProgramId;
-        public readonly string TitleIdText;
-        public readonly string DisplayVersion;
-        public readonly bool DiskCacheEnabled;
-        public readonly bool AllowCodeMemoryForJit;
-
-        public ProgramInfo(in Npdm npdm, string displayVersion, bool diskCacheEnabled, bool allowCodeMemoryForJit)
+        public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem)
         {
-            ulong programId = npdm.Aci.ProgramId.Value;
+            ulong applicationId = 0;
+            int   programCount  = 0;
 
-            Name = StringUtils.Utf8ZToString(npdm.Meta.ProgramName);
-            ProgramId = programId;
-            TitleIdText = programId.ToString("x16");
-            DisplayVersion = displayVersion;
-            DiskCacheEnabled = diskCacheEnabled;
-            AllowCodeMemoryForJit = allowCodeMemoryForJit;
+            Span<bool> hasIndex = stackalloc bool[0x10];
+
+            foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
+            {
+                Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
+
+                if (!nca.IsProgram() && nca.IsPatch())
+                {
+                    continue;
+                }
+
+                ulong currentProgramId     = nca.Header.TitleId;
+                ulong currentMainProgramId = currentProgramId & ~0xFFFul;
+
+                if (applicationId == 0 && currentMainProgramId != 0)
+                {
+                    applicationId = currentMainProgramId;
+                }
+
+                if (applicationId != currentMainProgramId)
+                {
+                    // Currently there aren't any known multi-application game cards containing multi-program applications,
+                    // so because multi-application game cards are the only way we could run into multiple applications
+                    // we'll just return that there's a single program.
+                    programCount = 1;
+
+                    break;
+                }
+
+                hasIndex[(int)(currentProgramId & 0xF)] = true;
+            }
+
+            if (programCount == 0)
+            {
+                for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
+                {
+                    programCount++;
+                }
+            }
+
+            if (programCount <= 0)
+            {
+                return LibHac.Result.Success;
+            }
+
+            Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
+
+            for (int i = 0; i < programCount; i++)
+            {
+                mapInfo[i].ProgramId     = new ProgramId(applicationId + (uint)i);
+                mapInfo[i].MainProgramId = new ApplicationId(applicationId);
+                mapInfo[i].ProgramIndex  = (byte)i;
+            }
+
+            return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]);
         }
-    }
 
-    struct ProgramLoadResult
-    {
-        public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null, 0);
-
-        public readonly bool Success;
-        public readonly ProcessTamperInfo TamperInfo;
-        public readonly IDiskCacheLoadState DiskCacheLoadState;
-        public readonly ulong ProcessId;
-
-        public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState, ulong pid)
+        public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct<ApplicationControlProperty> applicationControlProperty)
         {
-            Success = success;
-            TamperInfo = tamperInfo;
-            DiskCacheLoadState = diskCacheLoadState;
-            ProcessId = pid;
+            Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
+
+            ref ApplicationControlProperty control = ref applicationControlProperty.Value;
+
+            if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.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;
+                control.SaveDataOwnerId                = applicationId.Value;
+
+                Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
+            }
+
+            LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
+            if (resultCode.IsFailure())
+            {
+                Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
+
+                return resultCode;
+            }
+
+            Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
+
+            resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId);
+            if (resultCode.IsFailure())
+            {
+                Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
+            }
+
+            return resultCode;
         }
-    }
-
-    static class ProgramLoader
-    {
-        private const bool AslrEnabled = true;
-
-        private const int ArgsHeaderSize = 8;
-        private const int ArgsDataSize   = 0x9000;
-        private const int ArgsTotalSize  = ArgsHeaderSize + ArgsDataSize;
 
         public static bool LoadKip(KernelContext context, KipExecutable kip)
         {
@@ -74,17 +137,14 @@ namespace Ryujinx.HLE.HOS
                 endOffset = kip.BssOffset + kip.BssSize;
             }
 
-            uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize);
-
-            int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
-
+            uint  codeSize        = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize);
+            int   codePagesCount  = (int)(codeSize / KPageTableBase.PageSize);
             ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
-
-            ulong codeAddress = codeBaseAddress + kip.TextOffset;
+            ulong codeAddress     = codeBaseAddress + kip.TextOffset;
 
             ProcessCreationFlags flags = 0;
 
-            if (AslrEnabled)
+            if (ProcessConst.AslrEnabled)
             {
                 // TODO: Randomization.
 
@@ -101,24 +161,11 @@ namespace Ryujinx.HLE.HOS
                 flags |= ProcessCreationFlags.Is64Bit;
             }
 
-            ProcessCreationInfo creationInfo = new ProcessCreationInfo(
-                kip.Name,
-                kip.Version,
-                kip.ProgramId,
-                codeAddress,
-                codePagesCount,
-                flags,
-                0,
-                0);
-
-            MemoryRegion memoryRegion = kip.UsesSecureMemory
-                ? MemoryRegion.Service
-                : MemoryRegion.Application;
-
-            KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion];
+            ProcessCreationInfo  creationInfo = new(kip.Name, kip.Version, kip.ProgramId, codeAddress, codePagesCount, flags, 0, 0);
+            MemoryRegion         memoryRegion = kip.UsesSecureMemory ? MemoryRegion.Service : MemoryRegion.Application;
+            KMemoryRegionManager region       = context.MemoryManager.MemoryRegions[(int)memoryRegion];
 
             Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount);
-
             if (result != Result.Success)
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -126,7 +173,7 @@ namespace Ryujinx.HLE.HOS
                 return false;
             }
 
-            KProcess process = new KProcess(context);
+            KProcess process = new(context);
 
             var processContextFactory = new ArmProcessContextFactory(
                 context.Device.System.TickSource,
@@ -137,14 +184,7 @@ namespace Ryujinx.HLE.HOS
                 codeAddress,
                 codeSize);
 
-            result = process.InitializeKip(
-                creationInfo,
-                kip.Capabilities,
-                pageList,
-                context.ResourceLimit,
-                memoryRegion,
-                processContextFactory);
-
+            result = process.InitializeKip(creationInfo, kip.Capabilities, pageList, context.ResourceLimit, memoryRegion, processContextFactory);
             if (result != Result.Success)
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -153,7 +193,6 @@ namespace Ryujinx.HLE.HOS
             }
 
             result = LoadIntoMemory(process, kip, codeBaseAddress);
-
             if (result != Result.Success)
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -164,7 +203,6 @@ namespace Ryujinx.HLE.HOS
             process.DefaultCpuCore = kip.IdealCoreId;
 
             result = process.Start(kip.Priority, (ulong)kip.StackSize);
-
             if (result != Result.Success)
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
@@ -177,20 +215,27 @@ namespace Ryujinx.HLE.HOS
             return true;
         }
 
-        public static ProgramLoadResult LoadNsos(
+        public static ProcessResult LoadNsos(
+            Switch device,
             KernelContext context,
-            MetaLoader metaData,
-            ProgramInfo programInfo,
+            MetaLoader metaLoader,
+            ApplicationControlProperty applicationControlProperties,
+            bool diskCacheEnabled,
+            bool allowCodeMemoryForJit,
+            string name,
+            ulong programId,
             byte[] arguments = null,
             params IExecutable[] executables)
         {
             context.Device.System.ServiceTable.WaitServicesReady();
 
-            LibHac.Result rc = metaData.GetNpdm(out var npdm);
+            LibHac.Result resultCode = metaLoader.GetNpdm(out var npdm);
 
-            if (rc.IsFailure())
+            if (resultCode.IsFailure())
             {
-                return ProgramLoadResult.Failed;
+                Logger.Error?.Print(LogClass.Loader, $"Process initialization failed getting npdm. Result Code {resultCode.ToStringWithName()}");
+
+                return ProcessResult.Failed;
             }
 
             ref readonly var meta = ref npdm.Meta;
@@ -202,10 +247,10 @@ namespace Ryujinx.HLE.HOS
 
             var buildIds = executables.Select(e => (e switch
             {
-                NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()),
-                NroExecutable nro => BitConverter.ToString(nro.Header.BuildId),
+                NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()),
+                NroExecutable nro => Convert.ToHexString(nro.Header.BuildId),
                 _ => ""
-            }).Replace("-", "").ToUpper());
+            }).ToUpper());
 
             ulong[] nsoBase = new ulong[executables.Length];
 
@@ -214,7 +259,7 @@ namespace Ryujinx.HLE.HOS
                 IExecutable nso = executables[index];
 
                 uint textEnd = nso.TextOffset + (uint)nso.Text.Length;
-                uint roEnd   = nso.RoOffset   + (uint)nso.Ro.Length;
+                uint roEnd   = nso.RoOffset + (uint)nso.Ro.Length;
                 uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize;
 
                 uint nsoSize = textEnd;
@@ -239,31 +284,30 @@ namespace Ryujinx.HLE.HOS
                 {
                     argsStart = codeSize;
 
-                    argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize);
+                    argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ProcessConst.NsoArgsTotalSize - 1, KPageTableBase.PageSize);
 
                     codeSize += argsSize;
                 }
             }
 
-            int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
-
+            int codePagesCount           = (int)(codeSize / KPageTableBase.PageSize);
             int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize);
 
-            ProcessCreationInfo creationInfo = new ProcessCreationInfo(
-                programInfo.Name,
+            ProcessCreationInfo creationInfo = new(
+                name,
                 (int)meta.Version,
-                programInfo.ProgramId,
+                programId,
                 codeStart,
                 codePagesCount,
                 (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
                 0,
                 personalMmHeapPagesCount);
 
-            context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm);
+            context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programId), in npdm);
 
             Result result;
 
-            KResourceLimit resourceLimit = new KResourceLimit(context);
+            KResourceLimit resourceLimit = new(context);
 
             long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size;
 
@@ -293,26 +337,26 @@ namespace Ryujinx.HLE.HOS
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values.");
 
-                return ProgramLoadResult.Failed;
+                return ProcessResult.Failed;
             }
 
-            KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit);
-
-            MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Flags >> 2) & 0xf);
+            KProcess process = new(context, allowCodeMemoryForJit);
 
+            // NOTE: This field doesn't exists one firmware pre-5.0.0, a workaround have to be found.
+            MemoryRegion memoryRegion = (MemoryRegion)(npdm.Acid.Flags >> 2 & 0xf);
             if (memoryRegion > MemoryRegion.NvServices)
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags.");
 
-                return ProgramLoadResult.Failed;
+                return ProcessResult.Failed;
             }
 
             var processContextFactory = new ArmProcessContextFactory(
                 context.Device.System.TickSource,
                 context.Device.Gpu,
-                programInfo.TitleIdText,
-                programInfo.DisplayVersion,
-                programInfo.DiskCacheEnabled,
+                $"{programId:x16}",
+                applicationControlProperties.DisplayVersionString.ToString(),
+                diskCacheEnabled,
                 codeStart,
                 codeSize);
 
@@ -327,7 +371,7 @@ namespace Ryujinx.HLE.HOS
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
 
-                return ProgramLoadResult.Failed;
+                return ProcessResult.Failed;
             }
 
             for (int index = 0; index < executables.Length; index++)
@@ -335,32 +379,22 @@ namespace Ryujinx.HLE.HOS
                 Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
 
                 result = LoadIntoMemory(process, executables[index], nsoBase[index]);
-
                 if (result != Result.Success)
                 {
                     Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
 
-                    return ProgramLoadResult.Failed;
+                    return ProcessResult.Failed;
                 }
             }
 
             process.DefaultCpuCore = meta.DefaultCpuId;
 
-            result = process.Start(meta.MainThreadPriority, meta.MainThreadStackSize);
-
-            if (result != Result.Success)
-            {
-                Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
-
-                return ProgramLoadResult.Failed;
-            }
-
             context.Processes.TryAdd(process.Pid, process);
 
             // Keep the build ids because the tamper machine uses them to know which process to associate a
             // tamper to and also keep the starting address of each executable inside a process because some
             // memory modifications are relative to this address.
-            ProcessTamperInfo tamperInfo = new ProcessTamperInfo(
+            ProcessTamperInfo tamperInfo = new(
                 process,
                 buildIds,
                 nsoBase,
@@ -368,10 +402,13 @@ namespace Ryujinx.HLE.HOS
                 process.MemoryManager.AliasRegionStart,
                 process.MemoryManager.CodeRegionStart);
 
-            return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState, process.Pid);
+            // Once everything is loaded, we can load cheats.
+            device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine);
+
+            return new ProcessResult(metaLoader, applicationControlProperties, diskCacheEnabled, allowCodeMemoryForJit, processContextFactory.DiskCacheLoadState, process.Pid, meta.MainThreadPriority, meta.MainThreadStackSize);
         }
 
-        private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
+        public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
         {
             ulong textStart = baseAddress + image.TextOffset;
             ulong roStart   = baseAddress + image.RoOffset;
@@ -404,14 +441,12 @@ namespace Ryujinx.HLE.HOS
             }
 
             Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute);
-
             if (result != Result.Success)
             {
                 return result;
             }
 
             result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read);
-
             if (result != Result.Success)
             {
                 return result;
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs
new file mode 100644
index 0000000000..6bbeee1b87
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs
@@ -0,0 +1,92 @@
+using LibHac.Loader;
+using LibHac.Ns;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.Loaders.Processes
+{
+    public struct ProcessResult
+    {
+        public static ProcessResult Failed => new(null, new ApplicationControlProperty(), false, false, null, 0, 0, 0);
+
+        private readonly byte _mainThreadPriority;
+        private readonly uint _mainThreadStackSize;
+
+        public readonly IDiskCacheLoadState DiskCacheLoadState;
+
+        public readonly MetaLoader                 MetaLoader;
+        public readonly ApplicationControlProperty ApplicationControlProperties;
+
+        public readonly ulong  ProcessId;
+        public string          Name;
+        public ulong           ProgramId;
+        public readonly string ProgramIdText;
+        public readonly bool   Is64Bit;
+        public readonly bool   DiskCacheEnabled;
+        public readonly bool   AllowCodeMemoryForJit;
+
+        public ProcessResult(
+            MetaLoader                 metaLoader,
+            ApplicationControlProperty applicationControlProperties,
+            bool                       diskCacheEnabled,
+            bool                       allowCodeMemoryForJit,
+            IDiskCacheLoadState        diskCacheLoadState,
+            ulong                      pid,
+            byte                       mainThreadPriority,
+            uint                       mainThreadStackSize)
+        {
+            _mainThreadPriority  = mainThreadPriority;
+            _mainThreadStackSize = mainThreadStackSize;
+
+            DiskCacheLoadState = diskCacheLoadState;
+            ProcessId          = pid;
+
+            MetaLoader                   = metaLoader;
+            ApplicationControlProperties = applicationControlProperties;
+
+            if (metaLoader is not null)
+            {
+                ulong programId = metaLoader.GetProgramId();
+
+                Name          = metaLoader.GetProgramName();
+                ProgramId     = programId;
+                ProgramIdText = $"{programId:x16}";
+                Is64Bit       = metaLoader.IsProgram64Bit();
+            }
+
+            DiskCacheEnabled      = diskCacheEnabled;
+            AllowCodeMemoryForJit = allowCodeMemoryForJit;
+        }
+
+        public bool Start(Switch device)
+        {
+            device.Configuration.ContentManager.LoadEntries(device);
+
+            Result result = device.System.KernelContext.Processes[ProcessId].Start(_mainThreadPriority, _mainThreadStackSize);
+            if (result != Result.Success)
+            {
+                Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
+
+                return false;
+            }
+
+            // TODO: LibHac npdm currently doesn't support version field.
+            string version;
+
+            if (ProgramId > 0x0100000000007FFF)
+            {
+                version = ApplicationControlProperties.DisplayVersionString.ToString();
+            }
+            else
+            {
+                version = device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
+            }
+
+            Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
+
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs
index 61e5e57279..62d14a5460 100644
--- a/Ryujinx.HLE/Switch.cs
+++ b/Ryujinx.HLE/Switch.cs
@@ -6,6 +6,7 @@ using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services.Apm;
 using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.Loaders.Processes;
 using Ryujinx.HLE.Ui;
 using Ryujinx.Memory;
 using System;
@@ -20,7 +21,7 @@ namespace Ryujinx.HLE
         public GpuContext            Gpu               { get; }
         public VirtualFileSystem     FileSystem        { get; }
         public HOS.Horizon           System            { get; }
-        public ApplicationLoader     Application       { get; }
+        public ProcessLoader         Processes         { get; }
         public PerformanceStatistics Statistics        { get; }
         public Hid                   Hid               { get; }
         public TamperMachine         TamperMachine     { get; }
@@ -50,7 +51,7 @@ namespace Ryujinx.HLE
             System            = new HOS.Horizon(this);
             Statistics        = new PerformanceStatistics();
             Hid               = new Hid(this, System.HidStorage);
-            Application       = new ApplicationLoader(this);
+            Processes         = new ProcessLoader(this);
             TamperMachine     = new TamperMachine();
 
             System.State.SetLanguage(Configuration.SystemLanguage);
@@ -64,29 +65,29 @@ namespace Ryujinx.HLE
             System.GlobalAccessLogMode              = Configuration.FsGlobalAccessLogMode;
         }
 
-        public void LoadCart(string exeFsDir, string romFsFile = null)
+        public bool LoadCart(string exeFsDir, string romFsFile = null)
         {
-            Application.LoadCart(exeFsDir, romFsFile);
+            return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
         }
 
-        public void LoadXci(string xciFile)
+        public bool LoadXci(string xciFile)
         {
-            Application.LoadXci(xciFile);
+            return Processes.LoadXci(xciFile);
         }
 
-        public void LoadNca(string ncaFile)
+        public bool LoadNca(string ncaFile)
         {
-            Application.LoadNca(ncaFile);
+            return Processes.LoadNca(ncaFile);
         }
 
-        public void LoadNsp(string nspFile)
+        public bool LoadNsp(string nspFile)
         {
-            Application.LoadNsp(nspFile);
+            return Processes.LoadNsp(nspFile);
         }
 
-        public void LoadProgram(string fileName)
+        public bool LoadProgram(string fileName)
         {
-            Application.LoadProgram(fileName);
+            return Processes.LoadNxo(fileName);
         }
 
         public bool WaitFifo()
@@ -123,7 +124,7 @@ namespace Ryujinx.HLE
 
         public void EnableCheats()
         {
-            FileSystem.ModLoader.EnableCheats(Application.TitleId, TamperMachine);
+            FileSystem.ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine);
         }
 
         public bool IsAudioMuted()
@@ -152,4 +153,4 @@ namespace Ryujinx.HLE
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs
index f618e38d69..54ab18cdad 100644
--- a/Ryujinx.Headless.SDL2/Program.cs
+++ b/Ryujinx.Headless.SDL2/Program.cs
@@ -447,10 +447,10 @@ namespace Ryujinx.Headless.SDL2
 
         private static void SetupProgressHandler()
         {
-            if (_emulationContext.Application.DiskCacheLoadState != null)
+            if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
             {
-                _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
-                _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
+                _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
+                _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
             }
 
             _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -608,12 +608,24 @@ namespace Ryujinx.Headless.SDL2
                 if (romFsFiles.Length > 0)
                 {
                     Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
-                    _emulationContext.LoadCart(path, romFsFiles[0]);
+
+                    if (!_emulationContext.LoadCart(path, romFsFiles[0]))
+                    {
+                        _emulationContext.Dispose();
+
+                        return false;
+                    }
                 }
                 else
                 {
                     Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
-                    _emulationContext.LoadCart(path);
+
+                    if (!_emulationContext.LoadCart(path))
+                    {
+                        _emulationContext.Dispose();
+
+                        return false;
+                    }
                 }
             }
             else if (File.Exists(path))
@@ -622,27 +634,52 @@ namespace Ryujinx.Headless.SDL2
                 {
                     case ".xci":
                         Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
-                        _emulationContext.LoadXci(path);
+
+                        if (!_emulationContext.LoadXci(path))
+                        {
+                            _emulationContext.Dispose();
+
+                            return false;
+                        }
                         break;
                     case ".nca":
                         Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
-                        _emulationContext.LoadNca(path);
+
+                        if (!_emulationContext.LoadNca(path))
+                        {
+                            _emulationContext.Dispose();
+
+                            return false;
+                        }
                         break;
                     case ".nsp":
                     case ".pfs0":
                         Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
-                        _emulationContext.LoadNsp(path);
+
+                        if (!_emulationContext.LoadNsp(path))
+                        {
+                            _emulationContext.Dispose();
+
+                            return false;
+                        }
                         break;
                     default:
                         Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
                         try
                         {
-                            _emulationContext.LoadProgram(path);
+                            if (!_emulationContext.LoadProgram(path))
+                            {
+                                _emulationContext.Dispose();
+
+                                return false;
+                            }
                         }
                         catch (ArgumentOutOfRangeException)
                         {
                             Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
 
+                            _emulationContext.Dispose();
+
                             return false;
                         }
                         break;
@@ -664,4 +701,4 @@ namespace Ryujinx.Headless.SDL2
             return true;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs
index db6c8ec4d0..e337104219 100644
--- a/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -145,16 +145,14 @@ namespace Ryujinx.Headless.SDL2
 
         private void InitializeWindow()
         {
-            string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty
-                : $" - {Device.Application.TitleName}";
+            var activeProcess = Device.Processes.ActiveApplication;
+            var nacp = activeProcess.ApplicationControlProperties;
+            int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
 
-            string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty
-                : $" v{Device.Application.DisplayVersion}";
-
-            string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty
-                : $" ({Device.Application.TitleIdText.ToUpper()})";
-
-            string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
+            string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
+            string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
+            string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
+            string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
 
             WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags());
 
diff --git a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
index 43510d5ec5..113e9cb3ed 100644
--- a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
+++ b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
@@ -11,12 +11,12 @@ using LibHac.Tools.FsSystem.NcaUtils;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Loaders.Npdm;
 using Ryujinx.Ui.Common.Configuration.System;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Reflection;
 using System.Text;
@@ -112,9 +112,9 @@ namespace Ryujinx.Ui.App.Common
                             {
                                 return;
                             }
-                        
+
                             string extension = Path.GetExtension(app).ToLower();
-                        
+
                             if (!File.GetAttributes(app).HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso")
                             {
                                 applications.Add(app);
@@ -262,10 +262,9 @@ namespace Ryujinx.Ui.App.Common
                                             controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
 
                                             using MemoryStream stream = new();
-                                            
+
                                             icon.Get.AsStream().CopyTo(stream);
                                             applicationIcon = stream.ToArray();
-                                            
 
                                             if (applicationIcon != null)
                                             {
@@ -400,7 +399,7 @@ namespace Ryujinx.Ui.App.Common
                     });
 
                     if (appMetadata.LastPlayed != "Never")
-                    { 
+                    {
                         if (!DateTime.TryParse(appMetadata.LastPlayed, out _))
                         {
                             Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)");
@@ -470,7 +469,7 @@ namespace Ryujinx.Ui.App.Common
 
         private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
         {
-            (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0);
+            (_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
 
             // Return the ControlFS
             controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
@@ -766,12 +765,12 @@ namespace Ryujinx.Ui.App.Common
         private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
         {
             updatedControlFs = null;
-            
+
             string updatePath = "(unknown)";
 
             try
             {
-                (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
+                (Nca patchNca, Nca controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
 
                 if (patchNca != null && controlNca != null)
                 {
@@ -791,5 +790,119 @@ namespace Ryujinx.Ui.App.Common
 
             return false;
         }
+
+        public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
+        {
+            Nca mainNca = null;
+            Nca patchNca = null;
+            Nca controlNca = null;
+
+            fileSystem.ImportTickets(pfs);
+
+            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+            {
+                using var ncaFile = new UniqueRef<IFile>();
+
+                pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+                int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+                if (ncaProgramIndex != programIndex)
+                {
+                    continue;
+                }
+
+                if (nca.Header.ContentType == NcaContentType.Program)
+                {
+                    int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+                    if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+                    {
+                        patchNca = nca;
+                    }
+                    else
+                    {
+                        mainNca = nca;
+                    }
+                }
+                else if (nca.Header.ContentType == NcaContentType.Control)
+                {
+                    controlNca = nca;
+                }
+            }
+
+            return (mainNca, patchNca, controlNca);
+        }
+
+        public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
+        {
+            Nca patchNca = null;
+            Nca controlNca = null;
+
+            fileSystem.ImportTickets(pfs);
+
+            foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+            {
+                using var ncaFile = new UniqueRef<IFile>();
+
+                pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+                Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+                int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+                if (ncaProgramIndex != programIndex)
+                {
+                    continue;
+                }
+
+                if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
+                {
+                    break;
+                }
+
+                if (nca.Header.ContentType == NcaContentType.Program)
+                {
+                    patchNca = nca;
+                }
+                else if (nca.Header.ContentType == NcaContentType.Control)
+                {
+                    controlNca = nca;
+                }
+            }
+
+            return (patchNca, controlNca);
+        }
+
+        public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
+        {
+            updatePath = null;
+
+            if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+            {
+                // Clear the program index part.
+                titleIdBase &= ~0xFUL;
+
+                // Load update information if exists.
+                string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+
+                if (File.Exists(titleUpdateMetadataPath))
+                {
+                    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());
+
+                        return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
+                    }
+                }
+            }
+
+            return (null, null);
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index ace8b87f97..dca87abcb7 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -252,7 +252,7 @@ namespace Ryujinx
 
             if (CommandLineState.LaunchPathArg != null)
             {
-                mainWindow.LoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
+                mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
             }
 
             if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 6d3d4aad62..e0252016ff 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -590,10 +590,10 @@ namespace Ryujinx.Ui
 
         private void SetupProgressUiHandlers()
         {
-            if (_emulationContext.Application.DiskCacheLoadState != null)
+            if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
             {
-                _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
-                _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
+                _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
+                _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
             }
 
             _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -690,7 +690,111 @@ namespace Ryujinx.Ui
             }
         }
 
-        public void LoadApplication(string path, bool startFullscreen = false)
+        private bool LoadApplication(string path, bool isFirmwareTitle)
+        {
+            SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
+
+            if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError))
+            {
+                if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion))
+                {
+                    string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})";
+
+                    ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run();
+
+                    if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _))
+                    {
+                        UserErrorDialog.CreateUserErrorDialog(userError);
+
+                        return false;
+                    }
+
+                    // Tell the user that we installed a firmware for them.
+
+                    firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
+
+                    RefreshFirmwareLabel();
+
+                    message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start.";
+
+                    GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message);
+                }
+                else
+                {
+                    UserErrorDialog.CreateUserErrorDialog(userError);
+
+                    return false;
+                }
+            }
+
+            Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
+
+            if (isFirmwareTitle)
+            {
+                Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
+
+                return _emulationContext.LoadNca(path);
+            }
+
+            if (Directory.Exists(path))
+            {
+                string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
+
+                if (romFsFiles.Length == 0)
+                {
+                    romFsFiles = Directory.GetFiles(path, "*.romfs");
+                }
+
+                if (romFsFiles.Length > 0)
+                {
+                    Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
+
+                    return _emulationContext.LoadCart(path, romFsFiles[0]);
+                }
+
+                Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
+
+                return _emulationContext.LoadCart(path);
+            }
+
+            if (File.Exists(path))
+            {
+                switch (System.IO.Path.GetExtension(path).ToLowerInvariant())
+                {
+                    case ".xci":
+                        Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
+
+                        return _emulationContext.LoadXci(path);
+                    case ".nca":
+                        Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
+
+                        return _emulationContext.LoadNca(path);
+                    case ".nsp":
+                    case ".pfs0":
+                        Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
+
+                        return _emulationContext.LoadNsp(path);
+                    default:
+                        Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
+                        try
+                        {
+                            return _emulationContext.LoadProgram(path);
+                        }
+                        catch (ArgumentOutOfRangeException)
+                        {
+                            Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
+
+                            return false;
+                        }
+                }
+            }
+
+            Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
+
+            return false;
+        }
+
+        public void RunApplication(string path, bool startFullscreen = false)
         {
             if (_gameLoaded)
             {
@@ -710,9 +814,6 @@ namespace Ryujinx.Ui
 
                 UpdateGraphicsConfig();
 
-                SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
-
-                bool isDirectory     = Directory.Exists(path);
                 bool isFirmwareTitle = false;
 
                 if (path.StartsWith("@SystemContent"))
@@ -722,124 +823,10 @@ namespace Ryujinx.Ui
                     isFirmwareTitle = true;
                 }
 
-                if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError))
+                if (!LoadApplication(path, isFirmwareTitle))
                 {
-                    if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion))
-                    {
-                        if (userError == UserError.NoFirmware)
-                        {
-                            string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})";
-
-                            ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run();
-
-                            if (responseDialog != ResponseType.Yes)
-                            {
-                                UserErrorDialog.CreateUserErrorDialog(userError);
-
-                                _emulationContext.Dispose();
-                                SwitchToGameTable();
-
-                                return;
-                            }
-                        }
-
-                        if (!SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _))
-                        {
-                            UserErrorDialog.CreateUserErrorDialog(userError);
-
-                            _emulationContext.Dispose();
-                            SwitchToGameTable();
-
-                            return;
-                        }
-
-                        // Tell the user that we installed a firmware for them.
-                        if (userError == UserError.NoFirmware)
-                        {
-                            firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
-
-                            RefreshFirmwareLabel();
-
-                            string message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start.";
-
-                            GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message);
-                        }
-                    }
-                    else
-                    {
-                        UserErrorDialog.CreateUserErrorDialog(userError);
-
-                        _emulationContext.Dispose();
-                        SwitchToGameTable();
-
-                        return;
-                    }
-                }
-
-                Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
-
-                if (isFirmwareTitle)
-                {
-                    Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
-
-                    _emulationContext.LoadNca(path);
-                }
-                else if (Directory.Exists(path))
-                {
-                    string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
-
-                    if (romFsFiles.Length == 0)
-                    {
-                        romFsFiles = Directory.GetFiles(path, "*.romfs");
-                    }
-
-                    if (romFsFiles.Length > 0)
-                    {
-                        Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
-                        _emulationContext.LoadCart(path, romFsFiles[0]);
-                    }
-                    else
-                    {
-                        Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
-                        _emulationContext.LoadCart(path);
-                    }
-                }
-                else if (File.Exists(path))
-                {
-                    switch (System.IO.Path.GetExtension(path).ToLowerInvariant())
-                    {
-                        case ".xci":
-                            Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
-                            _emulationContext.LoadXci(path);
-                            break;
-                        case ".nca":
-                            Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
-                            _emulationContext.LoadNca(path);
-                            break;
-                        case ".nsp":
-                        case ".pfs0":
-                            Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
-                            _emulationContext.LoadNsp(path);
-                            break;
-                        default:
-                            Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
-                            try
-                            {
-                                _emulationContext.LoadProgram(path);
-                            }
-                            catch (ArgumentOutOfRangeException)
-                            {
-                                Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
-                            }
-                            break;
-                    }
-                }
-                else
-                {
-                    Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
-
                     _emulationContext.Dispose();
-                    RendererWidget.Dispose();
+                    SwitchToGameTable();
 
                     return;
                 }
@@ -852,10 +839,7 @@ namespace Ryujinx.Ui
 
                 Translator.IsReadyForTranslation.Reset();
 
-                Thread windowThread = new Thread(() =>
-                {
-                    CreateGameWindow();
-                })
+                Thread windowThread = new(CreateGameWindow)
                 {
                     Name = "GUI.WindowThread"
                 };
@@ -871,9 +855,10 @@ namespace Ryujinx.Ui
                 _firmwareInstallFile.Sensitive      = false;
                 _firmwareInstallDirectory.Sensitive = false;
 
-                DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Application.TitleIdText, _emulationContext.Application.TitleName);
+                DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText,
+                                                              _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString());
 
-                _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Application.TitleIdText, appMetadata =>
+                _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata =>
                 {
                     appMetadata.LastPlayed = DateTime.UtcNow.ToString();
                 });
@@ -1055,7 +1040,7 @@ namespace Ryujinx.Ui
 
             if (_emulationContext != null)
             {
-                UpdateGameMetadata(_emulationContext.Application.TitleIdText);
+                UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
 
                 if (RendererWidget != null)
                 {
@@ -1174,7 +1159,7 @@ namespace Ryujinx.Ui
 
             string path = (string)_tableStore.GetValue(treeIter, 9);
 
-            LoadApplication(path);
+            RunApplication(path);
         }
 
         private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
@@ -1260,7 +1245,7 @@ namespace Ryujinx.Ui
 
                 if (fileChooser.Run() == (int)ResponseType.Accept)
                 {
-                    LoadApplication(fileChooser.Filename);
+                    RunApplication(fileChooser.Filename);
                 }
             }
         }
@@ -1271,7 +1256,7 @@ namespace Ryujinx.Ui
             {
                 if (fileChooser.Run() == (int)ResponseType.Accept)
                 {
-                    LoadApplication(fileChooser.Filename);
+                    RunApplication(fileChooser.Filename);
                 }
             }
         }
@@ -1287,7 +1272,7 @@ namespace Ryujinx.Ui
         {
             string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
 
-            LoadApplication(contentPath);
+            RunApplication(contentPath);
         }
 
         private void Open_Ryu_Folder(object sender, EventArgs args)
@@ -1328,7 +1313,7 @@ namespace Ryujinx.Ui
         {
             if (_emulationContext != null)
             {
-                UpdateGameMetadata(_emulationContext.Application.TitleIdText);
+                UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
             }
 
             _pauseEmulation.Sensitive = false;
@@ -1533,7 +1518,7 @@ namespace Ryujinx.Ui
             {
                 _userChannelPersistence.ShouldRestart = false;
 
-                LoadApplication(_currentEmulatedGamePath);
+                RunApplication(_currentEmulatedGamePath);
             }
             else
             {
@@ -1596,7 +1581,9 @@ namespace Ryujinx.Ui
 
         private void ManageCheats_Pressed(object sender, EventArgs args)
         {
-           var window = new CheatWindow(_virtualFileSystem, _emulationContext.Application.TitleId, _emulationContext.Application.TitleName);
+           var window = new CheatWindow(_virtualFileSystem,
+                                        _emulationContext.Processes.ActiveApplication.ProgramId,
+                                        _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString());
 
             window.Destroyed += CheatWindow_Destroyed;
             window.Show();
@@ -1639,7 +1626,7 @@ namespace Ryujinx.Ui
                     LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll,
                     LastScannedAmiiboId      = _lastScannedAmiiboId,
                     DeviceId                 = deviceId,
-                    TitleId                  = _emulationContext.Application.TitleIdText.ToUpper()
+                    TitleId                  = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper()
                 };
 
                 amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent;
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index e5d22d65c3..7cb5b32750 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -495,16 +495,14 @@ namespace Ryujinx.Ui
             {
                 parent.Present();
 
-                string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty
-                    : $" - {Device.Application.TitleName}";
+                var activeProcess   = Device.Processes.ActiveApplication;
+                var nacp            = activeProcess.ApplicationControlProperties;
+                int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
 
-                string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty
-                    : $" v{Device.Application.DisplayVersion}";
-
-                string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty
-                    : $" ({Device.Application.TitleIdText.ToUpper()})";
-
-                string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
+                string titleNameSection    = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
+                string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString())              ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
+                string titleIdSection      = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText)                       ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
+                string titleArchSection    = activeProcess.Is64Bit                                                        ? " (64-bit)"  : " (32-bit)";
 
                 parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
             });
@@ -612,7 +610,7 @@ namespace Ryujinx.Ui
                     {
                         if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
                         {
-                            Device.Application.DiskCacheLoadState?.Cancel();
+                            Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
                         }
                     }
                 });
diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index a63d68ff2d..558288aab3 100644
--- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -15,6 +15,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.Ui.App.Common;
 using Ryujinx.Ui.Common.Configuration;
 using Ryujinx.Ui.Common.Helper;
 using Ryujinx.Ui.Windows;
@@ -260,7 +261,7 @@ namespace Ryujinx.Ui.Widgets
                             return;
                         }
 
-                        (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
+                        (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
 
                         if (updatePatchNca != null)
                         {
diff --git a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
index 4aea589556..fce751da11 100644
--- a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
+++ b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
@@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
 using Ryujinx.Common.Configuration;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
+using Ryujinx.Ui.App.Common;
 using Ryujinx.Ui.Widgets;
 using System;
 using System.Collections.Generic;
@@ -94,7 +95,7 @@ namespace Ryujinx.Ui.Windows
 
                     try
                     {
-                        (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
+                        (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
 
                         if (controlNca != null && patchNca != null)
                         {