From 4c2d9ff3ff9d7afb1fd0bd764bee5931fa5f053c Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 31 Mar 2023 21:16:46 +0200 Subject: [PATCH] HLE: Refactoring of ApplicationLoader (#4480) * 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> --- Ryujinx.Ava/AppHost.cs | 77 +- Ryujinx.Ava/Common/ApplicationHelper.cs | 3 +- .../UI/ViewModels/MainWindowViewModel.cs | 10 +- .../UI/ViewModels/TitleUpdateViewModel.cs | 3 +- .../UI/Views/Main/MainMenuBarView.axaml.cs | 12 +- Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 6 +- Ryujinx.HLE/HOS/ApplicationLoader.cs | 908 ------------------ Ryujinx.HLE/HOS/Horizon.cs | 5 +- .../HOS/Kernel/Process/ProcessTamperInfo.cs | 2 +- Ryujinx.HLE/HOS/ModLoader.cs | 9 +- .../Acc/IAccountServiceForApplication.cs | 2 +- .../ILibraryAppletSelfAccessor.cs | 4 +- .../ApplicationProxy/IApplicationFunctions.cs | 35 +- .../Services/Arp/ApplicationLaunchProperty.cs | 2 +- .../Caps/IScreenShotApplicationService.cs | 6 +- Ryujinx.HLE/HOS/Services/Fatal/IService.cs | 4 +- .../Friend/ServiceCreator/IFriendService.cs | 2 +- .../HOS/Services/Fs/IFileSystemProxy.cs | 2 +- .../Services/Ns/Aoc/IAddOnContentManager.cs | 14 +- .../Ns/IApplicationManagerInterface.cs | 10 +- ...ReadOnlyApplicationControlDataInterface.cs | 9 +- .../IParentalControlService.cs | 4 +- .../QueryPlayStatisticsManager.cs | 7 +- .../Extensions/FileSystemExtensions.cs | 133 +++ .../Extensions/LocalFileSystemExtensions.cs | 39 + .../Extensions/MetaLoaderExtensions.cs | 61 ++ .../Processes/Extensions/NcaExtensions.cs | 175 ++++ .../PartitionFileSystemExtensions.cs | 177 ++++ Ryujinx.HLE/Loaders/Processes/ProcessConst.cs | 33 + .../Loaders/Processes/ProcessLoader.cs | 244 +++++ .../Processes/ProcessLoaderHelper.cs} | 277 +++--- .../Loaders/Processes/ProcessResult.cs | 92 ++ Ryujinx.HLE/Switch.cs | 29 +- Ryujinx.Headless.SDL2/Program.cs | 57 +- Ryujinx.Headless.SDL2/WindowBase.cs | 16 +- Ryujinx.Ui.Common/App/ApplicationLibrary.cs | 131 ++- Ryujinx/Program.cs | 2 +- Ryujinx/Ui/MainWindow.cs | 263 +++-- Ryujinx/Ui/RendererWidgetBase.cs | 18 +- Ryujinx/Ui/Widgets/GameTableContextMenu.cs | 3 +- Ryujinx/Ui/Windows/TitleUpdateWindow.cs | 3 +- 41 files changed, 1567 insertions(+), 1322 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/ApplicationLoader.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/ProcessConst.cs create mode 100644 Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs rename Ryujinx.HLE/{HOS/ProgramLoader.cs => Loaders/Processes/ProcessLoaderHelper.cs} (54%) create mode 100644 Ryujinx.HLE/Loaders/Processes/ProcessResult.cs diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index eb22b39e9..3cdb3906f 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs @@ -320,10 +320,14 @@ namespace Ryujinx.Ava _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 276d18745..161ef8596 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 a3663af3e..d5ff78545 100644 --- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels 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 dd9e1b961..0798502cd 100644 --- a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs @@ -17,6 +17,7 @@ using Ryujinx.Common.Logging; 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 1c6f4265c..30d411508 100644 --- a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs @@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main 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 3f94ce61b..1b3968eab 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 82bd9b312..000000000 --- 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 _controlData; - - public BlitStruct 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(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(); - - 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(); - - 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(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(); - - 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(); - - 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 dlcContainerList = JsonHelper.DeserializeFromFile>(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 dummyControl = new BlitStruct(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(); - - 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 controlData, ref string titleName, ref string displayVersion) - { - using var controlFile = new UniqueRef(); - - 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(); - - 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 hasIndex = stackalloc bool[0x10]; - - fileSystem.ImportTickets(pfs); - - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef(); - - 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 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(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 2b77a7c2e..1639532ed 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger; 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(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 556703cf6..4cf67172d 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel.Process { - internal class ProcessTamperInfo + class ProcessTamperInfo { public KProcess Process { get; } public IEnumerable BuildIds { get; } diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs index a6dc90135..165125414 100644 --- a/Ryujinx.HLE/HOS/ModLoader.cs +++ b/Ryujinx.HLE/HOS/ModLoader.cs @@ -10,6 +10,7 @@ using Ryujinx.Common.Logging; 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 1b412d74e..413bedced 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc // 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 00081e1b1..720497141 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs @@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib 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 f8f88a1cb..924f54298 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -115,28 +115,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati Uid userId = context.RequestData.ReadStruct().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 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(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 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 c985092b8..3e4eca0ac 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp 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 1789122e4..e0c65f440 100644 --- a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs +++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps 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 6d663a4de..c884e8803 100644 --- a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs +++ b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs @@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal 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(cpuContext)[0]; diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs index 17a33b799..4317c8f61 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs @@ -334,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator } // 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 37143a5aa..1b63f362e 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -808,7 +808,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs { 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 0d5520038..b8f9e3b97 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs @@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // 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 d3a891782..249343d79 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 3b6965d0a..8f6acc1c6 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 e0017808d..02964749c 100644 --- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs +++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -56,8 +56,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory _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 1d6cc118e..52a07d466 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -6,7 +6,6 @@ using System; 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 titleIds = new List(); @@ -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 000000000..58759ddb1 --- /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 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(); + + 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 000000000..28d907851 --- /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(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 000000000..c639ee524 --- /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(); + + fileSystem.OpenFile(ref npdmFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); + + Span 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 000000000..473f374db --- /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(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 GetNacp(this Nca controlNca, Switch device) + { + var nacpData = new BlitStruct(1); + + using var controlFile = new UniqueRef(); + + 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 000000000..5147f5c3a --- /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(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 dlcContainerList = JsonHelper.DeserializeFromFile>(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(); + + 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 000000000..42ae2e89b --- /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 000000000..785db0e50 --- /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 _processesByPid; + + private ulong _latestPid; + + public ProcessResult ActiveApplication => _processesByPid[_latestPid]; + + public ProcessLoader(Switch device) + { + _device = device; + _processesByPid = new ConcurrentDictionary(); + } + + 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(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(1); + IFileSystem dummyExeFs = null; + Stream romfsStream = null; + + string programName = ""; + ulong programId = 0000000000000000; + + // Load executable. + IExecutable executable; + + if (Path.GetExtension(path).ToLower() == ".nro") + { + FileStream input = new(path, FileMode.Open); + NroExecutable nro = new(input.AsStorage()); + + executable = nro; + + // Open RomFS if exists. + IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false); + romFsStorage.GetSize(out long romFsSize).ThrowIfFailure(); + if (romFsSize != 0) + { + romfsStream = romFsStorage.AsStream(); + } + + // Load Nacp if exists. + IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false); + nacpStorage.GetSize(out long nacpSize).ThrowIfFailure(); + if (nacpSize != 0) + { + nacpStorage.Read(0, nacpData.ByteSpan); + + programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); + + if (string.IsNullOrWhiteSpace(programName)) + { + programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); + } + + if (nacpData.Value.PresenceGroupId != 0) + { + programId = nacpData.Value.PresenceGroupId; + } + else if (nacpData.Value.SaveDataOwnerId != 0) + { + programId = nacpData.Value.SaveDataOwnerId; + } + else if (nacpData.Value.AddOnContentBaseId != 0) + { + programId = nacpData.Value.AddOnContentBaseId - 0x1000; + } + } + + // TODO: Add icon maybe ? + } + else + { + programName = System.IO.Path.GetFileNameWithoutExtension(path); + + executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName); + } + + // Explicitly null TitleId to disable the shader cache. + Graphics.Gpu.GraphicsConfig.TitleId = null; + _device.Gpu.HostInitalized.Set(); + + ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, + _device.System.KernelContext, + dummyExeFs.GetNpdm(), + nacpData.Value, + diskCacheEnabled: false, + allowCodeMemoryForJit: true, + programName, + programId, + null, + executable); + + // Make sure the process id is valid. + if (processResult.ProcessId != 0) + { + // Load RomFS. + if (romfsStream != null) + { + _device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream); + } + + // Start process. + if (_processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs similarity index 54% rename from Ryujinx.HLE/HOS/ProgramLoader.cs rename to Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index 4ebcb7e7d..7176445e5 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -1,69 +1,132 @@ +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSystem; using LibHac.Loader; using LibHac.Ncm; -using LibHac.Util; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Cpu; +using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.Horizon.Common; using System; using System.Linq; using System.Runtime.InteropServices; -using Npdm = LibHac.Loader.Npdm; +using ApplicationId = LibHac.Ncm.ApplicationId; -namespace Ryujinx.HLE.HOS +namespace Ryujinx.HLE.Loaders.Processes { - struct ProgramInfo + static class ProcessLoaderHelper { - public string Name; - public ulong ProgramId; - public readonly string TitleIdText; - public readonly string DisplayVersion; - public readonly bool DiskCacheEnabled; - public readonly bool AllowCodeMemoryForJit; - - public ProgramInfo(in Npdm npdm, string displayVersion, bool diskCacheEnabled, bool allowCodeMemoryForJit) + public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem) { - ulong programId = npdm.Aci.ProgramId.Value; + ulong applicationId = 0; + int programCount = 0; - Name = StringUtils.Utf8ZToString(npdm.Meta.ProgramName); - ProgramId = programId; - TitleIdText = programId.ToString("x16"); - DisplayVersion = displayVersion; - DiskCacheEnabled = diskCacheEnabled; - AllowCodeMemoryForJit = allowCodeMemoryForJit; + Span hasIndex = stackalloc bool[0x10]; + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath); + + if (!nca.IsProgram() && nca.IsPatch()) + { + continue; + } + + ulong currentProgramId = nca.Header.TitleId; + ulong currentMainProgramId = currentProgramId & ~0xFFFul; + + if (applicationId == 0 && currentMainProgramId != 0) + { + applicationId = currentMainProgramId; + } + + if (applicationId != currentMainProgramId) + { + // Currently there aren't any known multi-application game cards containing multi-program applications, + // so because multi-application game cards are the only way we could run into multiple applications + // we'll just return that there's a single program. + programCount = 1; + + break; + } + + hasIndex[(int)(currentProgramId & 0xF)] = true; + } + + if (programCount == 0) + { + for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) + { + programCount++; + } + } + + if (programCount <= 0) + { + return LibHac.Result.Success; + } + + Span mapInfo = stackalloc ProgramIndexMapInfo[0x10]; + + for (int i = 0; i < programCount; i++) + { + mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); + mapInfo[i].MainProgramId = new ApplicationId(applicationId); + mapInfo[i].ProgramIndex = (byte)i; + } + + return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]); } - } - struct ProgramLoadResult - { - public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null, 0); - - public readonly bool Success; - public readonly ProcessTamperInfo TamperInfo; - public readonly IDiskCacheLoadState DiskCacheLoadState; - public readonly ulong ProcessId; - - public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState, ulong pid) + public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct applicationControlProperty) { - Success = success; - TamperInfo = tamperInfo; - DiskCacheLoadState = diskCacheLoadState; - ProcessId = pid; + Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); + + ref ApplicationControlProperty control = ref applicationControlProperty.Value; + + if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + control.SaveDataOwnerId = applicationId.Value; + + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); + + return resultCode; + } + + Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); + + resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId); + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); + } + + return resultCode; } - } - - static class ProgramLoader - { - private const bool AslrEnabled = true; - - private const int ArgsHeaderSize = 8; - private const int ArgsDataSize = 0x9000; - private const int ArgsTotalSize = ArgsHeaderSize + ArgsDataSize; public static bool LoadKip(KernelContext context, KipExecutable kip) { @@ -74,17 +137,14 @@ namespace Ryujinx.HLE.HOS endOffset = kip.BssOffset + kip.BssSize; } - uint codeSize = BitUtils.AlignUp(kip.TextOffset + endOffset, KPageTableBase.PageSize); - - int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); - + uint codeSize = BitUtils.AlignUp(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 000000000..6bbeee1b8 --- /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 61e5e5727..62d14a546 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 f618e38d6..54ab18cda 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 db6c8ec4d..e33710421 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs @@ -145,16 +145,14 @@ namespace Ryujinx.Headless.SDL2 private void InitializeWindow() { - string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty - : $" - {Device.Application.TitleName}"; + var activeProcess = Device.Processes.ActiveApplication; + var nacp = activeProcess.ApplicationControlProperties; + int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage; - string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty - : $" v{Device.Application.DisplayVersion}"; - - string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty - : $" ({Device.Application.TitleIdText.ToUpper()})"; - - string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; + string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}"; + string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}"; + string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})"; + string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DefaultWidth, DefaultHeight, DefaultFlags | GetWindowFlags()); diff --git a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs index 43510d5ec..113e9cb3e 100644 --- a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs +++ b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs @@ -11,12 +11,12 @@ using LibHac.Tools.FsSystem.NcaUtils; 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(); + + 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(); + + 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(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 ace8b87f9..dca87abcb 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 6d3d4aad6..e0252016f 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -590,10 +590,10 @@ namespace Ryujinx.Ui private void SetupProgressUiHandlers() { - if (_emulationContext.Application.DiskCacheLoadState != null) + if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) { - _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler; - _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler; + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; } _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; @@ -690,7 +690,111 @@ namespace Ryujinx.Ui } } - public void LoadApplication(string path, bool startFullscreen = false) + private bool LoadApplication(string path, bool isFirmwareTitle) + { + SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) + { + if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) + { + string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; + + ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); + + if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) + { + UserErrorDialog.CreateUserErrorDialog(userError); + + return false; + } + + // Tell the user that we installed a firmware for them. + + firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + RefreshFirmwareLabel(); + + message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; + + GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); + } + else + { + UserErrorDialog.CreateUserErrorDialog(userError); + + return false; + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + return _emulationContext.LoadNca(path); + } + + if (Directory.Exists(path)) + { + string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + return _emulationContext.LoadCart(path, romFsFiles[0]); + } + + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + return _emulationContext.LoadCart(path); + } + + if (File.Exists(path)) + { + switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) + { + case ".xci": + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + return _emulationContext.LoadXci(path); + case ".nca": + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + return _emulationContext.LoadNca(path); + case ".nsp": + case ".pfs0": + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + return _emulationContext.LoadNsp(path); + default: + Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); + try + { + return _emulationContext.LoadProgram(path); + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + return false; + } + } + } + + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + return false; + } + + public void RunApplication(string path, bool startFullscreen = false) { if (_gameLoaded) { @@ -710,9 +814,6 @@ namespace Ryujinx.Ui UpdateGraphicsConfig(); - SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); - - bool isDirectory = Directory.Exists(path); bool isFirmwareTitle = false; if (path.StartsWith("@SystemContent")) @@ -722,124 +823,10 @@ namespace Ryujinx.Ui isFirmwareTitle = true; } - if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) + if (!LoadApplication(path, isFirmwareTitle)) { - if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) - { - if (userError == UserError.NoFirmware) - { - string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; - - ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); - - if (responseDialog != ResponseType.Yes) - { - UserErrorDialog.CreateUserErrorDialog(userError); - - _emulationContext.Dispose(); - SwitchToGameTable(); - - return; - } - } - - if (!SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) - { - UserErrorDialog.CreateUserErrorDialog(userError); - - _emulationContext.Dispose(); - SwitchToGameTable(); - - return; - } - - // Tell the user that we installed a firmware for them. - if (userError == UserError.NoFirmware) - { - firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); - - RefreshFirmwareLabel(); - - string message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; - - GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); - } - } - else - { - UserErrorDialog.CreateUserErrorDialog(userError); - - _emulationContext.Dispose(); - SwitchToGameTable(); - - return; - } - } - - Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); - - if (isFirmwareTitle) - { - Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); - - _emulationContext.LoadNca(path); - } - else if (Directory.Exists(path)) - { - string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); - - if (romFsFiles.Length == 0) - { - romFsFiles = Directory.GetFiles(path, "*.romfs"); - } - - if (romFsFiles.Length > 0) - { - Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); - _emulationContext.LoadCart(path, romFsFiles[0]); - } - else - { - Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); - _emulationContext.LoadCart(path); - } - } - else if (File.Exists(path)) - { - switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) - { - case ".xci": - Logger.Info?.Print(LogClass.Application, "Loading as XCI."); - _emulationContext.LoadXci(path); - break; - case ".nca": - Logger.Info?.Print(LogClass.Application, "Loading as NCA."); - _emulationContext.LoadNca(path); - break; - case ".nsp": - case ".pfs0": - Logger.Info?.Print(LogClass.Application, "Loading as NSP."); - _emulationContext.LoadNsp(path); - break; - default: - Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); - try - { - _emulationContext.LoadProgram(path); - } - catch (ArgumentOutOfRangeException) - { - Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); - } - break; - } - } - else - { - Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); - _emulationContext.Dispose(); - RendererWidget.Dispose(); + SwitchToGameTable(); return; } @@ -852,10 +839,7 @@ namespace Ryujinx.Ui Translator.IsReadyForTranslation.Reset(); - Thread windowThread = new Thread(() => - { - CreateGameWindow(); - }) + Thread windowThread = new(CreateGameWindow) { Name = "GUI.WindowThread" }; @@ -871,9 +855,10 @@ namespace Ryujinx.Ui _firmwareInstallFile.Sensitive = false; _firmwareInstallDirectory.Sensitive = false; - DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Application.TitleIdText, _emulationContext.Application.TitleName); + DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText, + _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); - _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Application.TitleIdText, appMetadata => + _applicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata => { appMetadata.LastPlayed = DateTime.UtcNow.ToString(); }); @@ -1055,7 +1040,7 @@ namespace Ryujinx.Ui if (_emulationContext != null) { - UpdateGameMetadata(_emulationContext.Application.TitleIdText); + UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); if (RendererWidget != null) { @@ -1174,7 +1159,7 @@ namespace Ryujinx.Ui string path = (string)_tableStore.GetValue(treeIter, 9); - LoadApplication(path); + RunApplication(path); } private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) @@ -1260,7 +1245,7 @@ namespace Ryujinx.Ui if (fileChooser.Run() == (int)ResponseType.Accept) { - LoadApplication(fileChooser.Filename); + RunApplication(fileChooser.Filename); } } } @@ -1271,7 +1256,7 @@ namespace Ryujinx.Ui { if (fileChooser.Run() == (int)ResponseType.Accept) { - LoadApplication(fileChooser.Filename); + RunApplication(fileChooser.Filename); } } } @@ -1287,7 +1272,7 @@ namespace Ryujinx.Ui { string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); - LoadApplication(contentPath); + RunApplication(contentPath); } private void Open_Ryu_Folder(object sender, EventArgs args) @@ -1328,7 +1313,7 @@ namespace Ryujinx.Ui { if (_emulationContext != null) { - UpdateGameMetadata(_emulationContext.Application.TitleIdText); + UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); } _pauseEmulation.Sensitive = false; @@ -1533,7 +1518,7 @@ namespace Ryujinx.Ui { _userChannelPersistence.ShouldRestart = false; - LoadApplication(_currentEmulatedGamePath); + RunApplication(_currentEmulatedGamePath); } else { @@ -1596,7 +1581,9 @@ namespace Ryujinx.Ui private void ManageCheats_Pressed(object sender, EventArgs args) { - var window = new CheatWindow(_virtualFileSystem, _emulationContext.Application.TitleId, _emulationContext.Application.TitleName); + var window = new CheatWindow(_virtualFileSystem, + _emulationContext.Processes.ActiveApplication.ProgramId, + _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); window.Destroyed += CheatWindow_Destroyed; window.Show(); @@ -1639,7 +1626,7 @@ namespace Ryujinx.Ui LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, LastScannedAmiiboId = _lastScannedAmiiboId, DeviceId = deviceId, - TitleId = _emulationContext.Application.TitleIdText.ToUpper() + TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper() }; amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index e5d22d65c..7cb5b3275 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 a63d68ff2..558288aab 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 4aea58955..fce751da1 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) {