From bf82c5f5d5c64d09ef36178ed245fad74ae3c5de Mon Sep 17 00:00:00 2001 From: Xpl0itR Date: Sun, 7 Jul 2019 18:13:28 +0100 Subject: [PATCH] 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 --- Ryujinx.HLE/FileSystem/SaveHelper.cs | 2 +- Ryujinx.HLE/FileSystem/SaveInfo.cs | 4 +- Ryujinx.HLE/HOS/Horizon.cs | 29 +- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 4 +- .../HOS/Kernel/Process/ProcessCreationInfo.cs | 4 +- .../HOS/Kernel/SupervisorCall/SvcSystem.cs | 2 +- .../HOS/Services/FspSrv/IFileSystemProxy.cs | 2 +- .../Executables/KernelInitialProcess.cs | 4 +- Ryujinx.HLE/Loaders/Npdm/ACI0.cs | 6 +- Ryujinx.HLE/Loaders/Npdm/ACID.cs | 2 +- Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs | 2 +- Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs | 2 +- .../Loaders/Npdm/KernelAccessControl.cs | 2 +- Ryujinx.HLE/Loaders/Npdm/Npdm.cs | 4 +- .../Loaders/Npdm/ServiceAccessControl.cs | 2 +- Ryujinx/ApplicationLibrary.cs | 334 ++++++++++-------- Ryujinx/GUI/MainWindow.cs | 57 +-- 17 files changed, 269 insertions(+), 193 deletions(-) diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs index 411d13e250..51400458b0 100644 --- a/Ryujinx.HLE/FileSystem/SaveHelper.cs +++ b/Ryujinx.HLE/FileSystem/SaveHelper.cs @@ -10,7 +10,7 @@ namespace Ryujinx.HLE.FileSystem public static string GetSavePath(SaveInfo saveMetaData, ServiceCtx context) { string baseSavePath = NandPath; - long currentTitleId = saveMetaData.TitleId; + ulong currentTitleId = saveMetaData.TitleId; switch (saveMetaData.SaveSpaceId) { diff --git a/Ryujinx.HLE/FileSystem/SaveInfo.cs b/Ryujinx.HLE/FileSystem/SaveInfo.cs index db7f6765d7..8685e6ca54 100644 --- a/Ryujinx.HLE/FileSystem/SaveInfo.cs +++ b/Ryujinx.HLE/FileSystem/SaveInfo.cs @@ -4,7 +4,7 @@ namespace Ryujinx.HLE.FileSystem { struct SaveInfo { - public long TitleId { get; private set; } + public ulong TitleId { get; private set; } public long SaveId { get; private set; } public UInt128 UserId { get; private set; } @@ -12,7 +12,7 @@ namespace Ryujinx.HLE.FileSystem public SaveSpaceId SaveSpaceId { get; private set; } public SaveInfo( - long titleId, + ulong titleId, long saveId, SaveDataType saveDataType, UInt128 userId, diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 0ddd9c827c..65327c9d10 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -549,18 +549,35 @@ namespace Ryujinx.HLE.HOS if (asetVersion == 0) { ulong iconOffset = reader.ReadUInt64(); - ulong iconSize = reader.ReadUInt64(); + ulong iconSize = reader.ReadUInt64(); ulong nacpOffset = reader.ReadUInt64(); - ulong nacpSize = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); ulong romfsOffset = reader.ReadUInt64(); - ulong romfsSize = reader.ReadUInt64(); + ulong romfsSize = reader.ReadUInt64(); if (romfsSize != 0) { 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 { @@ -574,10 +591,10 @@ namespace Ryujinx.HLE.HOS staticObject = new NxStaticObject(input); } - ContentManager.LoadEntries(); + TitleName = CurrentTitle = metaData.TitleName; + TitleID = metaData.Aci0.TitleId.ToString("x16"); - TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16"); - TitleName = metaData.TitleName; + ContentManager.LoadEntries(); ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject }); } diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index beb376f64f..c6283afd43 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -60,8 +60,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public KProcessCapabilities Capabilities { get; private set; } - public long TitleId { get; private set; } - public long Pid { get; private set; } + public ulong TitleId { get; private set; } + public long Pid { get; private set; } private long _creationTimestamp; private ulong _entrypoint; diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs index ba9f54bf36..e992331023 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs @@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public string Name { 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 int CodePagesCount { get; private set; } @@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public ProcessCreationInfo( string name, int category, - long titleId, + ulong titleId, ulong codeAddress, int codePagesCount, int mmuFlags, diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs index 094e1935f8..6525628f26 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -285,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall break; - case 18: value = process.TitleId; break; + case 18: value = (long)process.TitleId; break; case 20: value = (long)process.UserExceptionContextAddress; break; diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs index 16bfc00e26..ab425cffe4 100644 --- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs @@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv { SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64(); - long titleId = context.RequestData.ReadInt64(); + ulong titleId = context.RequestData.ReadUInt64(); UInt128 userId = context.RequestData.ReadStruct(); diff --git a/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs index af57cf2d06..d6a1cb66bd 100644 --- a/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs +++ b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs @@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Executables { public string Name { get; private set; } - public long TitleId { get; private set; } + public ulong TitleId { get; private set; } public int ProcessCategory { get; private set; } @@ -65,7 +65,7 @@ namespace Ryujinx.HLE.Loaders.Executables Name = ReadString(reader, 12); - TitleId = reader.ReadInt64(); + TitleId = reader.ReadUInt64(); ProcessCategory = reader.ReadInt32(); diff --git a/Ryujinx.HLE/Loaders/Npdm/ACI0.cs b/Ryujinx.HLE/Loaders/Npdm/ACI0.cs index 29fc309a4a..8350acf724 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ACI0.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ACI0.cs @@ -3,11 +3,11 @@ using System.IO; namespace Ryujinx.HLE.Loaders.Npdm { - public class Aci0 + class Aci0 { 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 ulong FsPermissionsBitmask { get; private set; } @@ -28,7 +28,7 @@ namespace Ryujinx.HLE.Loaders.Npdm stream.Seek(0xc, SeekOrigin.Current); - TitleId = reader.ReadInt64(); + TitleId = reader.ReadUInt64(); // Reserved. stream.Seek(8, SeekOrigin.Current); diff --git a/Ryujinx.HLE/Loaders/Npdm/ACID.cs b/Ryujinx.HLE/Loaders/Npdm/ACID.cs index 365495c60b..4a181b294f 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ACID.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ACID.cs @@ -3,7 +3,7 @@ using System.IO; namespace Ryujinx.HLE.Loaders.Npdm { - public class Acid + class Acid { private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24; diff --git a/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs index d0f349eaf3..3359435dcb 100644 --- a/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs @@ -2,7 +2,7 @@ namespace Ryujinx.HLE.Loaders.Npdm { - public class FsAccessControl + class FsAccessControl { public int Version { get; private set; } public ulong PermissionsBitmask { get; private set; } diff --git a/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs b/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs index 57d1208db9..564b8dc391 100644 --- a/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs +++ b/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs @@ -4,7 +4,7 @@ using System.IO; namespace Ryujinx.HLE.Loaders.Npdm { - public class FsAccessHeader + class FsAccessHeader { public int Version { get; private set; } public ulong PermissionsBitmask { get; private set; } diff --git a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs index 39803642c9..d8e40d0b99 100644 --- a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs @@ -2,7 +2,7 @@ namespace Ryujinx.HLE.Loaders.Npdm { - public class KernelAccessControl + class KernelAccessControl { public int[] Capabilities { get; private set; } diff --git a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs index d2cce2c39e..e5f1126b2c 100644 --- a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +++ b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -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.h //http://switchbrew.org/index.php?title=NPDM - public class Npdm + class Npdm { 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 ProcessCategory { 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 Aci0 Aci0 { get; private set; } diff --git a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index 54012b8a97..03f62ff7c5 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -5,7 +5,7 @@ using System.Text; namespace Ryujinx.HLE.Loaders.Npdm { - public class ServiceAccessControl + class ServiceAccessControl { public IReadOnlyDictionary Services { get; private set; } diff --git a/Ryujinx/ApplicationLibrary.cs b/Ryujinx/ApplicationLibrary.cs index e355cfe97b..577adb39d6 100644 --- a/Ryujinx/ApplicationLibrary.cs +++ b/Ryujinx/ApplicationLibrary.cs @@ -2,7 +2,6 @@ using LibHac.Fs; using LibHac.Fs.NcaUtils; using Ryujinx.Common.Logging; -using Ryujinx.HLE.Loaders.Npdm; using System; using System.Collections.Generic; using System.IO; @@ -14,45 +13,68 @@ namespace Ryujinx { public class ApplicationLibrary { - public static Gdk.Pixbuf RyujinxNspIcon { get; private set; } - public static Gdk.Pixbuf RyujinxXciIcon { get; private set; } - public static Gdk.Pixbuf RyujinxNcaIcon { get; private set; } - public static Gdk.Pixbuf RyujinxNroIcon { get; private set; } - public static Gdk.Pixbuf RyujinxNsoIcon { get; private set; } + private static Keyset KeySet; + + public static byte[] RyujinxNspIcon { get; private set; } + public static byte[] RyujinxXciIcon { 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 ApplicationLibraryData { get; private set; } public struct ApplicationData { - public Gdk.Pixbuf Icon; - public string GameName; - public string GameId; - public string TimePlayed; - public string LastPlayed; - public string FileSize; - public string Path; + public byte[] Icon; + public string TitleName; + public string TitleId; + public string Developer; + public string Version; + public string TimePlayed; + public string LastPlayed; + public string FileSize; + 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); - RyujinxXciIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxXCIIcon.png", 75, 75); - 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); + // Load keyset + KeySet = keySet; - List Games = new List(); - - foreach (string GameDir in SwitchSettings.SwitchConfig.GameDirs) + // Loads the default application Icons + using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNSPIcon.png")) { - if (Directory.Exists(GameDir) == false) + 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(); } + } + + // Builds the applications list with paths to found applications + List applications = new List(); + foreach (string gameDir in SwitchSettings.SwitchConfig.GameDirs) + { + 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; } - DirectoryInfo GameDirInfo = new DirectoryInfo(GameDir); + DirectoryInfo GameDirInfo = new DirectoryInfo(gameDir); foreach (var Game in GameDirInfo.GetFiles()) { if ((Path.GetExtension(Game.ToString()) == ".xci") || @@ -62,182 +84,204 @@ namespace Ryujinx (Path.GetExtension(Game.ToString()) == ".nro") || (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(); - 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)) { - Nca mainNca = null; - Nca patchNca = null; - Nca controlNca = null; - PartitionFileSystem pfs = 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")) + if ((Path.GetExtension(applicationPath) == ".nsp") || (Path.GetExtension(applicationPath) == ".pfs0") || (Path.GetExtension(applicationPath) == ".xci")) { - pfs = new PartitionFileSystem(file.AsStorage()); - } + IFileSystem controlFs = null; - else if (Path.GetExtension(GamePath) == ".xci") - { - Xci xci = new Xci(MainWindow._device.System.KeySet, file.AsStorage()); - pfs = xci.OpenPartition(XciPartitionType.Secure); - } - - if (pfs != null) - { - foreach (DirectoryEntry ticketEntry in pfs.EnumerateEntries("*.tik")) + // Store the ControlFS in variable called controlFs + if (Path.GetExtension(applicationPath) == ".xci") { - 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)); - } + Xci xci = new Xci(MainWindow._device.System.KeySet, file.AsStorage()); + controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure)); } + else { controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage())); } - 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 - { + // Creates NACP class from the NACP file IFile controlFile = controlFs.OpenFile("/control.nacp", OpenMode.Read); Nacp controlData = new Nacp(controlFile.AsStream()); - TitleName = controlData.Descriptions[(int)MainWindow._device.System.State.DesiredTitleLanguage].Title; - if (string.IsNullOrWhiteSpace(TitleName)) + // Get the title name, title ID, developer name and version number from the NACP + 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 { - IFile logo = controlFs.OpenFile($"/icon_{MainWindow._device.System.State.DesiredTitleLanguage}.dat", OpenMode.Read); - GameIcon = new Gdk.Pixbuf(logo.AsStream(), 75, 75); + IFile logo = controlFs.OpenFile($"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read); + using (MemoryStream ms = new MemoryStream()) + { + logo.AsStream().CopyTo(ms); + applicationIcon = ms.ToArray(); + } } - catch(FileNotFoundException) + catch (FileNotFoundException) { try { 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) { - if (Path.GetExtension(GamePath) == ".xci") { GameIcon = RyujinxXciIcon; } - else { GameIcon = RyujinxNspIcon; } + if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; } + else { applicationIcon = RyujinxNspIcon; } } } } - else if (Path.GetExtension(GamePath) == ".nso") { GameIcon = RyujinxNsoIcon; } - - else if (Path.GetExtension(GamePath) == ".nro") + else if (Path.GetExtension(applicationPath) == ".nro") { BinaryReader reader = new BinaryReader(file); - file.Seek(24, SeekOrigin.Begin); - int AssetOffset = reader.ReadInt32(); - byte[] Read(long Position, int Size) { file.Seek(Position, SeekOrigin.Begin); return reader.ReadBytes(Size); } + file.Seek(24, SeekOrigin.Begin); + int AssetOffset = reader.ReadInt32(); + if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET") { byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10); - long IconOffset = BitConverter.ToInt64(IconSectionInfo, 0); - long IconSize = BitConverter.ToInt64(IconSectionInfo, 8); + long iconOffset = BitConverter.ToInt64(IconSectionInfo, 0); + 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 { GameIcon = RyujinxNroIcon; } + else { applicationIcon = RyujinxNroIcon; titleName = "Application"; titleId = "0000000000000000"; developer = "Unknown"; version = "?"; } } - ApplicationData data = new ApplicationData() + // If its an NCA or NSO we just set defaults + else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso")) { - Icon = GameIcon, - GameName = TitleName, - GameId = TitleId, - TimePlayed = GetPlayedData(TitleId)[0], - LastPlayed = GetPlayedData(TitleId)[1], - FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB", - Path = GamePath, - }; + if (Path.GetExtension(applicationPath) == ".nca") { applicationIcon = RyujinxNcaIcon; } + else if (Path.GetExtension(applicationPath) == ".nso") { applicationIcon = RyujinxNsoIcon; } - ApplicationLibraryData.Add(data); + 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"; + } + } + + ApplicationData data = new ApplicationData() + { + Icon = applicationIcon, + TitleName = titleName, + TitleId = titleId, + Developer = developer, + Version = version, + TimePlayed = GetPlayedData(titleId)[0], + LastPlayed = GetPlayedData(titleId)[1], + FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB", + Path = applicationPath, + }; + + 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) diff --git a/Ryujinx/GUI/MainWindow.cs b/Ryujinx/GUI/MainWindow.cs index 0bf7bc4f5b..2b049dc9af 100644 --- a/Ryujinx/GUI/MainWindow.cs +++ b/Ryujinx/GUI/MainWindow.cs @@ -10,6 +10,7 @@ using System; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading; namespace Ryujinx @@ -58,7 +59,7 @@ namespace Ryujinx Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); Configuration.InitialConfigure(_device); - ApplicationLibrary.Init(); + ApplicationLibrary.Init(_device.System.KeySet, _device.System.State.DesiredTitleLanguage); _gtkapp = gtkapp; @@ -97,12 +98,14 @@ namespace Ryujinx Nfc.Sensitive = false; ReturnMain.Sensitive = false; GameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0); - GameTable.AppendColumn("Game", new CellRendererText(), "text", 1); - GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 2); - GameTable.AppendColumn("Last Played", new CellRendererText(), "text", 3); - GameTable.AppendColumn("File Size", new CellRendererText(), "text", 4); - GameTable.AppendColumn("Path", new CellRendererText(), "text", 5); - _TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string)); + GameTable.AppendColumn("Application", new CellRendererText(), "text", 1); + GameTable.AppendColumn("Developer", new CellRendererText(), "text", 2); + GameTable.AppendColumn("Version", new CellRendererText(), "text", 3); + GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4); + 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), typeof(string), typeof(string)); GameTable.Model = _TableStore; UpdateGameTable(); //Temporary code section end @@ -117,13 +120,15 @@ namespace Ryujinx ReturnMain.Sensitive = false; GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0); - GameTable.AppendColumn("Game" , new CellRendererText() , "text" , 1); - GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 2); - GameTable.AppendColumn("Last Played", new CellRendererText() , "text" , 3); - GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 4); - GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 5); + GameTable.AppendColumn("Application", new CellRendererText() , "text" , 1); + GameTable.AppendColumn("Developer" , new CellRendererText() , "text" , 2); + GameTable.AppendColumn("Version" , new CellRendererText() , "text" , 3); + GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 4); + 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; UpdateGameTable(); @@ -133,11 +138,11 @@ namespace Ryujinx public static void UpdateGameTable() { _TableStore.Clear(); - ApplicationLibrary.Init(); + ApplicationLibrary.Init(_device.System.KeySet, _device.System.State.DesiredTitleLanguage); 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); } } @@ -259,13 +264,23 @@ namespace Ryujinx if (_device.System.TitleID != null) { - string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - 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"))) + string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + string savePath = System.IO.Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", _device.System.TitleID); + + 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"))) { - sr.WriteLine(DateTime.UtcNow); + using (StreamWriter sr = new StreamWriter(file)) + { + sr.WriteLine(DateTime.UtcNow); + } } } } @@ -325,7 +340,7 @@ namespace Ryujinx private void Row_Activated(object obj, RowActivatedArgs args) { _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);