aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.HLE/FileSystem/Content/ContentManager.cs6
-rw-r--r--Ryujinx.HLE/FileSystem/VirtualFileSystem.cs19
-rw-r--r--Ryujinx.HLE/HOS/ApplicationLoader.cs537
-rw-r--r--Ryujinx.HLE/HOS/Horizon.cs564
-rw-r--r--Ryujinx.HLE/HOS/ProgramLoader.cs18
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs4
-rw-r--r--Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs4
-rw-r--r--Ryujinx.HLE/Switch.cs18
-rw-r--r--Ryujinx/Ui/ApplicationLibrary.cs24
-rw-r--r--Ryujinx/Ui/GLRenderer.cs14
-rw-r--r--Ryujinx/Ui/GameTableContextMenu.cs12
-rw-r--r--Ryujinx/Ui/MainWindow.cs6
-rw-r--r--Ryujinx/Ui/TitleUpdateWindow.cs12
19 files changed, 609 insertions, 641 deletions
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<ApplicationControlProperty> ControlData { get; set; }
+
+ public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager)
+ {
+ _device = device;
+ _contentManager = contentManager;
+ _fileSystem = fileSystem;
+
+ ControlData = new BlitStruct<ApplicationControlProperty>(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<TitleUpdateMetadata>(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<IExecutable> nsos = new List<IExecutable>();
+
+ 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<ApplicationControlProperty>(1).Value;
+
+ // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
+ control.UserAccountSaveDataSize = 0x4000;
+ control.UserAccountSaveDataJournalSize = 0x4000;
+
+ Logger.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<ApplicationControlProperty> 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<ApplicationControlProperty>(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<TitleUpdateMetadata>(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<IExecutable> staticObjects = new List<IExecutable>();
-
- 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<ApplicationControlProperty>(1).Value;
-
- // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
- control.UserAccountSaveDataSize = 0x4000;
- control.UserAccountSaveDataJournalSize = 0x4000;
-
- Logger.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<AccountUid>().ToLibHacUid();
TitleId titleId = new TitleId(context.Process.TitleId);
- BlitStruct<ApplicationControlProperty> controlHolder = context.Device.System.ControlData;
+ BlitStruct<ApplicationControlProperty> 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<ulong> titleIds = new List<ulong>();
@@ -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"))
{