using LibHac; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Loader; using LibHac.Ncm; using LibHac.Ns; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; using System.IO; using System.Linq; using ApplicationId = LibHac.Ncm.ApplicationId; namespace Ryujinx.HLE.Loaders.Processes.Extensions { static class NcaExtensions { public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca) { // Extract RomFs and ExeFs from NCA. IStorage romFs = nca.GetRomFs(device, patchNca); IFileSystem exeFs = nca.GetExeFs(device, patchNca); if (exeFs == null) { Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); return ProcessResult.Failed; } // Load Npdm file. MetaLoader metaLoader = exeFs.GetNpdm(); // Collecting mods related to AocTitleIds and ProgramId. device.Configuration.VirtualFileSystem.ModLoader.CollectMods( device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()), device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); // Load Nacp file. var nacpData = new BlitStruct(1); if (controlNca != null) { nacpData = controlNca.GetNacp(device); } /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update. // Load program 0 control NCA as we are going to need it for display version. (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. // As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program. if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) { nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion; } */ ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); // Load RomFS. if (romFs == null) { Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); } else { romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs); device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read)); } // Don't create save data for system programs. if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value)) { // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. // We'll know if this changes in the future because applications will get errors when trying to mount the correct save. ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData); } return processResult; } public static int GetProgramIndex(this Nca nca) { return (int)(nca.Header.TitleId & 0xF); } public static bool IsProgram(this Nca nca) { return nca.Header.ContentType == NcaContentType.Program; } public static bool IsPatch(this Nca nca) { int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection(); } public static bool IsControl(this Nca nca) { return nca.Header.ContentType == NcaContentType.Control; } public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null) { IFileSystem exeFs = null; if (patchNca == null) { if (nca.CanOpenSection(NcaSectionType.Code)) { exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel); } } else { if (patchNca.CanOpenSection(NcaSectionType.Code)) { exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel); } } return exeFs; } public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null) { IStorage romFs = null; if (patchNca == null) { if (nca.CanOpenSection(NcaSectionType.Data)) { romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); } } else { if (patchNca.CanOpenSection(NcaSectionType.Data)) { romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel); } } return romFs; } public static BlitStruct GetNacp(this Nca controlNca, Switch device) { var nacpData = new BlitStruct(1); using var controlFile = new UniqueRef(); Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel) .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); if (result.IsSuccess()) { result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None); } else { nacpData.ByteSpan.Clear(); } return nacpData; } } }