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