NcaExtensions.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. using LibHac;
  2. using LibHac.Common;
  3. using LibHac.Fs;
  4. using LibHac.Fs.Fsa;
  5. using LibHac.Loader;
  6. using LibHac.Ncm;
  7. using LibHac.Ns;
  8. using LibHac.Tools.FsSystem;
  9. using LibHac.Tools.FsSystem.NcaUtils;
  10. using Ryujinx.Common.Logging;
  11. using System.IO;
  12. using System.Linq;
  13. using ApplicationId = LibHac.Ncm.ApplicationId;
  14. namespace Ryujinx.HLE.Loaders.Processes.Extensions
  15. {
  16. static class NcaExtensions
  17. {
  18. public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
  19. {
  20. // Extract RomFs and ExeFs from NCA.
  21. IStorage romFs = nca.GetRomFs(device, patchNca);
  22. IFileSystem exeFs = nca.GetExeFs(device, patchNca);
  23. if (exeFs == null)
  24. {
  25. Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
  26. return ProcessResult.Failed;
  27. }
  28. // Load Npdm file.
  29. MetaLoader metaLoader = exeFs.GetNpdm();
  30. // Collecting mods related to AocTitleIds and ProgramId.
  31. device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
  32. device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()),
  33. device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
  34. device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
  35. // Load Nacp file.
  36. var nacpData = new BlitStruct<ApplicationControlProperty>(1);
  37. if (controlNca != null)
  38. {
  39. nacpData = controlNca.GetNacp(device);
  40. }
  41. /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
  42. // Load program 0 control NCA as we are going to need it for display version.
  43. (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
  44. // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
  45. // As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program.
  46. if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
  47. {
  48. nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion;
  49. }
  50. */
  51. ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader);
  52. // Load RomFS.
  53. if (romFs == null)
  54. {
  55. Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
  56. }
  57. else
  58. {
  59. romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs);
  60. device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read));
  61. }
  62. // Don't create save data for system programs.
  63. if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value))
  64. {
  65. // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
  66. // We'll know if this changes in the future because applications will get errors when trying to mount the correct save.
  67. ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData);
  68. }
  69. return processResult;
  70. }
  71. public static int GetProgramIndex(this Nca nca)
  72. {
  73. return (int)(nca.Header.TitleId & 0xF);
  74. }
  75. public static bool IsProgram(this Nca nca)
  76. {
  77. return nca.Header.ContentType == NcaContentType.Program;
  78. }
  79. public static bool IsPatch(this Nca nca)
  80. {
  81. int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
  82. return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection();
  83. }
  84. public static bool IsControl(this Nca nca)
  85. {
  86. return nca.Header.ContentType == NcaContentType.Control;
  87. }
  88. public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
  89. {
  90. IFileSystem exeFs = null;
  91. if (patchNca == null)
  92. {
  93. if (nca.CanOpenSection(NcaSectionType.Code))
  94. {
  95. exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel);
  96. }
  97. }
  98. else
  99. {
  100. if (patchNca.CanOpenSection(NcaSectionType.Code))
  101. {
  102. exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel);
  103. }
  104. }
  105. return exeFs;
  106. }
  107. public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null)
  108. {
  109. IStorage romFs = null;
  110. if (patchNca == null)
  111. {
  112. if (nca.CanOpenSection(NcaSectionType.Data))
  113. {
  114. romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
  115. }
  116. }
  117. else
  118. {
  119. if (patchNca.CanOpenSection(NcaSectionType.Data))
  120. {
  121. romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
  122. }
  123. }
  124. return romFs;
  125. }
  126. public static BlitStruct<ApplicationControlProperty> GetNacp(this Nca controlNca, Switch device)
  127. {
  128. var nacpData = new BlitStruct<ApplicationControlProperty>(1);
  129. using var controlFile = new UniqueRef<IFile>();
  130. Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel)
  131. .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
  132. if (result.IsSuccess())
  133. {
  134. result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None);
  135. }
  136. else
  137. {
  138. nacpData.ByteSpan.Clear();
  139. }
  140. return nacpData;
  141. }
  142. }
  143. }