PartitionFileSystemExtensions.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using LibHac.Common;
  2. using LibHac.Fs;
  3. using LibHac.Fs.Fsa;
  4. using LibHac.FsSystem;
  5. using LibHac.Tools.Fs;
  6. using LibHac.Tools.FsSystem;
  7. using LibHac.Tools.FsSystem.NcaUtils;
  8. using Ryujinx.Common.Configuration;
  9. using Ryujinx.Common.Logging;
  10. using Ryujinx.Common.Utilities;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Globalization;
  14. using System.IO;
  15. namespace Ryujinx.HLE.Loaders.Processes.Extensions
  16. {
  17. public static class PartitionFileSystemExtensions
  18. {
  19. private static readonly DownloadableContentJsonSerializerContext ContentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
  20. private static readonly TitleUpdateMetadataJsonSerializerContext TitleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
  21. internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage)
  22. {
  23. errorMessage = null;
  24. // Load required NCAs.
  25. Nca mainNca = null;
  26. Nca patchNca = null;
  27. Nca controlNca = null;
  28. try
  29. {
  30. device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
  31. // TODO: To support multi-games container, this should use CNMT NCA instead.
  32. foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
  33. {
  34. Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
  35. if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
  36. {
  37. continue;
  38. }
  39. if (nca.IsPatch())
  40. {
  41. patchNca = nca;
  42. }
  43. else if (nca.IsProgram())
  44. {
  45. mainNca = nca;
  46. }
  47. else if (nca.IsControl())
  48. {
  49. controlNca = nca;
  50. }
  51. }
  52. ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
  53. }
  54. catch (Exception ex)
  55. {
  56. errorMessage = $"Unable to load: {ex.Message}";
  57. return (false, ProcessResult.Failed);
  58. }
  59. if (mainNca != null)
  60. {
  61. if (mainNca.Header.ContentType != NcaContentType.Program)
  62. {
  63. errorMessage = "Selected NCA file is not a \"Program\" NCA";
  64. return (false, ProcessResult.Failed);
  65. }
  66. // Load Update NCAs.
  67. Nca updatePatchNca = null;
  68. Nca updateControlNca = null;
  69. if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
  70. {
  71. // Clear the program index part.
  72. titleIdBase &= ~0xFUL;
  73. // Load update information if exists.
  74. string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
  75. if (File.Exists(titleUpdateMetadataPath))
  76. {
  77. string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, TitleSerializerContext.TitleUpdateMetadata).Selected;
  78. if (File.Exists(updatePath))
  79. {
  80. PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage());
  81. device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
  82. // TODO: This should use CNMT NCA instead.
  83. foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca"))
  84. {
  85. Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath);
  86. if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
  87. {
  88. continue;
  89. }
  90. if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16"))
  91. {
  92. break;
  93. }
  94. if (nca.IsProgram())
  95. {
  96. updatePatchNca = nca;
  97. }
  98. else if (nca.IsControl())
  99. {
  100. updateControlNca = nca;
  101. }
  102. }
  103. }
  104. }
  105. }
  106. if (updatePatchNca != null)
  107. {
  108. patchNca = updatePatchNca;
  109. }
  110. if (updateControlNca != null)
  111. {
  112. controlNca = updateControlNca;
  113. }
  114. // Load contained DownloadableContents.
  115. // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
  116. device.Configuration.ContentManager.ClearAocData();
  117. device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel);
  118. // Load DownloadableContents.
  119. string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
  120. if (File.Exists(addOnContentMetadataPath))
  121. {
  122. List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, ContentSerializerContext.ListDownloadableContentContainer);
  123. foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
  124. {
  125. foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
  126. {
  127. if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
  128. {
  129. device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
  130. }
  131. else
  132. {
  133. Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
  134. }
  135. }
  136. }
  137. }
  138. return (true, mainNca.Load(device, patchNca, controlNca));
  139. }
  140. errorMessage = "Unable to load: Could not find Main NCA";
  141. return (false, ProcessResult.Failed);
  142. }
  143. public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
  144. {
  145. using var ncaFile = new UniqueRef<IFile>();
  146. fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
  147. return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
  148. }
  149. }
  150. }