/ src / Ryujinx.UI.Common / App / ApplicationLibrary.cs
ApplicationLibrary.cs
  1  using LibHac;
  2  using LibHac.Common;
  3  using LibHac.Common.Keys;
  4  using LibHac.Fs;
  5  using LibHac.Fs.Fsa;
  6  using LibHac.FsSystem;
  7  using LibHac.Ncm;
  8  using LibHac.Ns;
  9  using LibHac.Tools.Fs;
 10  using LibHac.Tools.FsSystem;
 11  using LibHac.Tools.FsSystem.NcaUtils;
 12  using Ryujinx.Common.Configuration;
 13  using Ryujinx.Common.Logging;
 14  using Ryujinx.Common.Utilities;
 15  using Ryujinx.HLE.FileSystem;
 16  using Ryujinx.HLE.HOS.SystemState;
 17  using Ryujinx.HLE.Loaders.Npdm;
 18  using Ryujinx.HLE.Loaders.Processes.Extensions;
 19  using Ryujinx.UI.Common.Configuration;
 20  using Ryujinx.UI.Common.Configuration.System;
 21  using System;
 22  using System.Collections.Generic;
 23  using System.IO;
 24  using System.Linq;
 25  using System.Reflection;
 26  using System.Text;
 27  using System.Text.Json;
 28  using System.Threading;
 29  using ContentType = LibHac.Ncm.ContentType;
 30  using Path = System.IO.Path;
 31  using TimeSpan = System.TimeSpan;
 32  
 33  namespace Ryujinx.UI.App.Common
 34  {
 35      public class ApplicationLibrary
 36      {
 37          public Language DesiredLanguage { get; set; }
 38          public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
 39          public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
 40  
 41          private readonly byte[] _nspIcon;
 42          private readonly byte[] _xciIcon;
 43          private readonly byte[] _ncaIcon;
 44          private readonly byte[] _nroIcon;
 45          private readonly byte[] _nsoIcon;
 46  
 47          private readonly VirtualFileSystem _virtualFileSystem;
 48          private readonly IntegrityCheckLevel _checkLevel;
 49          private CancellationTokenSource _cancellationToken;
 50  
 51          private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
 52  
 53          public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel)
 54          {
 55              _virtualFileSystem = virtualFileSystem;
 56              _checkLevel = checkLevel;
 57  
 58              _nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png");
 59              _xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png");
 60              _ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png");
 61              _nroIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NRO.png");
 62              _nsoIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSO.png");
 63          }
 64  
 65          private static byte[] GetResourceBytes(string resourceName)
 66          {
 67              Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
 68              byte[] resourceByteArray = new byte[resourceStream.Length];
 69  
 70              resourceStream.ReadExactly(resourceByteArray);
 71  
 72              return resourceByteArray;
 73          }
 74  
 75          /// <exception cref="Ryujinx.HLE.Exceptions.InvalidNpdmException">The npdm file doesn't contain valid data.</exception>
 76          /// <exception cref="NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception>
 77          /// <exception cref="ArgumentException">An error occured while reading bytes from the stream.</exception>
 78          /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
 79          /// <exception cref="IOException">An I/O error occurred.</exception>
 80          private ApplicationData GetApplicationFromExeFs(PartitionFileSystem pfs, string filePath)
 81          {
 82              ApplicationData data = new()
 83              {
 84                  Icon = _nspIcon,
 85                  Path = filePath,
 86              };
 87  
 88              using UniqueRef<IFile> npdmFile = new();
 89  
 90              Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
 91  
 92              if (ResultFs.PathNotFound.Includes(result))
 93              {
 94                  Npdm npdm = new(npdmFile.Get.AsStream());
 95  
 96                  data.Name = npdm.TitleName;
 97                  data.Id = npdm.Aci0.TitleId;
 98              }
 99  
100              return data;
101          }
102  
103          /// <exception cref="MissingKeyException">The configured key set is missing a key.</exception>
104          /// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
105          /// <exception cref="NotSupportedException">The NCA version is not supported.</exception>
106          /// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception>
107          /// <exception cref="Ryujinx.HLE.Exceptions.InvalidNpdmException">The npdm file doesn't contain valid data.</exception>
108          /// <exception cref="NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception>
109          /// <exception cref="ArgumentException">An error occured while reading bytes from the stream.</exception>
110          /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
111          /// <exception cref="IOException">An I/O error occurred.</exception>
112          private ApplicationData GetApplicationFromNsp(PartitionFileSystem pfs, string filePath)
113          {
114              bool isExeFs = false;
115  
116              // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
117              bool hasMainNca = false;
118  
119              foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
120              {
121                  if (Path.GetExtension(fileEntry.FullPath)?.ToLower() == ".nca")
122                  {
123                      using UniqueRef<IFile> ncaFile = new();
124  
125                      try
126                      {
127                          pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
128  
129                          Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
130                          int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
131  
132                          // Some main NCAs don't have a data partition, so check if the partition exists before opening it
133                          if (nca.Header.ContentType == NcaContentType.Program &&
134                              !(nca.SectionExists(NcaSectionType.Data) &&
135                                nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
136                          {
137                              hasMainNca = true;
138  
139                              break;
140                          }
141                      }
142                      catch (Exception exception)
143                      {
144                          Logger.Warning?.Print(LogClass.Application, $"Encountered an error while trying to load applications from file '{filePath}': {exception}");
145  
146                          return null;
147                      }
148                  }
149                  else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
150                  {
151                      isExeFs = true;
152                  }
153              }
154  
155              if (hasMainNca)
156              {
157                  List<ApplicationData> applications = GetApplicationsFromPfs(pfs, filePath);
158  
159                  switch (applications.Count)
160                  {
161                      case 1:
162                          return applications[0];
163                      case >= 1:
164                          Logger.Warning?.Print(LogClass.Application, $"File '{filePath}' contains more applications than expected: {applications.Count}");
165                          return applications[0];
166                      default:
167                          return null;
168                  }
169              }
170  
171              if (isExeFs)
172              {
173                  return GetApplicationFromExeFs(pfs, filePath);
174              }
175  
176              return null;
177          }
178  
179          /// <exception cref="MissingKeyException">The configured key set is missing a key.</exception>
180          /// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception>
181          /// <exception cref="NotSupportedException">The NCA version is not supported.</exception>
182          /// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception>
183          private List<ApplicationData> GetApplicationsFromPfs(IFileSystem pfs, string filePath)
184          {
185              var applications = new List<ApplicationData>();
186              string extension = Path.GetExtension(filePath).ToLower();
187  
188              foreach ((ulong titleId, ContentMetaData content) in pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel))
189              {
190                  ApplicationData applicationData = new()
191                  {
192                      Id = titleId,
193                      Path = filePath,
194                  };
195  
196                  Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program);
197                  Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control);
198  
199                  BlitStruct<ApplicationControlProperty> controlHolder = new(1);
200  
201                  IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, _checkLevel);
202  
203                  // Check if there is an update available.
204                  if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs))
205                  {
206                      // Replace the original ControlFs by the updated one.
207                      controlFs = updatedControlFs;
208                  }
209  
210                  if (controlFs == null)
211                  {
212                      continue;
213                  }
214  
215                  ReadControlData(controlFs, controlHolder.ByteSpan);
216  
217                  GetApplicationInformation(ref controlHolder.Value, ref applicationData);
218  
219                  // Read the icon from the ControlFS and store it as a byte array
220                  try
221                  {
222                      using UniqueRef<IFile> icon = new();
223  
224                      controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
225  
226                      using MemoryStream stream = new();
227  
228                      icon.Get.AsStream().CopyTo(stream);
229                      applicationData.Icon = stream.ToArray();
230                  }
231                  catch (HorizonResultException)
232                  {
233                      foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
234                      {
235                          if (entry.Name == "control.nacp")
236                          {
237                              continue;
238                          }
239  
240                          using var icon = new UniqueRef<IFile>();
241  
242                          controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
243  
244                          using MemoryStream stream = new();
245  
246                          icon.Get.AsStream().CopyTo(stream);
247                          applicationData.Icon = stream.ToArray();
248  
249                          if (applicationData.Icon != null)
250                          {
251                              break;
252                          }
253                      }
254  
255                      applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon;
256                  }
257  
258                  applicationData.ControlHolder = controlHolder;
259  
260                  applications.Add(applicationData);
261              }
262  
263              return applications;
264          }
265  
266          public bool TryGetApplicationsFromFile(string applicationPath, out List<ApplicationData> applications)
267          {
268              applications = [];
269              long fileSize;
270  
271              try
272              {
273                  fileSize = new FileInfo(applicationPath).Length;
274              }
275              catch (FileNotFoundException)
276              {
277                  Logger.Warning?.Print(LogClass.Application, $"The file was not found: '{applicationPath}'");
278  
279                  return false;
280              }
281  
282              BlitStruct<ApplicationControlProperty> controlHolder = new(1);
283  
284              try
285              {
286                  string extension = Path.GetExtension(applicationPath).ToLower();
287  
288                  using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
289  
290                  switch (extension)
291                  {
292                      case ".xci":
293                          {
294                              Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
295  
296                              applications = GetApplicationsFromPfs(xci.OpenPartition(XciPartitionType.Secure), applicationPath);
297  
298                              if (applications.Count == 0)
299                              {
300                                  return false;
301                              }
302  
303                              break;
304                          }
305                      case ".nsp":
306                      case ".pfs0":
307                          {
308                              var pfs = new PartitionFileSystem();
309                              pfs.Initialize(file.AsStorage()).ThrowIfFailure();
310  
311                              ApplicationData result = GetApplicationFromNsp(pfs, applicationPath);
312  
313                              if (result == null)
314                              {
315                                  return false;
316                              }
317  
318                              applications.Add(result);
319  
320                              break;
321                          }
322                      case ".nro":
323                          {
324                              BinaryReader reader = new(file);
325                              ApplicationData application = new();
326  
327                              file.Seek(24, SeekOrigin.Begin);
328  
329                              int assetOffset = reader.ReadInt32();
330  
331                              if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
332                              {
333                                  byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
334  
335                                  long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
336                                  long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
337  
338                                  ulong nacpOffset = reader.ReadUInt64();
339                                  ulong nacpSize = reader.ReadUInt64();
340  
341                                  // Reads and stores game icon as byte array
342                                  if (iconSize > 0)
343                                  {
344                                      application.Icon = Read(assetOffset + iconOffset, (int)iconSize);
345                                  }
346                                  else
347                                  {
348                                      application.Icon = _nroIcon;
349                                  }
350  
351                                  // Read the NACP data
352                                  Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
353  
354                                  GetApplicationInformation(ref controlHolder.Value, ref application);
355                              }
356                              else
357                              {
358                                  application.Icon = _nroIcon;
359                                  application.Name = Path.GetFileNameWithoutExtension(applicationPath);
360                              }
361  
362                              application.ControlHolder = controlHolder;
363                              applications.Add(application);
364  
365                              break;
366  
367                              byte[] Read(long position, int size)
368                              {
369                                  file.Seek(position, SeekOrigin.Begin);
370  
371                                  return reader.ReadBytes(size);
372                              }
373                          }
374                      case ".nca":
375                          {
376                              ApplicationData application = new();
377  
378                              Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
379  
380                              if (!nca.IsProgram() || nca.IsPatch())
381                              {
382                                  return false;
383                              }
384  
385                              application.Icon = _ncaIcon;
386                              application.Name = Path.GetFileNameWithoutExtension(applicationPath);
387                              application.ControlHolder = controlHolder;
388  
389                              applications.Add(application);
390  
391                              break;
392                          }
393                      // If its an NSO we just set defaults
394                      case ".nso":
395                          {
396                              ApplicationData application = new()
397                              {
398                                  Icon = _nsoIcon,
399                                  Name = Path.GetFileNameWithoutExtension(applicationPath),
400                              };
401  
402                              applications.Add(application);
403  
404                              break;
405                          }
406                  }
407              }
408              catch (MissingKeyException exception)
409              {
410                  Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
411  
412                  return false;
413              }
414              catch (InvalidDataException)
415              {
416                  Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
417  
418                  return false;
419              }
420              catch (IOException exception)
421              {
422                  Logger.Warning?.Print(LogClass.Application, exception.Message);
423  
424                  return false;
425              }
426              catch (Exception exception)
427              {
428                  Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
429  
430                  return false;
431              }
432  
433              foreach (var data in applications)
434              {
435                  // Only load metadata for applications with an ID
436                  if (data.Id != 0)
437                  {
438                      ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
439                      {
440                          appMetadata.Title = data.Name;
441  
442                          // Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
443                          if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
444                          {
445                              appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
446                              appMetadata.TimePlayedOld = default;
447                          }
448  
449                          // Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
450                          if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
451                          {
452                              // Migrate from string-based last_played to DateTime-based last_played_utc.
453                              if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
454                              {
455                                  appMetadata.LastPlayed = lastPlayedOldParsed;
456  
457                                  // Migration successful: deleting last_played from the metadata file.
458                                  appMetadata.LastPlayedOld = default;
459                              }
460  
461                          }
462                      });
463  
464                      data.Favorite = appMetadata.Favorite;
465                      data.TimePlayed = appMetadata.TimePlayed;
466                      data.LastPlayed = appMetadata.LastPlayed;
467                  }
468  
469                  data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
470                  data.FileSize = fileSize;
471                  data.Path = applicationPath;
472              }
473  
474              return true;
475          }
476  
477          public void CancelLoading()
478          {
479              _cancellationToken?.Cancel();
480          }
481  
482          public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
483          {
484              using UniqueRef<IFile> controlFile = new();
485  
486              controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
487              controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
488          }
489  
490          public void LoadApplications(List<string> appDirs)
491          {
492              int numApplicationsFound = 0;
493              int numApplicationsLoaded = 0;
494  
495              _cancellationToken = new CancellationTokenSource();
496  
497              // Builds the applications list with paths to found applications
498              List<string> applicationPaths = new();
499  
500              try
501              {
502                  foreach (string appDir in appDirs)
503                  {
504                      if (_cancellationToken.Token.IsCancellationRequested)
505                      {
506                          return;
507                      }
508  
509                      if (!Directory.Exists(appDir))
510                      {
511                          Logger.Warning?.Print(LogClass.Application, $"The specified game directory \"{appDir}\" does not exist.");
512  
513                          continue;
514                      }
515  
516                      try
517                      {
518                          EnumerationOptions options = new()
519                          {
520                              RecurseSubdirectories = true,
521                              IgnoreInaccessible = false,
522                          };
523  
524                          IEnumerable<string> files = Directory.EnumerateFiles(appDir, "*", options).Where(file =>
525                          {
526                              return
527                              (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) ||
528                              (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value) ||
529                              (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value) ||
530                              (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value) ||
531                              (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value) ||
532                              (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value);
533                          });
534  
535                          foreach (string app in files)
536                          {
537                              if (_cancellationToken.Token.IsCancellationRequested)
538                              {
539                                  return;
540                              }
541  
542                              var fileInfo = new FileInfo(app);
543  
544                              try
545                              {
546                                  var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName;
547  
548                                  applicationPaths.Add(fullPath);
549                                  numApplicationsFound++;
550                              }
551                              catch (IOException exception)
552                              {
553                                  Logger.Warning?.Print(LogClass.Application, $"Failed to resolve the full path to file: \"{app}\" Error: {exception}");
554                              }
555                          }
556                      }
557                      catch (UnauthorizedAccessException)
558                      {
559                          Logger.Warning?.Print(LogClass.Application, $"Failed to get access to directory: \"{appDir}\"");
560                      }
561                  }
562  
563                  // Loops through applications list, creating a struct and then firing an event containing the struct for each application
564                  foreach (string applicationPath in applicationPaths)
565                  {
566                      if (_cancellationToken.Token.IsCancellationRequested)
567                      {
568                          return;
569                      }
570  
571                      if (TryGetApplicationsFromFile(applicationPath, out List<ApplicationData> applications))
572                      {
573                          foreach (var application in applications)
574                          {
575                              OnApplicationAdded(new ApplicationAddedEventArgs
576                              {
577                                  AppData = application,
578                              });
579                          }
580  
581                          if (applications.Count > 1)
582                          {
583                              numApplicationsFound += applications.Count - 1;
584                          }
585  
586                          numApplicationsLoaded += applications.Count;
587                      }
588                      else
589                      {
590                          numApplicationsFound--;
591                      }
592  
593                      OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
594                      {
595                          NumAppsFound = numApplicationsFound,
596                          NumAppsLoaded = numApplicationsLoaded,
597                      });
598                  }
599  
600                  OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
601                  {
602                      NumAppsFound = numApplicationsFound,
603                      NumAppsLoaded = numApplicationsLoaded,
604                  });
605              }
606              finally
607              {
608                  _cancellationToken.Dispose();
609                  _cancellationToken = null;
610              }
611          }
612  
613          protected void OnApplicationAdded(ApplicationAddedEventArgs e)
614          {
615              ApplicationAdded?.Invoke(null, e);
616          }
617  
618          protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
619          {
620              ApplicationCountUpdated?.Invoke(null, e);
621          }
622  
623          public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
624          {
625              string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui");
626              string metadataFile = Path.Combine(metadataFolder, "metadata.json");
627  
628              ApplicationMetadata appMetadata;
629  
630              if (!File.Exists(metadataFile))
631              {
632                  Directory.CreateDirectory(metadataFolder);
633  
634                  appMetadata = new ApplicationMetadata();
635  
636                  JsonHelper.SerializeToFile(metadataFile, appMetadata, _serializerContext.ApplicationMetadata);
637              }
638  
639              try
640              {
641                  appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata);
642              }
643              catch (JsonException)
644              {
645                  Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults.");
646  
647                  appMetadata = new ApplicationMetadata();
648              }
649  
650              if (modifyFunction != null)
651              {
652                  modifyFunction(appMetadata);
653  
654                  JsonHelper.SerializeToFile(metadataFile, appMetadata, _serializerContext.ApplicationMetadata);
655              }
656  
657              return appMetadata;
658          }
659  
660          public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage, ulong applicationId)
661          {
662              byte[] applicationIcon = null;
663  
664              if (applicationId == 0)
665              {
666                  if (Directory.Exists(applicationPath))
667                  {
668                      return _ncaIcon;
669                  }
670  
671                  return Path.GetExtension(applicationPath).ToLower() switch
672                  {
673                      ".nsp" => _nspIcon,
674                      ".pfs0" => _nspIcon,
675                      ".xci" => _xciIcon,
676                      ".nso" => _nsoIcon,
677                      ".nro" => _nroIcon,
678                      ".nca" => _ncaIcon,
679                      _ => _ncaIcon,
680                  };
681              }
682  
683              try
684              {
685                  // Look for icon only if applicationPath is not a directory
686                  if (!Directory.Exists(applicationPath))
687                  {
688                      string extension = Path.GetExtension(applicationPath).ToLower();
689  
690                      using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
691  
692                      if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
693                      {
694                          try
695                          {
696                              IFileSystem pfs;
697  
698                              bool isExeFs = false;
699  
700                              if (extension == ".xci")
701                              {
702                                  Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
703  
704                                  pfs = xci.OpenPartition(XciPartitionType.Secure);
705                              }
706                              else
707                              {
708                                  var pfsTemp = new PartitionFileSystem();
709                                  pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
710                                  pfs = pfsTemp;
711  
712                                  foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
713                                  {
714                                      if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
715                                      {
716                                          isExeFs = true;
717                                      }
718                                  }
719                              }
720  
721                              if (isExeFs)
722                              {
723                                  applicationIcon = _nspIcon;
724                              }
725                              else
726                              {
727                                  // Store the ControlFS in variable called controlFs
728                                  Dictionary<ulong, ContentMetaData> programs = pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel);
729                                  IFileSystem controlFs = null;
730  
731                                  if (programs.TryGetValue(applicationId, out ContentMetaData value))
732                                  {
733                                      if (value.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control) is { } controlNca)
734                                      {
735                                          controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
736                                      }
737                                  }
738  
739                                  // Read the icon from the ControlFS and store it as a byte array
740                                  try
741                                  {
742                                      using var icon = new UniqueRef<IFile>();
743  
744                                      controlFs.OpenFile(ref icon.Ref, $"/icon_{desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
745  
746                                      using MemoryStream stream = new();
747  
748                                      icon.Get.AsStream().CopyTo(stream);
749                                      applicationIcon = stream.ToArray();
750                                  }
751                                  catch (HorizonResultException)
752                                  {
753                                      foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
754                                      {
755                                          if (entry.Name == "control.nacp")
756                                          {
757                                              continue;
758                                          }
759  
760                                          using var icon = new UniqueRef<IFile>();
761  
762                                          controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
763  
764                                          using MemoryStream stream = new();
765                                          icon.Get.AsStream().CopyTo(stream);
766                                          applicationIcon = stream.ToArray();
767  
768                                          break;
769                                      }
770  
771                                      applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
772                                  }
773                              }
774                          }
775                          catch (MissingKeyException)
776                          {
777                              applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
778                          }
779                          catch (InvalidDataException)
780                          {
781                              applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
782                          }
783                          catch (Exception exception)
784                          {
785                              Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
786                          }
787                      }
788                      else if (extension == ".nro")
789                      {
790                          BinaryReader reader = new(file);
791  
792                          byte[] Read(long position, int size)
793                          {
794                              file.Seek(position, SeekOrigin.Begin);
795  
796                              return reader.ReadBytes(size);
797                          }
798  
799                          try
800                          {
801                              file.Seek(24, SeekOrigin.Begin);
802  
803                              int assetOffset = reader.ReadInt32();
804  
805                              if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
806                              {
807                                  byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
808  
809                                  long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
810                                  long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
811  
812                                  // Reads and stores game icon as byte array
813                                  if (iconSize > 0)
814                                  {
815                                      applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
816                                  }
817                                  else
818                                  {
819                                      applicationIcon = _nroIcon;
820                                  }
821                              }
822                              else
823                              {
824                                  applicationIcon = _nroIcon;
825                              }
826                          }
827                          catch
828                          {
829                              Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
830                          }
831                      }
832                      else if (extension == ".nca")
833                      {
834                          applicationIcon = _ncaIcon;
835                      }
836                      // If its an NSO we just set defaults
837                      else if (extension == ".nso")
838                      {
839                          applicationIcon = _nsoIcon;
840                      }
841                  }
842              }
843              catch (Exception)
844              {
845                  Logger.Warning?.Print(LogClass.Application, $"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}");
846              }
847  
848              return applicationIcon ?? _ncaIcon;
849          }
850  
851          private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
852          {
853              _ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
854  
855              if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
856              {
857                  data.Name = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
858                  data.Developer = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
859              }
860              else
861              {
862                  data.Name = null;
863                  data.Developer = null;
864              }
865  
866              if (string.IsNullOrWhiteSpace(data.Name))
867              {
868                  foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
869                  {
870                      if (!controlTitle.NameString.IsEmpty())
871                      {
872                          data.Name = controlTitle.NameString.ToString();
873  
874                          break;
875                      }
876                  }
877              }
878  
879              if (string.IsNullOrWhiteSpace(data.Developer))
880              {
881                  foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
882                  {
883                      if (!controlTitle.PublisherString.IsEmpty())
884                      {
885                          data.Developer = controlTitle.PublisherString.ToString();
886  
887                          break;
888                      }
889                  }
890              }
891  
892              if (data.Id == 0)
893              {
894                  if (controlData.SaveDataOwnerId != 0)
895                  {
896                      data.Id = controlData.SaveDataOwnerId;
897                  }
898                  else if (controlData.PresenceGroupId != 0)
899                  {
900                      data.Id = controlData.PresenceGroupId;
901                  }
902                  else if (controlData.AddOnContentBaseId != 0)
903                  {
904                      data.Id = (controlData.AddOnContentBaseId - 0x1000);
905                  }
906              }
907  
908              data.Version = controlData.DisplayVersionString.ToString();
909          }
910  
911          private bool IsUpdateApplied(Nca mainNca, out IFileSystem updatedControlFs)
912          {
913              updatedControlFs = null;
914  
915              string updatePath = null;
916  
917              try
918              {
919                  (Nca patchNca, Nca controlNca) = mainNca.GetUpdateData(_virtualFileSystem, _checkLevel, 0, out updatePath);
920  
921                  if (patchNca != null && controlNca != null)
922                  {
923                      updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
924  
925                      return true;
926                  }
927              }
928              catch (InvalidDataException)
929              {
930                  Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
931              }
932              catch (MissingKeyException exception)
933              {
934                  Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
935              }
936  
937              return false;
938          }
939      }
940  }