Fix application list (#891)
* Fix application list * Convert file extensions to lowercase before comparing * AcK's requested changes * fixed bug found by gdkchan's requested changes * Account for mismatch between LibHac.TitleLanguage and ...System.Language
This commit is contained in:
parent
0202f150d4
commit
7b0576db71
6 changed files with 248 additions and 168 deletions
|
@ -617,19 +617,19 @@ namespace Ryujinx.HLE.HOS
|
||||||
metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
metaData.Aci0.TitleId = nacp.PresenceGroupId;
|
if (nacp.PresenceGroupId != 0)
|
||||||
|
{
|
||||||
if (metaData.Aci0.TitleId == 0)
|
metaData.Aci0.TitleId = nacp.PresenceGroupId;
|
||||||
|
}
|
||||||
|
else if (nacp.SaveDataOwnerId.Value != 0)
|
||||||
{
|
{
|
||||||
metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
|
metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
|
||||||
}
|
}
|
||||||
|
else if (nacp.AddOnContentBaseId != 0)
|
||||||
if (metaData.Aci0.TitleId == 0)
|
|
||||||
{
|
{
|
||||||
metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
|
metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (metaData.Aci0.TitleId.ToString("x16") == "fffffffffffff000")
|
|
||||||
{
|
{
|
||||||
metaData.Aci0.TitleId = 0000000000000000;
|
metaData.Aci0.TitleId = 0000000000000000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,6 @@ namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
public class ApplicationAddedEventArgs : EventArgs
|
public class ApplicationAddedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public ApplicationData AppData { get; set; }
|
public ApplicationData AppData { get; set; }
|
||||||
public int NumAppsFound { get; set; }
|
|
||||||
public int NumAppsLoaded { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
10
Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs
Normal file
10
Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui
|
||||||
|
{
|
||||||
|
public class ApplicationCountUpdatedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public int NumAppsFound { get; set; }
|
||||||
|
public int NumAppsLoaded { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,8 @@ namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
public class ApplicationLibrary
|
public class ApplicationLibrary
|
||||||
{
|
{
|
||||||
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||||
|
public static event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||||
|
|
||||||
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
|
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
|
||||||
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
|
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
|
||||||
|
@ -36,12 +37,14 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
private static VirtualFileSystem _virtualFileSystem;
|
private static VirtualFileSystem _virtualFileSystem;
|
||||||
private static Language _desiredTitleLanguage;
|
private static Language _desiredTitleLanguage;
|
||||||
|
private static bool _loadingError;
|
||||||
|
|
||||||
public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
|
public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
|
||||||
{
|
{
|
||||||
int numApplicationsFound = 0;
|
int numApplicationsFound = 0;
|
||||||
int numApplicationsLoaded = 0;
|
int numApplicationsLoaded = 0;
|
||||||
|
|
||||||
|
_loadingError = false;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_desiredTitleLanguage = desiredTitleLanguage;
|
_desiredTitleLanguage = desiredTitleLanguage;
|
||||||
|
|
||||||
|
@ -49,7 +52,7 @@ namespace Ryujinx.Ui
|
||||||
List<string> applications = new List<string>();
|
List<string> applications = new List<string>();
|
||||||
foreach (string appDir in appDirs)
|
foreach (string appDir in appDirs)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(appDir) == false)
|
if (!Directory.Exists(appDir))
|
||||||
{
|
{
|
||||||
Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
|
Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
|
||||||
|
|
||||||
|
@ -58,67 +61,16 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories))
|
foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
if ((Path.GetExtension(app) == ".xci") ||
|
if ((Path.GetExtension(app).ToLower() == ".nsp") ||
|
||||||
(Path.GetExtension(app) == ".nro") ||
|
(Path.GetExtension(app).ToLower() == ".pfs0")||
|
||||||
(Path.GetExtension(app) == ".nso") ||
|
(Path.GetExtension(app).ToLower() == ".xci") ||
|
||||||
(Path.GetFileName(app) == "hbl.nsp"))
|
(Path.GetExtension(app).ToLower() == ".nca") ||
|
||||||
|
(Path.GetExtension(app).ToLower() == ".nro") ||
|
||||||
|
(Path.GetExtension(app).ToLower() == ".nso"))
|
||||||
{
|
{
|
||||||
applications.Add(app);
|
applications.Add(app);
|
||||||
numApplicationsFound++;
|
numApplicationsFound++;
|
||||||
}
|
}
|
||||||
else if ((Path.GetExtension(app) == ".nsp") || (Path.GetExtension(app) == ".pfs0"))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool hasMainNca = false;
|
|
||||||
|
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
|
||||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
|
||||||
{
|
|
||||||
hasMainNca = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMainNca)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InvalidDataException)
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
applications.Add(app);
|
|
||||||
numApplicationsFound++;
|
|
||||||
}
|
|
||||||
else if (Path.GetExtension(app) == ".nca")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InvalidDataException)
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
applications.Add(app);
|
|
||||||
numApplicationsFound++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,15 +87,16 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
||||||
{
|
{
|
||||||
if ((Path.GetExtension(applicationPath) == ".nsp") ||
|
if ((Path.GetExtension(applicationPath).ToLower() == ".nsp") ||
|
||||||
(Path.GetExtension(applicationPath) == ".pfs0") ||
|
(Path.GetExtension(applicationPath).ToLower() == ".pfs0") ||
|
||||||
(Path.GetExtension(applicationPath) == ".xci"))
|
(Path.GetExtension(applicationPath).ToLower() == ".xci"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PartitionFileSystem pfs;
|
PartitionFileSystem pfs;
|
||||||
|
bool isExeFs = false;
|
||||||
if (Path.GetExtension(applicationPath) == ".xci")
|
|
||||||
|
if (Path.GetExtension(applicationPath).ToLower() == ".xci")
|
||||||
{
|
{
|
||||||
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
|
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
|
|
||||||
|
@ -152,13 +105,41 @@ namespace Ryujinx.Ui
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
pfs = new PartitionFileSystem(file.AsStorage());
|
||||||
|
|
||||||
|
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
|
||||||
|
bool hasMainNca = false;
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
|
||||||
|
{
|
||||||
|
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
|
{
|
||||||
|
hasMainNca = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
||||||
|
{
|
||||||
|
isExeFs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMainNca && !isExeFs)
|
||||||
|
{
|
||||||
|
numApplicationsFound--;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the ControlFS in variable called controlFs
|
if (isExeFs)
|
||||||
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 = _nspIcon;
|
applicationIcon = _nspIcon;
|
||||||
|
|
||||||
|
@ -174,6 +155,9 @@ namespace Ryujinx.Ui
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Store the ControlFS in variable called controlFs
|
||||||
|
IFileSystem controlFs = GetControlFs(pfs);
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
||||||
|
@ -182,31 +166,7 @@ namespace Ryujinx.Ui
|
||||||
// Get the title name, title ID, developer name and version number from the NACP
|
// Get the title name, title ID, developer name and version number from the NACP
|
||||||
version = controlData.DisplayVersion;
|
version = controlData.DisplayVersion;
|
||||||
|
|
||||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
GetNameIdDeveloper(controlData, out titleName, out titleId, out developer);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleName))
|
|
||||||
{
|
|
||||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleId))
|
|
||||||
{
|
|
||||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleId))
|
|
||||||
{
|
|
||||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
|
||||||
}
|
|
||||||
|
|
||||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(developer))
|
|
||||||
{
|
|
||||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the icon from the ControlFS and store it as a byte array
|
// Read the icon from the ControlFS and store it as a byte array
|
||||||
try
|
try
|
||||||
|
@ -244,25 +204,35 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
if (applicationIcon == null)
|
if (applicationIcon == null)
|
||||||
{
|
{
|
||||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (MissingKeyException exception)
|
catch (MissingKeyException exception)
|
||||||
{
|
{
|
||||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon;
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon;
|
||||||
|
|
||||||
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}");
|
||||||
}
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||||
|
Logger.PrintDebug(LogClass.Application, exception.ToString());
|
||||||
|
|
||||||
|
numApplicationsFound--;
|
||||||
|
_loadingError = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (Path.GetExtension(applicationPath) == ".nro")
|
else if (Path.GetExtension(applicationPath).ToLower() == ".nro")
|
||||||
{
|
{
|
||||||
BinaryReader reader = new BinaryReader(file);
|
BinaryReader reader = new BinaryReader(file);
|
||||||
|
|
||||||
|
@ -273,67 +243,87 @@ namespace Ryujinx.Ui
|
||||||
return reader.ReadBytes(size);
|
return reader.ReadBytes(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Seek(24, SeekOrigin.Begin);
|
try
|
||||||
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);
|
int assetOffset = reader.ReadInt32();
|
||||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
|
||||||
|
|
||||||
ulong nacpOffset = reader.ReadUInt64();
|
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||||
ulong nacpSize = reader.ReadUInt64();
|
|
||||||
|
|
||||||
// Reads and stores game icon as byte array
|
|
||||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
|
||||||
|
|
||||||
// Creates memory stream out of byte array which is the NACP
|
|
||||||
using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int)nacpOffset, (int)nacpSize)))
|
|
||||||
{
|
{
|
||||||
// Creates NACP class from the memory stream
|
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||||
Nacp controlData = new Nacp(stream);
|
|
||||||
|
|
||||||
// Get the title name, title ID, developer name and version number from the NACP
|
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||||
version = controlData.DisplayVersion;
|
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||||
|
|
||||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
ulong nacpOffset = reader.ReadUInt64();
|
||||||
|
ulong nacpSize = reader.ReadUInt64();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleName))
|
// Reads and stores game icon as byte array
|
||||||
|
applicationIcon = Read(assetOffset + iconOffset, (int) iconSize);
|
||||||
|
|
||||||
|
// Creates memory stream out of byte array which is the NACP
|
||||||
|
using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int) nacpOffset, (int) nacpSize)))
|
||||||
{
|
{
|
||||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
// Creates NACP class from the memory stream
|
||||||
}
|
Nacp controlData = new Nacp(stream);
|
||||||
|
|
||||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
// Get the title name, title ID, developer name and version number from the NACP
|
||||||
|
version = controlData.DisplayVersion;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleId))
|
GetNameIdDeveloper(controlData, out titleName, out titleId, out developer);
|
||||||
{
|
|
||||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleId))
|
|
||||||
{
|
|
||||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
|
||||||
}
|
|
||||||
|
|
||||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(developer))
|
|
||||||
{
|
|
||||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationIcon = _nroIcon;
|
||||||
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
catch
|
||||||
{
|
{
|
||||||
applicationIcon = _nroIcon;
|
Logger.PrintError(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||||
|
|
||||||
|
numApplicationsFound--;
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If its an NCA or NSO we just set defaults
|
else if (Path.GetExtension(applicationPath).ToLower() == ".nca")
|
||||||
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
|
||||||
{
|
{
|
||||||
applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ncaIcon : _nsoIcon;
|
try
|
||||||
|
{
|
||||||
|
Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
|
{
|
||||||
|
numApplicationsFound--;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidDataException)
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(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.PrintError(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||||
|
|
||||||
|
numApplicationsFound--;
|
||||||
|
_loadingError = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationIcon = _ncaIcon;
|
||||||
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
|
}
|
||||||
|
// If its an NSO we just set defaults
|
||||||
|
else if (Path.GetExtension(applicationPath).ToLower() == ".nso")
|
||||||
|
{
|
||||||
|
applicationIcon = _nsoIcon;
|
||||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,12 +363,30 @@ namespace Ryujinx.Ui
|
||||||
numApplicationsLoaded++;
|
numApplicationsLoaded++;
|
||||||
|
|
||||||
OnApplicationAdded(new ApplicationAddedEventArgs()
|
OnApplicationAdded(new ApplicationAddedEventArgs()
|
||||||
{
|
{
|
||||||
AppData = data,
|
AppData = data
|
||||||
|
});
|
||||||
|
|
||||||
|
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs()
|
||||||
|
{
|
||||||
NumAppsFound = numApplicationsFound,
|
NumAppsFound = numApplicationsFound,
|
||||||
NumAppsLoaded = numApplicationsLoaded
|
NumAppsLoaded = numApplicationsLoaded
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs()
|
||||||
|
{
|
||||||
|
NumAppsFound = numApplicationsFound,
|
||||||
|
NumAppsLoaded = numApplicationsLoaded
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_loadingError)
|
||||||
|
{
|
||||||
|
Gtk.Application.Invoke(delegate
|
||||||
|
{
|
||||||
|
GtkDialog.CreateErrorDialog("One or more files encountered were not of a valid type, check logs for more info.");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
|
protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
|
||||||
|
@ -386,6 +394,11 @@ namespace Ryujinx.Ui
|
||||||
ApplicationAdded?.Invoke(null, e);
|
ApplicationAdded?.Invoke(null, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
|
||||||
|
{
|
||||||
|
ApplicationCountUpdated?.Invoke(null, e);
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] GetResourceBytes(string resourceName)
|
private static byte[] GetResourceBytes(string resourceName)
|
||||||
{
|
{
|
||||||
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
||||||
|
@ -433,7 +446,7 @@ namespace Ryujinx.Ui
|
||||||
internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
|
internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
|
||||||
{
|
{
|
||||||
string metadataFolder = Path.Combine(_virtualFileSystem.GetBasePath(), "games", titleId, "gui");
|
string metadataFolder = Path.Combine(_virtualFileSystem.GetBasePath(), "games", titleId, "gui");
|
||||||
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
||||||
|
|
||||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||||
|
|
||||||
|
@ -497,5 +510,50 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
return readableString;
|
return readableString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void GetNameIdDeveloper(Nacp controlData, out string titleName, out string titleId, out string developer)
|
||||||
|
{
|
||||||
|
Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||||
|
|
||||||
|
NacpDescription nacpDescription = controlData.Descriptions.ToList().Find(x => x.Language == desiredTitleLanguage);
|
||||||
|
|
||||||
|
if (nacpDescription != null)
|
||||||
|
{
|
||||||
|
titleName = nacpDescription.Title;
|
||||||
|
developer = nacpDescription.Developer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
titleName = null;
|
||||||
|
developer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(titleName))
|
||||||
|
{
|
||||||
|
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(developer))
|
||||||
|
{
|
||||||
|
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlData.PresenceGroupId != 0)
|
||||||
|
{
|
||||||
|
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||||
|
}
|
||||||
|
else if (controlData.SaveDataOwnerId != 0)
|
||||||
|
{
|
||||||
|
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||||
|
}
|
||||||
|
else if (controlData.AddOnContentBaseId != 0)
|
||||||
|
{
|
||||||
|
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
titleId = "0000000000000000";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,8 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
DeleteEvent += Window_Close;
|
DeleteEvent += Window_Close;
|
||||||
|
|
||||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||||
|
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||||
|
|
||||||
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
||||||
|
|
||||||
|
@ -135,9 +136,7 @@ namespace Ryujinx.Ui
|
||||||
_tableStore.SetSortColumnId(0, SortType.Descending);
|
_tableStore.SetSortColumnId(0, SortType.Descending);
|
||||||
|
|
||||||
UpdateColumns();
|
UpdateColumns();
|
||||||
#pragma warning disable CS4014
|
|
||||||
UpdateGameTable();
|
UpdateGameTable();
|
||||||
#pragma warning restore CS4014
|
|
||||||
|
|
||||||
Task.Run(RefreshFirmwareLabel);
|
Task.Run(RefreshFirmwareLabel);
|
||||||
}
|
}
|
||||||
|
@ -209,7 +208,7 @@ namespace Ryujinx.Ui
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task UpdateGameTable()
|
internal static void UpdateGameTable()
|
||||||
{
|
{
|
||||||
if (_updatingGameTable)
|
if (_updatingGameTable)
|
||||||
{
|
{
|
||||||
|
@ -220,10 +219,16 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
_tableStore.Clear();
|
_tableStore.Clear();
|
||||||
|
|
||||||
await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
|
Thread applicationLibraryThread = new Thread(() =>
|
||||||
_virtualFileSystem, ConfigurationState.Instance.System.Language));
|
{
|
||||||
|
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
|
||||||
|
_virtualFileSystem, ConfigurationState.Instance.System.Language);
|
||||||
|
|
||||||
_updatingGameTable = false;
|
_updatingGameTable = false;
|
||||||
|
});
|
||||||
|
applicationLibraryThread.Name = "GUI.ApplicationLibraryThread";
|
||||||
|
applicationLibraryThread.IsBackground = true;
|
||||||
|
applicationLibraryThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void LoadApplication(string path)
|
internal void LoadApplication(string path)
|
||||||
|
@ -423,9 +428,22 @@ namespace Ryujinx.Ui
|
||||||
args.AppData.FileExtension,
|
args.AppData.FileExtension,
|
||||||
args.AppData.FileSize,
|
args.AppData.FileSize,
|
||||||
args.AppData.Path);
|
args.AppData.Path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args)
|
||||||
|
{
|
||||||
|
Application.Invoke(delegate
|
||||||
|
{
|
||||||
_progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded";
|
_progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded";
|
||||||
_progressBar.Value = (float)args.NumAppsLoaded / args.NumAppsFound;
|
float barValue = 0;
|
||||||
|
|
||||||
|
if (args.NumAppsFound != 0)
|
||||||
|
{
|
||||||
|
barValue = (float)args.NumAppsLoaded / args.NumAppsFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
_progressBar.Value = barValue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -838,9 +856,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
|
private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
|
||||||
{
|
{
|
||||||
#pragma warning disable CS4014
|
|
||||||
UpdateGameTable();
|
UpdateGameTable();
|
||||||
#pragma warning restore CS4014
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||||
|
|
|
@ -438,9 +438,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
MainWindow.SaveConfig();
|
MainWindow.SaveConfig();
|
||||||
MainWindow.ApplyTheme();
|
MainWindow.ApplyTheme();
|
||||||
#pragma warning disable CS4014
|
|
||||||
MainWindow.UpdateGameTable();
|
MainWindow.UpdateGameTable();
|
||||||
#pragma warning restore CS4014
|
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue