using LibHac; using LibHac.Account; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using LibHac.FsSystem; using LibHac.Loader; using LibHac.Ncm; using LibHac.Ns; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Cpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.Memory; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using static Ryujinx.HLE.HOS.ModLoader; using ApplicationId = LibHac.Ncm.ApplicationId; using Path = System.IO.Path; namespace Ryujinx.HLE.HOS { using JsonHelper = Common.Utilities.JsonHelper; public class ApplicationLoader { // Binaries from exefs are loaded into mem in this order. Do not change. internal static readonly string[] ExeFsPrefixes = { "rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", "sdk" }; private readonly Switch _device; private string _titleName; private string _displayVersion; private BlitStruct _controlData; public BlitStruct ControlData => _controlData; public string TitleName => _titleName; public string DisplayVersion => _displayVersion; public ulong TitleId { get; private set; } public bool TitleIs64Bit { get; private set; } public string TitleIdText => TitleId.ToString("x16"); public IDiskCacheLoadState DiskCacheLoadState { get; private set; } public ApplicationLoader(Switch device) { _device = device; _controlData = new BlitStruct(1); } public void LoadCart(string exeFsDir, string romFsFile = null) { LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); MetaLoader metaData = ReadNpdm(codeFs); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( new[] { TitleId }, _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); if (TitleId != 0) { EnsureSaveData(new ApplicationId(TitleId)); } ulong pid = LoadExeFs(codeFs, string.Empty, metaData); if (romFsFile != null) { _device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile); } } public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex) { Nca mainNca = null; Nca patchNca = null; Nca controlNca = null; fileSystem.ImportTickets(pfs); foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) { using var ncaFile = new UniqueRef(); pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); if (ncaProgramIndex != programIndex) { continue; } if (nca.Header.ContentType == NcaContentType.Program) { int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) { patchNca = nca; } else { mainNca = nca; } } else if (nca.Header.ContentType == NcaContentType.Control) { controlNca = nca; } } return (mainNca, patchNca, controlNca); } public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex) { Nca patchNca = null; Nca controlNca = null; fileSystem.ImportTickets(pfs); foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) { using var ncaFile = new UniqueRef(); pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); if (ncaProgramIndex != programIndex) { continue; } if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId) { break; } if (nca.Header.ContentType == NcaContentType.Program) { patchNca = nca; } else if (nca.Header.ContentType == NcaContentType.Control) { controlNca = nca; } } return (patchNca, controlNca); } public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath) { updatePath = null; if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) { // Clear the program index part. titleIdBase &= 0xFFFFFFFFFFFFFFF0; // Load update informations if existing. string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); if (File.Exists(titleUpdateMetadataPath)) { updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected; if (File.Exists(updatePath)) { FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read); PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex); } } } return (null, null); } public void LoadXci(string xciFile) { FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage()); if (!xci.HasPartition(XciPartitionType.Secure)) { Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition"); return; } PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure); Nca mainNca; Nca patchNca; Nca controlNca; try { (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index); RegisterProgramMapInfo(securePartition).ThrowIfFailure(); } catch (Exception e) { Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}"); return; } if (mainNca == null) { Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA"); return; } _device.Configuration.ContentManager.LoadEntries(_device); _device.Configuration.ContentManager.ClearAocData(); _device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel); LoadNca(mainNca, patchNca, controlNca); } public void LoadNsp(string nspFile) { FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); Nca mainNca; Nca patchNca; Nca controlNca; try { (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index); RegisterProgramMapInfo(nsp).ThrowIfFailure(); } catch (Exception e) { Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}"); return; } if (mainNca != null) { _device.Configuration.ContentManager.ClearAocData(); _device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel); LoadNca(mainNca, patchNca, controlNca); return; } // This is not a normal NSP, it's actually a ExeFS as a NSP LoadExeFs(nsp, null, isHomebrew: true); } public void LoadNca(string ncaFile) { FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); LoadNca(nca, null, null); } public void LoadServiceNca(string ncaFile) { FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); if (mainNca.Header.ContentType != NcaContentType.Program) { Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); return; } IFileSystem codeFs = null; if (mainNca.CanOpenSection(NcaSectionType.Code)) { codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); } if (codeFs == null) { Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); return; } using var npdmFile = new UniqueRef(); Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); MetaLoader metaData; npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); var npdmBuffer = new byte[fileSize]; npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); metaData = new MetaLoader(); metaData.Load(npdmBuffer).ThrowIfFailure(); NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; for (int i = 0; i < nsos.Length; i++) { string name = ExeFsPrefixes[i]; if (!codeFs.FileExists($"/{name}")) { continue; // File doesn't exist, skip. } Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); using var nsoFile = new UniqueRef(); codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); } // Collect the nsos, ignoring ones that aren't used. NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString; bool usePtc = _device.System.EnablePtc; metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false); ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs); string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16"); bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0; string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0'); Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]"); } private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) { if (mainNca.Header.ContentType != NcaContentType.Program) { Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); return; } IStorage dataStorage = null; IFileSystem codeFs = null; (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _); if (updatePatchNca != null) { patchNca = updatePatchNca; } if (updateControlNca != null) { controlNca = updateControlNca; } // Load program 0 control NCA as we are going to need it for display version. (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); // Load Aoc string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); if (File.Exists(titleAocMetadataPath)) { List dlcContainerList = JsonHelper.DeserializeFromFile>(titleAocMetadataPath); foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) { foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) { if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled) { _device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); } else { Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); } } } } if (patchNca == null) { if (mainNca.CanOpenSection(NcaSectionType.Data)) { dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); } if (mainNca.CanOpenSection(NcaSectionType.Code)) { codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); } } else { if (patchNca.CanOpenSection(NcaSectionType.Data)) { dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); } if (patchNca.CanOpenSection(NcaSectionType.Code)) { codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); } } if (codeFs == null) { Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); return; } MetaLoader metaData = ReadNpdm(codeFs); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); string displayVersion = string.Empty; if (controlNca != null) { ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion); } else { ControlData.ByteSpan.Clear(); } // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. // BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program. if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) { string dummyTitleName = ""; BlitStruct dummyControl = new BlitStruct(1); ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion); } _displayVersion = displayVersion; ulong pid = LoadExeFs(codeFs, displayVersion, metaData); if (dataStorage == null) { Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); } else { IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage); _device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read)); } // Don't create save data for system programs. if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value)) { // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. // We'll know if this changes in the future because stuff will get errors when trying to mount the correct save. EnsureSaveData(new ApplicationId(TitleId & ~0xFul)); } Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]"); } // Sets TitleId, so be sure to call before using it private MetaLoader ReadNpdm(IFileSystem fs) { using var npdmFile = new UniqueRef(); Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); MetaLoader metaData; if (ResultFs.PathNotFound.Includes(result)) { Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!"); metaData = GetDefaultNpdm(); } else { npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); var npdmBuffer = new byte[fileSize]; npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); metaData = new MetaLoader(); metaData.Load(npdmBuffer).ThrowIfFailure(); } metaData.GetNpdm(out var npdm).ThrowIfFailure(); TitleId = npdm.Aci.ProgramId.Value; TitleIs64Bit = (npdm.Meta.Flags & 1) != 0; _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); return metaData; } private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct controlData, ref string titleName, ref string displayVersion) { using var controlFile = new UniqueRef(); IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); if (result.IsSuccess()) { result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None); if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length) { titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString(); if (string.IsNullOrWhiteSpace(titleName)) { titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); } displayVersion = controlData.Value.DisplayVersionString.ToString(); } } else { controlData.ByteSpan.Clear(); } } private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false) { if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) { metaData = null; // TODO: Check if we should retain old npdm. } metaData ??= ReadNpdm(codeFs); NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; for (int i = 0; i < nsos.Length; i++) { string name = ExeFsPrefixes[i]; if (!codeFs.FileExists($"/{name}")) { continue; // File doesn't exist, skip. } Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); using var nsoFile = new UniqueRef(); codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); } // ExeFs file replacements. ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos); // Collect the nsos, ignoring ones that aren't used. NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); // Take the npdm from mods if present. if (modLoadResult.Npdm != null) { metaData = modLoadResult.Npdm; } _device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs); _device.Configuration.ContentManager.LoadEntries(_device); bool usePtc = _device.System.EnablePtc; // Don't use PPTC if ExeFs files have been replaced. usePtc &= !modLoadResult.Modified; if (_device.System.EnablePtc && !usePtc) { Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled."); } Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText; _device.Gpu.HostInitalized.Set(); MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode; if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) { memoryManagerMode = MemoryManagerMode.SoftwarePageTable; } // We allow it for nx-hbloader because it can be used to launch homebrew. bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew; metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit); ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs); DiskCacheLoadState = result.DiskCacheLoadState; _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine); return result.ProcessId; } public void LoadProgram(string filePath) { MetaLoader metaData = GetDefaultNpdm(); metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true); bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; IExecutable executable; Stream romfsStream = null; if (isNro) { FileStream input = new FileStream(filePath, FileMode.Open); NroExecutable obj = new NroExecutable(input.AsStorage()); executable = obj; // Homebrew NRO can actually have some data after the actual NRO. if (input.Length > obj.FileSize) { input.Position = obj.FileSize; BinaryReader reader = new BinaryReader(input); uint asetMagic = reader.ReadUInt32(); if (asetMagic == 0x54455341) { uint asetVersion = reader.ReadUInt32(); if (asetVersion == 0) { ulong iconOffset = reader.ReadUInt64(); ulong iconSize = reader.ReadUInt64(); ulong nacpOffset = reader.ReadUInt64(); ulong nacpSize = reader.ReadUInt64(); ulong romfsOffset = reader.ReadUInt64(); ulong romfsSize = reader.ReadUInt64(); if (romfsSize != 0) { romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset); } if (nacpSize != 0) { input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin); reader.Read(ControlData.ByteSpan); ref ApplicationControlProperty nacp = ref ControlData.Value; programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); if (string.IsNullOrWhiteSpace(programInfo.Name)) { programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); } if (nacp.PresenceGroupId != 0) { programInfo.ProgramId = nacp.PresenceGroupId; } else if (nacp.SaveDataOwnerId != 0) { programInfo.ProgramId = nacp.SaveDataOwnerId; } else if (nacp.AddOnContentBaseId != 0) { programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000; } else { programInfo.ProgramId = 0000000000000000; } } } else { Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\""); } } } } else { executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath)); } _device.Configuration.ContentManager.LoadEntries(_device); _titleName = programInfo.Name; TitleId = programInfo.ProgramId; TitleIs64Bit = (npdm.Meta.Flags & 1) != 0; _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); // Explicitly null titleid to disable the shader cache. Graphics.Gpu.GraphicsConfig.TitleId = null; _device.Gpu.HostInitalized.Set(); ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable); if (romfsStream != null) { _device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream); } DiskCacheLoadState = result.DiskCacheLoadState; _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine); } private MetaLoader GetDefaultNpdm() { Assembly asm = Assembly.GetCallingAssembly(); using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) { var npdmBuffer = new byte[npdmStream.Length]; npdmStream.Read(npdmBuffer); var metaLoader = new MetaLoader(); metaLoader.Load(npdmBuffer).ThrowIfFailure(); return metaLoader; } } private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs) { ulong mainProgramId = 0; Span hasIndex = stackalloc bool[0x10]; fileSystem.ImportTickets(pfs); foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) { using var ncaFile = new UniqueRef(); pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); if (nca.Header.ContentType != NcaContentType.Program) { continue; } int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) { continue; } ulong currentProgramId = nca.Header.TitleId; ulong currentMainProgramId = currentProgramId & ~0xFFFul; if (mainProgramId == 0 && currentMainProgramId != 0) { mainProgramId = currentMainProgramId; } if (mainProgramId != currentMainProgramId) { // As far as I know there aren't any multi-application game cards containing multi-program applications, // so because multi-application game cards are the only way we should run into multiple applications // we'll just return that there's a single program. return (mainProgramId, 1); } hasIndex[(int)(currentProgramId & 0xF)] = true; } int programCount = 0; for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) { programCount++; } return (mainProgramId, programCount); } private Result RegisterProgramMapInfo(PartitionFileSystem pfs) { (ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs); if (programCount <= 0) return Result.Success; Span mapInfo = stackalloc ProgramIndexMapInfo[0x10]; for (int i = 0; i < programCount; i++) { mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); mapInfo[i].MainProgramId = new ApplicationId(applicationId); mapInfo[i].ProgramIndex = (byte)i; } return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount)); } private Result EnsureSaveData(ApplicationId applicationId) { Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); ref ApplicationControlProperty control = ref ControlData.Value; if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan)) { // If the current application doesn't have a loaded control property, create a dummy one // and set the savedata sizes so a user savedata will be created. control = ref new BlitStruct(1).Value; // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. control.UserAccountSaveDataSize = 0x4000; control.UserAccountSaveDataJournalSize = 0x4000; control.SaveDataOwnerId = applicationId.Value; Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); } HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient; Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); if (resultCode.IsFailure()) { Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); return resultCode; } resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user); if (resultCode.IsFailure()) { Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); } return resultCode; } } }