/ src / Ryujinx.UI.Common / App / ApplicationData.cs
ApplicationData.cs
  1  using LibHac.Common;
  2  using LibHac.Fs;
  3  using LibHac.Fs.Fsa;
  4  using LibHac.FsSystem;
  5  using LibHac.Loader;
  6  using LibHac.Ns;
  7  using LibHac.Tools.Fs;
  8  using LibHac.Tools.FsSystem;
  9  using LibHac.Tools.FsSystem.NcaUtils;
 10  using Ryujinx.Common.Logging;
 11  using Ryujinx.HLE.FileSystem;
 12  using Ryujinx.HLE.Loaders.Processes.Extensions;
 13  using Ryujinx.UI.Common.Helper;
 14  using System;
 15  using System.IO;
 16  using System.Text.Json.Serialization;
 17  
 18  namespace Ryujinx.UI.App.Common
 19  {
 20      public class ApplicationData
 21      {
 22          public bool Favorite { get; set; }
 23          public byte[] Icon { get; set; }
 24          public string Name { get; set; } = "Unknown";
 25          public ulong Id { get; set; }
 26          public string Developer { get; set; } = "Unknown";
 27          public string Version { get; set; } = "0";
 28          public TimeSpan TimePlayed { get; set; }
 29          public DateTime? LastPlayed { get; set; }
 30          public string FileExtension { get; set; }
 31          public long FileSize { get; set; }
 32          public string Path { get; set; }
 33          public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
 34  
 35          public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
 36  
 37          public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed);
 38  
 39          public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
 40  
 41          [JsonIgnore] public string IdString => Id.ToString("x16");
 42  
 43          [JsonIgnore] public ulong IdBase => Id & ~0x1FFFUL;
 44  
 45          [JsonIgnore] public string IdBaseString => IdBase.ToString("x16");
 46  
 47          public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
 48          {
 49              using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
 50  
 51              Nca mainNca = null;
 52              Nca patchNca = null;
 53  
 54              if (!System.IO.Path.Exists(titleFilePath))
 55              {
 56                  Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist.");
 57                  return string.Empty;
 58              }
 59  
 60              string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
 61  
 62              if (extension is ".nsp" or ".xci")
 63              {
 64                  IFileSystem pfs;
 65  
 66                  if (extension == ".xci")
 67                  {
 68                      Xci xci = new(virtualFileSystem.KeySet, file.AsStorage());
 69  
 70                      pfs = xci.OpenPartition(XciPartitionType.Secure);
 71                  }
 72                  else
 73                  {
 74                      var pfsTemp = new PartitionFileSystem();
 75                      pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
 76                      pfs = pfsTemp;
 77                  }
 78  
 79                  foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
 80                  {
 81                      using var ncaFile = new UniqueRef<IFile>();
 82  
 83                      pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
 84  
 85                      Nca nca = new(virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
 86  
 87                      if (nca.Header.ContentType != NcaContentType.Program)
 88                      {
 89                          continue;
 90                      }
 91  
 92                      int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
 93  
 94                      if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
 95                      {
 96                          patchNca = nca;
 97                      }
 98                      else
 99                      {
100                          mainNca = nca;
101                      }
102                  }
103              }
104              else if (extension == ".nca")
105              {
106                  mainNca = new Nca(virtualFileSystem.KeySet, file.AsStorage());
107              }
108  
109              if (mainNca == null)
110              {
111                  Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
112  
113                  return string.Empty;
114              }
115  
116              (Nca updatePatchNca, _) = mainNca.GetUpdateData(virtualFileSystem, checkLevel, 0, out string _);
117  
118              if (updatePatchNca != null)
119              {
120                  patchNca = updatePatchNca;
121              }
122  
123              IFileSystem codeFs = null;
124  
125              if (patchNca == null)
126              {
127                  if (mainNca.CanOpenSection(NcaSectionType.Code))
128                  {
129                      codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
130                  }
131              }
132              else
133              {
134                  if (patchNca.CanOpenSection(NcaSectionType.Code))
135                  {
136                      codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
137                  }
138              }
139  
140              if (codeFs == null)
141              {
142                  Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
143  
144                  return string.Empty;
145              }
146  
147              const string MainExeFs = "main";
148  
149              if (!codeFs.FileExists($"/{MainExeFs}"))
150              {
151                  Logger.Error?.Print(LogClass.Loader, "No main binary ExeFS found in ExeFS");
152  
153                  return string.Empty;
154              }
155  
156              using var nsoFile = new UniqueRef<IFile>();
157  
158              codeFs.OpenFile(ref nsoFile.Ref, $"/{MainExeFs}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
159  
160              NsoReader reader = new();
161              reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure();
162  
163              return BitConverter.ToString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", "").ToUpper()[..16];
164          }
165      }
166  }