Large changes

Rewrote ApplicationLibrary.cs (added comments too) so any devs reading it wont get eye cancer, also its probably more efficient now. Added 2 new columns (Developer name and application version) to the game list and wrote the logic for it. Ryujinx now loads NRO's TitleName and TitleID from the NACP file instead of the default NPDM. I also killed a lot of bugs
This commit is contained in:
Xpl0itR 2019-07-07 18:13:28 +01:00
commit bf82c5f5d5
No known key found for this signature in database
GPG key ID: 91798184109676AD
17 changed files with 269 additions and 193 deletions

View file

@ -10,7 +10,7 @@ namespace Ryujinx.HLE.FileSystem
public static string GetSavePath(SaveInfo saveMetaData, ServiceCtx context) public static string GetSavePath(SaveInfo saveMetaData, ServiceCtx context)
{ {
string baseSavePath = NandPath; string baseSavePath = NandPath;
long currentTitleId = saveMetaData.TitleId; ulong currentTitleId = saveMetaData.TitleId;
switch (saveMetaData.SaveSpaceId) switch (saveMetaData.SaveSpaceId)
{ {

View file

@ -4,7 +4,7 @@ namespace Ryujinx.HLE.FileSystem
{ {
struct SaveInfo struct SaveInfo
{ {
public long TitleId { get; private set; } public ulong TitleId { get; private set; }
public long SaveId { get; private set; } public long SaveId { get; private set; }
public UInt128 UserId { get; private set; } public UInt128 UserId { get; private set; }
@ -12,7 +12,7 @@ namespace Ryujinx.HLE.FileSystem
public SaveSpaceId SaveSpaceId { get; private set; } public SaveSpaceId SaveSpaceId { get; private set; }
public SaveInfo( public SaveInfo(
long titleId, ulong titleId,
long saveId, long saveId,
SaveDataType saveDataType, SaveDataType saveDataType,
UInt128 userId, UInt128 userId,

View file

@ -561,6 +561,23 @@ namespace Ryujinx.HLE.HOS
{ {
Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset)); Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
} }
if (nacpSize != 0)
{
input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
using (MemoryStream stream = new MemoryStream(reader.ReadBytes((int)nacpSize))) { ControlData = new Nacp(stream); }
metaData.TitleName = ControlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
if (string.IsNullOrWhiteSpace(metaData.TitleName))
{
metaData.TitleName = ControlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
}
metaData.Aci0.TitleId = ControlData.PresenceGroupId;
if (metaData.Aci0.TitleId == 0) { metaData.Aci0.TitleId = ControlData.SaveDataOwnerId; }
if (metaData.Aci0.TitleId == 0) { metaData.Aci0.TitleId = ControlData.AddOnContentBaseId - 0x1000; }
if (metaData.Aci0.TitleId.ToString("x16") == "fffffffffffff000") { metaData.Aci0.TitleId = 0000000000000000; }
}
} }
else else
{ {
@ -574,10 +591,10 @@ namespace Ryujinx.HLE.HOS
staticObject = new NxStaticObject(input); staticObject = new NxStaticObject(input);
} }
ContentManager.LoadEntries(); TitleName = CurrentTitle = metaData.TitleName;
TitleID = metaData.Aci0.TitleId.ToString("x16");
TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16"); ContentManager.LoadEntries();
TitleName = metaData.TitleName;
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject }); ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
} }

View file

@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public KProcessCapabilities Capabilities { get; private set; } public KProcessCapabilities Capabilities { get; private set; }
public long TitleId { get; private set; } public ulong TitleId { get; private set; }
public long Pid { get; private set; } public long Pid { get; private set; }
private long _creationTimestamp; private long _creationTimestamp;

View file

@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public string Name { get; private set; } public string Name { get; private set; }
public int Category { get; private set; } public int Category { get; private set; }
public long TitleId { get; private set; } public ulong TitleId { get; private set; }
public ulong CodeAddress { get; private set; } public ulong CodeAddress { get; private set; }
public int CodePagesCount { get; private set; } public int CodePagesCount { get; private set; }
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public ProcessCreationInfo( public ProcessCreationInfo(
string name, string name,
int category, int category,
long titleId, ulong titleId,
ulong codeAddress, ulong codeAddress,
int codePagesCount, int codePagesCount,
int mmuFlags, int mmuFlags,

View file

@ -285,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
break; break;
case 18: value = process.TitleId; break; case 18: value = (long)process.TitleId; break;
case 20: value = (long)process.UserExceptionContextAddress; break; case 20: value = (long)process.UserExceptionContextAddress; break;

View file

@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
{ {
SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64(); SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64();
long titleId = context.RequestData.ReadInt64(); ulong titleId = context.RequestData.ReadUInt64();
UInt128 userId = context.RequestData.ReadStruct<UInt128>(); UInt128 userId = context.RequestData.ReadStruct<UInt128>();

View file

@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Executables
{ {
public string Name { get; private set; } public string Name { get; private set; }
public long TitleId { get; private set; } public ulong TitleId { get; private set; }
public int ProcessCategory { get; private set; } public int ProcessCategory { get; private set; }
@ -65,7 +65,7 @@ namespace Ryujinx.HLE.Loaders.Executables
Name = ReadString(reader, 12); Name = ReadString(reader, 12);
TitleId = reader.ReadInt64(); TitleId = reader.ReadUInt64();
ProcessCategory = reader.ReadInt32(); ProcessCategory = reader.ReadInt32();

View file

@ -3,11 +3,11 @@ using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm namespace Ryujinx.HLE.Loaders.Npdm
{ {
public class Aci0 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;
public long TitleId { get; private set; } public ulong TitleId { get; set; }
public int FsVersion { get; private set; } public int FsVersion { get; private set; }
public ulong FsPermissionsBitmask { get; private set; } public ulong FsPermissionsBitmask { get; private set; }
@ -28,7 +28,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
stream.Seek(0xc, SeekOrigin.Current); stream.Seek(0xc, SeekOrigin.Current);
TitleId = reader.ReadInt64(); TitleId = reader.ReadUInt64();
// Reserved. // Reserved.
stream.Seek(8, SeekOrigin.Current); stream.Seek(8, SeekOrigin.Current);

View file

@ -3,7 +3,7 @@ using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm namespace Ryujinx.HLE.Loaders.Npdm
{ {
public class Acid 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;

View file

@ -2,7 +2,7 @@
namespace Ryujinx.HLE.Loaders.Npdm namespace Ryujinx.HLE.Loaders.Npdm
{ {
public class FsAccessControl 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; }

View file

@ -4,7 +4,7 @@ using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm namespace Ryujinx.HLE.Loaders.Npdm
{ {
public class FsAccessHeader class FsAccessHeader
{ {
public int Version { get; private set; } public int Version { get; private set; }
public ulong PermissionsBitmask { get; private set; } public ulong PermissionsBitmask { get; private set; }

View file

@ -2,7 +2,7 @@
namespace Ryujinx.HLE.Loaders.Npdm namespace Ryujinx.HLE.Loaders.Npdm
{ {
public class KernelAccessControl class KernelAccessControl
{ {
public int[] Capabilities { get; private set; } public int[] Capabilities { get; private set; }

View file

@ -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
public class Npdm 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;
@ -18,7 +18,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
public int PersonalMmHeapSize { get; private set; } public int PersonalMmHeapSize { get; private set; }
public int ProcessCategory { get; private set; } public int ProcessCategory { get; private set; }
public int MainThreadStackSize { get; private set; } public int MainThreadStackSize { get; private set; }
public string TitleName { get; private set; } public string TitleName { get; set; }
public byte[] ProductCode { get; private set; } public byte[] ProductCode { get; private set; }
public Aci0 Aci0 { get; private set; } public Aci0 Aci0 { get; private set; }

View file

@ -5,7 +5,7 @@ using System.Text;
namespace Ryujinx.HLE.Loaders.Npdm namespace Ryujinx.HLE.Loaders.Npdm
{ {
public class ServiceAccessControl class ServiceAccessControl
{ {
public IReadOnlyDictionary<string, bool> Services { get; private set; } public IReadOnlyDictionary<string, bool> Services { get; private set; }

View file

@ -2,7 +2,6 @@
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.NcaUtils; using LibHac.Fs.NcaUtils;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.Loaders.Npdm;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -14,45 +13,68 @@ namespace Ryujinx
{ {
public class ApplicationLibrary public class ApplicationLibrary
{ {
public static Gdk.Pixbuf RyujinxNspIcon { get; private set; } private static Keyset KeySet;
public static Gdk.Pixbuf RyujinxXciIcon { get; private set; }
public static Gdk.Pixbuf RyujinxNcaIcon { get; private set; } public static byte[] RyujinxNspIcon { get; private set; }
public static Gdk.Pixbuf RyujinxNroIcon { get; private set; } public static byte[] RyujinxXciIcon { get; private set; }
public static Gdk.Pixbuf RyujinxNsoIcon { get; private set; } public static byte[] RyujinxNcaIcon { get; private set; }
public static byte[] RyujinxNroIcon { get; private set; }
public static byte[] RyujinxNsoIcon { get; private set; }
public static List<ApplicationData> ApplicationLibraryData { get; private set; } public static List<ApplicationData> ApplicationLibraryData { get; private set; }
public struct ApplicationData public struct ApplicationData
{ {
public Gdk.Pixbuf Icon; public byte[] Icon;
public string GameName; public string TitleName;
public string GameId; public string TitleId;
public string Developer;
public string Version;
public string TimePlayed; public string TimePlayed;
public string LastPlayed; public string LastPlayed;
public string FileSize; public string FileSize;
public string Path; public string Path;
} }
public static void Init() public static void Init(Keyset keySet, HLE.HOS.SystemState.TitleLanguage DesiredTitleLanguage)
{ {
RyujinxNspIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNSPIcon.png", 75, 75); // Load keyset
RyujinxXciIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxXCIIcon.png", 75, 75); KeySet = keySet;
RyujinxNcaIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNCAIcon.png", 75, 75);
RyujinxNroIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNROIcon.png", 75, 75);
RyujinxNsoIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNSOIcon.png", 75, 75);
List<string> Games = new List<string>(); // Loads the default application Icons
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNSPIcon.png"))
{
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNspIcon = ms.ToArray(); }
}
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxXCIIcon.png"))
{
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxXciIcon = ms.ToArray(); }
}
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNCAIcon.png"))
{
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNcaIcon = ms.ToArray(); }
}
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNROIcon.png"))
{
using(MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNroIcon = ms.ToArray(); }
}
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNSOIcon.png"))
{
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNsoIcon = ms.ToArray(); }
}
foreach (string GameDir in SwitchSettings.SwitchConfig.GameDirs) // Builds the applications list with paths to found applications
List<string> applications = new List<string>();
foreach (string gameDir in SwitchSettings.SwitchConfig.GameDirs)
{ {
if (Directory.Exists(GameDir) == false) if (Directory.Exists(gameDir) == false)
{ {
Logger.PrintError(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{GameDir}\""); Logger.PrintError(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{gameDir}\"");
continue; continue;
} }
DirectoryInfo GameDirInfo = new DirectoryInfo(GameDir); DirectoryInfo GameDirInfo = new DirectoryInfo(gameDir);
foreach (var Game in GameDirInfo.GetFiles()) foreach (var Game in GameDirInfo.GetFiles())
{ {
if ((Path.GetExtension(Game.ToString()) == ".xci") || if ((Path.GetExtension(Game.ToString()) == ".xci") ||
@ -62,182 +84,204 @@ namespace Ryujinx
(Path.GetExtension(Game.ToString()) == ".nro") || (Path.GetExtension(Game.ToString()) == ".nro") ||
(Path.GetExtension(Game.ToString()) == ".nso")) (Path.GetExtension(Game.ToString()) == ".nso"))
{ {
Games.Add(Game.ToString()); applications.Add(Game.ToString());
} }
} }
} }
// Loops through applications list, creating a struct for each application and then adding the struct to a list of structs
ApplicationLibraryData = new List<ApplicationData>(); ApplicationLibraryData = new List<ApplicationData>();
foreach (string GamePath in Games) foreach (string applicationPath in applications)
{ {
double filesize = new FileInfo(GamePath).Length * 0.000000000931; double filesize = new FileInfo(applicationPath).Length * 0.000000000931;
string titleName = null;
string titleId = null;
string developer = null;
string version = null;
byte[] applicationIcon = null;
using (FileStream file = new FileStream(GamePath, FileMode.Open, FileAccess.Read)) using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
{
if ((Path.GetExtension(applicationPath) == ".nsp") || (Path.GetExtension(applicationPath) == ".pfs0") || (Path.GetExtension(applicationPath) == ".xci"))
{ {
Nca mainNca = null;
Nca patchNca = null;
Nca controlNca = null;
PartitionFileSystem pfs = null;
IFileSystem controlFs = null; IFileSystem controlFs = null;
Npdm metaData = null;
string TitleName = null;
string TitleId = "010000000000100D";
Gdk.Pixbuf GameIcon = null;
if ((Path.GetExtension(GamePath) == ".nsp") || (Path.GetExtension(GamePath) == ".pfs0")) // Store the ControlFS in variable called controlFs
{ if (Path.GetExtension(applicationPath) == ".xci")
pfs = new PartitionFileSystem(file.AsStorage());
}
else if (Path.GetExtension(GamePath) == ".xci")
{ {
Xci xci = new Xci(MainWindow._device.System.KeySet, file.AsStorage()); Xci xci = new Xci(MainWindow._device.System.KeySet, file.AsStorage());
pfs = xci.OpenPartition(XciPartitionType.Secure); controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
} }
else { controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage())); }
if (pfs != null) // Creates NACP class from the NACP file
{
foreach (DirectoryEntry ticketEntry in pfs.EnumerateEntries("*.tik"))
{
Ticket ticket = new Ticket(pfs.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream());
if (!MainWindow._device.System.KeySet.TitleKeys.ContainsKey(ticket.RightsId))
{
MainWindow._device.System.KeySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(MainWindow._device.System.KeySet));
}
}
foreach (DirectoryEntry fileEntry in pfs.EnumerateEntries("*.nca"))
{
Nca nca = new Nca(MainWindow._device.System.KeySet, pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
if (nca.Header.ContentType == ContentType.Program)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, ContentType.Program);
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
patchNca = nca;
}
else
{
mainNca = nca;
}
}
else if (nca.Header.ContentType == ContentType.Control)
{
controlNca = nca;
}
}
controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, MainWindow._device.System.FsIntegrityCheckLevel);
if (patchNca == null)
{
if (mainNca.CanOpenSection(NcaSectionType.Code))
{
IFileSystem codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, MainWindow._device.System.FsIntegrityCheckLevel);
metaData = new Npdm(codeFs.OpenFile("/main.npdm", OpenMode.Read).AsStream());
TitleId = metaData.Aci0.TitleId.ToString("x16");
}
}
else
{
if (patchNca.CanOpenSection(NcaSectionType.Code))
{
IFileSystem codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, MainWindow._device.System.FsIntegrityCheckLevel);
metaData = new Npdm(codeFs.OpenFile("/main.npdm", OpenMode.Read).AsStream());
TitleId = metaData.Aci0.TitleId.ToString("x16");
}
}
}
if ((Path.GetExtension(GamePath) == ".nca") || (Path.GetExtension(GamePath) == ".nro") || (Path.GetExtension(GamePath) == ".nso"))
{
StringBuilder titleName = new StringBuilder();
titleName.Append(Path.GetFileName(GamePath));
titleName.Remove(Path.GetFileName(GamePath).Length - Path.GetExtension(GamePath).Length, Path.GetExtension(GamePath).Length);
TitleName = titleName.ToString();
}
else
{
IFile controlFile = controlFs.OpenFile("/control.nacp", OpenMode.Read); IFile controlFile = controlFs.OpenFile("/control.nacp", OpenMode.Read);
Nacp controlData = new Nacp(controlFile.AsStream()); Nacp controlData = new Nacp(controlFile.AsStream());
TitleName = controlData.Descriptions[(int)MainWindow._device.System.State.DesiredTitleLanguage].Title; // Get the title name, title ID, developer name and version number from the NACP
if (string.IsNullOrWhiteSpace(TitleName)) version = controlData.DisplayVersion;
titleName = controlData.Descriptions[(int)MainWindow._device.System.State.DesiredTitleLanguage].Title;
if (string.IsNullOrWhiteSpace(titleName))
{ {
TitleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title; titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
}
} }
if (Path.GetExtension(GamePath) == ".nca") { GameIcon = RyujinxNcaIcon; } titleId = controlData.PresenceGroupId.ToString("X16");
if (string.IsNullOrWhiteSpace(titleId)) { titleId = controlData.SaveDataOwnerId.ToString("X16"); }
if (string.IsNullOrWhiteSpace(titleId)) { titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("X16"); }
else if ((Path.GetExtension(GamePath) == ".xci") || (Path.GetExtension(GamePath) == ".nsp") || (Path.GetExtension(GamePath) == ".pfs0")) developer = controlData.Descriptions[(int)MainWindow._device.System.State.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
try try
{ {
IFile logo = controlFs.OpenFile($"/icon_{MainWindow._device.System.State.DesiredTitleLanguage}.dat", OpenMode.Read); IFile logo = controlFs.OpenFile($"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read);
GameIcon = new Gdk.Pixbuf(logo.AsStream(), 75, 75); using (MemoryStream ms = new MemoryStream())
{
logo.AsStream().CopyTo(ms);
applicationIcon = ms.ToArray();
} }
catch(FileNotFoundException) }
catch (FileNotFoundException)
{ {
try try
{ {
IFile logo = controlFs.OpenFile($"/icon_AmericanEnglish.dat", OpenMode.Read); IFile logo = controlFs.OpenFile($"/icon_AmericanEnglish.dat", OpenMode.Read);
GameIcon = new Gdk.Pixbuf(logo.AsStream(), 75, 75); using (MemoryStream ms = new MemoryStream())
{
logo.AsStream().CopyTo(ms);
applicationIcon = ms.ToArray();
}
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
if (Path.GetExtension(GamePath) == ".xci") { GameIcon = RyujinxXciIcon; } if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; }
else { GameIcon = RyujinxNspIcon; } else { applicationIcon = RyujinxNspIcon; }
} }
} }
} }
else if (Path.GetExtension(GamePath) == ".nso") { GameIcon = RyujinxNsoIcon; } else if (Path.GetExtension(applicationPath) == ".nro")
else if (Path.GetExtension(GamePath) == ".nro")
{ {
BinaryReader reader = new BinaryReader(file); BinaryReader reader = new BinaryReader(file);
file.Seek(24, SeekOrigin.Begin);
int AssetOffset = reader.ReadInt32();
byte[] Read(long Position, int Size) byte[] Read(long Position, int Size)
{ {
file.Seek(Position, SeekOrigin.Begin); file.Seek(Position, SeekOrigin.Begin);
return reader.ReadBytes(Size); return reader.ReadBytes(Size);
} }
file.Seek(24, SeekOrigin.Begin);
int AssetOffset = reader.ReadInt32();
if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET") if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
{ {
byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10); byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
long IconOffset = BitConverter.ToInt64(IconSectionInfo, 0); long iconOffset = BitConverter.ToInt64(IconSectionInfo, 0);
long IconSize = BitConverter.ToInt64(IconSectionInfo, 8); long iconSize = BitConverter.ToInt64(IconSectionInfo, 8);
byte[] IconData = Read(AssetOffset + IconOffset, (int)IconSize); ulong nacpOffset = reader.ReadUInt64();
ulong nacpSize = reader.ReadUInt64();
GameIcon = new Gdk.Pixbuf(IconData, 75, 75); // 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
Nacp controlData = new Nacp(stream);
// Get the title name, title ID, developer name and version number from the NACP
version = controlData.DisplayVersion;
titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
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;
}
}
}
else { applicationIcon = RyujinxNroIcon; titleName = "Application"; titleId = "0000000000000000"; developer = "Unknown"; version = "?"; }
}
// If its an NCA or NSO we just set defaults
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
{
if (Path.GetExtension(applicationPath) == ".nca") { applicationIcon = RyujinxNcaIcon; }
else if (Path.GetExtension(applicationPath) == ".nso") { applicationIcon = RyujinxNsoIcon; }
StringBuilder titlename = new StringBuilder();
titlename.Append(Path.GetFileName(applicationPath));
titlename.Remove(Path.GetFileName(applicationPath).Length - Path.GetExtension(applicationPath).Length, Path.GetExtension(applicationPath).Length);
titleName = titlename.ToString();
titleId = "0000000000000000";
version = "?";
developer = "Unknown";
} }
else { GameIcon = RyujinxNroIcon; }
} }
ApplicationData data = new ApplicationData() ApplicationData data = new ApplicationData()
{ {
Icon = GameIcon, Icon = applicationIcon,
GameName = TitleName, TitleName = titleName,
GameId = TitleId, TitleId = titleId,
TimePlayed = GetPlayedData(TitleId)[0], Developer = developer,
LastPlayed = GetPlayedData(TitleId)[1], Version = version,
TimePlayed = GetPlayedData(titleId)[0],
LastPlayed = GetPlayedData(titleId)[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 = GamePath, Path = applicationPath,
}; };
ApplicationLibraryData.Add(data); ApplicationLibraryData.Add(data);
} }
} }
private static IFileSystem GetControlFs(PartitionFileSystem Pfs)
{
Nca controlNca = null;
// Add keys to keyset if needed
foreach (DirectoryEntry ticketEntry in Pfs.EnumerateEntries("*.tik"))
{
Ticket ticket = new Ticket(Pfs.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream());
if (!KeySet.TitleKeys.ContainsKey(ticket.RightsId))
{
KeySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(MainWindow._device.System.KeySet));
}
}
// Find the Control NCA and store it in variable called controlNca
foreach (DirectoryEntry fileEntry in Pfs.EnumerateEntries("*.nca"))
{
Nca nca = new Nca(MainWindow._device.System.KeySet, Pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
if (nca.Header.ContentType == ContentType.Control)
{
controlNca = nca;
}
}
// Return the ControlFS
return controlNca.OpenFileSystem(NcaSectionType.Data, MainWindow._device.System.FsIntegrityCheckLevel);
} }
private static string[] GetPlayedData(string TitleId) private static string[] GetPlayedData(string TitleId)

View file

@ -10,6 +10,7 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading; using System.Threading;
namespace Ryujinx namespace Ryujinx
@ -58,7 +59,7 @@ namespace Ryujinx
Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
Configuration.InitialConfigure(_device); Configuration.InitialConfigure(_device);
ApplicationLibrary.Init(); ApplicationLibrary.Init(_device.System.KeySet, _device.System.State.DesiredTitleLanguage);
_gtkapp = gtkapp; _gtkapp = gtkapp;
@ -97,12 +98,14 @@ namespace Ryujinx
Nfc.Sensitive = false; Nfc.Sensitive = false;
ReturnMain.Sensitive = false; ReturnMain.Sensitive = false;
GameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0); GameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0);
GameTable.AppendColumn("Game", new CellRendererText(), "text", 1); GameTable.AppendColumn("Application", new CellRendererText(), "text", 1);
GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 2); GameTable.AppendColumn("Developer", new CellRendererText(), "text", 2);
GameTable.AppendColumn("Last Played", new CellRendererText(), "text", 3); GameTable.AppendColumn("Version", new CellRendererText(), "text", 3);
GameTable.AppendColumn("File Size", new CellRendererText(), "text", 4); GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4);
GameTable.AppendColumn("Path", new CellRendererText(), "text", 5); GameTable.AppendColumn("Last Played", new CellRendererText(), "text", 5);
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string)); GameTable.AppendColumn("File Size", new CellRendererText(), "text", 6);
GameTable.AppendColumn("Path", new CellRendererText(), "text", 7);
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
GameTable.Model = _TableStore; GameTable.Model = _TableStore;
UpdateGameTable(); UpdateGameTable();
//Temporary code section end //Temporary code section end
@ -117,13 +120,15 @@ namespace Ryujinx
ReturnMain.Sensitive = false; ReturnMain.Sensitive = false;
GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0); GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0);
GameTable.AppendColumn("Game" , new CellRendererText() , "text" , 1); GameTable.AppendColumn("Application", new CellRendererText() , "text" , 1);
GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 2); GameTable.AppendColumn("Developer" , new CellRendererText() , "text" , 2);
GameTable.AppendColumn("Last Played", new CellRendererText() , "text" , 3); GameTable.AppendColumn("Version" , new CellRendererText() , "text" , 3);
GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 4); GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 4);
GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 5); GameTable.AppendColumn("Last Played", new CellRendererText() , "text" , 5);
GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 6);
GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 7);
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string)); _TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
GameTable.Model = _TableStore; GameTable.Model = _TableStore;
UpdateGameTable(); UpdateGameTable();
@ -133,11 +138,11 @@ namespace Ryujinx
public static void UpdateGameTable() public static void UpdateGameTable()
{ {
_TableStore.Clear(); _TableStore.Clear();
ApplicationLibrary.Init(); ApplicationLibrary.Init(_device.System.KeySet, _device.System.State.DesiredTitleLanguage);
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData) foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
{ {
_TableStore.AppendValues(AppData.Icon, $"{AppData.GameName}\n{AppData.GameId.ToUpper()}", AppData.TimePlayed, AppData.LastPlayed, AppData.FileSize, AppData.Path); _TableStore.AppendValues(new Gdk.Pixbuf(AppData.Icon, 75, 75), $"{AppData.TitleName}\n{AppData.TitleId.ToUpper()}", AppData.Developer, AppData.Version, AppData.TimePlayed, AppData.LastPlayed, AppData.FileSize, AppData.Path);
} }
} }
@ -261,9 +266,18 @@ namespace Ryujinx
{ {
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string savePath = System.IO.Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", _device.System.TitleID); string savePath = System.IO.Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", _device.System.TitleID);
using (FileStream fs = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
if (File.Exists(System.IO.Path.Combine(savePath, "TimePlayed.dat")) == false)
{ {
using (StreamWriter sr = new StreamWriter(fs)) Directory.CreateDirectory(savePath);
using (FileStream file = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat"))) { file.Write(Encoding.ASCII.GetBytes("0")); }
}
if (File.Exists(System.IO.Path.Combine(savePath, "LastPlayed.dat")) == false)
{
Directory.CreateDirectory(savePath);
using (FileStream file = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
{
using (StreamWriter sr = new StreamWriter(file))
{ {
sr.WriteLine(DateTime.UtcNow); sr.WriteLine(DateTime.UtcNow);
} }
@ -271,6 +285,7 @@ namespace Ryujinx
} }
} }
} }
}
private static void CreateGameWindow() private static void CreateGameWindow()
{ {
@ -325,7 +340,7 @@ namespace Ryujinx
private void Row_Activated(object obj, RowActivatedArgs args) private void Row_Activated(object obj, RowActivatedArgs args)
{ {
_TableStore.GetIter(out TreeIter treeiter, new TreePath(args.Path.ToString())); _TableStore.GetIter(out TreeIter treeiter, new TreePath(args.Path.ToString()));
string path = (string)_TableStore.GetValue(treeiter, 5); string path = (string)_TableStore.GetValue(treeiter, 7);
LoadApplication(path); LoadApplication(path);