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);