Fix issue where an ExeFS as a NSP didn't show up in the application list
This commit is contained in:
parent
9e2fc45cb6
commit
3b45fcdcc6
7 changed files with 104 additions and 122 deletions
|
@ -3,7 +3,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class Aci0
|
public class Aci0
|
||||||
{
|
{
|
||||||
private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
|
private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class Acid
|
public class Acid
|
||||||
{
|
{
|
||||||
private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
|
private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class FsAccessControl
|
public class FsAccessControl
|
||||||
{
|
{
|
||||||
public int Version { get; private set; }
|
public int Version { get; private set; }
|
||||||
public ulong PermissionsBitmask { get; private set; }
|
public ulong PermissionsBitmask { get; private set; }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class KernelAccessControl
|
public class KernelAccessControl
|
||||||
{
|
{
|
||||||
public int[] Capabilities { get; private set; }
|
public int[] Capabilities { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
// https://github.com/SciresM/hactool/blob/master/npdm.c
|
// https://github.com/SciresM/hactool/blob/master/npdm.c
|
||||||
// https://github.com/SciresM/hactool/blob/master/npdm.h
|
// https://github.com/SciresM/hactool/blob/master/npdm.h
|
||||||
// http://switchbrew.org/index.php?title=NPDM
|
// http://switchbrew.org/index.php?title=NPDM
|
||||||
class Npdm
|
public class Npdm
|
||||||
{
|
{
|
||||||
private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
|
private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class ServiceAccessControl
|
public class ServiceAccessControl
|
||||||
{
|
{
|
||||||
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using LibHac.FsSystem.NcaUtils;
|
||||||
using LibHac.Spl;
|
using LibHac.Spl;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Npdm;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -80,7 +81,8 @@ namespace Ryujinx.UI
|
||||||
{
|
{
|
||||||
if ((Path.GetExtension(app) == ".xci") ||
|
if ((Path.GetExtension(app) == ".xci") ||
|
||||||
(Path.GetExtension(app) == ".nro") ||
|
(Path.GetExtension(app) == ".nro") ||
|
||||||
(Path.GetExtension(app) == ".nso"))
|
(Path.GetExtension(app) == ".nso") ||
|
||||||
|
(Path.GetFileName(app) == "hbl.nsp"))
|
||||||
{
|
{
|
||||||
applications.Add(app);
|
applications.Add(app);
|
||||||
numApplicationsFound++;
|
numApplicationsFound++;
|
||||||
|
@ -141,10 +143,10 @@ namespace Ryujinx.UI
|
||||||
foreach (string applicationPath in applications)
|
foreach (string applicationPath in applications)
|
||||||
{
|
{
|
||||||
double fileSize = new FileInfo(applicationPath).Length * 0.000000000931;
|
double fileSize = new FileInfo(applicationPath).Length * 0.000000000931;
|
||||||
string titleName = null;
|
string titleName = "Unknown";
|
||||||
string titleId = null;
|
string titleId = "0000000000000000";
|
||||||
string developer = null;
|
string developer = "Unknown";
|
||||||
string version = null;
|
string version = "?";
|
||||||
byte[] applicationIcon = null;
|
byte[] applicationIcon = null;
|
||||||
|
|
||||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
||||||
|
@ -155,20 +157,39 @@ namespace Ryujinx.UI
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IFileSystem controlFs;
|
PartitionFileSystem pfs;
|
||||||
|
|
||||||
// Store the ControlFS in variable called controlFs
|
|
||||||
if (Path.GetExtension(applicationPath) == ".xci")
|
if (Path.GetExtension(applicationPath) == ".xci")
|
||||||
{
|
{
|
||||||
Xci xci = new Xci(_keySet, file.AsStorage());
|
Xci xci = new Xci(_keySet, file.AsStorage());
|
||||||
|
|
||||||
controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
|
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage()));
|
pfs = new PartitionFileSystem(file.AsStorage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the ControlFS in variable called controlFs
|
||||||
|
IFileSystem controlFs = GetControlFs(pfs);
|
||||||
|
|
||||||
|
// If this is null then this is probably not a normal NSP, it's probably an ExeFS as an NSP
|
||||||
|
if (controlFs == null)
|
||||||
|
{
|
||||||
|
applicationIcon = _ryujinxNspIcon;
|
||||||
|
|
||||||
|
Result result = pfs.OpenFile(out IFile npdmFile, "/main.npdm", OpenMode.Read);
|
||||||
|
|
||||||
|
if (result != ResultFs.PathNotFound)
|
||||||
|
{
|
||||||
|
Npdm npdm = new Npdm(npdmFile.AsStream());
|
||||||
|
|
||||||
|
titleName = npdm.TitleName;
|
||||||
|
titleId = npdm.Aci0.TitleId.ToString("x16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Creates NACP class from the NACP file
|
// Creates NACP class from the NACP file
|
||||||
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
@ -208,10 +229,12 @@ namespace Ryujinx.UI
|
||||||
{
|
{
|
||||||
controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
|
controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
using MemoryStream stream = new MemoryStream();
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
icon.AsStream().CopyTo(stream);
|
icon.AsStream().CopyTo(stream);
|
||||||
applicationIcon = stream.ToArray();
|
applicationIcon = stream.ToArray();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (HorizonResultException)
|
catch (HorizonResultException)
|
||||||
{
|
{
|
||||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||||
|
@ -237,27 +260,20 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
if (applicationIcon == null)
|
if (applicationIcon == null)
|
||||||
{
|
{
|
||||||
applicationIcon = NspOrXciIcon(applicationPath);
|
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _ryujinxXciIcon : _ryujinxNspIcon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (MissingKeyException exception)
|
catch (MissingKeyException exception)
|
||||||
{
|
{
|
||||||
titleName = "Unknown";
|
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _ryujinxXciIcon : _ryujinxNspIcon;
|
||||||
titleId = "Unknown";
|
|
||||||
developer = "Unknown";
|
|
||||||
version = "?";
|
|
||||||
applicationIcon = NspOrXciIcon(applicationPath);
|
|
||||||
|
|
||||||
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||||
}
|
}
|
||||||
catch (InvalidDataException)
|
catch (InvalidDataException)
|
||||||
{
|
{
|
||||||
titleName = "Unknown";
|
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _ryujinxXciIcon : _ryujinxNspIcon;
|
||||||
titleId = "Unknown";
|
|
||||||
developer = "Unknown";
|
|
||||||
version = "?";
|
|
||||||
applicationIcon = NspOrXciIcon(applicationPath);
|
|
||||||
|
|
||||||
Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
||||||
}
|
}
|
||||||
|
@ -326,50 +342,28 @@ namespace Ryujinx.UI
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
applicationIcon = _ryujinxNroIcon;
|
applicationIcon = _ryujinxNroIcon;
|
||||||
titleName = "Application";
|
|
||||||
titleId = "0000000000000000";
|
|
||||||
developer = "Unknown";
|
|
||||||
version = "?";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If its an NCA or NSO we just set defaults
|
// If its an NCA or NSO we just set defaults
|
||||||
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(applicationPath) == ".nca")
|
applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ryujinxNcaIcon : _ryujinxNsoIcon;
|
||||||
{
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
applicationIcon = _ryujinxNcaIcon;
|
|
||||||
}
|
|
||||||
else if (Path.GetExtension(applicationPath) == ".nso")
|
|
||||||
{
|
|
||||||
applicationIcon = _ryujinxNsoIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
string fileName = Path.GetFileName(applicationPath);
|
|
||||||
string fileExt = Path.GetExtension(applicationPath);
|
|
||||||
|
|
||||||
StringBuilder titlename = new StringBuilder();
|
|
||||||
titlename.Append(fileName);
|
|
||||||
titlename.Remove(fileName.Length - fileExt.Length, fileExt.Length);
|
|
||||||
|
|
||||||
titleName = titlename.ToString();
|
|
||||||
titleId = "0000000000000000";
|
|
||||||
version = "?";
|
|
||||||
developer = "Unknown";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(bool fav, string timePlayed, string lastPlayed) metaData = GetMetadata(titleId);
|
(bool fav, string timePlayed, string lastPlayed) = GetMetadata(titleId);
|
||||||
|
|
||||||
ApplicationData data = new ApplicationData()
|
ApplicationData data = new ApplicationData()
|
||||||
{
|
{
|
||||||
Favorite = metaData.fav,
|
Favorite = fav,
|
||||||
Icon = applicationIcon,
|
Icon = applicationIcon,
|
||||||
TitleName = titleName,
|
TitleName = titleName,
|
||||||
TitleId = titleId,
|
TitleId = titleId,
|
||||||
Developer = developer,
|
Developer = developer,
|
||||||
Version = version,
|
Version = version,
|
||||||
TimePlayed = metaData.timePlayed,
|
TimePlayed = timePlayed,
|
||||||
LastPlayed = metaData.lastPlayed,
|
LastPlayed = lastPlayed,
|
||||||
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
|
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
|
||||||
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB",
|
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB",
|
||||||
Path = applicationPath,
|
Path = applicationPath,
|
||||||
|
@ -432,7 +426,7 @@ namespace Ryujinx.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the ControlFS
|
// Return the ControlFS
|
||||||
return controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
return controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (bool fav, string timePlayed, string lastPlayed) GetMetadata(string titleId)
|
private static (bool fav, string timePlayed, string lastPlayed) GetMetadata(string titleId)
|
||||||
|
@ -465,18 +459,6 @@ namespace Ryujinx.UI
|
||||||
return (_appMetadata.Favorite, ConvertSecondsToReadableString(_appMetadata.TimePlayed), _appMetadata.LastPlayed);
|
return (_appMetadata.Favorite, ConvertSecondsToReadableString(_appMetadata.TimePlayed), _appMetadata.LastPlayed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] NspOrXciIcon(string applicationPath)
|
|
||||||
{
|
|
||||||
if (Path.GetExtension(applicationPath) == ".xci")
|
|
||||||
{
|
|
||||||
return _ryujinxXciIcon;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return _ryujinxNspIcon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertSecondsToReadableString(double seconds)
|
private static string ConvertSecondsToReadableString(double seconds)
|
||||||
{
|
{
|
||||||
const int secondsPerMinute = 60;
|
const int secondsPerMinute = 60;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue