From c6dc00815aa15ef95671f19b6fecc3ecc1c84c4c Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 20 Jul 2024 21:35:43 +0200 Subject: Make sure TryGetApplicationsFromFile() doesn't throw exceptions anymore (#7046) * Add docstrings for exceptions to methods near TryGetApplicationsFromFile() * Make sure TryGetApplicationsFromFile() doesn't throw exceptions anymore * Add missing filePath to ApplicationData when loading applications from ExeFS * Fix typo Co-authored-by: riperiperi <rhy3756547@hotmail.com> --------- Co-authored-by: riperiperi <rhy3756547@hotmail.com> --- src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs | 6 + src/Ryujinx.HLE/Loaders/Npdm/ACID.cs | 5 + src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs | 4 + src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs | 6 + .../Loaders/Npdm/KernelAccessControl.cs | 4 + src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs | 7 + .../Loaders/Npdm/ServiceAccessControl.cs | 5 + src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 260 ++++++++++----------- 8 files changed, 163 insertions(+), 134 deletions(-) diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs b/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs index 9a5b6b0a..8d828e8e 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs @@ -15,6 +15,12 @@ namespace Ryujinx.HLE.Loaders.Npdm public ServiceAccessControl ServiceAccessControl { get; private set; } public KernelAccessControl KernelAccessControl { get; private set; } + /// <exception cref="InvalidNpdmException">The stream doesn't contain valid ACI0 data.</exception> + /// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="System.ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> + /// <exception cref="System.NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception> public Aci0(Stream stream, int offset) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs b/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs index ab30b40c..57d0ee27 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs @@ -19,6 +19,11 @@ namespace Ryujinx.HLE.Loaders.Npdm public ServiceAccessControl ServiceAccessControl { get; private set; } public KernelAccessControl KernelAccessControl { get; private set; } + /// <exception cref="InvalidNpdmException">The stream doesn't contain valid ACID data.</exception> + /// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="System.ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> public Acid(Stream stream, int offset) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs index f17ca348..a369f9f2 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs @@ -11,6 +11,10 @@ namespace Ryujinx.HLE.Loaders.Npdm public int Unknown3 { get; private set; } public int Unknown4 { get; private set; } + /// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="System.ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> public FsAccessControl(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs index 5987be0e..249f8dd9 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs @@ -9,6 +9,12 @@ namespace Ryujinx.HLE.Loaders.Npdm public int Version { get; private set; } public ulong PermissionsBitmask { get; private set; } + /// <exception cref="InvalidNpdmException">The stream contains invalid data.</exception> + /// <exception cref="NotImplementedException">The ContentOwnerId section is not implemented.</exception> + /// <exception cref="ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> public FsAccessHeader(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs index 17124379..979c6f66 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs @@ -6,6 +6,10 @@ namespace Ryujinx.HLE.Loaders.Npdm { public int[] Capabilities { get; private set; } + /// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="System.ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> public KernelAccessControl(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs index 622d7ee0..4a99de98 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -24,6 +24,13 @@ namespace Ryujinx.HLE.Loaders.Npdm public Aci0 Aci0 { get; private set; } public Acid Acid { get; private set; } + /// <exception cref="InvalidNpdmException">The stream doesn't contain valid NPDM data.</exception> + /// <exception cref="System.NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception> + /// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="System.ArgumentException">An error occured while reading bytes from the stream.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="System.ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> public Npdm(Stream stream) { BinaryReader reader = new(stream); diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index bb6df27f..b6bc6492 100644 --- a/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs +++ b/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -9,6 +9,11 @@ namespace Ryujinx.HLE.Loaders.Npdm { public IReadOnlyDictionary<string, bool> Services { get; private set; } + /// <exception cref="System.ArgumentException">The stream does not support reading, is <see langword="null"/>, or is already closed.</exception> + /// <exception cref="System.ArgumentException">An error occured while reading bytes from the stream.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="System.ObjectDisposedException">The stream is closed.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> public ServiceAccessControl(Stream stream, int offset, int size) { stream.Seek(offset, SeekOrigin.Begin); diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 2baf0608..e7c48162 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -72,37 +72,43 @@ namespace Ryujinx.UI.App.Common return resourceByteArray; } + /// <exception cref="Ryujinx.HLE.Exceptions.InvalidNpdmException">The npdm file doesn't contain valid data.</exception> + /// <exception cref="NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception> + /// <exception cref="ArgumentException">An error occured while reading bytes from the stream.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> private ApplicationData GetApplicationFromExeFs(PartitionFileSystem pfs, string filePath) { ApplicationData data = new() { Icon = _nspIcon, + Path = filePath, }; using UniqueRef<IFile> npdmFile = new(); - try - { - Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); - - if (ResultFs.PathNotFound.Includes(result)) - { - Npdm npdm = new(npdmFile.Get.AsStream()); + Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); - data.Name = npdm.TitleName; - data.Id = npdm.Aci0.TitleId; - } - - return data; - } - catch (Exception exception) + if (ResultFs.PathNotFound.Includes(result)) { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception.Message}"); + Npdm npdm = new(npdmFile.Get.AsStream()); - return null; + data.Name = npdm.TitleName; + data.Id = npdm.Aci0.TitleId; } + + return data; } + /// <exception cref="MissingKeyException">The configured key set is missing a key.</exception> + /// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception> + /// <exception cref="NotSupportedException">The NCA version is not supported.</exception> + /// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception> + /// <exception cref="Ryujinx.HLE.Exceptions.InvalidNpdmException">The npdm file doesn't contain valid data.</exception> + /// <exception cref="NotImplementedException">The FsAccessHeader.ContentOwnerId section is not implemented.</exception> + /// <exception cref="ArgumentException">An error occured while reading bytes from the stream.</exception> + /// <exception cref="EndOfStreamException">The end of the stream is reached.</exception> + /// <exception cref="IOException">An I/O error occurred.</exception> private ApplicationData GetApplicationFromNsp(PartitionFileSystem pfs, string filePath) { bool isExeFs = false; @@ -170,99 +176,88 @@ namespace Ryujinx.UI.App.Common return null; } + /// <exception cref="MissingKeyException">The configured key set is missing a key.</exception> + /// <exception cref="InvalidDataException">The NCA header could not be decrypted.</exception> + /// <exception cref="NotSupportedException">The NCA version is not supported.</exception> + /// <exception cref="HorizonResultException">An error occured while reading PFS data.</exception> private List<ApplicationData> GetApplicationsFromPfs(IFileSystem pfs, string filePath) { var applications = new List<ApplicationData>(); string extension = Path.GetExtension(filePath).ToLower(); - try + foreach ((ulong titleId, ContentMetaData content) in pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel)) { - foreach ((ulong titleId, ContentMetaData content) in pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel)) + ApplicationData applicationData = new() { - ApplicationData applicationData = new() - { - Id = titleId, - Path = filePath, - }; + Id = titleId, + Path = filePath, + }; - Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program); - Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control); + Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program); + Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control); - BlitStruct<ApplicationControlProperty> controlHolder = new(1); + BlitStruct<ApplicationControlProperty> controlHolder = new(1); - IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, _checkLevel); + IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, _checkLevel); - // Check if there is an update available. - if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs)) - { - // Replace the original ControlFs by the updated one. - controlFs = updatedControlFs; - } + // Check if there is an update available. + if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs)) + { + // Replace the original ControlFs by the updated one. + controlFs = updatedControlFs; + } - if (controlFs == null) - { - continue; - } + if (controlFs == null) + { + continue; + } - ReadControlData(controlFs, controlHolder.ByteSpan); + ReadControlData(controlFs, controlHolder.ByteSpan); - GetApplicationInformation(ref controlHolder.Value, ref applicationData); + GetApplicationInformation(ref controlHolder.Value, ref applicationData); - // Read the icon from the ControlFS and store it as a byte array - try - { - using UniqueRef<IFile> icon = new(); + // Read the icon from the ControlFS and store it as a byte array + try + { + using UniqueRef<IFile> icon = new(); - controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - using MemoryStream stream = new(); + using MemoryStream stream = new(); - icon.Get.AsStream().CopyTo(stream); - applicationData.Icon = stream.ToArray(); - } - catch (HorizonResultException) + icon.Get.AsStream().CopyTo(stream); + applicationData.Icon = stream.ToArray(); + } + catch (HorizonResultException) + { + foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) { - foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) + if (entry.Name == "control.nacp") { - if (entry.Name == "control.nacp") - { - continue; - } + continue; + } - using var icon = new UniqueRef<IFile>(); + using var icon = new UniqueRef<IFile>(); - controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - using MemoryStream stream = new(); + using MemoryStream stream = new(); - icon.Get.AsStream().CopyTo(stream); - applicationData.Icon = stream.ToArray(); + icon.Get.AsStream().CopyTo(stream); + applicationData.Icon = stream.ToArray(); - if (applicationData.Icon != null) - { - break; - } + if (applicationData.Icon != null) + { + break; } - - applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon; } - applicationData.ControlHolder = controlHolder; - - applications.Add(applicationData); + applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon; } - } - catch (MissingKeyException exception) - { - Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); - } - catch (InvalidDataException) - { - Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {filePath}"); - } - catch (Exception exception) - { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception}"); + + applicationData.ControlHolder = controlHolder; + + applications.Add(applicationData); } return applications; @@ -319,53 +314,44 @@ namespace Ryujinx.UI.App.Common BinaryReader reader = new(file); ApplicationData application = new(); - try - { - file.Seek(24, SeekOrigin.Begin); - - int assetOffset = reader.ReadInt32(); - - if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") - { - byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); + file.Seek(24, SeekOrigin.Begin); - long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); - long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); + int assetOffset = reader.ReadInt32(); - ulong nacpOffset = reader.ReadUInt64(); - ulong nacpSize = reader.ReadUInt64(); + if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") + { + byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); - // Reads and stores game icon as byte array - if (iconSize > 0) - { - application.Icon = Read(assetOffset + iconOffset, (int)iconSize); - } - else - { - application.Icon = _nroIcon; - } + long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); + long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); - // Read the NACP data - Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); - GetApplicationInformation(ref controlHolder.Value, ref application); + // Reads and stores game icon as byte array + if (iconSize > 0) + { + application.Icon = Read(assetOffset + iconOffset, (int)iconSize); } else { application.Icon = _nroIcon; - application.Name = Path.GetFileNameWithoutExtension(applicationPath); } - application.ControlHolder = controlHolder; - applications.Add(application); + // Read the NACP data + Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); + + GetApplicationInformation(ref controlHolder.Value, ref application); } - catch + else { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); - - return false; + application.Icon = _nroIcon; + application.Name = Path.GetFileNameWithoutExtension(applicationPath); } + application.ControlHolder = controlHolder; + applications.Add(application); + break; byte[] Read(long position, int size) @@ -377,34 +363,21 @@ namespace Ryujinx.UI.App.Common } case ".nca": { - try - { - ApplicationData application = new(); - - Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); - - if (!nca.IsProgram() || nca.IsPatch()) - { - return false; - } + ApplicationData application = new(); - application.Icon = _ncaIcon; - application.Name = Path.GetFileNameWithoutExtension(applicationPath); - application.ControlHolder = controlHolder; + Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); - applications.Add(application); - } - catch (InvalidDataException) + if (!nca.IsProgram() || nca.IsPatch()) { - Logger.Warning?.Print(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}"); - } - catch - { - Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); - return false; } + application.Icon = _ncaIcon; + application.Name = Path.GetFileNameWithoutExtension(applicationPath); + application.ControlHolder = controlHolder; + + applications.Add(application); + break; } // If its an NSO we just set defaults @@ -417,16 +390,35 @@ namespace Ryujinx.UI.App.Common }; applications.Add(application); + break; } } } + catch (MissingKeyException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); + + return false; + } + catch (InvalidDataException) + { + 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}"); + + return false; + } catch (IOException exception) { Logger.Warning?.Print(LogClass.Application, exception.Message); return false; } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}"); + + return false; + } foreach (var data in applications) { -- cgit v1.2.3-70-g09d2