From ba4830293e4786851d743865b1232928f9873c7e Mon Sep 17 00:00:00 2001 From: mageven <62494521+mageven@users.noreply.github.com> Date: Fri, 15 May 2020 11:46:46 +0530 Subject: [PATCH] Refactor out Application details from Horizon (#1236) * Initial Application refactor * Misc typo and access modifier fixes * Clean unused namespaces * Address gdkchan's comments * Move ticket reading to common method * Change IParentalControlService to use ApplicationLoader.ControlData --- .../FileSystem/Content/ContentManager.cs | 6 +- Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 19 +- Ryujinx.HLE/HOS/ApplicationLoader.cs | 537 +++++++++++++++++ Ryujinx.HLE/HOS/Horizon.cs | 564 +----------------- Ryujinx.HLE/HOS/ProgramLoader.cs | 18 +- .../Acc/IAccountServiceForApplication.cs | 2 +- .../ApplicationProxy/IApplicationFunctions.cs | 2 +- .../Services/Arp/ApplicationLaunchProperty.cs | 2 +- Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs | 2 +- .../Ns/IApplicationManagerInterface.cs | 2 +- ...ReadOnlyApplicationControlDataInterface.cs | 2 +- .../IParentalControlService.cs | 4 +- .../QueryPlayStatisticsManager.cs | 4 +- Ryujinx.HLE/Switch.cs | 18 +- Ryujinx/Ui/ApplicationLibrary.cs | 24 +- Ryujinx/Ui/GLRenderer.cs | 14 +- Ryujinx/Ui/GameTableContextMenu.cs | 12 +- Ryujinx/Ui/MainWindow.cs | 6 +- Ryujinx/Ui/TitleUpdateWindow.cs | 12 +- 19 files changed, 609 insertions(+), 641 deletions(-) create mode 100644 Ryujinx.HLE/HOS/ApplicationLoader.cs diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs index aae58153..22d97f3b 100644 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -580,7 +580,7 @@ namespace Ryujinx.HLE.FileSystem.Content SystemVersion VerifyAndGetVersionZip(ZipArchive archive) { - IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel(); + IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel(); SystemVersion systemVersion = null; @@ -743,7 +743,7 @@ namespace Ryujinx.HLE.FileSystem.Content SystemVersion VerifyAndGetVersion(IFileSystem filesystem) { - IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel(); + IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel(); SystemVersion systemVersion = null; @@ -874,7 +874,7 @@ namespace Ryujinx.HLE.FileSystem.Content public SystemVersion GetCurrentFirmwareVersion() { - IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel(); + IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel(); LoadEntries(); diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index d4ffc5d3..347b4fa4 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -1,7 +1,9 @@ using LibHac; +using LibHac.Common; using LibHac.Fs; using LibHac.FsService; using LibHac.FsSystem; +using LibHac.Spl; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using System; @@ -261,6 +263,21 @@ namespace Ryujinx.HLE.FileSystem KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile); } + public void ImportTickets(IFileSystem fs) + { + foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik")) + { + Result result = fs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new Ticket(ticketFile.AsStream()); + + KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet))); + } + } + } + public void Unload() { RomFs?.Dispose(); @@ -283,7 +300,7 @@ namespace Ryujinx.HLE.FileSystem { if (_isInitialized) { - throw new InvalidOperationException($"VirtualFileSystem can only be instanciated once!"); + throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!"); } _isInitialized = true; diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs new file mode 100644 index 00000000..c8bb37c5 --- /dev/null +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -0,0 +1,537 @@ +using LibHac; +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Spl; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Npdm; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +using static LibHac.Fs.ApplicationSaveDataManagement; + +namespace Ryujinx.HLE.HOS +{ + using JsonHelper = Common.Utilities.JsonHelper; + + public class ApplicationLoader + { + private readonly Switch _device; + private readonly ContentManager _contentManager; + private readonly VirtualFileSystem _fileSystem; + + public IntegrityCheckLevel FsIntegrityCheckLevel => _device.System.FsIntegrityCheckLevel; + + public ulong TitleId { get; private set; } + public string TitleIdText => TitleId.ToString("x16"); + public string TitleName { get; private set; } + + public string TitleVersionString { get; private set; } + + public bool TitleIs64Bit { get; private set; } + + public BlitStruct ControlData { get; set; } + + public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager) + { + _device = device; + _contentManager = contentManager; + _fileSystem = fileSystem; + + ControlData = new BlitStruct(1); + } + + public void LoadCart(string exeFsDir, string romFsFile = null) + { + if (romFsFile != null) + { + _fileSystem.LoadRomFs(romFsFile); + } + + LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); + + LoadExeFs(codeFs, out _); + + if (TitleId != 0) + { + EnsureSaveData(new TitleId(TitleId)); + } + } + + private (Nca Main, Nca Patch, Nca Control) GetGameData(PartitionFileSystem pfs) + { + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + _fileSystem.ImportTickets(pfs); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage()); + + if (nca.Header.ContentType == NcaContentType.Program) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + patchNca = nca; + } + else + { + mainNca = nca; + } + } + else if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + + return (mainNca, patchNca, controlNca); + } + + public void LoadXci(string xciFile) + { + FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); + + Xci xci = new Xci(_fileSystem.KeySet, file.AsStorage()); + + if (!xci.HasPartition(XciPartitionType.Secure)) + { + Logger.PrintError(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition"); + + return; + } + + PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure); + + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + try + { + (mainNca, patchNca, controlNca) = GetGameData(securePartition); + } + catch (Exception e) + { + Logger.PrintError(LogClass.Loader, $"Unable to load XCI: {e.Message}"); + + return; + } + + if (mainNca == null) + { + Logger.PrintError(LogClass.Loader, "Unable to load XCI: Could not find Main NCA"); + + return; + } + + _contentManager.LoadEntries(_device); + + LoadNca(mainNca, patchNca, controlNca); + } + + public void LoadNsp(string nspFile) + { + FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); + + PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); + + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + try + { + (mainNca, patchNca, controlNca) = GetGameData(nsp); + } + catch (Exception e) + { + Logger.PrintError(LogClass.Loader, $"Unable to load NSP: {e.Message}"); + + return; + } + + if (mainNca == null) + { + Logger.PrintError(LogClass.Loader, "Unable to load NSP: Could not find Main NCA"); + + return; + } + + if (mainNca != null) + { + LoadNca(mainNca, patchNca, controlNca); + + return; + } + + // This is not a normal NSP, it's actually a ExeFS as a NSP + LoadExeFs(nsp, out _); + } + + public void LoadNca(string ncaFile) + { + FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); + + Nca nca = new Nca(_fileSystem.KeySet, file.AsStorage(false)); + + LoadNca(nca, null, null); + } + + private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) + { + if (mainNca.Header.ContentType != NcaContentType.Program) + { + Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); + + return; + } + + IStorage dataStorage = null; + IFileSystem codeFs = null; + + string titleUpdateMetadataPath = System.IO.Path.Combine(_fileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json"); + + if (File.Exists(titleUpdateMetadataPath)) + { + string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected; + + if (File.Exists(updatePath)) + { + FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read); + PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); + + _fileSystem.ImportTickets(nsp); + + foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) + { + nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage()); + + if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16")) + { + break; + } + + if (nca.Header.ContentType == NcaContentType.Program) + { + patchNca = nca; + } + else if (nca.Header.ContentType == NcaContentType.Control) + { + controlNca = nca; + } + } + } + } + + if (patchNca == null) + { + if (mainNca.CanOpenSection(NcaSectionType.Data)) + { + dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel); + } + + if (mainNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Data)) + { + dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel); + } + + if (patchNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel); + } + } + + if (codeFs == null) + { + Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA"); + + return; + } + + if (dataStorage == null) + { + Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA"); + } + else + { + _fileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read)); + } + + LoadExeFs(codeFs, out Npdm metaData); + + TitleId = metaData.Aci0.TitleId; + TitleIs64Bit = metaData.Is64Bit; + + if (controlNca != null) + { + ReadControlData(controlNca); + } + else + { + ControlData.ByteSpan.Clear(); + } + + if (TitleId != 0) + { + EnsureSaveData(new TitleId(TitleId)); + } + + Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{TitleVersionString} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]"); + } + + public void ReadControlData(Nca controlNca) + { + IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel); + + Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None); + + if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length) + { + TitleName = ControlData.Value + .Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString(); + + if (string.IsNullOrWhiteSpace(TitleName)) + { + TitleName = ControlData.Value.Titles.ToArray() + .FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); + } + + TitleVersionString = ControlData.Value.DisplayVersion.ToString(); + } + } + else + { + ControlData.ByteSpan.Clear(); + } + } + + private void LoadExeFs(IFileSystem codeFs, out Npdm metaData) + { + Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read); + + if (ResultFs.PathNotFound.Includes(result)) + { + Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!"); + + metaData = GetDefaultNpdm(); + } + else + { + metaData = new Npdm(npdmFile.AsStream()); + } + + List nsos = new List(); + + void LoadNso(string filename) + { + foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*")) + { + if (Path.GetExtension(file.Name) != string.Empty) + { + continue; + } + + Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}..."); + + codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage()); + + nsos.Add(nso); + } + } + + TitleId = metaData.Aci0.TitleId; + TitleIs64Bit = metaData.Is64Bit; + + LoadNso("rtld"); + LoadNso("main"); + LoadNso("subsdk"); + LoadNso("sdk"); + + _contentManager.LoadEntries(_device); + + ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nsos.ToArray()); + } + + public void LoadProgram(string filePath) + { + Npdm metaData = GetDefaultNpdm(); + + bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; + + IExecutable nro; + + if (isNro) + { + FileStream input = new FileStream(filePath, FileMode.Open); + NroExecutable obj = new NroExecutable(input); + nro = obj; + + // homebrew NRO can actually have some data after the actual NRO + if (input.Length > obj.FileSize) + { + input.Position = obj.FileSize; + + BinaryReader reader = new BinaryReader(input); + + uint asetMagic = reader.ReadUInt32(); + + if (asetMagic == 0x54455341) + { + uint asetVersion = reader.ReadUInt32(); + if (asetVersion == 0) + { + ulong iconOffset = reader.ReadUInt64(); + ulong iconSize = reader.ReadUInt64(); + + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); + + ulong romfsOffset = reader.ReadUInt64(); + ulong romfsSize = reader.ReadUInt64(); + + if (romfsSize != 0) + { + _fileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset)); + } + + if (nacpSize != 0) + { + input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin); + + reader.Read(ControlData.ByteSpan); + + ref ApplicationControlProperty nacp = ref ControlData.Value; + + metaData.TitleName = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString(); + + if (string.IsNullOrWhiteSpace(metaData.TitleName)) + { + metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); + } + + if (nacp.PresenceGroupId != 0) + { + metaData.Aci0.TitleId = nacp.PresenceGroupId; + } + else if (nacp.SaveDataOwnerId.Value != 0) + { + metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value; + } + else if (nacp.AddOnContentBaseId != 0) + { + metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000; + } + else + { + metaData.Aci0.TitleId = 0000000000000000; + } + } + } + else + { + Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\""); + } + } + } + } + else + { + nro = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read)); + } + + _contentManager.LoadEntries(_device); + + TitleName = metaData.TitleName; + TitleId = metaData.Aci0.TitleId; + TitleIs64Bit = metaData.Is64Bit; + + ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nro); + } + + private Npdm GetDefaultNpdm() + { + Assembly asm = Assembly.GetCallingAssembly(); + + using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) + { + return new Npdm(npdmStream); + } + } + + private Result EnsureSaveData(TitleId titleId) + { + Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists."); + + Uid user = _device.System.State.Account.LastOpenedUser.UserId.ToLibHacUid(); + + ref ApplicationControlProperty control = ref ControlData.Value; + + if (Util.IsEmpty(ControlData.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one + // and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.PrintWarning(LogClass.Application, + "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + FileSystemClient fs = _fileSystem.FsClient; + + Result rc = fs.EnsureApplicationCacheStorage(out _, titleId, ref control); + + if (rc.IsFailure()) + { + Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}"); + + return rc; + } + + rc = EnsureApplicationSaveData(fs, out _, titleId, ref control, ref user); + + if (rc.IsFailure()) + { + Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}"); + } + + return rc; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index c6154b43..27200377 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -1,16 +1,8 @@ using LibHac; -using LibHac.Account; using LibHac.Bcat; -using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; -using LibHac.FsSystem.NcaUtils; -using LibHac.Ncm; -using LibHac.Ns; -using LibHac.Spl; using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; using Ryujinx.Configuration; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Font; @@ -30,20 +22,14 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger; using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Utilities; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using static LibHac.Fs.ApplicationSaveDataManagement; namespace Ryujinx.HLE.HOS { using TimeServiceManager = Services.Time.TimeManager; - using JsonHelper = Common.Utilities.JsonHelper; public class Horizon : IDisposable { @@ -80,16 +66,6 @@ namespace Ryujinx.HLE.HOS #pragma warning restore CS0649 private bool _isDisposed; - public BlitStruct ControlData { get; set; } - - public string TitleName { get; private set; } - - public ulong TitleId { get; private set; } - public string TitleIdText => TitleId.ToString("x16"); - - public string TitleVersionString { get; private set; } - - public bool TitleIs64Bit { get; private set; } public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } @@ -104,8 +80,6 @@ namespace Ryujinx.HLE.HOS public Horizon(Switch device, ContentManager contentManager) { - ControlData = new BlitStruct(1); - KernelContext = new KernelContext(device, device.Memory); Device = device; @@ -207,6 +181,13 @@ namespace Ryujinx.HLE.HOS InitLibHacHorizon(); } + public void LoadKip(string kipFile) + { + using IStorage fs = new LocalStorage(kipFile, FileAccess.Read); + + ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs)); + } + private void InitLibHacHorizon() { LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer); @@ -233,537 +214,6 @@ namespace Ryujinx.HLE.HOS } } - public void LoadCart(string exeFsDir, string romFsFile = null) - { - if (romFsFile != null) - { - Device.FileSystem.LoadRomFs(romFsFile); - } - - LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); - - LoadExeFs(codeFs, out _); - - if (TitleId != 0) - { - EnsureSaveData(new TitleId(TitleId)); - } - } - - public void LoadXci(string xciFile) - { - FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); - - Xci xci = new Xci(KeySet, file.AsStorage()); - - (Nca mainNca, Nca patchNca, Nca controlNca) = GetXciGameData(xci); - - if (mainNca == null) - { - Logger.PrintError(LogClass.Loader, "Unable to load XCI"); - - return; - } - - ContentManager.LoadEntries(Device); - - LoadNca(mainNca, patchNca, controlNca); - } - - public void LoadKip(string kipFile) - { - using (IStorage fs = new LocalStorage(kipFile, FileAccess.Read)) - { - ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs)); - } - } - - private (Nca Main, Nca patch, Nca Control) GetXciGameData(Xci xci) - { - if (!xci.HasPartition(XciPartitionType.Secure)) - { - throw new InvalidDataException("Could not find XCI secure partition"); - } - - Nca mainNca = null; - Nca patchNca = null; - Nca controlNca = null; - - XciPartition securePartition = xci.OpenPartition(XciPartitionType.Secure); - - foreach (DirectoryEntryEx ticketEntry in securePartition.EnumerateEntries("/", "*.tik")) - { - Result result = securePartition.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet))); - } - } - - foreach (DirectoryEntryEx fileEntry in securePartition.EnumerateEntries("/", "*.nca")) - { - Result result = securePartition.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read); - if (result.IsFailure()) - { - continue; - } - - Nca nca = new Nca(KeySet, ncaFile.AsStorage()); - - if (nca.Header.ContentType == NcaContentType.Program) - { - int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); - - if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) - { - patchNca = nca; - } - else - { - mainNca = nca; - } - } - else if (nca.Header.ContentType == NcaContentType.Control) - { - controlNca = nca; - } - } - - if (mainNca == null) - { - Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file"); - } - - if (controlNca != null) - { - ReadControlData(controlNca); - } - else - { - ControlData.ByteSpan.Clear(); - } - - return (mainNca, patchNca, controlNca); - } - - public void ReadControlData(Nca controlNca) - { - IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel); - - Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None); - - if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length) - { - TitleName = ControlData.Value - .Titles[(int) State.DesiredTitleLanguage].Name.ToString(); - - if (string.IsNullOrWhiteSpace(TitleName)) - { - TitleName = ControlData.Value.Titles.ToArray() - .FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); - } - - TitleVersionString = ControlData.Value.DisplayVersion.ToString(); - } - } - else - { - ControlData.ByteSpan.Clear(); - } - } - - public void LoadNca(string ncaFile) - { - FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); - - Nca nca = new Nca(KeySet, file.AsStorage(false)); - - LoadNca(nca, null, null); - } - - public void LoadNsp(string nspFile) - { - FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); - - PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); - - foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) - { - Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet))); - } - } - - Nca mainNca = null; - Nca patchNca = null; - Nca controlNca = null; - - foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) - { - nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = new Nca(KeySet, ncaFile.AsStorage()); - - if (nca.Header.ContentType == NcaContentType.Program) - { - int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); - - if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) - { - patchNca = nca; - } - else - { - mainNca = nca; - } - } - else if (nca.Header.ContentType == NcaContentType.Control) - { - controlNca = nca; - } - } - - if (mainNca != null) - { - LoadNca(mainNca, patchNca, controlNca); - - return; - } - - // This is not a normal NSP, it's actually a ExeFS as a NSP - LoadExeFs(nsp, out _); - } - - public void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) - { - if (mainNca.Header.ContentType != NcaContentType.Program) - { - Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); - - return; - } - - IStorage dataStorage = null; - IFileSystem codeFs = null; - - string titleUpdateMetadataPath = System.IO.Path.Combine(Device.FileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json"); - - if (File.Exists(titleUpdateMetadataPath)) - { - string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected; - - if (File.Exists(updatePath)) - { - FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read); - PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); - - foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) - { - Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet))); - } - } - - foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) - { - nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = new Nca(KeySet, ncaFile.AsStorage()); - - if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16")) - { - break; - } - - if (nca.Header.ContentType == NcaContentType.Program) - { - patchNca = nca; - } - else if (nca.Header.ContentType == NcaContentType.Control) - { - controlNca = nca; - } - } - } - } - - if (patchNca == null) - { - if (mainNca.CanOpenSection(NcaSectionType.Data)) - { - dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel); - } - - if (mainNca.CanOpenSection(NcaSectionType.Code)) - { - codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel); - } - } - else - { - if (patchNca.CanOpenSection(NcaSectionType.Data)) - { - dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel); - } - - if (patchNca.CanOpenSection(NcaSectionType.Code)) - { - codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel); - } - } - - if (codeFs == null) - { - Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA"); - - return; - } - - if (dataStorage == null) - { - Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA"); - } - else - { - Device.FileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read)); - } - - LoadExeFs(codeFs, out Npdm metaData); - - TitleId = metaData.Aci0.TitleId; - TitleIs64Bit = metaData.Is64Bit; - - if (controlNca != null) - { - ReadControlData(controlNca); - } - else - { - ControlData.ByteSpan.Clear(); - } - - if (TitleId != 0) - { - EnsureSaveData(new TitleId(TitleId)); - } - - Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{TitleVersionString} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]"); - } - - private void LoadExeFs(IFileSystem codeFs, out Npdm metaData) - { - Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read); - - if (ResultFs.PathNotFound.Includes(result)) - { - Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!"); - - metaData = GetDefaultNpdm(); - } - else - { - metaData = new Npdm(npdmFile.AsStream()); - } - - List staticObjects = new List(); - - void LoadNso(string filename) - { - foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*")) - { - if (Path.GetExtension(file.Name) != string.Empty) - { - continue; - } - - Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}..."); - - codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - NsoExecutable staticObject = new NsoExecutable(nsoFile.AsStorage()); - - staticObjects.Add(staticObject); - } - } - - TitleId = metaData.Aci0.TitleId; - TitleIs64Bit = metaData.Is64Bit; - - LoadNso("rtld"); - LoadNso("main"); - LoadNso("subsdk"); - LoadNso("sdk"); - - ContentManager.LoadEntries(Device); - - ProgramLoader.LoadNsos(KernelContext, metaData, staticObjects.ToArray()); - } - - public void LoadProgram(string filePath) - { - Npdm metaData = GetDefaultNpdm(); - - bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; - - - IExecutable staticObject; - - if (isNro) - { - FileStream input = new FileStream(filePath, FileMode.Open); - NroExecutable obj = new NroExecutable(input); - staticObject = obj; - - // homebrew NRO can actually have some data after the actual NRO - if (input.Length > obj.FileSize) - { - input.Position = obj.FileSize; - - BinaryReader reader = new BinaryReader(input); - - uint asetMagic = reader.ReadUInt32(); - - if (asetMagic == 0x54455341) - { - uint asetVersion = reader.ReadUInt32(); - if (asetVersion == 0) - { - ulong iconOffset = reader.ReadUInt64(); - ulong iconSize = reader.ReadUInt64(); - - ulong nacpOffset = reader.ReadUInt64(); - ulong nacpSize = reader.ReadUInt64(); - - ulong romfsOffset = reader.ReadUInt64(); - ulong romfsSize = reader.ReadUInt64(); - - if (romfsSize != 0) - { - Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset)); - } - - if (nacpSize != 0) - { - input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin); - - reader.Read(ControlData.ByteSpan); - - ref ApplicationControlProperty nacp = ref ControlData.Value; - - metaData.TitleName = nacp.Titles[(int)State.DesiredTitleLanguage].Name.ToString(); - - if (string.IsNullOrWhiteSpace(metaData.TitleName)) - { - metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); - } - - if (nacp.PresenceGroupId != 0) - { - metaData.Aci0.TitleId = nacp.PresenceGroupId; - } - else if (nacp.SaveDataOwnerId.Value != 0) - { - metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value; - } - else if (nacp.AddOnContentBaseId != 0) - { - metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000; - } - else - { - metaData.Aci0.TitleId = 0000000000000000; - } - } - } - else - { - Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\""); - } - } - } - } - else - { - staticObject = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read)); - } - - ContentManager.LoadEntries(Device); - - TitleName = metaData.TitleName; - TitleId = metaData.Aci0.TitleId; - TitleIs64Bit = metaData.Is64Bit; - - ProgramLoader.LoadNsos(KernelContext, metaData, new IExecutable[] { staticObject }); - } - - private Npdm GetDefaultNpdm() - { - Assembly asm = Assembly.GetCallingAssembly(); - - using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) - { - return new Npdm(npdmStream); - } - } - - private Result EnsureSaveData(TitleId titleId) - { - Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists."); - - Uid user = State.Account.LastOpenedUser.UserId.ToLibHacUid(); - - ref ApplicationControlProperty control = ref ControlData.Value; - - if (LibHac.Util.IsEmpty(ControlData.ByteSpan)) - { - // If the current application doesn't have a loaded control property, create a dummy one - // and set the savedata sizes so a user savedata will be created. - control = ref new BlitStruct(1).Value; - - // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. - control.UserAccountSaveDataSize = 0x4000; - control.UserAccountSaveDataJournalSize = 0x4000; - - Logger.PrintWarning(LogClass.Application, - "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); - } - - FileSystemClient fs = Device.FileSystem.FsClient; - - Result rc = fs.EnsureApplicationCacheStorage(out _, titleId, ref control); - - if (rc.IsFailure()) - { - Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}"); - } - - rc = EnsureApplicationSaveData(fs, out _, titleId, ref control, ref user); - - if (rc.IsFailure()) - { - Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}"); - } - - return rc; - } - public void SignalDisplayResolutionChange() { DisplayResolutionChangeEvent.ReadableEvent.Signal(); diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs index c1e18491..07b1a18b 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -121,21 +121,21 @@ namespace Ryujinx.HLE.HOS } public static bool LoadNsos( - KernelContext context, - Npdm metaData, - IExecutable[] nsos, - byte[] arguments = null) + KernelContext context, + Npdm metaData, + byte[] arguments = null, + params IExecutable[] executables) { ulong argsStart = 0; int argsSize = 0; ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL; int codeSize = 0; - ulong[] nsoBase = new ulong[nsos.Length]; + ulong[] nsoBase = new ulong[executables.Length]; - for (int index = 0; index < nsos.Length; index++) + for (int index = 0; index < executables.Length; index++) { - IExecutable staticObject = nsos[index]; + IExecutable staticObject = executables[index]; int textEnd = staticObject.TextOffset + staticObject.Text.Length; int roEnd = staticObject.RoOffset + staticObject.Ro.Length; @@ -226,11 +226,11 @@ namespace Ryujinx.HLE.HOS return false; } - for (int index = 0; index < nsos.Length; index++) + for (int index = 0; index < executables.Length; index++) { Logger.PrintInfo(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); - result = LoadIntoMemory(process, nsos[index], nsoBase[index]); + result = LoadIntoMemory(process, executables[index], nsoBase[index]); if (result != KernelResult.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs index 3709db3a..96407d1d 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -289,7 +289,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc // Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current PID and store the result (NACP File) internally. // But since we use LibHac and we load one Application at a time, it's not necessary. - context.ResponseData.Write(context.Device.System.ControlData.Value.UserAccountSwitchLock); + context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock); Logger.PrintStub(LogClass.ServiceAcc); 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 b83c6b69..0787ce24 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati Uid userId = context.RequestData.ReadStruct().ToLibHacUid(); TitleId titleId = new TitleId(context.Process.TitleId); - BlitStruct controlHolder = context.Device.System.ControlData; + BlitStruct controlHolder = context.Device.Application.ControlData; ref ApplicationControlProperty control = ref controlHolder.Value; diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs index 25c84984..5e396110 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.System.TitleId, + TitleId = context.Device.Application.TitleId, Version = 0x00, BaseGameStorageId = (byte)StorageId.NandSystem, UpdateGameStorageId = (byte)StorageId.None diff --git a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs index 77f02e8d..44d674a4 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs @@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp launchProperty = new LibHac.Arp.ApplicationLaunchProperty(); launchProperty.BaseStorageId = StorageId.BuiltInUser; - launchProperty.ApplicationId = new ApplicationId(System.TitleId); + launchProperty.ApplicationId = new ApplicationId(System.Device.Application.TitleId); return Result.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs index f6a8530f..8edf13c7 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs @@ -14,7 +14,7 @@ long position = context.Request.ReceiveBuff[0].Position; - byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray(); + byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); context.Memory.Write((ulong)position, nacpData); diff --git a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs index 01aa04a1..98e12472 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs @@ -13,7 +13,7 @@ long position = context.Request.ReceiveBuff[0].Position; - byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray(); + byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); context.Memory.Write((ulong)position, nacpData); diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs index 6768f888..12009a26 100644 --- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs +++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -43,8 +43,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory _titleId = titleId; // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. - _ratingAge = Array.ConvertAll(context.Device.System.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32); - _freeCommunicationEnabled = context.Device.System.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication; + _ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32); + _freeCommunicationEnabled = context.Device.Application.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication; } } diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs index b2abced6..317decad 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService } } - PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.System.ControlData.Value.PlayLogQueryCapability; + PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability; List titleIds = new List(); @@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService // Check if input title ids are in the whitelist. foreach (ulong titleId in titleIds) { - if (!context.Device.System.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId)) + if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId)) { return (ResultCode)Am.ResultCode.ObjectInvalid; } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 4aedc2f3..d5c0b3b2 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -27,6 +27,8 @@ namespace Ryujinx.HLE public Horizon System { get; private set; } + public ApplicationLoader Application { get; } + public PerformanceStatistics Statistics { get; private set; } public Hid Hid { get; private set; } @@ -59,6 +61,8 @@ namespace Ryujinx.HLE Hid = new Hid(this, System.HidBaseAddress); Hid.InitDevices(); + + Application = new ApplicationLoader(this, fileSystem, contentManager); } public void Initialize() @@ -76,14 +80,14 @@ namespace Ryujinx.HLE System.EnableMultiCoreScheduling(); } - System.FsIntegrityCheckLevel = GetIntigrityCheckLevel(); + System.FsIntegrityCheckLevel = GetIntegrityCheckLevel(); System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode; ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices; } - public static IntegrityCheckLevel GetIntigrityCheckLevel() + public static IntegrityCheckLevel GetIntegrityCheckLevel() { return ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid @@ -92,27 +96,27 @@ namespace Ryujinx.HLE public void LoadCart(string exeFsDir, string romFsFile = null) { - System.LoadCart(exeFsDir, romFsFile); + Application.LoadCart(exeFsDir, romFsFile); } public void LoadXci(string xciFile) { - System.LoadXci(xciFile); + Application.LoadXci(xciFile); } public void LoadNca(string ncaFile) { - System.LoadNca(ncaFile); + Application.LoadNca(ncaFile); } public void LoadNsp(string nspFile) { - System.LoadNsp(nspFile); + Application.LoadNsp(nspFile); } public void LoadProgram(string fileName) { - System.LoadProgram(fileName); + Application.LoadProgram(fileName); } public bool WaitFifo() diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs index 2dd88196..776f4fa4 100644 --- a/Ryujinx/Ui/ApplicationLibrary.cs +++ b/Ryujinx/Ui/ApplicationLibrary.cs @@ -474,17 +474,7 @@ namespace Ryujinx.Ui Nca controlNca = null; // Add keys to key set if needed - foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik")) - { - Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - _virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet))); - } - } + _virtualFileSystem.ImportTickets(pfs); // Find the Control NCA and store it in variable called controlNca foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) @@ -661,17 +651,7 @@ namespace Ryujinx.Ui { PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); - foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) - { - Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - _virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet))); - } - } + _virtualFileSystem.ImportTickets(nsp); foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) { diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index b5eb8a03..3ac935f3 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -180,16 +180,16 @@ namespace Ryujinx.Ui { parent.Present(); - string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty - : $" - {_device.System.TitleName}"; + string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty + : $" - {_device.Application.TitleName}"; - string titleVersionSection = string.IsNullOrWhiteSpace(_device.System.TitleVersionString) ? string.Empty - : $" v{_device.System.TitleVersionString}"; + string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.TitleVersionString) ? string.Empty + : $" v{_device.Application.TitleVersionString}"; - string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty - : $" ({_device.System.TitleIdText.ToUpper()})"; + string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty + : $" ({_device.Application.TitleIdText.ToUpper()})"; - string titleArchSection = _device.System.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; + string titleArchSection = _device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)"; parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; }); diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs index a1433f51..2aee44d5 100644 --- a/Ryujinx/Ui/GameTableContextMenu.cs +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -280,17 +280,7 @@ namespace Ryujinx.Ui FileStream updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read); PartitionFileSystem nsp = new PartitionFileSystem(updateFile.AsStorage()); - foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) - { - Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - _virtualFileSystem.KeySet.ExternalKeySet.Add(new LibHac.Fs.RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet))); - } - } + _virtualFileSystem.ImportTickets(nsp); foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) { diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index caf5d2d0..bd83d859 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -435,9 +435,9 @@ namespace Ryujinx.Ui _firmwareInstallFile.Sensitive = false; _firmwareInstallDirectory.Sensitive = false; - DiscordIntegrationModule.SwitchToPlayingState(device.System.TitleIdText, device.System.TitleName); + DiscordIntegrationModule.SwitchToPlayingState(device.Application.TitleIdText, device.Application.TitleName); - ApplicationLibrary.LoadAndSaveMetaData(device.System.TitleIdText, appMetadata => + ApplicationLibrary.LoadAndSaveMetaData(device.Application.TitleIdText, appMetadata => { appMetadata.LastPlayed = DateTime.UtcNow.ToString(); }); @@ -580,7 +580,7 @@ namespace Ryujinx.Ui if (device != null) { - UpdateGameMetadata(device.System.TitleIdText); + UpdateGameMetadata(device.Application.TitleIdText); if (_glWidget != null) { diff --git a/Ryujinx/Ui/TitleUpdateWindow.cs b/Ryujinx/Ui/TitleUpdateWindow.cs index 06d0dcdb..acf0c2bd 100644 --- a/Ryujinx/Ui/TitleUpdateWindow.cs +++ b/Ryujinx/Ui/TitleUpdateWindow.cs @@ -81,17 +81,7 @@ namespace Ryujinx.Ui { PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); - foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) - { - Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - Ticket ticket = new Ticket(ticketFile.AsStream()); - - _virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet))); - } - } + _virtualFileSystem.ImportTickets(nsp); foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca")) {