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:
parent
fd5853fc24
commit
bf82c5f5d5
17 changed files with 269 additions and 193 deletions
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<UInt128>();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
public class KernelAccessControl
|
||||
class KernelAccessControl
|
||||
{
|
||||
public int[] Capabilities { get; private set; }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
|||
//https://github.com/SciresM/hactool/blob/master/npdm.c
|
||||
//https://github.com/SciresM/hactool/blob/master/npdm.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; }
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.Text;
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
public class ServiceAccessControl
|
||||
class ServiceAccessControl
|
||||
{
|
||||
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
||||
|
||||
|
|
|
@ -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<ApplicationData> 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<string> Games = new List<string>();
|
||||
|
||||
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<string> applications = new List<string>();
|
||||
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<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))
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue