/ src / Ryujinx.HLE / Loaders / Processes / Extensions / PartitionFileSystemExtensions.cs
PartitionFileSystemExtensions.cs
  1  using LibHac.Common;
  2  using LibHac.Common.Keys;
  3  using LibHac.Fs;
  4  using LibHac.Fs.Fsa;
  5  using LibHac.FsSystem;
  6  using LibHac.Ncm;
  7  using LibHac.Tools.Fs;
  8  using LibHac.Tools.FsSystem;
  9  using LibHac.Tools.FsSystem.NcaUtils;
 10  using LibHac.Tools.Ncm;
 11  using Ryujinx.Common.Configuration;
 12  using Ryujinx.Common.Logging;
 13  using Ryujinx.Common.Utilities;
 14  using Ryujinx.HLE.FileSystem;
 15  using System;
 16  using System.Collections.Generic;
 17  using System.IO;
 18  using ContentType = LibHac.Ncm.ContentType;
 19  
 20  namespace Ryujinx.HLE.Loaders.Processes.Extensions
 21  {
 22      public static class PartitionFileSystemExtensions
 23      {
 24          private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
 25  
 26          public static Dictionary<ulong, ContentMetaData> GetContentData(this IFileSystem partitionFileSystem,
 27              ContentMetaType contentType, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel)
 28          {
 29              fileSystem.ImportTickets(partitionFileSystem);
 30  
 31              var programs = new Dictionary<ulong, ContentMetaData>();
 32  
 33              foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca"))
 34              {
 35                  Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, contentType);
 36  
 37                  if (cnmt == null)
 38                  {
 39                      continue;
 40                  }
 41  
 42                  ContentMetaData content = new(partitionFileSystem, cnmt);
 43  
 44                  if (content.Type != contentType)
 45                  {
 46                      continue;
 47                  }
 48  
 49                  programs.TryAdd(content.ApplicationId, content);
 50              }
 51  
 52              return programs;
 53          }
 54  
 55          internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage)
 56              where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
 57              where TFormat : IPartitionFileSystemFormat
 58              where THeader : unmanaged, IPartitionFileSystemHeader
 59              where TEntry : unmanaged, IPartitionFileSystemEntry
 60          {
 61              errorMessage = null;
 62  
 63              // Load required NCAs.
 64              Nca mainNca = null;
 65              Nca patchNca = null;
 66              Nca controlNca = null;
 67  
 68              try
 69              {
 70                  Dictionary<ulong, ContentMetaData> applications = partitionFileSystem.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel);
 71  
 72                  if (applicationId == 0)
 73                  {
 74                      foreach ((ulong _, ContentMetaData content) in applications)
 75                      {
 76                          mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
 77                          controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
 78                          break;
 79                      }
 80                  }
 81                  else if (applications.TryGetValue(applicationId, out ContentMetaData content))
 82                  {
 83                      mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
 84                      controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
 85                  }
 86  
 87                  ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
 88              }
 89              catch (Exception ex)
 90              {
 91                  errorMessage = $"Unable to load: {ex.Message}";
 92  
 93                  return (false, ProcessResult.Failed);
 94              }
 95  
 96              if (mainNca != null)
 97              {
 98                  if (mainNca.Header.ContentType != NcaContentType.Program)
 99                  {
100                      errorMessage = "Selected NCA file is not a \"Program\" NCA";
101  
102                      return (false, ProcessResult.Failed);
103                  }
104  
105                  (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
106  
107                  if (updatePatchNca != null)
108                  {
109                      patchNca = updatePatchNca;
110                  }
111  
112                  if (updateControlNca != null)
113                  {
114                      controlNca = updateControlNca;
115                  }
116  
117                  // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
118                  device.Configuration.ContentManager.ClearAocData();
119  
120                  // Load DownloadableContents.
121                  string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json");
122                  if (File.Exists(addOnContentMetadataPath))
123                  {
124                      List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer);
125  
126                      foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
127                      {
128                          foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
129                          {
130                              if (File.Exists(downloadableContentContainer.ContainerPath))
131                              {
132                                  if (downloadableContentNca.Enabled)
133                                  {
134                                      device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
135                                  }
136                              }
137                              else
138                              {
139                                  Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
140                              }
141                          }
142                      }
143                  }
144  
145                  return (true, mainNca.Load(device, patchNca, controlNca));
146              }
147  
148              errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\"";
149  
150              return (false, ProcessResult.Failed);
151          }
152  
153          public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path)
154          {
155              using var ncaFile = new UniqueRef<IFile>();
156  
157              fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
158  
159              return new Nca(keySet, ncaFile.Release().AsStorage());
160          }
161      }
162  }