aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAc_K <Acoustik666@gmail.com>2023-03-31 21:16:46 +0200
committerGitHub <noreply@github.com>2023-03-31 21:16:46 +0200
commit4c2d9ff3ff9d7afb1fd0bd764bee5931fa5f053c (patch)
tree1245f5ec356551bd20a9594d114d06a53c41d036
parent8198b99935f562ffb2fb9a75175a8df24d235152 (diff)
HLE: Refactoring of ApplicationLoader (#4480)1.1.689
* HLE: Refactoring of ApplicationLoader * Fix SDL2 Headless * Addresses gdkchan feedback * Fixes LoadUnpackedNca RomFS loading * remove useless casting * Cleanup and fixe empty application name * Remove ProcessInfo * Fixes typo * ActiveProcess to ActiveApplication * Update check * Clean using. * Use the correct filepath when loading Homebrew.npdm * Fix NRE in ProcessResult if MetaLoader is null * Add more checks for valid processId & return success * Add missing logging statement for npdm error * Return result for LoadKip() * Move error logging out of PFS load extension method This avoids logging "Could not find Main NCA" followed by "Loading main..." when trying to start hbl. * Fix GUIs not checking load results * Fix style and formatting issues * Fix formatting and wording * gtk: Refactor LoadApplication() --------- Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
-rw-r--r--Ryujinx.Ava/AppHost.cs77
-rw-r--r--Ryujinx.Ava/Common/ApplicationHelper.cs3
-rw-r--r--Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs10
-rw-r--r--Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs3
-rw-r--r--Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs12
-rw-r--r--Ryujinx.HLE/FileSystem/VirtualFileSystem.cs6
-rw-r--r--Ryujinx.HLE/HOS/ApplicationLoader.cs908
-rw-r--r--Ryujinx.HLE/HOS/Horizon.cs5
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs2
-rw-r--r--Ryujinx.HLE/HOS/ModLoader.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs4
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs35
-rw-r--r--Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs6
-rw-r--r--Ryujinx.HLE/HOS/Services/Fatal/IService.cs4
-rw-r--r--Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs4
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs7
-rw-r--r--Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs133
-rw-r--r--Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs39
-rw-r--r--Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs61
-rw-r--r--Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs175
-rw-r--r--Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs177
-rw-r--r--Ryujinx.HLE/Loaders/Processes/ProcessConst.cs33
-rw-r--r--Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs244
-rw-r--r--Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs (renamed from Ryujinx.HLE/HOS/ProgramLoader.cs)275
-rw-r--r--Ryujinx.HLE/Loaders/Processes/ProcessResult.cs92
-rw-r--r--Ryujinx.HLE/Switch.cs29
-rw-r--r--Ryujinx.Headless.SDL2/Program.cs57
-rw-r--r--Ryujinx.Headless.SDL2/WindowBase.cs18
-rw-r--r--Ryujinx.Ui.Common/App/ApplicationLibrary.cs131
-rw-r--r--Ryujinx/Program.cs2
-rw-r--r--Ryujinx/Ui/MainWindow.cs249
-rw-r--r--Ryujinx/Ui/RendererWidgetBase.cs18
-rw-r--r--Ryujinx/Ui/Widgets/GameTableContextMenu.cs3
-rw-r--r--Ryujinx/Ui/Windows/TitleUpdateWindow.cs3
41 files changed, 1560 insertions, 1315 deletions
diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs
index eb22b39e9..3cdb3906f 100644
--- a/Ryujinx.Ava/AppHost.cs
+++ b/Ryujinx.Ava/AppHost.cs
@@ -320,10 +320,14 @@ namespace Ryujinx.Ava
320 320
321 _viewModel.IsGameRunning = true; 321 _viewModel.IsGameRunning = true;
322 322
323 string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}"; 323 var activeProcess = Device.Processes.ActiveApplication;
324 string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}"; 324 var nacp = activeProcess.ApplicationControlProperties;
325 string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})"; 325 int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
326 string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; 326
327 string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
328 string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
329 string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
330 string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
327 331
328 Dispatcher.UIThread.InvokeAsync(() => 332 Dispatcher.UIThread.InvokeAsync(() =>
329 { 333 {
@@ -423,9 +427,9 @@ namespace Ryujinx.Ava
423 427
424 private void Dispose() 428 private void Dispose()
425 { 429 {
426 if (Device.Application != null) 430 if (Device.Processes != null)
427 { 431 {
428 _viewModel.UpdateGameMetadata(Device.Application.TitleIdText); 432 _viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
429 } 433 }
430 434
431 ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; 435 ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
@@ -539,7 +543,12 @@ namespace Ryujinx.Ava
539 { 543 {
540 Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); 544 Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
541 545
542 Device.LoadNca(ApplicationPath); 546 if (!Device.LoadNca(ApplicationPath))
547 {
548 Device.Dispose();
549
550 return false;
551 }
543 } 552 }
544 else if (Directory.Exists(ApplicationPath)) 553 else if (Directory.Exists(ApplicationPath))
545 { 554 {
@@ -554,13 +563,23 @@ namespace Ryujinx.Ava
554 { 563 {
555 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); 564 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
556 565
557 Device.LoadCart(ApplicationPath, romFsFiles[0]); 566 if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
567 {
568 Device.Dispose();
569
570 return false;
571 }
558 } 572 }
559 else 573 else
560 { 574 {
561 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); 575 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
562 576
563 Device.LoadCart(ApplicationPath); 577 if (!Device.LoadCart(ApplicationPath))
578 {
579 Device.Dispose();
580
581 return false;
582 }
564 } 583 }
565 } 584 }
566 else if (File.Exists(ApplicationPath)) 585 else if (File.Exists(ApplicationPath))
@@ -571,7 +590,12 @@ namespace Ryujinx.Ava
571 { 590 {
572 Logger.Info?.Print(LogClass.Application, "Loading as XCI."); 591 Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
573 592
574 Device.LoadXci(ApplicationPath); 593 if (!Device.LoadXci(ApplicationPath))
594 {
595 Device.Dispose();
596
597 return false;
598 }
575 599
576 break; 600 break;
577 } 601 }
@@ -579,7 +603,12 @@ namespace Ryujinx.Ava
579 { 603 {
580 Logger.Info?.Print(LogClass.Application, "Loading as NCA."); 604 Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
581 605
582 Device.LoadNca(ApplicationPath); 606 if (!Device.LoadNca(ApplicationPath))
607 {
608 Device.Dispose();
609
610 return false;
611 }
583 612
584 break; 613 break;
585 } 614 }
@@ -588,7 +617,12 @@ namespace Ryujinx.Ava
588 { 617 {
589 Logger.Info?.Print(LogClass.Application, "Loading as NSP."); 618 Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
590 619
591 Device.LoadNsp(ApplicationPath); 620 if (!Device.LoadNsp(ApplicationPath))
621 {
622 Device.Dispose();
623
624 return false;
625 }
592 626
593 break; 627 break;
594 } 628 }
@@ -598,13 +632,18 @@ namespace Ryujinx.Ava
598 632
599 try 633 try
600 { 634 {
601 Device.LoadProgram(ApplicationPath); 635 if (!Device.LoadProgram(ApplicationPath))
636 {
637 Device.Dispose();
638
639 return false;
640 }
602 } 641 }
603 catch (ArgumentOutOfRangeException) 642 catch (ArgumentOutOfRangeException)
604 { 643 {
605 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); 644 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
606 645
607 Dispose(); 646 Device.Dispose();
608 647
609 return false; 648 return false;
610 } 649 }
@@ -617,14 +656,14 @@ namespace Ryujinx.Ava
617 { 656 {
618 Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); 657 Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
619 658
620 Dispose(); 659 Device.Dispose();
621 660
622 return false; 661 return false;
623 } 662 }
624 663
625 DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName); 664 DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
626 665
627 _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata => 666 _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
628 { 667 {
629 appMetadata.LastPlayed = DateTime.UtcNow.ToString(); 668 appMetadata.LastPlayed = DateTime.UtcNow.ToString();
630 }); 669 });
@@ -950,7 +989,7 @@ namespace Ryujinx.Ava
950 { 989 {
951 if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) 990 if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
952 { 991 {
953 Device.Application.DiskCacheLoadState?.Cancel(); 992 Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
954 } 993 }
955 }); 994 });
956 995
@@ -1088,4 +1127,4 @@ namespace Ryujinx.Ava
1088 return state; 1127 return state;
1089 } 1128 }
1090 } 1129 }
1091} \ No newline at end of file 1130}
diff --git a/Ryujinx.Ava/Common/ApplicationHelper.cs b/Ryujinx.Ava/Common/ApplicationHelper.cs
index 276d18745..161ef8596 100644
--- a/Ryujinx.Ava/Common/ApplicationHelper.cs
+++ b/Ryujinx.Ava/Common/ApplicationHelper.cs
@@ -19,6 +19,7 @@ using Ryujinx.Common.Logging;
19using Ryujinx.HLE.FileSystem; 19using Ryujinx.HLE.FileSystem;
20using Ryujinx.HLE.HOS; 20using Ryujinx.HLE.HOS;
21using Ryujinx.HLE.HOS.Services.Account.Acc; 21using Ryujinx.HLE.HOS.Services.Account.Acc;
22using Ryujinx.Ui.App.Common;
22using Ryujinx.Ui.Common.Helper; 23using Ryujinx.Ui.Common.Helper;
23using System; 24using System;
24using System.Buffers; 25using System.Buffers;
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
227 return; 228 return;
228 } 229 }
229 230
230 (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); 231 (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
231 if (updatePatchNca != null) 232 if (updatePatchNca != null)
232 { 233 {
233 patchNca = updatePatchNca; 234 patchNca = updatePatchNca;
diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index a3663af3e..d5ff78545 100644
--- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
1208 1208
1209 public void SetUIProgressHandlers(Switch emulationContext) 1209 public void SetUIProgressHandlers(Switch emulationContext)
1210 { 1210 {
1211 if (emulationContext.Application.DiskCacheLoadState != null) 1211 if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
1212 { 1212 {
1213 emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; 1213 emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
1214 emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; 1214 emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
1215 } 1215 }
1216 1216
1217 emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; 1217 emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -1705,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels
1705 1705
1706 if (string.IsNullOrWhiteSpace(titleName)) 1706 if (string.IsNullOrWhiteSpace(titleName))
1707 { 1707 {
1708 LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName); 1708 LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
1709 TitleName = AppHost.Device.Application.TitleName; 1709 TitleName = AppHost.Device.Processes.ActiveApplication.Name;
1710 } 1710 }
1711 1711
1712 SwitchToRenderer(startFullscreen); 1712 SwitchToRenderer(startFullscreen);
diff --git a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
index dd9e1b961..0798502cd 100644
--- a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
+++ b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
@@ -17,6 +17,7 @@ using Ryujinx.Common.Logging;
17using Ryujinx.Common.Utilities; 17using Ryujinx.Common.Utilities;
18using Ryujinx.HLE.FileSystem; 18using Ryujinx.HLE.FileSystem;
19using Ryujinx.HLE.HOS; 19using Ryujinx.HLE.HOS;
20using Ryujinx.Ui.App.Common;
20using System; 21using System;
21using System.Collections.Generic; 22using System.Collections.Generic;
22using System.IO; 23using System.IO;
@@ -162,7 +163,7 @@ public class TitleUpdateViewModel : BaseModel
162 163
163 try 164 try
164 { 165 {
165 (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); 166 (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
166 167
167 if (controlNca != null && patchNca != null) 168 if (controlNca != null && patchNca != null)
168 { 169 {
diff --git a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
index 1c6f4265c..30d411508 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
126 126
127 if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) 127 if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
128 { 128 {
129 string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper(); 129 string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
130 AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); 130 AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
131 131
132 await window.ShowDialog(Window); 132 await window.ShowDialog(Window);
@@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main
148 return; 148 return;
149 } 149 }
150 150
151 ApplicationLoader application = ViewModel.AppHost.Device.Application; 151 string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
152 if (application != null)
153 {
154 await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
155 152
156 ViewModel.AppHost.Device.EnableCheats(); 153 await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
157 } 154
155 ViewModel.AppHost.Device.EnableCheats();
158 } 156 }
159 157
160 private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) 158 private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
index 3f94ce61b..1b3968eab 100644
--- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
+++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
@@ -20,7 +20,6 @@ using System.Collections.Concurrent;
20using System.Collections.Generic; 20using System.Collections.Generic;
21using System.IO; 21using System.IO;
22using System.Runtime.CompilerServices; 22using System.Runtime.CompilerServices;
23
24using Path = System.IO.Path; 23using Path = System.IO.Path;
25using RightsId = LibHac.Fs.RightsId; 24using RightsId = LibHac.Fs.RightsId;
26 25
@@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem
146 145
147 return $"{basePath}:/{fileName}"; 146 return $"{basePath}:/{fileName}";
148 } 147 }
148
149 return null; 149 return null;
150 } 150 }
151 151
@@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem
191 fsServerClient = horizon.CreatePrivilegedHorizonClient(); 191 fsServerClient = horizon.CreatePrivilegedHorizonClient();
192 var fsServer = new FileSystemServer(fsServerClient); 192 var fsServer = new FileSystemServer(fsServerClient);
193 193
194 RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer); 194 RandomDataGenerator randomGenerator = Random.Shared.NextBytes;
195 195
196 DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator); 196 DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
197 197
@@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem
264 264
265 if (result.IsSuccess()) 265 if (result.IsSuccess())
266 { 266 {
267 Ticket ticket = new Ticket(ticketFile.Get.AsStream()); 267 Ticket ticket = new(ticketFile.Get.AsStream());
268 var titleKey = ticket.GetTitleKey(KeySet); 268 var titleKey = ticket.GetTitleKey(KeySet);
269 269
270 if (titleKey != null) 270 if (titleKey != null)
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
deleted file mode 100644
index 82bd9b312..000000000
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ /dev/null
@@ -1,908 +0,0 @@
1using LibHac;
2using LibHac.Account;
3using LibHac.Common;
4using LibHac.Fs;
5using LibHac.Fs.Fsa;
6using LibHac.Fs.Shim;
7using LibHac.FsSystem;
8using LibHac.Loader;
9using LibHac.Ncm;
10using LibHac.Ns;
11using LibHac.Tools.Fs;
12using LibHac.Tools.FsSystem;
13using LibHac.Tools.FsSystem.NcaUtils;
14using Ryujinx.Common.Configuration;
15using Ryujinx.Common.Logging;
16using Ryujinx.Cpu;
17using Ryujinx.HLE.FileSystem;
18using Ryujinx.HLE.Loaders.Executables;
19using Ryujinx.Memory;
20using System;
21using System.Collections.Generic;
22using System.Globalization;
23using System.IO;
24using System.Linq;
25using System.Reflection;
26using System.Text;
27using static Ryujinx.HLE.HOS.ModLoader;
28using ApplicationId = LibHac.Ncm.ApplicationId;
29using Path = System.IO.Path;
30
31namespace Ryujinx.HLE.HOS
32{
33 using JsonHelper = Common.Utilities.JsonHelper;
34
35 public class ApplicationLoader
36 {
37 // Binaries from exefs are loaded into mem in this order. Do not change.
38 internal static readonly string[] ExeFsPrefixes =
39 {
40 "rtld",
41 "main",
42 "subsdk0",
43 "subsdk1",
44 "subsdk2",
45 "subsdk3",
46 "subsdk4",
47 "subsdk5",
48 "subsdk6",
49 "subsdk7",
50 "subsdk8",
51 "subsdk9",
52 "sdk"
53 };
54
55 private readonly Switch _device;
56 private string _titleName;
57 private string _displayVersion;
58 private BlitStruct<ApplicationControlProperty> _controlData;
59
60 public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
61 public string TitleName => _titleName;
62 public string DisplayVersion => _displayVersion;
63
64 public ulong TitleId { get; private set; }
65 public bool TitleIs64Bit { get; private set; }
66
67 public string TitleIdText => TitleId.ToString("x16");
68
69 public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
70
71 public ApplicationLoader(Switch device)
72 {
73 _device = device;
74 _controlData = new BlitStruct<ApplicationControlProperty>(1);
75 }
76
77 public void LoadCart(string exeFsDir, string romFsFile = null)
78 {
79 LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
80
81 MetaLoader metaData = ReadNpdm(codeFs);
82
83 _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
84 new[] { TitleId },
85 _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
86 _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
87
88 if (TitleId != 0)
89 {
90 EnsureSaveData(new ApplicationId(TitleId));
91 }
92
93 ulong pid = LoadExeFs(codeFs, string.Empty, metaData);
94
95 if (romFsFile != null)
96 {
97 _device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile);
98 }
99 }
100
101 public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
102 {
103 Nca mainNca = null;
104 Nca patchNca = null;
105 Nca controlNca = null;
106
107 fileSystem.ImportTickets(pfs);
108
109 foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
110 {
111 using var ncaFile = new UniqueRef<IFile>();
112
113 pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
114
115 Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
116
117 int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
118
119 if (ncaProgramIndex != programIndex)
120 {
121 continue;
122 }
123
124 if (nca.Header.ContentType == NcaContentType.Program)
125 {
126 int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
127
128 if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
129 {
130 patchNca = nca;
131 }
132 else
133 {
134 mainNca = nca;
135 }
136 }
137 else if (nca.Header.ContentType == NcaContentType.Control)
138 {
139 controlNca = nca;
140 }
141 }
142
143 return (mainNca, patchNca, controlNca);
144 }
145
146 public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
147 {
148 Nca patchNca = null;
149 Nca controlNca = null;
150
151 fileSystem.ImportTickets(pfs);
152
153 foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
154 {
155 using var ncaFile = new UniqueRef<IFile>();
156
157 pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
158
159 Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
160
161 int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
162
163 if (ncaProgramIndex != programIndex)
164 {
165 continue;
166 }
167
168 if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
169 {
170 break;
171 }
172
173 if (nca.Header.ContentType == NcaContentType.Program)
174 {
175 patchNca = nca;
176 }
177 else if (nca.Header.ContentType == NcaContentType.Control)
178 {
179 controlNca = nca;
180 }
181 }
182
183 return (patchNca, controlNca);
184 }
185
186 public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
187 {
188 updatePath = null;
189
190 if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
191 {
192 // Clear the program index part.
193 titleIdBase &= 0xFFFFFFFFFFFFFFF0;
194
195 // Load update informations if existing.
196 string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
197
198 if (File.Exists(titleUpdateMetadataPath))
199 {
200 updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
201
202 if (File.Exists(updatePath))
203 {
204 FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
205 PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
206
207 return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
208 }
209 }
210 }
211
212 return (null, null);
213 }
214
215 public void LoadXci(string xciFile)
216 {
217 FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
218 Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage());
219
220 if (!xci.HasPartition(XciPartitionType.Secure))
221 {
222 Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
223
224 return;
225 }
226
227 PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
228
229 Nca mainNca;
230 Nca patchNca;
231 Nca controlNca;
232
233 try
234 {
235 (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
236
237 RegisterProgramMapInfo(securePartition).ThrowIfFailure();
238 }
239 catch (Exception e)
240 {
241 Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}");
242
243 return;
244 }
245
246 if (mainNca == null)
247 {
248 Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
249
250 return;
251 }
252
253 _device.Configuration.ContentManager.LoadEntries(_device);
254 _device.Configuration.ContentManager.ClearAocData();
255 _device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
256
257 LoadNca(mainNca, patchNca, controlNca);
258 }
259
260 public void LoadNsp(string nspFile)
261 {
262 FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
263 PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
264
265 Nca mainNca;
266 Nca patchNca;
267 Nca controlNca;
268
269 try
270 {
271 (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
272
273 RegisterProgramMapInfo(nsp).ThrowIfFailure();
274 }
275 catch (Exception e)
276 {
277 Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}");
278
279 return;
280 }
281
282 if (mainNca != null)
283 {
284 _device.Configuration.ContentManager.ClearAocData();
285 _device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
286
287 LoadNca(mainNca, patchNca, controlNca);
288
289 return;
290 }
291
292 // This is not a normal NSP, it's actually a ExeFS as a NSP
293 LoadExeFs(nsp, null, isHomebrew: true);
294 }
295
296 public void LoadNca(string ncaFile)
297 {
298 FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
299 Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
300
301 LoadNca(nca, null, null);
302 }
303
304 public void LoadServiceNca(string ncaFile)
305 {
306 FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
307 Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
308
309 if (mainNca.Header.ContentType != NcaContentType.Program)
310 {
311 Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
312
313 return;
314 }
315
316 IFileSystem codeFs = null;
317
318 if (mainNca.CanOpenSection(NcaSectionType.Code))
319 {
320 codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
321 }
322
323 if (codeFs == null)
324 {
325 Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
326
327 return;
328 }
329
330 using var npdmFile = new UniqueRef<IFile>();
331
332 Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
333
334 MetaLoader metaData;
335
336 npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
337
338 var npdmBuffer = new byte[fileSize];
339 npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
340
341 metaData = new MetaLoader();
342 metaData.Load(npdmBuffer).ThrowIfFailure();
343
344 NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
345
346 for (int i = 0; i < nsos.Length; i++)
347 {
348 string name = ExeFsPrefixes[i];
349
350 if (!codeFs.FileExists($"/{name}"))
351 {
352 continue; // File doesn't exist, skip.
353 }
354
355 Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
356
357 using var nsoFile = new UniqueRef<IFile>();
358
359 codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
360
361 nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
362 }
363
364 // Collect the nsos, ignoring ones that aren't used.
365 NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
366
367 string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
368 bool usePtc = _device.System.EnablePtc;
369
370 metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
371 ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false);
372 ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
373
374 string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16");
375 bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0;
376
377 string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0');
378
379 Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
380 }
381
382 private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
383 {
384 if (mainNca.Header.ContentType != NcaContentType.Program)
385 {
386 Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
387
388 return;
389 }
390
391 IStorage dataStorage = null;
392 IFileSystem codeFs = null;
393
394 (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _);
395
396 if (updatePatchNca != null)
397 {
398 patchNca = updatePatchNca;
399 }
400
401 if (updateControlNca != null)
402 {
403 controlNca = updateControlNca;
404 }
405
406 // Load program 0 control NCA as we are going to need it for display version.
407 (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
408
409 // Load Aoc
410 string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
411
412 if (File.Exists(titleAocMetadataPath))
413 {
414 List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
415
416 foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
417 {
418 foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
419 {
420 if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
421 {
422 _device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
423 }
424 else
425 {
426 Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
427 }
428 }
429 }
430 }
431
432 if (patchNca == null)
433 {
434 if (mainNca.CanOpenSection(NcaSectionType.Data))
435 {
436 dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
437 }
438
439 if (mainNca.CanOpenSection(NcaSectionType.Code))
440 {
441 codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
442 }
443 }
444 else
445 {
446 if (patchNca.CanOpenSection(NcaSectionType.Data))
447 {
448 dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
449 }
450
451 if (patchNca.CanOpenSection(NcaSectionType.Code))
452 {
453 codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
454 }
455 }
456
457 if (codeFs == null)
458 {
459 Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
460
461 return;
462 }
463
464 MetaLoader metaData = ReadNpdm(codeFs);
465
466 _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
467 _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
468 _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
469 _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
470
471 string displayVersion = string.Empty;
472
473 if (controlNca != null)
474 {
475 ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion);
476 }
477 else
478 {
479 ControlData.ByteSpan.Clear();
480 }
481
482 // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
483 // BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
484 if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
485 {
486 string dummyTitleName = "";
487 BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
488
489 ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion);
490 }
491
492 _displayVersion = displayVersion;
493
494 ulong pid = LoadExeFs(codeFs, displayVersion, metaData);
495
496 if (dataStorage == null)
497 {
498 Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
499 }
500 else
501 {
502 IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
503
504 _device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read));
505 }
506
507 // Don't create save data for system programs.
508 if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
509 {
510 // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
511 // We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
512 EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
513 }
514
515 Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
516 }
517
518 // Sets TitleId, so be sure to call before using it
519 private MetaLoader ReadNpdm(IFileSystem fs)
520 {
521 using var npdmFile = new UniqueRef<IFile>();
522
523 Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
524
525 MetaLoader metaData;
526
527 if (ResultFs.PathNotFound.Includes(result))
528 {
529 Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
530
531 metaData = GetDefaultNpdm();
532 }
533 else
534 {
535 npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
536
537 var npdmBuffer = new byte[fileSize];
538 npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
539
540 metaData = new MetaLoader();
541 metaData.Load(npdmBuffer).ThrowIfFailure();
542 }
543
544 metaData.GetNpdm(out var npdm).ThrowIfFailure();
545
546 TitleId = npdm.Aci.ProgramId.Value;
547 TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
548 _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
549
550 return metaData;
551 }
552
553 private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
554 {
555 using var controlFile = new UniqueRef<IFile>();
556
557 IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
558 Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
559
560 if (result.IsSuccess())
561 {
562 result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
563
564 if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
565 {
566 titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
567
568 if (string.IsNullOrWhiteSpace(titleName))
569 {
570 titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
571 }
572
573 displayVersion = controlData.Value.DisplayVersionString.ToString();
574 }
575 }
576 else
577 {
578 controlData.ByteSpan.Clear();
579 }
580 }
581
582 private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
583 {
584 if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
585 {
586 metaData = null; // TODO: Check if we should retain old npdm.
587 }
588
589 metaData ??= ReadNpdm(codeFs);
590
591 NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
592
593 for (int i = 0; i < nsos.Length; i++)
594 {
595 string name = ExeFsPrefixes[i];
596
597 if (!codeFs.FileExists($"/{name}"))
598 {
599 continue; // File doesn't exist, skip.
600 }
601
602 Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
603
604 using var nsoFile = new UniqueRef<IFile>();
605
606 codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
607
608 nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
609 }
610
611 // ExeFs file replacements.
612 ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
613
614 // Collect the nsos, ignoring ones that aren't used.
615 NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
616
617 // Take the npdm from mods if present.
618 if (modLoadResult.Npdm != null)
619 {
620 metaData = modLoadResult.Npdm;
621 }
622
623 _device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
624
625 _device.Configuration.ContentManager.LoadEntries(_device);
626
627 bool usePtc = _device.System.EnablePtc;
628
629 // Don't use PPTC if ExeFs files have been replaced.
630 usePtc &= !modLoadResult.Modified;
631
632 if (_device.System.EnablePtc && !usePtc)
633 {
634 Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
635 }
636
637 Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
638 _device.Gpu.HostInitalized.Set();
639
640 MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
641
642 if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
643 {
644 memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
645 }
646
647 // We allow it for nx-hbloader because it can be used to launch homebrew.
648 bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
649
650 metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
651 ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit);
652 ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
653
654 DiskCacheLoadState = result.DiskCacheLoadState;
655
656 _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
657
658 return result.ProcessId;
659 }
660
661 public void LoadProgram(string filePath)
662 {
663 MetaLoader metaData = GetDefaultNpdm();
664 metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
665 ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true);
666
667 bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
668
669 IExecutable executable;
670 Stream romfsStream = null;
671
672 if (isNro)
673 {
674 FileStream input = new FileStream(filePath, FileMode.Open);
675 NroExecutable obj = new NroExecutable(input.AsStorage());
676
677 executable = obj;
678
679 // Homebrew NRO can actually have some data after the actual NRO.
680 if (input.Length > obj.FileSize)
681 {
682 input.Position = obj.FileSize;
683
684 BinaryReader reader = new BinaryReader(input);
685
686 uint asetMagic = reader.ReadUInt32();
687 if (asetMagic == 0x54455341)
688 {
689 uint asetVersion = reader.ReadUInt32();
690 if (asetVersion == 0)
691 {
692 ulong iconOffset = reader.ReadUInt64();
693 ulong iconSize = reader.ReadUInt64();
694
695 ulong nacpOffset = reader.ReadUInt64();
696 ulong nacpSize = reader.ReadUInt64();
697
698 ulong romfsOffset = reader.ReadUInt64();
699 ulong romfsSize = reader.ReadUInt64();
700
701 if (romfsSize != 0)
702 {
703 romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset);
704 }
705
706 if (nacpSize != 0)
707 {
708 input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
709
710 reader.Read(ControlData.ByteSpan);
711
712 ref ApplicationControlProperty nacp = ref ControlData.Value;
713
714 programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
715
716 if (string.IsNullOrWhiteSpace(programInfo.Name))
717 {
718 programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
719 }
720
721 if (nacp.PresenceGroupId != 0)
722 {
723 programInfo.ProgramId = nacp.PresenceGroupId;
724 }
725 else if (nacp.SaveDataOwnerId != 0)
726 {
727 programInfo.ProgramId = nacp.SaveDataOwnerId;
728 }
729 else if (nacp.AddOnContentBaseId != 0)
730 {
731 programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
732 }
733 else
734 {
735 programInfo.ProgramId = 0000000000000000;
736 }
737 }
738 }
739 else
740 {
741 Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
742 }
743 }
744 }
745 }
746 else
747 {
748 executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
749 }
750
751 _device.Configuration.ContentManager.LoadEntries(_device);
752
753 _titleName = programInfo.Name;
754 TitleId = programInfo.ProgramId;
755 TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
756 _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
757
758 // Explicitly null titleid to disable the shader cache.
759 Graphics.Gpu.GraphicsConfig.TitleId = null;
760 _device.Gpu.HostInitalized.Set();
761
762 ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
763
764 if (romfsStream != null)
765 {
766 _device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream);
767 }
768
769 DiskCacheLoadState = result.DiskCacheLoadState;
770
771 _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
772 }
773
774 private MetaLoader GetDefaultNpdm()
775 {
776 Assembly asm = Assembly.GetCallingAssembly();
777
778 using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
779 {
780 var npdmBuffer = new byte[npdmStream.Length];
781 npdmStream.Read(npdmBuffer);
782
783 var metaLoader = new MetaLoader();
784 metaLoader.Load(npdmBuffer).ThrowIfFailure();
785
786 return metaLoader;
787 }
788 }
789
790 private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
791 {
792 ulong mainProgramId = 0;
793 Span<bool> hasIndex = stackalloc bool[0x10];
794
795 fileSystem.ImportTickets(pfs);
796
797 foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
798 {
799 using var ncaFile = new UniqueRef<IFile>();
800
801 pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
802
803 Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
804
805 if (nca.Header.ContentType != NcaContentType.Program)
806 {
807 continue;
808 }
809
810 int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
811
812 if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
813 {
814 continue;
815 }
816
817 ulong currentProgramId = nca.Header.TitleId;
818 ulong currentMainProgramId = currentProgramId & ~0xFFFul;
819
820 if (mainProgramId == 0 && currentMainProgramId != 0)
821 {
822 mainProgramId = currentMainProgramId;
823 }
824
825 if (mainProgramId != currentMainProgramId)
826 {
827 // As far as I know there aren't any multi-application game cards containing multi-program applications,
828 // so because multi-application game cards are the only way we should run into multiple applications
829 // we'll just return that there's a single program.
830 return (mainProgramId, 1);
831 }
832
833 hasIndex[(int)(currentProgramId & 0xF)] = true;
834 }
835
836 int programCount = 0;
837
838 for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
839 {
840 programCount++;
841 }
842
843 return (mainProgramId, programCount);
844 }
845
846 private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
847 {
848 (ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
849
850 if (programCount <= 0)
851 return Result.Success;
852
853 Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
854
855 for (int i = 0; i < programCount; i++)
856 {
857 mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
858 mapInfo[i].MainProgramId = new ApplicationId(applicationId);
859 mapInfo[i].ProgramIndex = (byte)i;
860 }
861
862 return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
863 }
864
865 private Result EnsureSaveData(ApplicationId applicationId)
866 {
867 Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
868
869 Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
870
871 ref ApplicationControlProperty control = ref ControlData.Value;
872
873 if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan))
874 {
875 // If the current application doesn't have a loaded control property, create a dummy one
876 // and set the savedata sizes so a user savedata will be created.
877 control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
878
879 // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
880 control.UserAccountSaveDataSize = 0x4000;
881 control.UserAccountSaveDataJournalSize = 0x4000;
882 control.SaveDataOwnerId = applicationId.Value;
883
884 Logger.Warning?.Print(LogClass.Application,
885 "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
886 }
887
888 HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
889 Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
890
891 if (resultCode.IsFailure())
892 {
893 Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
894
895 return resultCode;
896 }
897
898 resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
899
900 if (resultCode.IsFailure())
901 {
902 Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
903 }
904
905 return resultCode;
906 }
907 }
908}
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 2b77a7c2e..1639532ed 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
35using Ryujinx.HLE.HOS.Services.Time.Clock; 35using Ryujinx.HLE.HOS.Services.Time.Clock;
36using Ryujinx.HLE.HOS.SystemState; 36using Ryujinx.HLE.HOS.SystemState;
37using Ryujinx.HLE.Loaders.Executables; 37using Ryujinx.HLE.Loaders.Executables;
38using Ryujinx.HLE.Loaders.Processes;
38using Ryujinx.Horizon; 39using Ryujinx.Horizon;
39using System; 40using System;
40using System.Collections.Generic; 41using System.Collections.Generic;
@@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS
358 } 359 }
359 } 360 }
360 361
361 public void LoadKip(string kipPath) 362 public bool LoadKip(string kipPath)
362 { 363 {
363 using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read)); 364 using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
364 365
365 ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile)); 366 return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
366 } 367 }
367 368
368 public void ChangeDockedModeState(bool newState) 369 public void ChangeDockedModeState(bool newState)
diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
index 556703cf6..4cf67172d 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;
2 2
3namespace Ryujinx.HLE.HOS.Kernel.Process 3namespace Ryujinx.HLE.HOS.Kernel.Process
4{ 4{
5 internal class ProcessTamperInfo 5 class ProcessTamperInfo
6 { 6 {
7 public KProcess Process { get; } 7 public KProcess Process { get; }
8 public IEnumerable<string> BuildIds { get; } 8 public IEnumerable<string> BuildIds { get; }
diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs
index a6dc90135..165125414 100644
--- a/Ryujinx.HLE/HOS/ModLoader.cs
+++ b/Ryujinx.HLE/HOS/ModLoader.cs
@@ -10,6 +10,7 @@ using Ryujinx.Common.Logging;
10using Ryujinx.HLE.HOS.Kernel.Process; 10using Ryujinx.HLE.HOS.Kernel.Process;
11using Ryujinx.HLE.Loaders.Executables; 11using Ryujinx.HLE.Loaders.Executables;
12using Ryujinx.HLE.Loaders.Mods; 12using Ryujinx.HLE.Loaders.Mods;
13using Ryujinx.HLE.Loaders.Processes;
13using System; 14using System;
14using System.Collections.Generic; 15using System.Collections.Generic;
15using System.Collections.Specialized; 16using System.Collections.Specialized;
@@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS
547 return modLoadResult; 548 return modLoadResult;
548 } 549 }
549 550
550 if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length) 551 if (nsos.Length != ProcessConst.ExeFsPrefixes.Length)
551 { 552 {
552 throw new ArgumentOutOfRangeException("NSO Count is incorrect"); 553 throw new ArgumentOutOfRangeException("NSO Count is incorrect");
553 } 554 }
@@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS
556 557
557 foreach (var mod in exeMods) 558 foreach (var mod in exeMods)
558 { 559 {
559 for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i) 560 for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i)
560 { 561 {
561 var nsoName = ApplicationLoader.ExeFsPrefixes[i]; 562 var nsoName = ProcessConst.ExeFsPrefixes[i];
562 563
563 FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName)); 564 FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
564 if (nsoFile.Exists) 565 if (nsoFile.Exists)
@@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS
596 } 597 }
597 } 598 }
598 599
599 for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i) 600 for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i)
600 { 601 {
601 if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs 602 if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
602 { 603 {
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
index 1b412d74e..413bedced 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
190 // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally. 190 // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
191 // But since we use LibHac and we load one Application at a time, it's not necessary. 191 // But since we use LibHac and we load one Application at a time, it's not necessary.
192 192
193 context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock); 193 context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
194 194
195 Logger.Stub?.PrintStub(LogClass.ServiceAcc); 195 Logger.Stub?.PrintStub(LogClass.ServiceAcc);
196 196
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 00081e1b1..720497141 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
9 9
10 public ILibraryAppletSelfAccessor(ServiceCtx context) 10 public ILibraryAppletSelfAccessor(ServiceCtx context)
11 { 11 {
12 if (context.Device.Application.TitleId == 0x0100000000001009) 12 if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009)
13 { 13 {
14 // Create MiiEdit data. 14 // Create MiiEdit data.
15 _appletStandalone = new AppletStandalone() 15 _appletStandalone = new AppletStandalone()
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
25 } 25 }
26 else 26 else
27 { 27 {
28 throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented."); 28 throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
29 } 29 }
30 } 30 }
31 31
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 f8f88a1cb..924f54298 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
115 Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); 115 Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
116 116
117 // Mask out the low nibble of the program ID to get the application ID 117 // Mask out the low nibble of the program ID to get the application ID
118 ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); 118 ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
119 119
120 BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; 120 ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
121
122 ref ApplicationControlProperty control = ref controlHolder.Value;
123
124 if (LibHac.Common.Utilities.IsZeros(controlHolder.ByteSpan))
125 {
126 // If the current application doesn't have a loaded control property, create a dummy one
127 // and set the savedata sizes so a user savedata will be created.
128 control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
129
130 // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
131 control.UserAccountSaveDataSize = 0x4000;
132 control.UserAccountSaveDataJournalSize = 0x4000;
133
134 Logger.Warning?.Print(LogClass.ServiceAm,
135 "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
136 }
137 121
138 LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; 122 LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
139 LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId); 123 LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId);
140 124
141 context.ResponseData.Write(requiredSize); 125 context.ResponseData.Write(requiredSize);
142 126
@@ -153,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
153 // TODO: When above calls are implemented, switch to using ns:am 137 // TODO: When above calls are implemented, switch to using ns:am
154 138
155 long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; 139 long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
156 int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag; 140 int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag;
157 int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages); 141 int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
158 142
159 if (firstSupported > (int)TitleLanguage.BrazilianPortuguese) 143 if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
@@ -196,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
196 public ResultCode GetDisplayVersion(ServiceCtx context) 180 public ResultCode GetDisplayVersion(ServiceCtx context)
197 { 181 {
198 // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation. 182 // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
199 context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion); 183 context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
200 184
201 return ResultCode.Success; 185 return ResultCode.Success;
202 } 186 }
@@ -251,13 +235,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
251 long journalSize = context.RequestData.ReadInt64(); 235 long journalSize = context.RequestData.ReadInt64();
252 236
253 // Mask out the low nibble of the program ID to get the application ID 237 // Mask out the low nibble of the program ID to get the application ID
254 ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); 238 ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
255 239
256 BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; 240 ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
257 241
258 LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, 242 LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
259 out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize, 243 out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize);
260 journalSize);
261 244
262 if (result.IsFailure()) return (ResultCode)result.Value; 245 if (result.IsFailure()) return (ResultCode)result.Value;
263 246
@@ -677,7 +660,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
677 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)"); 660 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)");
678 } 661 }
679 662
680 context.Device.Application.LoadServiceNca(filePath); 663 context.Device.LoadNca(filePath);
681 664
682 // FIXME: Most likely not how this should be done? 665 // FIXME: Most likely not how this should be done?
683 while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) 666 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 c985092b8..3e4eca0ac 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
33 33
34 return new ApplicationLaunchProperty 34 return new ApplicationLaunchProperty
35 { 35 {
36 TitleId = context.Device.Application.TitleId, 36 TitleId = context.Device.Processes.ActiveApplication.ProgramId,
37 Version = 0x00, 37 Version = 0x00,
38 BaseGameStorageId = (byte)StorageId.BuiltInSystem, 38 BaseGameStorageId = (byte)StorageId.BuiltInSystem,
39 UpdateGameStorageId = (byte)StorageId.None 39 UpdateGameStorageId = (byte)StorageId.None
diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
index 1789122e4..e0c65f440 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
31 31
32 byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); 32 byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
33 33
34 ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); 34 ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
35 35
36 context.ResponseData.WriteStruct(applicationAlbumEntry); 36 context.ResponseData.WriteStruct(applicationAlbumEntry);
37 37
@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
60 60
61 byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); 61 byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
62 62
63 ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); 63 ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
64 64
65 context.ResponseData.WriteStruct(applicationAlbumEntry); 65 context.ResponseData.WriteStruct(applicationAlbumEntry);
66 66
@@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
88 88
89 byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); 89 byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
90 90
91 ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); 91 ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
92 92
93 context.ResponseData.WriteStruct(applicationAlbumEntry); 93 context.ResponseData.WriteStruct(applicationAlbumEntry);
94 94
diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs
index 6d663a4de..c884e8803 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
55 errorReport.AppendLine(); 55 errorReport.AppendLine();
56 errorReport.AppendLine("ErrorReport log:"); 56 errorReport.AppendLine("ErrorReport log:");
57 57
58 errorReport.AppendLine($"\tTitleId: {context.Device.Application.TitleId:x16}"); 58 errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}");
59 errorReport.AppendLine($"\tPid: {pid}"); 59 errorReport.AppendLine($"\tPid: {pid}");
60 errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); 60 errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
61 errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); 61 errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
@@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
64 { 64 {
65 errorReport.AppendLine("CPU Context:"); 65 errorReport.AppendLine("CPU Context:");
66 66
67 if (context.Device.Application.TitleIs64Bit) 67 if (context.Device.Processes.ActiveApplication.Is64Bit)
68 { 68 {
69 CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0]; 69 CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0];
70 70
diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
index 17a33b799..4317c8f61 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
334 } 334 }
335 335
336 // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. 336 // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
337 ApplicationControlProperty controlProperty = context.Device.Application.ControlData.Value; 337 ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
338 338
339 /* 339 /*
340 340
diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs
index 37143a5aa..1b63f362e 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
808 { 808 {
809 byte programIndex = context.RequestData.ReadByte(); 809 byte programIndex = context.RequestData.ReadByte();
810 810
811 if ((context.Device.Application.TitleId & 0xf) != programIndex) 811 if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex)
812 { 812 {
813 throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex})."); 813 throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
814 } 814 }
diff --git a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs
index 0d5520038..b8f9e3b97 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
48 48
49 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. 49 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
50 50
51 return CountAddOnContentImpl(context, context.Device.Application.TitleId); 51 return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
52 } 52 }
53 53
54 [CommandHipc(3)] 54 [CommandHipc(3)]
@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
59 59
60 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. 60 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
61 61
62 return ListAddContentImpl(context, context.Device.Application.TitleId); 62 return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
63 } 63 }
64 64
65 [CommandHipc(4)] // 1.0.0-6.2.0 65 [CommandHipc(4)] // 1.0.0-6.2.0
@@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
79 79
80 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. 80 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
81 81
82 return GetAddOnContentBaseIdImpl(context, context.Device.Application.TitleId); 82 return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
83 } 83 }
84 84
85 [CommandHipc(6)] // 1.0.0-6.2.0 85 [CommandHipc(6)] // 1.0.0-6.2.0
@@ -99,7 +99,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
99 99
100 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. 100 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
101 101
102 return PrepareAddOnContentImpl(context, context.Device.Application.TitleId); 102 return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId);
103 } 103 }
104 104
105 [CommandHipc(8)] // 4.0.0+ 105 [CommandHipc(8)] // 4.0.0+
@@ -128,7 +128,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
128 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. 128 // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId.
129 129
130 // TODO: Found where stored value is used. 130 // TODO: Found where stored value is used.
131 ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId); 131 ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId);
132 132
133 if (resultCode != ResultCode.Success) 133 if (resultCode != ResultCode.Success)
134 { 134 {
@@ -294,7 +294,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
294 // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId, 294 // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId,
295 // If the call fails, it returns ResultCode.InvalidPid. 295 // If the call fails, it returns ResultCode.InvalidPid.
296 296
297 _addOnContentBaseId = context.Device.Application.ControlData.Value.AddOnContentBaseId; 297 _addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId;
298 298
299 if (_addOnContentBaseId == 0) 299 if (_addOnContentBaseId == 0)
300 { 300 {
@@ -308,7 +308,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc
308 { 308 {
309 uint index = context.RequestData.ReadUInt32(); 309 uint index = context.RequestData.ReadUInt32();
310 310
311 ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId); 311 ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId);
312 312
313 if (resultCode != ResultCode.Success) 313 if (resultCode != ResultCode.Success)
314 { 314 {
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
index d3a891782..249343d79 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
@@ -1,4 +1,8 @@
1namespace Ryujinx.HLE.HOS.Services.Ns 1using LibHac.Ns;
2using Ryujinx.Common.Utilities;
3using System;
4
5namespace Ryujinx.HLE.HOS.Services.Ns
2{ 6{
3 [Service("ns:am")] 7 [Service("ns:am")]
4 class IApplicationManagerInterface : IpcService 8 class IApplicationManagerInterface : IpcService
@@ -14,9 +18,9 @@
14 18
15 ulong position = context.Request.ReceiveBuff[0].Position; 19 ulong position = context.Request.ReceiveBuff[0].Position;
16 20
17 byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); 21 ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
18 22
19 context.Memory.Write(position, nacpData); 23 context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
20 24
21 return ResultCode.Success; 25 return ResultCode.Success;
22 } 26 }
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
index 3b6965d0a..8f6acc1c6 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
@@ -1,4 +1,7 @@
1namespace Ryujinx.HLE.HOS.Services.Ns 1using LibHac.Common;
2using LibHac.Ns;
3
4namespace Ryujinx.HLE.HOS.Services.Ns
2{ 5{
3 class IReadOnlyApplicationControlDataInterface : IpcService 6 class IReadOnlyApplicationControlDataInterface : IpcService
4 { 7 {
@@ -13,9 +16,9 @@
13 16
14 ulong position = context.Request.ReceiveBuff[0].Position; 17 ulong position = context.Request.ReceiveBuff[0].Position;
15 18
16 byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); 19 ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
17 20
18 context.Memory.Write(position, nacpData); 21 context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
19 22
20 return ResultCode.Success; 23 return ResultCode.Success;
21 } 24 }
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
index e0017808d..02964749c 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
56 _titleId = titleId; 56 _titleId = titleId;
57 57
58 // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. 58 // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
59 _ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); 59 _ratingAge = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
60 _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag; 60 _parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag;
61 } 61 }
62 } 62 }
63 63
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
index 1d6cc118e..52a07d466 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;
6using System.Collections.Generic; 6using System.Collections.Generic;
7using System.Linq; 7using System.Linq;
8using System.Runtime.CompilerServices; 8using System.Runtime.CompilerServices;
9using System.Runtime.InteropServices;
10 9
11namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService 10namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
12{ 11{
@@ -16,8 +15,6 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
16 15
17 internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) 16 internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
18 { 17 {
19 ref readonly var controlProperty = ref context.Device.Application.ControlData.Value;
20
21 ulong inputPosition = context.Request.SendBuff[0].Position; 18 ulong inputPosition = context.Request.SendBuff[0].Position;
22 ulong inputSize = context.Request.SendBuff[0].Size; 19 ulong inputSize = context.Request.SendBuff[0].Size;
23 20
@@ -34,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
34 } 31 }
35 } 32 }
36 33
37 PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability; 34 PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability;
38 35
39 List<ulong> titleIds = new List<ulong>(); 36 List<ulong> titleIds = new List<ulong>();
40 37
@@ -48,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
48 // Check if input title ids are in the whitelist. 45 // Check if input title ids are in the whitelist.
49 foreach (ulong titleId in titleIds) 46 foreach (ulong titleId in titleIds)
50 { 47 {
51 if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) 48 if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
52 { 49 {
53 return (ResultCode)Am.ResultCode.ObjectInvalid; 50 return (ResultCode)Am.ResultCode.ObjectInvalid;
54 } 51 }
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs
new file mode 100644
index 000000000..58759ddb1
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs
@@ -0,0 +1,133 @@
1using LibHac.Common;
2using LibHac.Fs;
3using LibHac.Fs.Fsa;
4using LibHac.Loader;
5using LibHac.Ns;
6using LibHac.Tools.FsSystem;
7using Ryujinx.Common.Configuration;
8using Ryujinx.Common.Logging;
9using Ryujinx.HLE.Loaders.Executables;
10using Ryujinx.Memory;
11using System.Linq;
12using static Ryujinx.HLE.HOS.ModLoader;
13
14namespace Ryujinx.HLE.Loaders.Processes.Extensions
15{
16 static class FileSystemExtensions
17 {
18 public static MetaLoader GetNpdm(this IFileSystem fileSystem)
19 {
20 MetaLoader metaLoader = new();
21
22 if (fileSystem == null || !fileSystem.FileExists(ProcessConst.MainNpdmPath))
23 {
24 Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
25
26 metaLoader.LoadDefault();
27 }
28 else
29 {
30 metaLoader.LoadFromFile(fileSystem);
31 }
32
33 return metaLoader;
34 }
35
36 public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct<ApplicationControlProperty> nacpData, MetaLoader metaLoader, bool isHomebrew = false)
37 {
38 ulong programId = metaLoader.GetProgramId();
39
40 // Replace the whole ExeFs partition by the modded one.
41 if (device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(programId, ref exeFs))
42 {
43 metaLoader = null;
44 }
45
46 // Reload the MetaLoader in case of ExeFs partition replacement.
47 metaLoader ??= exeFs.GetNpdm();
48
49 NsoExecutable[] nsoExecutables = new NsoExecutable[ProcessConst.ExeFsPrefixes.Length];
50
51 for (int i = 0; i < nsoExecutables.Length; i++)
52 {
53 string name = ProcessConst.ExeFsPrefixes[i];
54
55 if (!exeFs.FileExists($"/{name}"))
56 {
57 continue; // File doesn't exist, skip.
58 }
59
60 Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
61
62 using var nsoFile = new UniqueRef<IFile>();
63
64 exeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
65
66 nsoExecutables[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
67 }
68
69 // ExeFs file replacements.
70 ModLoadResult modLoadResult = device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(programId, nsoExecutables);
71
72 // Take the Npdm from mods if present.
73 if (modLoadResult.Npdm != null)
74 {
75 metaLoader = modLoadResult.Npdm;
76 }
77
78 // Collect the Nsos, ignoring ones that aren't used.
79 nsoExecutables = nsoExecutables.Where(x => x != null).ToArray();
80
81 // Apply Nsos patches.
82 device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables);
83
84 // Don't use PTC if ExeFS files have been replaced.
85 bool enablePtc = device.System.EnablePtc && !modLoadResult.Modified;
86 if (!enablePtc)
87 {
88 Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PTC disabled.");
89 }
90
91 // We allow it for nx-hbloader because it can be used to launch homebrew.
92 bool allowCodeMemoryForJit = programId == 0x010000000000100DUL || isHomebrew;
93
94 string programName = "";
95
96 if (!isHomebrew && programId > 0x010000000000FFFF)
97 {
98 programName = nacpData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
99
100 if (string.IsNullOrWhiteSpace(programName))
101 {
102 programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
103 }
104 }
105
106 // Initialize GPU.
107 Graphics.Gpu.GraphicsConfig.TitleId = $"{programId:x16}";
108 device.Gpu.HostInitalized.Set();
109
110 if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
111 {
112 device.Configuration.MemoryManagerMode = MemoryManagerMode.SoftwarePageTable;
113 }
114
115 ProcessResult processResult = ProcessLoaderHelper.LoadNsos(
116 device,
117 device.System.KernelContext,
118 metaLoader,
119 nacpData.Value,
120 enablePtc,
121 allowCodeMemoryForJit,
122 programName,
123 metaLoader.GetProgramId(),
124 null,
125 nsoExecutables);
126
127 // TODO: This should be stored using ProcessId instead.
128 device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(metaLoader.GetProgramId());
129
130 return processResult;
131 }
132 }
133} \ 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 000000000..28d907851
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs
@@ -0,0 +1,39 @@
1using LibHac.Common;
2using LibHac.FsSystem;
3using LibHac.Loader;
4using LibHac.Ns;
5using Ryujinx.HLE.Loaders.Processes.Extensions;
6using ApplicationId = LibHac.Ncm.ApplicationId;
7
8namespace Ryujinx.HLE.Loaders.Processes
9{
10 static class LocalFileSystemExtensions
11 {
12 public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, string romFsPath = "")
13 {
14 MetaLoader metaLoader = exeFs.GetNpdm();
15 var nacpData = new BlitStruct<ApplicationControlProperty>(1);
16 ulong programId = metaLoader.GetProgramId();
17
18 device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
19 new[] { programId },
20 device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
21 device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
22
23 if (programId != 0)
24 {
25 ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData);
26 }
27
28 ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader);
29
30 // Load RomFS.
31 if (!string.IsNullOrEmpty(romFsPath))
32 {
33 device.Configuration.VirtualFileSystem.LoadRomFs(processResult.ProcessId, romFsPath);
34 }
35
36 return processResult;
37 }
38 }
39}
diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs
new file mode 100644
index 000000000..c639ee524
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs
@@ -0,0 +1,61 @@
1using LibHac.Common;
2using LibHac.Fs;
3using LibHac.Fs.Fsa;
4using LibHac.Loader;
5using LibHac.Util;
6using Ryujinx.Common;
7using System;
8
9namespace Ryujinx.HLE.Loaders.Processes.Extensions
10{
11 public static class MetaLoaderExtensions
12 {
13 public static ulong GetProgramId(this MetaLoader metaLoader)
14 {
15 metaLoader.GetNpdm(out var npdm).ThrowIfFailure();
16
17 return npdm.Aci.ProgramId.Value;
18 }
19
20 public static string GetProgramName(this MetaLoader metaLoader)
21 {
22 metaLoader.GetNpdm(out var npdm).ThrowIfFailure();
23
24 return StringUtils.Utf8ZToString(npdm.Meta.ProgramName);
25 }
26
27 public static bool IsProgram64Bit(this MetaLoader metaLoader)
28 {
29 metaLoader.GetNpdm(out var npdm).ThrowIfFailure();
30
31 return (npdm.Meta.Flags & 1) != 0;
32 }
33
34 public static void LoadDefault(this MetaLoader metaLoader)
35 {
36 byte[] npdmBuffer = EmbeddedResources.Read("Ryujinx.HLE/Homebrew.npdm");
37
38 metaLoader.Load(npdmBuffer).ThrowIfFailure();
39 }
40
41 public static void LoadFromFile(this MetaLoader metaLoader, IFileSystem fileSystem, string path = "")
42 {
43 if (string.IsNullOrEmpty(path))
44 {
45 path = ProcessConst.MainNpdmPath;
46 }
47
48 using var npdmFile = new UniqueRef<IFile>();
49
50 fileSystem.OpenFile(ref npdmFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
51
52 npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
53
54 Span<byte> npdmBuffer = new byte[fileSize];
55
56 npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
57
58 metaLoader.Load(npdmBuffer).ThrowIfFailure();
59 }
60 }
61} \ 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 000000000..473f374db
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs
@@ -0,0 +1,175 @@
1using LibHac;
2using LibHac.Common;
3using LibHac.Fs;
4using LibHac.Fs.Fsa;
5using LibHac.Loader;
6using LibHac.Ncm;
7using LibHac.Ns;
8using LibHac.Tools.FsSystem;
9using LibHac.Tools.FsSystem.NcaUtils;
10using Ryujinx.Common.Logging;
11using System.IO;
12using System.Linq;
13using ApplicationId = LibHac.Ncm.ApplicationId;
14
15namespace Ryujinx.HLE.Loaders.Processes.Extensions
16{
17 static class NcaExtensions
18 {
19 public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
20 {
21 // Extract RomFs and ExeFs from NCA.
22 IStorage romFs = nca.GetRomFs(device, patchNca);
23 IFileSystem exeFs = nca.GetExeFs(device, patchNca);
24
25 if (exeFs == null)
26 {
27 Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
28
29 return ProcessResult.Failed;
30 }
31
32 // Load Npdm file.
33 MetaLoader metaLoader = exeFs.GetNpdm();
34
35 // Collecting mods related to AocTitleIds and ProgramId.
36 device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
37 device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()),
38 device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
39 device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
40
41 // Load Nacp file.
42 var nacpData = new BlitStruct<ApplicationControlProperty>(1);
43
44 if (controlNca != null)
45 {
46 nacpData = controlNca.GetNacp(device);
47 }
48
49 /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
50
51 // Load program 0 control NCA as we are going to need it for display version.
52 (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
53
54 // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
55 // As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program.
56 if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
57 {
58 nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion;
59 }
60
61 */
62
63 ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader);
64
65 // Load RomFS.
66 if (romFs == null)
67 {
68 Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
69 }
70 else
71 {
72 romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs);
73
74 device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read));
75 }
76
77 // Don't create save data for system programs.
78 if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value))
79 {
80 // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
81 // We'll know if this changes in the future because applications will get errors when trying to mount the correct save.
82 ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData);
83 }
84
85 return processResult;
86 }
87
88 public static int GetProgramIndex(this Nca nca)
89 {
90 return (int)(nca.Header.TitleId & 0xF);
91 }
92
93 public static bool IsProgram(this Nca nca)
94 {
95 return nca.Header.ContentType == NcaContentType.Program;
96 }
97
98 public static bool IsPatch(this Nca nca)
99 {
100 int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
101
102 return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection();
103 }
104
105 public static bool IsControl(this Nca nca)
106 {
107 return nca.Header.ContentType == NcaContentType.Control;
108 }
109
110 public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
111 {
112 IFileSystem exeFs = null;
113
114 if (patchNca == null)
115 {
116 if (nca.CanOpenSection(NcaSectionType.Code))
117 {
118 exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel);
119 }
120 }
121 else
122 {
123 if (patchNca.CanOpenSection(NcaSectionType.Code))
124 {
125 exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel);
126 }
127 }
128
129 return exeFs;
130 }
131
132 public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null)
133 {
134 IStorage romFs = null;
135
136 if (patchNca == null)
137 {
138 if (nca.CanOpenSection(NcaSectionType.Data))
139 {
140 romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
141 }
142 }
143 else
144 {
145 if (patchNca.CanOpenSection(NcaSectionType.Data))
146 {
147 romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
148 }
149 }
150
151 return romFs;
152 }
153
154 public static BlitStruct<ApplicationControlProperty> GetNacp(this Nca controlNca, Switch device)
155 {
156 var nacpData = new BlitStruct<ApplicationControlProperty>(1);
157
158 using var controlFile = new UniqueRef<IFile>();
159
160 Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel)
161 .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
162
163 if (result.IsSuccess())
164 {
165 result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None);
166 }
167 else
168 {
169 nacpData.ByteSpan.Clear();
170 }
171
172 return nacpData;
173 }
174 }
175} \ 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 000000000..5147f5c3a
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs
@@ -0,0 +1,177 @@
1using LibHac.Common;
2using LibHac.Fs;
3using LibHac.Fs.Fsa;
4using LibHac.FsSystem;
5using LibHac.Tools.Fs;
6using LibHac.Tools.FsSystem;
7using LibHac.Tools.FsSystem.NcaUtils;
8using Ryujinx.Common.Configuration;
9using Ryujinx.Common.Logging;
10using Ryujinx.Common.Utilities;
11using System;
12using System.Collections.Generic;
13using System.Globalization;
14using System.IO;
15
16namespace Ryujinx.HLE.Loaders.Processes.Extensions
17{
18 public static class PartitionFileSystemExtensions
19 {
20 internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage)
21 {
22 errorMessage = null;
23
24 // Load required NCAs.
25 Nca mainNca = null;
26 Nca patchNca = null;
27 Nca controlNca = null;
28
29 try
30 {
31 device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
32
33 // TODO: To support multi-games container, this should use CNMT NCA instead.
34 foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
35 {
36 Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
37
38 if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
39 {
40 continue;
41 }
42
43 if (nca.IsPatch())
44 {
45 patchNca = nca;
46 }
47 else if (nca.IsProgram())
48 {
49 mainNca = nca;
50 }
51 else if (nca.IsControl())
52 {
53 controlNca = nca;
54 }
55 }
56
57 ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
58 }
59 catch (Exception ex)
60 {
61 errorMessage = $"Unable to load: {ex.Message}";
62
63 return (false, ProcessResult.Failed);
64 }
65
66 if (mainNca != null)
67 {
68 if (mainNca.Header.ContentType != NcaContentType.Program)
69 {
70 errorMessage = "Selected NCA file is not a \"Program\" NCA";
71
72 return (false, ProcessResult.Failed);
73 }
74
75 // Load Update NCAs.
76 Nca updatePatchNca = null;
77 Nca updateControlNca = null;
78
79 if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
80 {
81 // Clear the program index part.
82 titleIdBase &= ~0xFUL;
83
84 // Load update information if exists.
85 string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
86 if (File.Exists(titleUpdateMetadataPath))
87 {
88 string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
89 if (File.Exists(updatePath))
90 {
91 PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage());
92
93 device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
94
95 // TODO: This should use CNMT NCA instead.
96 foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca"))
97 {
98 Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath);
99
100 if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
101 {
102 continue;
103 }
104
105 if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16"))
106 {
107 break;
108 }
109
110 if (nca.IsProgram())
111 {
112 updatePatchNca = nca;
113 }
114 else if (nca.IsControl())
115 {
116 updateControlNca = nca;
117 }
118 }
119 }
120 }
121 }
122
123 if (updatePatchNca != null)
124 {
125 patchNca = updatePatchNca;
126 }
127
128 if (updateControlNca != null)
129 {
130 controlNca = updateControlNca;
131 }
132
133 // Load contained DownloadableContents.
134 // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
135 device.Configuration.ContentManager.ClearAocData();
136 device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel);
137
138 // Load DownloadableContents.
139 string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
140 if (File.Exists(addOnContentMetadataPath))
141 {
142 List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(addOnContentMetadataPath);
143
144 foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
145 {
146 foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
147 {
148 if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
149 {
150 device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
151 }
152 else
153 {
154 Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
155 }
156 }
157 }
158 }
159
160 return (true, mainNca.Load(device, patchNca, controlNca));
161 }
162
163 errorMessage = "Unable to load: Could not find Main NCA";
164
165 return (false, ProcessResult.Failed);
166 }
167
168 public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
169 {
170 using var ncaFile = new UniqueRef<IFile>();
171
172 fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
173
174 return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
175 }
176 }
177} \ 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 000000000..42ae2e89b
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs
@@ -0,0 +1,33 @@
1namespace Ryujinx.HLE.Loaders.Processes
2{
3 static class ProcessConst
4 {
5 // Binaries from exefs are loaded into mem in this order. Do not change.
6 public static readonly string[] ExeFsPrefixes =
7 {
8 "rtld",
9 "main",
10 "subsdk0",
11 "subsdk1",
12 "subsdk2",
13 "subsdk3",
14 "subsdk4",
15 "subsdk5",
16 "subsdk6",
17 "subsdk7",
18 "subsdk8",
19 "subsdk9",
20 "sdk"
21 };
22
23 public static readonly string MainNpdmPath = "/main.npdm";
24
25 public const int NroAsetMagic = ('A' << 0) | ('S' << 8) | ('E' << 16) | ('T' << 24);
26
27 public const bool AslrEnabled = true;
28
29 public const int NsoArgsHeaderSize = 8;
30 public const int NsoArgsDataSize = 0x9000;
31 public const int NsoArgsTotalSize = NsoArgsHeaderSize + NsoArgsDataSize;
32 }
33} \ 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 000000000..785db0e50
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs
@@ -0,0 +1,244 @@
1using LibHac.Common;
2using LibHac.Fs;
3using LibHac.Fs.Fsa;
4using LibHac.FsSystem;
5using LibHac.Ns;
6using LibHac.Tools.Fs;
7using LibHac.Tools.FsSystem;
8using LibHac.Tools.FsSystem.NcaUtils;
9using Ryujinx.Common.Logging;
10using Ryujinx.HLE.Loaders.Executables;
11using Ryujinx.HLE.Loaders.Processes.Extensions;
12using System.Collections.Concurrent;
13using System.IO;
14using System.Linq;
15using Path = System.IO.Path;
16
17namespace Ryujinx.HLE.Loaders.Processes
18{
19 public class ProcessLoader
20 {
21 private readonly Switch _device;
22
23 private readonly ConcurrentDictionary<ulong, ProcessResult> _processesByPid;
24
25 private ulong _latestPid;
26
27 public ProcessResult ActiveApplication => _processesByPid[_latestPid];
28
29 public ProcessLoader(Switch device)
30 {
31 _device = device;
32 _processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
33 }
34
35 public bool LoadXci(string path)
36 {
37 FileStream stream = new(path, FileMode.Open, FileAccess.Read);
38 Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
39
40 if (!xci.HasPartition(XciPartitionType.Secure))
41 {
42 Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition");
43
44 return false;
45 }
46
47 (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage);
48
49 if (!success)
50 {
51 Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad));
52
53 return false;
54 }
55
56 if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
57 {
58 if (processResult.Start(_device))
59 {
60 _latestPid = processResult.ProcessId;
61
62 return true;
63 }
64 }
65
66 return false;
67 }
68
69 public bool LoadNsp(string path)
70 {
71 FileStream file = new(path, FileMode.Open, FileAccess.Read);
72 PartitionFileSystem partitionFileSystem = new(file.AsStorage());
73
74 (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
75
76 if (processResult.ProcessId == 0)
77 {
78 // This is not a normal NSP, it's actually a ExeFS as a NSP
79 processResult = partitionFileSystem.Load(_device, new BlitStruct<ApplicationControlProperty>(1), partitionFileSystem.GetNpdm(), true);
80 }
81
82 if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
83 {
84 if (processResult.Start(_device))
85 {
86 _latestPid = processResult.ProcessId;
87
88 return true;
89 }
90 }
91
92 if (!success)
93 {
94 Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad));
95 }
96
97 return false;
98 }
99
100 public bool LoadNca(string path)
101 {
102 FileStream file = new(path, FileMode.Open, FileAccess.Read);
103 Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
104
105 ProcessResult processResult = nca.Load(_device, null, null);
106
107 if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
108 {
109 if (processResult.Start(_device))
110 {
111 // NOTE: Check if process is SystemApplicationId or ApplicationId
112 if (processResult.ProgramId > 0x01000000000007FF)
113 {
114 _latestPid = processResult.ProcessId;
115 }
116
117 return true;
118 }
119 }
120
121 return false;
122 }
123
124 public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null)
125 {
126 ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
127
128 if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
129 {
130 if (processResult.Start(_device))
131 {
132 _latestPid = processResult.ProcessId;
133
134 return true;
135 }
136 }
137
138 return false;
139 }
140
141 public bool LoadNxo(string path)
142 {
143 var nacpData = new BlitStruct<ApplicationControlProperty>(1);
144 IFileSystem dummyExeFs = null;
145 Stream romfsStream = null;
146
147 string programName = "";
148 ulong programId = 0000000000000000;
149
150 // Load executable.
151 IExecutable executable;
152
153 if (Path.GetExtension(path).ToLower() == ".nro")
154 {
155 FileStream input = new(path, FileMode.Open);
156 NroExecutable nro = new(input.AsStorage());
157
158 executable = nro;
159
160 // Open RomFS if exists.
161 IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false);
162 romFsStorage.GetSize(out long romFsSize).ThrowIfFailure();
163 if (romFsSize != 0)
164 {
165 romfsStream = romFsStorage.AsStream();
166 }
167
168 // Load Nacp if exists.
169 IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false);
170 nacpStorage.GetSize(out long nacpSize).ThrowIfFailure();
171 if (nacpSize != 0)
172 {
173 nacpStorage.Read(0, nacpData.ByteSpan);
174
175 programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
176
177 if (string.IsNullOrWhiteSpace(programName))
178 {
179 programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
180 }
181
182 if (nacpData.Value.PresenceGroupId != 0)
183 {
184 programId = nacpData.Value.PresenceGroupId;
185 }
186 else if (nacpData.Value.SaveDataOwnerId != 0)
187 {
188 programId = nacpData.Value.SaveDataOwnerId;
189 }
190 else if (nacpData.Value.AddOnContentBaseId != 0)
191 {
192 programId = nacpData.Value.AddOnContentBaseId - 0x1000;
193 }
194 }
195
196 // TODO: Add icon maybe ?
197 }
198 else
199 {
200 programName = System.IO.Path.GetFileNameWithoutExtension(path);
201
202 executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName);
203 }
204
205 // Explicitly null TitleId to disable the shader cache.
206 Graphics.Gpu.GraphicsConfig.TitleId = null;
207 _device.Gpu.HostInitalized.Set();
208
209 ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
210 _device.System.KernelContext,
211 dummyExeFs.GetNpdm(),
212 nacpData.Value,
213 diskCacheEnabled: false,
214 allowCodeMemoryForJit: true,
215 programName,
216 programId,
217 null,
218 executable);
219
220 // Make sure the process id is valid.
221 if (processResult.ProcessId != 0)
222 {
223 // Load RomFS.
224 if (romfsStream != null)
225 {
226 _device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream);
227 }
228
229 // Start process.
230 if (_processesByPid.TryAdd(processResult.ProcessId, processResult))
231 {
232 if (processResult.Start(_device))
233 {
234 _latestPid = processResult.ProcessId;
235
236 return true;
237 }
238 }
239 }
240
241 return false;
242 }
243 }
244} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
index 4ebcb7e7d..7176445e5 100644
--- a/Ryujinx.HLE/HOS/ProgramLoader.cs
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs
@@ -1,69 +1,132 @@
1using LibHac.Account;
2using LibHac.Common;
3using LibHac.Fs;
4using LibHac.Fs.Shim;
5using LibHac.FsSystem;
1using LibHac.Loader; 6using LibHac.Loader;
2using LibHac.Ncm; 7using LibHac.Ncm;
3using LibHac.Util; 8using LibHac.Ns;
9using LibHac.Tools.Fs;
10using LibHac.Tools.FsSystem;
11using LibHac.Tools.FsSystem.NcaUtils;
4using Ryujinx.Common; 12using Ryujinx.Common;
5using Ryujinx.Common.Logging; 13using Ryujinx.Common.Logging;
6using Ryujinx.Cpu; 14using Ryujinx.HLE.HOS;
7using Ryujinx.HLE.HOS.Kernel; 15using Ryujinx.HLE.HOS.Kernel;
8using Ryujinx.HLE.HOS.Kernel.Common; 16using Ryujinx.HLE.HOS.Kernel.Common;
9using Ryujinx.HLE.HOS.Kernel.Memory; 17using Ryujinx.HLE.HOS.Kernel.Memory;
10using Ryujinx.HLE.HOS.Kernel.Process; 18using Ryujinx.HLE.HOS.Kernel.Process;
11using Ryujinx.HLE.Loaders.Executables; 19using Ryujinx.HLE.Loaders.Executables;
20using Ryujinx.HLE.Loaders.Processes.Extensions;
12using Ryujinx.Horizon.Common; 21using Ryujinx.Horizon.Common;
13using System; 22using System;
14using System.Linq; 23using System.Linq;
15using System.Runtime.InteropServices; 24using System.Runtime.InteropServices;
16using Npdm = LibHac.Loader.Npdm; 25using ApplicationId = LibHac.Ncm.ApplicationId;
17 26
18namespace Ryujinx.HLE.HOS 27namespace Ryujinx.HLE.Loaders.Processes
19{ 28{
20 struct ProgramInfo 29 static class ProcessLoaderHelper
21 { 30 {
22 public string Name; 31 public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem)
23 public ulong ProgramId;
24 public readonly string TitleIdText;
25 public readonly string DisplayVersion;
26 public readonly bool DiskCacheEnabled;
27 public readonly bool AllowCodeMemoryForJit;
28
29 public ProgramInfo(in Npdm npdm, string displayVersion, bool diskCacheEnabled, bool allowCodeMemoryForJit)
30 { 32 {
31 ulong programId = npdm.Aci.ProgramId.Value; 33 ulong applicationId = 0;
32 34 int programCount = 0;
33 Name = StringUtils.Utf8ZToString(npdm.Meta.ProgramName);
34 ProgramId = programId;
35 TitleIdText = programId.ToString("x16");
36 DisplayVersion = displayVersion;
37 DiskCacheEnabled = diskCacheEnabled;
38 AllowCodeMemoryForJit = allowCodeMemoryForJit;
39 }
40 }
41 35
42 struct ProgramLoadResult 36 Span<bool> hasIndex = stackalloc bool[0x10];
43 {
44 public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null, 0);
45 37
46 public readonly bool Success; 38 foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
47 public readonly ProcessTamperInfo TamperInfo; 39 {
48 public readonly IDiskCacheLoadState DiskCacheLoadState; 40 Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
49 public readonly ulong ProcessId;
50 41
51 public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState, ulong pid) 42 if (!nca.IsProgram() && nca.IsPatch())
52 { 43 {
53 Success = success; 44 continue;
54 TamperInfo = tamperInfo; 45 }
55 DiskCacheLoadState = diskCacheLoadState; 46
56 ProcessId = pid; 47 ulong currentProgramId = nca.Header.TitleId;
48 ulong currentMainProgramId = currentProgramId & ~0xFFFul;
49
50 if (applicationId == 0 && currentMainProgramId != 0)
51 {
52 applicationId = currentMainProgramId;
53 }
54
55 if (applicationId != currentMainProgramId)
56 {
57 // Currently there aren't any known multi-application game cards containing multi-program applications,
58 // so because multi-application game cards are the only way we could run into multiple applications
59 // we'll just return that there's a single program.
60 programCount = 1;
61
62 break;
63 }
64
65 hasIndex[(int)(currentProgramId & 0xF)] = true;
66 }
67
68 if (programCount == 0)
69 {
70 for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
71 {
72 programCount++;
73 }
74 }
75
76 if (programCount <= 0)
77 {
78 return LibHac.Result.Success;
79 }
80
81 Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
82
83 for (int i = 0; i < programCount; i++)
84 {
85 mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
86 mapInfo[i].MainProgramId = new ApplicationId(applicationId);
87 mapInfo[i].ProgramIndex = (byte)i;
88 }
89
90 return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]);
57 } 91 }
58 }
59 92
60 static class ProgramLoader 93 public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct<ApplicationControlProperty> applicationControlProperty)
61 { 94 {
62 private const bool AslrEnabled = true; 95 Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
63 96
64 private const int ArgsHeaderSize = 8; 97 ref ApplicationControlProperty control = ref applicationControlProperty.Value;
65 private const int ArgsDataSize = 0x9000; 98
66 private const int ArgsTotalSize = ArgsHeaderSize + ArgsDataSize; 99 if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.ByteSpan))
100 {
101 // 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.
102 control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
103
104 // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
105 control.UserAccountSaveDataSize = 0x4000;
106 control.UserAccountSaveDataJournalSize = 0x4000;
107 control.SaveDataOwnerId = applicationId.Value;
108
109 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.");
110 }
111
112 LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
113 if (resultCode.IsFailure())
114 {
115 Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
116
117 return resultCode;
118 }
119
120 Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
121
122 resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId);
123 if (resultCode.IsFailure())
124 {
125 Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
126 }
127
128 return resultCode;
129 }
67 130
68 public static bool LoadKip(KernelContext context, KipExecutable kip) 131 public static bool LoadKip(KernelContext context, KipExecutable kip)
69 { 132 {
@@ -74,17 +137,14 @@ namespace Ryujinx.HLE.HOS
74 endOffset = kip.BssOffset + kip.BssSize; 137 endOffset = kip.BssOffset + kip.BssSize;
75 } 138 }
76 139
77 uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize); 140 uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize);
78 141 int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
79 int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
80
81 ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL; 142 ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
82 143 ulong codeAddress = codeBaseAddress + kip.TextOffset;
83 ulong codeAddress = codeBaseAddress + kip.TextOffset;
84 144
85 ProcessCreationFlags flags = 0; 145 ProcessCreationFlags flags = 0;
86 146
87 if (AslrEnabled) 147 if (ProcessConst.AslrEnabled)
88 { 148 {
89 // TODO: Randomization. 149 // TODO: Randomization.
90 150
@@ -101,24 +161,11 @@ namespace Ryujinx.HLE.HOS
101 flags |= ProcessCreationFlags.Is64Bit; 161 flags |= ProcessCreationFlags.Is64Bit;
102 } 162 }
103 163
104 ProcessCreationInfo creationInfo = new ProcessCreationInfo( 164 ProcessCreationInfo creationInfo = new(kip.Name, kip.Version, kip.ProgramId, codeAddress, codePagesCount, flags, 0, 0);
105 kip.Name, 165 MemoryRegion memoryRegion = kip.UsesSecureMemory ? MemoryRegion.Service : MemoryRegion.Application;
106 kip.Version, 166 KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion];
107 kip.ProgramId,
108 codeAddress,
109 codePagesCount,
110 flags,
111 0,
112 0);
113
114 MemoryRegion memoryRegion = kip.UsesSecureMemory
115 ? MemoryRegion.Service
116 : MemoryRegion.Application;
117
118 KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion];
119 167
120 Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount); 168 Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount);
121
122 if (result != Result.Success) 169 if (result != Result.Success)
123 { 170 {
124 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); 171 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -126,7 +173,7 @@ namespace Ryujinx.HLE.HOS
126 return false; 173 return false;
127 } 174 }
128 175
129 KProcess process = new KProcess(context); 176 KProcess process = new(context);
130 177
131 var processContextFactory = new ArmProcessContextFactory( 178 var processContextFactory = new ArmProcessContextFactory(
132 context.Device.System.TickSource, 179 context.Device.System.TickSource,
@@ -137,14 +184,7 @@ namespace Ryujinx.HLE.HOS
137 codeAddress, 184 codeAddress,
138 codeSize); 185 codeSize);
139 186
140 result = process.InitializeKip( 187 result = process.InitializeKip(creationInfo, kip.Capabilities, pageList, context.ResourceLimit, memoryRegion, processContextFactory);
141 creationInfo,
142 kip.Capabilities,
143 pageList,
144 context.ResourceLimit,
145 memoryRegion,
146 processContextFactory);
147
148 if (result != Result.Success) 188 if (result != Result.Success)
149 { 189 {
150 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); 190 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -153,7 +193,6 @@ namespace Ryujinx.HLE.HOS
153 } 193 }
154 194
155 result = LoadIntoMemory(process, kip, codeBaseAddress); 195 result = LoadIntoMemory(process, kip, codeBaseAddress);
156
157 if (result != Result.Success) 196 if (result != Result.Success)
158 { 197 {
159 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); 198 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@@ -164,7 +203,6 @@ namespace Ryujinx.HLE.HOS
164 process.DefaultCpuCore = kip.IdealCoreId; 203 process.DefaultCpuCore = kip.IdealCoreId;
165 204
166 result = process.Start(kip.Priority, (ulong)kip.StackSize); 205 result = process.Start(kip.Priority, (ulong)kip.StackSize);
167
168 if (result != Result.Success) 206 if (result != Result.Success)
169 { 207 {
170 Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); 208 Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
@@ -177,20 +215,27 @@ namespace Ryujinx.HLE.HOS
177 return true; 215 return true;
178 } 216 }
179 217
180 public static ProgramLoadResult LoadNsos( 218 public static ProcessResult LoadNsos(
219 Switch device,
181 KernelContext context, 220 KernelContext context,
182 MetaLoader metaData, 221 MetaLoader metaLoader,
183 ProgramInfo programInfo, 222 ApplicationControlProperty applicationControlProperties,
223 bool diskCacheEnabled,
224 bool allowCodeMemoryForJit,
225 string name,
226 ulong programId,
184 byte[] arguments = null, 227 byte[] arguments = null,
185 params IExecutable[] executables) 228 params IExecutable[] executables)
186 { 229 {
187 context.Device.System.ServiceTable.WaitServicesReady(); 230 context.Device.System.ServiceTable.WaitServicesReady();
188 231
189 LibHac.Result rc = metaData.GetNpdm(out var npdm); 232 LibHac.Result resultCode = metaLoader.GetNpdm(out var npdm);
190 233
191 if (rc.IsFailure()) 234 if (resultCode.IsFailure())
192 { 235 {
193 return ProgramLoadResult.Failed; 236 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed getting npdm. Result Code {resultCode.ToStringWithName()}");
237
238 return ProcessResult.Failed;
194 } 239 }
195 240
196 ref readonly var meta = ref npdm.Meta; 241 ref readonly var meta = ref npdm.Meta;
@@ -202,10 +247,10 @@ namespace Ryujinx.HLE.HOS
202 247
203 var buildIds = executables.Select(e => (e switch 248 var buildIds = executables.Select(e => (e switch
204 { 249 {
205 NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()), 250 NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()),
206 NroExecutable nro => BitConverter.ToString(nro.Header.BuildId), 251 NroExecutable nro => Convert.ToHexString(nro.Header.BuildId),
207 _ => "" 252 _ => ""
208 }).Replace("-", "").ToUpper()); 253 }).ToUpper());
209 254
210 ulong[] nsoBase = new ulong[executables.Length]; 255 ulong[] nsoBase = new ulong[executables.Length];
211 256
@@ -214,7 +259,7 @@ namespace Ryujinx.HLE.HOS
214 IExecutable nso = executables[index]; 259 IExecutable nso = executables[index];
215 260
216 uint textEnd = nso.TextOffset + (uint)nso.Text.Length; 261 uint textEnd = nso.TextOffset + (uint)nso.Text.Length;
217 uint roEnd = nso.RoOffset + (uint)nso.Ro.Length; 262 uint roEnd = nso.RoOffset + (uint)nso.Ro.Length;
218 uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize; 263 uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize;
219 264
220 uint nsoSize = textEnd; 265 uint nsoSize = textEnd;
@@ -239,31 +284,30 @@ namespace Ryujinx.HLE.HOS
239 { 284 {
240 argsStart = codeSize; 285 argsStart = codeSize;
241 286
242 argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize); 287 argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ProcessConst.NsoArgsTotalSize - 1, KPageTableBase.PageSize);
243 288
244 codeSize += argsSize; 289 codeSize += argsSize;
245 } 290 }
246 } 291 }
247 292
248 int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); 293 int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
249
250 int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize); 294 int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize);
251 295
252 ProcessCreationInfo creationInfo = new ProcessCreationInfo( 296 ProcessCreationInfo creationInfo = new(
253 programInfo.Name, 297 name,
254 (int)meta.Version, 298 (int)meta.Version,
255 programInfo.ProgramId, 299 programId,
256 codeStart, 300 codeStart,
257 codePagesCount, 301 codePagesCount,
258 (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, 302 (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
259 0, 303 0,
260 personalMmHeapPagesCount); 304 personalMmHeapPagesCount);
261 305
262 context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm); 306 context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programId), in npdm);
263 307
264 Result result; 308 Result result;
265 309
266 KResourceLimit resourceLimit = new KResourceLimit(context); 310 KResourceLimit resourceLimit = new(context);
267 311
268 long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size; 312 long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size;
269 313
@@ -293,26 +337,26 @@ namespace Ryujinx.HLE.HOS
293 { 337 {
294 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values."); 338 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values.");
295 339
296 return ProgramLoadResult.Failed; 340 return ProcessResult.Failed;
297 } 341 }
298 342
299 KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit); 343 KProcess process = new(context, allowCodeMemoryForJit);
300
301 MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Flags >> 2) & 0xf);
302 344
345 // NOTE: This field doesn't exists one firmware pre-5.0.0, a workaround have to be found.
346 MemoryRegion memoryRegion = (MemoryRegion)(npdm.Acid.Flags >> 2 & 0xf);
303 if (memoryRegion > MemoryRegion.NvServices) 347 if (memoryRegion > MemoryRegion.NvServices)
304 { 348 {
305 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags."); 349 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags.");
306 350
307 return ProgramLoadResult.Failed; 351 return ProcessResult.Failed;
308 } 352 }
309 353
310 var processContextFactory = new ArmProcessContextFactory( 354 var processContextFactory = new ArmProcessContextFactory(
311 context.Device.System.TickSource, 355 context.Device.System.TickSource,
312 context.Device.Gpu, 356 context.Device.Gpu,
313 programInfo.TitleIdText, 357 $"{programId:x16}",
314 programInfo.DisplayVersion, 358 applicationControlProperties.DisplayVersionString.ToString(),
315 programInfo.DiskCacheEnabled, 359 diskCacheEnabled,
316 codeStart, 360 codeStart,
317 codeSize); 361 codeSize);
318 362
@@ -327,7 +371,7 @@ namespace Ryujinx.HLE.HOS
327 { 371 {
328 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); 372 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
329 373
330 return ProgramLoadResult.Failed; 374 return ProcessResult.Failed;
331 } 375 }
332 376
333 for (int index = 0; index < executables.Length; index++) 377 for (int index = 0; index < executables.Length; index++)
@@ -335,32 +379,22 @@ namespace Ryujinx.HLE.HOS
335 Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); 379 Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
336 380
337 result = LoadIntoMemory(process, executables[index], nsoBase[index]); 381 result = LoadIntoMemory(process, executables[index], nsoBase[index]);
338
339 if (result != Result.Success) 382 if (result != Result.Success)
340 { 383 {
341 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); 384 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
342 385
343 return ProgramLoadResult.Failed; 386 return ProcessResult.Failed;
344 } 387 }
345 } 388 }
346 389
347 process.DefaultCpuCore = meta.DefaultCpuId; 390 process.DefaultCpuCore = meta.DefaultCpuId;
348 391
349 result = process.Start(meta.MainThreadPriority, meta.MainThreadStackSize);
350
351 if (result != Result.Success)
352 {
353 Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
354
355 return ProgramLoadResult.Failed;
356 }
357
358 context.Processes.TryAdd(process.Pid, process); 392 context.Processes.TryAdd(process.Pid, process);
359 393
360 // Keep the build ids because the tamper machine uses them to know which process to associate a 394 // Keep the build ids because the tamper machine uses them to know which process to associate a
361 // tamper to and also keep the starting address of each executable inside a process because some 395 // tamper to and also keep the starting address of each executable inside a process because some
362 // memory modifications are relative to this address. 396 // memory modifications are relative to this address.
363 ProcessTamperInfo tamperInfo = new ProcessTamperInfo( 397 ProcessTamperInfo tamperInfo = new(
364 process, 398 process,
365 buildIds, 399 buildIds,
366 nsoBase, 400 nsoBase,
@@ -368,10 +402,13 @@ namespace Ryujinx.HLE.HOS
368 process.MemoryManager.AliasRegionStart, 402 process.MemoryManager.AliasRegionStart,
369 process.MemoryManager.CodeRegionStart); 403 process.MemoryManager.CodeRegionStart);
370 404
371 return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState, process.Pid); 405 // Once everything is loaded, we can load cheats.
406 device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine);
407
408 return new ProcessResult(metaLoader, applicationControlProperties, diskCacheEnabled, allowCodeMemoryForJit, processContextFactory.DiskCacheLoadState, process.Pid, meta.MainThreadPriority, meta.MainThreadStackSize);
372 } 409 }
373 410
374 private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) 411 public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
375 { 412 {
376 ulong textStart = baseAddress + image.TextOffset; 413 ulong textStart = baseAddress + image.TextOffset;
377 ulong roStart = baseAddress + image.RoOffset; 414 ulong roStart = baseAddress + image.RoOffset;
@@ -404,14 +441,12 @@ namespace Ryujinx.HLE.HOS
404 } 441 }
405 442
406 Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute); 443 Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute);
407
408 if (result != Result.Success) 444 if (result != Result.Success)
409 { 445 {
410 return result; 446 return result;
411 } 447 }
412 448
413 result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read); 449 result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read);
414
415 if (result != Result.Success) 450 if (result != Result.Success)
416 { 451 {
417 return result; 452 return result;
diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs
new file mode 100644
index 000000000..6bbeee1b8
--- /dev/null
+++ b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs
@@ -0,0 +1,92 @@
1using LibHac.Loader;
2using LibHac.Ns;
3using Ryujinx.Common.Logging;
4using Ryujinx.Cpu;
5using Ryujinx.HLE.Loaders.Processes.Extensions;
6using Ryujinx.Horizon.Common;
7
8namespace Ryujinx.HLE.Loaders.Processes
9{
10 public struct ProcessResult
11 {
12 public static ProcessResult Failed => new(null, new ApplicationControlProperty(), false, false, null, 0, 0, 0);
13
14 private readonly byte _mainThreadPriority;
15 private readonly uint _mainThreadStackSize;
16
17 public readonly IDiskCacheLoadState DiskCacheLoadState;
18
19 public readonly MetaLoader MetaLoader;
20 public readonly ApplicationControlProperty ApplicationControlProperties;
21
22 public readonly ulong ProcessId;
23 public string Name;
24 public ulong ProgramId;
25 public readonly string ProgramIdText;
26 public readonly bool Is64Bit;
27 public readonly bool DiskCacheEnabled;
28 public readonly bool AllowCodeMemoryForJit;
29
30 public ProcessResult(
31 MetaLoader metaLoader,
32 ApplicationControlProperty applicationControlProperties,
33 bool diskCacheEnabled,
34 bool allowCodeMemoryForJit,
35 IDiskCacheLoadState diskCacheLoadState,
36 ulong pid,
37 byte mainThreadPriority,
38 uint mainThreadStackSize)
39 {
40 _mainThreadPriority = mainThreadPriority;
41 _mainThreadStackSize = mainThreadStackSize;
42
43 DiskCacheLoadState = diskCacheLoadState;
44 ProcessId = pid;
45
46 MetaLoader = metaLoader;
47 ApplicationControlProperties = applicationControlProperties;
48
49 if (metaLoader is not null)
50 {
51 ulong programId = metaLoader.GetProgramId();
52
53 Name = metaLoader.GetProgramName();
54 ProgramId = programId;
55 ProgramIdText = $"{programId:x16}";
56 Is64Bit = metaLoader.IsProgram64Bit();
57 }
58
59 DiskCacheEnabled = diskCacheEnabled;
60 AllowCodeMemoryForJit = allowCodeMemoryForJit;
61 }
62
63 public bool Start(Switch device)
64 {
65 device.Configuration.ContentManager.LoadEntries(device);
66
67 Result result = device.System.KernelContext.Processes[ProcessId].Start(_mainThreadPriority, _mainThreadStackSize);
68 if (result != Result.Success)
69 {
70 Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
71
72 return false;
73 }
74
75 // TODO: LibHac npdm currently doesn't support version field.
76 string version;
77
78 if (ProgramId > 0x0100000000007FFF)
79 {
80 version = ApplicationControlProperties.DisplayVersionString.ToString();
81 }
82 else
83 {
84 version = device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
85 }
86
87 Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
88
89 return true;
90 }
91 }
92} \ No newline at end of file
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs
index 61e5e5727..62d14a546 100644
--- a/Ryujinx.HLE/Switch.cs
+++ b/Ryujinx.HLE/Switch.cs
@@ -6,6 +6,7 @@ using Ryujinx.HLE.FileSystem;
6using Ryujinx.HLE.HOS; 6using Ryujinx.HLE.HOS;
7using Ryujinx.HLE.HOS.Services.Apm; 7using Ryujinx.HLE.HOS.Services.Apm;
8using Ryujinx.HLE.HOS.Services.Hid; 8using Ryujinx.HLE.HOS.Services.Hid;
9using Ryujinx.HLE.Loaders.Processes;
9using Ryujinx.HLE.Ui; 10using Ryujinx.HLE.Ui;
10using Ryujinx.Memory; 11using Ryujinx.Memory;
11using System; 12using System;
@@ -20,7 +21,7 @@ namespace Ryujinx.HLE
20 public GpuContext Gpu { get; } 21 public GpuContext Gpu { get; }
21 public VirtualFileSystem FileSystem { get; } 22 public VirtualFileSystem FileSystem { get; }
22 public HOS.Horizon System { get; } 23 public HOS.Horizon System { get; }
23 public ApplicationLoader Application { get; } 24 public ProcessLoader Processes { get; }
24 public PerformanceStatistics Statistics { get; } 25 public PerformanceStatistics Statistics { get; }
25 public Hid Hid { get; } 26 public Hid Hid { get; }
26 public TamperMachine TamperMachine { get; } 27 public TamperMachine TamperMachine { get; }
@@ -50,7 +51,7 @@ namespace Ryujinx.HLE
50 System = new HOS.Horizon(this); 51 System = new HOS.Horizon(this);
51 Statistics = new PerformanceStatistics(); 52 Statistics = new PerformanceStatistics();
52 Hid = new Hid(this, System.HidStorage); 53 Hid = new Hid(this, System.HidStorage);
53 Application = new ApplicationLoader(this); 54 Processes = new ProcessLoader(this);
54 TamperMachine = new TamperMachine(); 55 TamperMachine = new TamperMachine();
55 56
56 System.State.SetLanguage(Configuration.SystemLanguage); 57 System.State.SetLanguage(Configuration.SystemLanguage);
@@ -64,29 +65,29 @@ namespace Ryujinx.HLE
64 System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; 65 System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode;
65 } 66 }
66 67
67 public void LoadCart(string exeFsDir, string romFsFile = null) 68 public bool LoadCart(string exeFsDir, string romFsFile = null)
68 { 69 {
69 Application.LoadCart(exeFsDir, romFsFile); 70 return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
70 } 71 }
71 72
72 public void LoadXci(string xciFile) 73 public bool LoadXci(string xciFile)
73 { 74 {
74 Application.LoadXci(xciFile); 75 return Processes.LoadXci(xciFile);
75 } 76 }
76 77
77 public void LoadNca(string ncaFile) 78 public bool LoadNca(string ncaFile)
78 { 79 {
79 Application.LoadNca(ncaFile); 80 return Processes.LoadNca(ncaFile);
80 } 81 }
81 82
82 public void LoadNsp(string nspFile) 83 public bool LoadNsp(string nspFile)
83 { 84 {
84 Application.LoadNsp(nspFile); 85 return Processes.LoadNsp(nspFile);
85 } 86 }
86 87
87 public void LoadProgram(string fileName) 88 public bool LoadProgram(string fileName)
88 { 89 {
89 Application.LoadProgram(fileName); 90 return Processes.LoadNxo(fileName);
90 } 91 }
91 92
92 public bool WaitFifo() 93 public bool WaitFifo()
@@ -123,7 +124,7 @@ namespace Ryujinx.HLE
123 124
124 public void EnableCheats() 125 public void EnableCheats()
125 { 126 {
126 FileSystem.ModLoader.EnableCheats(Application.TitleId, TamperMachine); 127 FileSystem.ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine);
127 } 128 }
128 129
129 public bool IsAudioMuted() 130 public bool IsAudioMuted()
@@ -152,4 +153,4 @@ namespace Ryujinx.HLE
152 } 153 }
153 } 154 }
154 } 155 }
155} \ No newline at end of file 156}
diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs
index f618e38d6..54ab18cda 100644
--- a/Ryujinx.Headless.SDL2/Program.cs
+++ b/Ryujinx.Headless.SDL2/Program.cs
@@ -447,10 +447,10 @@ namespace Ryujinx.Headless.SDL2
447 447
448 private static void SetupProgressHandler() 448 private static void SetupProgressHandler()
449 { 449 {
450 if (_emulationContext.Application.DiskCacheLoadState != null) 450 if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
451 { 451 {
452 _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; 452 _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
453 _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; 453 _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
454 } 454 }
455 455
456 _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; 456 _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -608,12 +608,24 @@ namespace Ryujinx.Headless.SDL2
608 if (romFsFiles.Length > 0) 608 if (romFsFiles.Length > 0)
609 { 609 {
610 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); 610 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
611 _emulationContext.LoadCart(path, romFsFiles[0]); 611
612 if (!_emulationContext.LoadCart(path, romFsFiles[0]))
613 {
614 _emulationContext.Dispose();
615
616 return false;
617 }
612 } 618 }
613 else 619 else
614 { 620 {
615 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); 621 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
616 _emulationContext.LoadCart(path); 622
623 if (!_emulationContext.LoadCart(path))
624 {
625 _emulationContext.Dispose();
626
627 return false;
628 }
617 } 629 }
618 } 630 }
619 else if (File.Exists(path)) 631 else if (File.Exists(path))
@@ -622,27 +634,52 @@ namespace Ryujinx.Headless.SDL2
622 { 634 {
623 case ".xci": 635 case ".xci":
624 Logger.Info?.Print(LogClass.Application, "Loading as XCI."); 636 Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
625 _emulationContext.LoadXci(path); 637
638 if (!_emulationContext.LoadXci(path))
639 {
640 _emulationContext.Dispose();
641
642 return false;
643 }
626 break; 644 break;
627 case ".nca": 645 case ".nca":
628 Logger.Info?.Print(LogClass.Application, "Loading as NCA."); 646 Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
629 _emulationContext.LoadNca(path); 647
648 if (!_emulationContext.LoadNca(path))
649 {
650 _emulationContext.Dispose();
651
652 return false;
653 }
630 break; 654 break;
631 case ".nsp": 655 case ".nsp":
632 case ".pfs0": 656 case ".pfs0":
633 Logger.Info?.Print(LogClass.Application, "Loading as NSP."); 657 Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
634 _emulationContext.LoadNsp(path); 658
659 if (!_emulationContext.LoadNsp(path))
660 {
661 _emulationContext.Dispose();
662
663 return false;
664 }
635 break; 665 break;
636 default: 666 default:
637 Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); 667 Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
638 try 668 try
639 { 669 {
640 _emulationContext.LoadProgram(path); 670 if (!_emulationContext.LoadProgram(path))
671 {
672 _emulationContext.Dispose();
673
674 return false;
675 }
641 } 676 }
642 catch (ArgumentOutOfRangeException) 677 catch (ArgumentOutOfRangeException)
643 { 678 {
644 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); 679 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
645 680
681 _emulationContext.Dispose();
682
646 return false; 683 return false;
647 } 684 }
648 break; 685 break;
@@ -664,4 +701,4 @@ namespace Ryujinx.Headless.SDL2
664 return true; 701 return true;
665 } 702 }
666 } 703 }
667} \ No newline at end of file 704}
diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs
index db6c8ec4d..e33710421 100644
--- a/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -145,16 +145,14 @@ namespace Ryujinx.Headless.SDL2
145 145
146 private void InitializeWindow() 146 private void InitializeWindow()
147 { 147 {
148 string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty 148 var activeProcess = Device.Processes.ActiveApplication;
149 : $" - {Device.Application.TitleName}"; 149 var nacp = activeProcess.ApplicationControlProperties;
150 150 int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
151 string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty 151
152 : $" v{Device.Application.DisplayVersion}"; 152 string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
153 153 string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
154 string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty 154 string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
155 : $" ({Device.Application.TitleIdText.ToUpper()})"; 155 string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
156
157 string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
158 156
159 WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags()); 157 WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags());
160 158
diff --git a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
index 43510d5ec..113e9cb3e 100644
--- a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
+++ b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
@@ -11,12 +11,12 @@ using LibHac.Tools.FsSystem.NcaUtils;
11using Ryujinx.Common.Configuration; 11using Ryujinx.Common.Configuration;
12using Ryujinx.Common.Logging; 12using Ryujinx.Common.Logging;
13using Ryujinx.HLE.FileSystem; 13using Ryujinx.HLE.FileSystem;
14using Ryujinx.HLE.HOS;
15using Ryujinx.HLE.HOS.SystemState; 14using Ryujinx.HLE.HOS.SystemState;
16using Ryujinx.HLE.Loaders.Npdm; 15using Ryujinx.HLE.Loaders.Npdm;
17using Ryujinx.Ui.Common.Configuration.System; 16using Ryujinx.Ui.Common.Configuration.System;
18using System; 17using System;
19using System.Collections.Generic; 18using System.Collections.Generic;
19using System.Globalization;
20using System.IO; 20using System.IO;
21using System.Reflection; 21using System.Reflection;
22using System.Text; 22using System.Text;
@@ -112,9 +112,9 @@ namespace Ryujinx.Ui.App.Common
112 { 112 {
113 return; 113 return;
114 } 114 }
115 115
116 string extension = Path.GetExtension(app).ToLower(); 116 string extension = Path.GetExtension(app).ToLower();
117 117
118 if (!File.GetAttributes(app).HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso") 118 if (!File.GetAttributes(app).HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso")
119 { 119 {
120 applications.Add(app); 120 applications.Add(app);
@@ -262,10 +262,9 @@ namespace Ryujinx.Ui.App.Common
262 controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); 262 controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
263 263
264 using MemoryStream stream = new(); 264 using MemoryStream stream = new();
265 265
266 icon.Get.AsStream().CopyTo(stream); 266 icon.Get.AsStream().CopyTo(stream);
267 applicationIcon = stream.ToArray(); 267 applicationIcon = stream.ToArray();
268
269 268
270 if (applicationIcon != null) 269 if (applicationIcon != null)
271 { 270 {
@@ -400,7 +399,7 @@ namespace Ryujinx.Ui.App.Common
400 }); 399 });
401 400
402 if (appMetadata.LastPlayed != "Never") 401 if (appMetadata.LastPlayed != "Never")
403 { 402 {
404 if (!DateTime.TryParse(appMetadata.LastPlayed, out _)) 403 if (!DateTime.TryParse(appMetadata.LastPlayed, out _))
405 { 404 {
406 Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)"); 405 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
470 469
471 private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId) 470 private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
472 { 471 {
473 (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0); 472 (_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
474 473
475 // Return the ControlFS 474 // Return the ControlFS
476 controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); 475 controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
@@ -766,12 +765,12 @@ namespace Ryujinx.Ui.App.Common
766 private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs) 765 private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
767 { 766 {
768 updatedControlFs = null; 767 updatedControlFs = null;
769 768
770 string updatePath = "(unknown)"; 769 string updatePath = "(unknown)";
771 770
772 try 771 try
773 { 772 {
774 (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath); 773 (Nca patchNca, Nca controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
775 774
776 if (patchNca != null && controlNca != null) 775 if (patchNca != null && controlNca != null)
777 { 776 {
@@ -791,5 +790,119 @@ namespace Ryujinx.Ui.App.Common
791 790
792 return false; 791 return false;
793 } 792 }
793
794 public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
795 {
796 Nca mainNca = null;
797 Nca patchNca = null;
798 Nca controlNca = null;
799
800 fileSystem.ImportTickets(pfs);
801
802 foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
803 {
804 using var ncaFile = new UniqueRef<IFile>();
805
806 pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
807
808 Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
809
810 int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
811
812 if (ncaProgramIndex != programIndex)
813 {
814 continue;
815 }
816
817 if (nca.Header.ContentType == NcaContentType.Program)
818 {
819 int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
820
821 if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
822 {
823 patchNca = nca;
824 }
825 else
826 {
827 mainNca = nca;
828 }
829 }
830 else if (nca.Header.ContentType == NcaContentType.Control)
831 {
832 controlNca = nca;
833 }
834 }
835
836 return (mainNca, patchNca, controlNca);
837 }
838
839 public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
840 {
841 Nca patchNca = null;
842 Nca controlNca = null;
843
844 fileSystem.ImportTickets(pfs);
845
846 foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
847 {
848 using var ncaFile = new UniqueRef<IFile>();
849
850 pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
851
852 Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
853
854 int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
855
856 if (ncaProgramIndex != programIndex)
857 {
858 continue;
859 }
860
861 if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
862 {
863 break;
864 }
865
866 if (nca.Header.ContentType == NcaContentType.Program)
867 {
868 patchNca = nca;
869 }
870 else if (nca.Header.ContentType == NcaContentType.Control)
871 {
872 controlNca = nca;
873 }
874 }
875
876 return (patchNca, controlNca);
877 }
878
879 public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
880 {
881 updatePath = null;
882
883 if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
884 {
885 // Clear the program index part.
886 titleIdBase &= ~0xFUL;
887
888 // Load update information if exists.
889 string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
890
891 if (File.Exists(titleUpdateMetadataPath))
892 {
893 updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
894
895 if (File.Exists(updatePath))
896 {
897 FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
898 PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
899
900 return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
901 }
902 }
903 }
904
905 return (null, null);
906 }
794 } 907 }
795} \ No newline at end of file 908} \ No newline at end of file
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index ace8b87f9..dca87abcb 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -252,7 +252,7 @@ namespace Ryujinx
252 252
253 if (CommandLineState.LaunchPathArg != null) 253 if (CommandLineState.LaunchPathArg != null)
254 { 254 {
255 mainWindow.LoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg); 255 mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
256 } 256 }
257 257
258 if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) 258 if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 6d3d4aad6..e0252016f 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -590,10 +590,10 @@ namespace Ryujinx.Ui
590 590
591 private void SetupProgressUiHandlers() 591 private void SetupProgressUiHandlers()
592 { 592 {
593 if (_emulationContext.Application.DiskCacheLoadState != null) 593 if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
594 { 594 {
595 _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; 595 _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
596 _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; 596 _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
597 } 597 }
598 598
599 _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; 599 _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@@ -690,156 +690,143 @@ namespace Ryujinx.Ui
690 } 690 }
691 } 691 }
692 692
693 public void LoadApplication(string path, bool startFullscreen = false) 693 private bool LoadApplication(string path, bool isFirmwareTitle)
694 { 694 {
695 if (_gameLoaded) 695 SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
696 { 696
697 GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game."); 697 if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError))
698 }
699 else
700 { 698 {
701 PerformanceCheck(); 699 if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion))
700 {
701 string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})";
702 702
703 Logger.RestartTime(); 703 ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run();
704 704
705 RendererWidget = CreateRendererWidget(); 705 if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _))
706 {
707 UserErrorDialog.CreateUserErrorDialog(userError);
706 708
707 SwitchToRenderWidget(startFullscreen); 709 return false;
710 }
708 711
709 InitializeSwitchInstance(); 712 // Tell the user that we installed a firmware for them.
710 713
711 UpdateGraphicsConfig(); 714 firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
712 715
713 SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); 716 RefreshFirmwareLabel();
714 717
715 bool isDirectory = Directory.Exists(path); 718 message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start.";
716 bool isFirmwareTitle = false;
717 719
718 if (path.StartsWith("@SystemContent")) 720 GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message);
721 }
722 else
719 { 723 {
720 path = _virtualFileSystem.SwitchPathToSystemPath(path); 724 UserErrorDialog.CreateUserErrorDialog(userError);
721 725
722 isFirmwareTitle = true; 726 return false;
723 } 727 }
728 }
724 729
725 if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) 730 Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
726 {
727 if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion))
728 {
729 if (userError == UserError.NoFirmware)
730 {
731 string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})";
732 731
733 ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); 732 if (isFirmwareTitle)
733 {
734 Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
734 735
735 if (responseDialog != ResponseType.Yes) 736 return _emulationContext.LoadNca(path);
736 { 737 }
737 UserErrorDialog.CreateUserErrorDialog(userError);
738 738
739 _emulationContext.Dispose(); 739 if (Directory.Exists(path))
740 SwitchToGameTable(); 740 {
741 string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
741 742
742 return; 743 if (romFsFiles.Length == 0)
743 } 744 {
744 } 745 romFsFiles = Directory.GetFiles(path, "*.romfs");
746 }
745 747
746 if (!SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) 748 if (romFsFiles.Length > 0)
747 { 749 {
748 UserErrorDialog.CreateUserErrorDialog(userError); 750 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
751
752 return _emulationContext.LoadCart(path, romFsFiles[0]);
753 }
749 754
750 _emulationContext.Dispose(); 755 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
751 SwitchToGameTable();
752 756
753 return; 757 return _emulationContext.LoadCart(path);
754 } 758 }
755 759
756 // Tell the user that we installed a firmware for them. 760 if (File.Exists(path))
757 if (userError == UserError.NoFirmware) 761 {
762 switch (System.IO.Path.GetExtension(path).ToLowerInvariant())
763 {
764 case ".xci":
765 Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
766
767 return _emulationContext.LoadXci(path);
768 case ".nca":
769 Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
770
771 return _emulationContext.LoadNca(path);
772 case ".nsp":
773 case ".pfs0":
774 Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
775
776 return _emulationContext.LoadNsp(path);
777 default:
778 Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
779 try
780 {
781 return _emulationContext.LoadProgram(path);
782 }
783 catch (ArgumentOutOfRangeException)
758 { 784 {
759 firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); 785 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
760 786
761 RefreshFirmwareLabel(); 787 return false;
788 }
789 }
790 }
762 791
763 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."; 792 Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
764 793
765 GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); 794 return false;
766 } 795 }
767 }
768 else
769 {
770 UserErrorDialog.CreateUserErrorDialog(userError);
771 796
772 _emulationContext.Dispose(); 797 public void RunApplication(string path, bool startFullscreen = false)
773 SwitchToGameTable(); 798 {
799 if (_gameLoaded)
800 {
801 GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game.");
802 }
803 else
804 {
805 PerformanceCheck();
774 806
775 return; 807 Logger.RestartTime();
776 }
777 }
778 808
779 Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); 809 RendererWidget = CreateRendererWidget();
780 810
781 if (isFirmwareTitle) 811 SwitchToRenderWidget(startFullscreen);
782 {
783 Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
784 812
785 _emulationContext.LoadNca(path); 813 InitializeSwitchInstance();
786 }
787 else if (Directory.Exists(path))
788 {
789 string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
790 814
791 if (romFsFiles.Length == 0) 815 UpdateGraphicsConfig();
792 {
793 romFsFiles = Directory.GetFiles(path, "*.romfs");
794 }
795 816
796 if (romFsFiles.Length > 0) 817 bool isFirmwareTitle = false;
797 { 818
798 Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); 819 if (path.StartsWith("@SystemContent"))
799 _emulationContext.LoadCart(path, romFsFiles[0]);
800 }
801 else
802 {
803 Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
804 _emulationContext.LoadCart(path);
805 }
806 }
807 else if (File.Exists(path))
808 { 820 {
809 switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) 821 path = _virtualFileSystem.SwitchPathToSystemPath(path);
810 { 822
811 case ".xci": 823 isFirmwareTitle = true;
812 Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
813 _emulationContext.LoadXci(path);
814 break;
815 case ".nca":
816 Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
817 _emulationContext.LoadNca(path);
818 break;
819 case ".nsp":
820 case ".pfs0":
821 Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
822 _emulationContext.LoadNsp(path);
823 break;
824 default:
825 Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
826 try
827 {
828 _emulationContext.LoadProgram(path);
829 }
830 catch (ArgumentOutOfRangeException)
831 {
832 Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
833 }
834 break;
835 }
836 } 824 }
837 else
838 {
839 Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
840 825
826 if (!LoadApplication(path, isFirmwareTitle))
827 {
841 _emulationContext.Dispose(); 828 _emulationContext.Dispose();
842 RendererWidget.Dispose(); 829 SwitchToGameTable();
843 830
844 return; 831 return;
845 } 832 }
@@ -852,10 +839,7 @@ namespace Ryujinx.Ui
852 839
853 Translator.IsReadyForTranslation.Reset(); 840 Translator.IsReadyForTranslation.Reset();
854 841
855 Thread windowThread = new Thread(() => 842 Thread windowThread = new(CreateGameWindow)
856 {
857 CreateGameWindow();
858 })
859 { 843 {
860 Name = "GUI.WindowThread" 844 Name = "GUI.WindowThread"
861 }; 845 };
@@ -871,9 +855,10 @@ namespace Ryujinx.Ui
871 _firmwareInstallFile.Sensitive = false; 855 _firmwareInstallFile.Sensitive = false;
872 _firmwareInstallDirectory.Sensitive = false; 856 _firmwareInstallDirectory.Sensitive = false;
873 857
874 DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Application.TitleIdText, _emulationContext.Application.TitleName); 858 DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText,
859 _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString());
875 860
876 _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Application.TitleIdText, appMetadata => 861 _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata =>
877 { 862 {
878 appMetadata.LastPlayed = DateTime.UtcNow.ToString(); 863 appMetadata.LastPlayed = DateTime.UtcNow.ToString();
879 }); 864 });
@@ -1055,7 +1040,7 @@ namespace Ryujinx.Ui
1055 1040
1056 if (_emulationContext != null) 1041 if (_emulationContext != null)
1057 { 1042 {
1058 UpdateGameMetadata(_emulationContext.Application.TitleIdText); 1043 UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
1059 1044
1060 if (RendererWidget != null) 1045 if (RendererWidget != null)
1061 { 1046 {
@@ -1174,7 +1159,7 @@ namespace Ryujinx.Ui
1174 1159
1175 string path = (string)_tableStore.GetValue(treeIter, 9); 1160 string path = (string)_tableStore.GetValue(treeIter, 9);
1176 1161
1177 LoadApplication(path); 1162 RunApplication(path);
1178 } 1163 }
1179 1164
1180 private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) 1165 private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
@@ -1260,7 +1245,7 @@ namespace Ryujinx.Ui
1260 1245
1261 if (fileChooser.Run() == (int)ResponseType.Accept) 1246 if (fileChooser.Run() == (int)ResponseType.Accept)
1262 { 1247 {
1263 LoadApplication(fileChooser.Filename); 1248 RunApplication(fileChooser.Filename);
1264 } 1249 }
1265 } 1250 }
1266 } 1251 }
@@ -1271,7 +1256,7 @@ namespace Ryujinx.Ui
1271 { 1256 {
1272 if (fileChooser.Run() == (int)ResponseType.Accept) 1257 if (fileChooser.Run() == (int)ResponseType.Accept)
1273 { 1258 {
1274 LoadApplication(fileChooser.Filename); 1259 RunApplication(fileChooser.Filename);
1275 } 1260 }
1276 } 1261 }
1277 } 1262 }
@@ -1287,7 +1272,7 @@ namespace Ryujinx.Ui
1287 { 1272 {
1288 string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); 1273 string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
1289 1274
1290 LoadApplication(contentPath); 1275 RunApplication(contentPath);
1291 } 1276 }
1292 1277
1293 private void Open_Ryu_Folder(object sender, EventArgs args) 1278 private void Open_Ryu_Folder(object sender, EventArgs args)
@@ -1328,7 +1313,7 @@ namespace Ryujinx.Ui
1328 { 1313 {
1329 if (_emulationContext != null) 1314 if (_emulationContext != null)
1330 { 1315 {
1331 UpdateGameMetadata(_emulationContext.Application.TitleIdText); 1316 UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
1332 } 1317 }
1333 1318
1334 _pauseEmulation.Sensitive = false; 1319 _pauseEmulation.Sensitive = false;
@@ -1533,7 +1518,7 @@ namespace Ryujinx.Ui
1533 { 1518 {
1534 _userChannelPersistence.ShouldRestart = false; 1519 _userChannelPersistence.ShouldRestart = false;
1535 1520
1536 LoadApplication(_currentEmulatedGamePath); 1521 RunApplication(_currentEmulatedGamePath);
1537 } 1522 }
1538 else 1523 else
1539 { 1524 {
@@ -1596,7 +1581,9 @@ namespace Ryujinx.Ui
1596 1581
1597 private void ManageCheats_Pressed(object sender, EventArgs args) 1582 private void ManageCheats_Pressed(object sender, EventArgs args)
1598 { 1583 {
1599 var window = new CheatWindow(_virtualFileSystem, _emulationContext.Application.TitleId, _emulationContext.Application.TitleName); 1584 var window = new CheatWindow(_virtualFileSystem,
1585 _emulationContext.Processes.ActiveApplication.ProgramId,
1586 _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString());
1600 1587
1601 window.Destroyed += CheatWindow_Destroyed; 1588 window.Destroyed += CheatWindow_Destroyed;
1602 window.Show(); 1589 window.Show();
@@ -1639,7 +1626,7 @@ namespace Ryujinx.Ui
1639 LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, 1626 LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll,
1640 LastScannedAmiiboId = _lastScannedAmiiboId, 1627 LastScannedAmiiboId = _lastScannedAmiiboId,
1641 DeviceId = deviceId, 1628 DeviceId = deviceId,
1642 TitleId = _emulationContext.Application.TitleIdText.ToUpper() 1629 TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper()
1643 }; 1630 };
1644 1631
1645 amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; 1632 amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent;
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index e5d22d65c..7cb5b3275 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -495,16 +495,14 @@ namespace Ryujinx.Ui
495 { 495 {
496 parent.Present(); 496 parent.Present();
497 497
498 string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty 498 var activeProcess = Device.Processes.ActiveApplication;
499 : $" - {Device.Application.TitleName}"; 499 var nacp = activeProcess.ApplicationControlProperties;
500 int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
500 501
501 string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty 502 string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
502 : $" v{Device.Application.DisplayVersion}"; 503 string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
503 504 string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
504 string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty 505 string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
505 : $" ({Device.Application.TitleIdText.ToUpper()})";
506
507 string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
508 506
509 parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; 507 parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
510 }); 508 });
@@ -612,7 +610,7 @@ namespace Ryujinx.Ui
612 { 610 {
613 if (!ParentWindow.State.HasFlag(WindowState.Fullscreen)) 611 if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
614 { 612 {
615 Device.Application.DiskCacheLoadState?.Cancel(); 613 Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
616 } 614 }
617 } 615 }
618 }); 616 });
diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index a63d68ff2..558288aab 100644
--- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -15,6 +15,7 @@ using Ryujinx.Common.Logging;
15using Ryujinx.HLE.FileSystem; 15using Ryujinx.HLE.FileSystem;
16using Ryujinx.HLE.HOS; 16using Ryujinx.HLE.HOS;
17using Ryujinx.HLE.HOS.Services.Account.Acc; 17using Ryujinx.HLE.HOS.Services.Account.Acc;
18using Ryujinx.Ui.App.Common;
18using Ryujinx.Ui.Common.Configuration; 19using Ryujinx.Ui.Common.Configuration;
19using Ryujinx.Ui.Common.Helper; 20using Ryujinx.Ui.Common.Helper;
20using Ryujinx.Ui.Windows; 21using Ryujinx.Ui.Windows;
@@ -260,7 +261,7 @@ namespace Ryujinx.Ui.Widgets
260 return; 261 return;
261 } 262 }
262 263
263 (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _); 264 (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
264 265
265 if (updatePatchNca != null) 266 if (updatePatchNca != null)
266 { 267 {
diff --git a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
index 4aea58955..fce751da1 100644
--- a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
+++ b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
@@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
9using Ryujinx.Common.Configuration; 9using Ryujinx.Common.Configuration;
10using Ryujinx.HLE.FileSystem; 10using Ryujinx.HLE.FileSystem;
11using Ryujinx.HLE.HOS; 11using Ryujinx.HLE.HOS;
12using Ryujinx.Ui.App.Common;
12using Ryujinx.Ui.Widgets; 13using Ryujinx.Ui.Widgets;
13using System; 14using System;
14using System.Collections.Generic; 15using System.Collections.Generic;
@@ -94,7 +95,7 @@ namespace Ryujinx.Ui.Windows
94 95
95 try 96 try
96 { 97 {
97 (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0); 98 (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
98 99
99 if (controlNca != null && patchNca != null) 100 if (controlNca != null && patchNca != null)
100 { 101 {