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)
|
public static string GetSavePath(SaveInfo saveMetaData, ServiceCtx context)
|
||||||
{
|
{
|
||||||
string baseSavePath = NandPath;
|
string baseSavePath = NandPath;
|
||||||
long currentTitleId = saveMetaData.TitleId;
|
ulong currentTitleId = saveMetaData.TitleId;
|
||||||
|
|
||||||
switch (saveMetaData.SaveSpaceId)
|
switch (saveMetaData.SaveSpaceId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
struct SaveInfo
|
struct SaveInfo
|
||||||
{
|
{
|
||||||
public long TitleId { get; private set; }
|
public ulong TitleId { get; private set; }
|
||||||
public long SaveId { get; private set; }
|
public long SaveId { get; private set; }
|
||||||
public UInt128 UserId { get; private set; }
|
public UInt128 UserId { get; private set; }
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
public SaveSpaceId SaveSpaceId { get; private set; }
|
public SaveSpaceId SaveSpaceId { get; private set; }
|
||||||
|
|
||||||
public SaveInfo(
|
public SaveInfo(
|
||||||
long titleId,
|
ulong titleId,
|
||||||
long saveId,
|
long saveId,
|
||||||
SaveDataType saveDataType,
|
SaveDataType saveDataType,
|
||||||
UInt128 userId,
|
UInt128 userId,
|
||||||
|
|
|
@ -561,6 +561,23 @@ namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
|
Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nacpSize != 0)
|
||||||
|
{
|
||||||
|
input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
|
||||||
|
using (MemoryStream stream = new MemoryStream(reader.ReadBytes((int)nacpSize))) { ControlData = new Nacp(stream); }
|
||||||
|
|
||||||
|
metaData.TitleName = ControlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||||
|
if (string.IsNullOrWhiteSpace(metaData.TitleName))
|
||||||
|
{
|
||||||
|
metaData.TitleName = ControlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
metaData.Aci0.TitleId = ControlData.PresenceGroupId;
|
||||||
|
if (metaData.Aci0.TitleId == 0) { metaData.Aci0.TitleId = ControlData.SaveDataOwnerId; }
|
||||||
|
if (metaData.Aci0.TitleId == 0) { metaData.Aci0.TitleId = ControlData.AddOnContentBaseId - 0x1000; }
|
||||||
|
if (metaData.Aci0.TitleId.ToString("x16") == "fffffffffffff000") { metaData.Aci0.TitleId = 0000000000000000; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -574,10 +591,10 @@ namespace Ryujinx.HLE.HOS
|
||||||
staticObject = new NxStaticObject(input);
|
staticObject = new NxStaticObject(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager.LoadEntries();
|
TitleName = CurrentTitle = metaData.TitleName;
|
||||||
|
TitleID = metaData.Aci0.TitleId.ToString("x16");
|
||||||
|
|
||||||
TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
|
ContentManager.LoadEntries();
|
||||||
TitleName = metaData.TitleName;
|
|
||||||
|
|
||||||
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
|
||||||
public KProcessCapabilities Capabilities { get; private set; }
|
public KProcessCapabilities Capabilities { get; private set; }
|
||||||
|
|
||||||
public long TitleId { get; private set; }
|
public ulong TitleId { get; private set; }
|
||||||
public long Pid { get; private set; }
|
public long Pid { get; private set; }
|
||||||
|
|
||||||
private long _creationTimestamp;
|
private long _creationTimestamp;
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
|
|
||||||
public int Category { get; private set; }
|
public int Category { get; private set; }
|
||||||
public long TitleId { get; private set; }
|
public ulong TitleId { get; private set; }
|
||||||
|
|
||||||
public ulong CodeAddress { get; private set; }
|
public ulong CodeAddress { get; private set; }
|
||||||
public int CodePagesCount { get; private set; }
|
public int CodePagesCount { get; private set; }
|
||||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
public ProcessCreationInfo(
|
public ProcessCreationInfo(
|
||||||
string name,
|
string name,
|
||||||
int category,
|
int category,
|
||||||
long titleId,
|
ulong titleId,
|
||||||
ulong codeAddress,
|
ulong codeAddress,
|
||||||
int codePagesCount,
|
int codePagesCount,
|
||||||
int mmuFlags,
|
int mmuFlags,
|
||||||
|
|
|
@ -285,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 18: value = process.TitleId; break;
|
case 18: value = (long)process.TitleId; break;
|
||||||
|
|
||||||
case 20: value = (long)process.UserExceptionContextAddress; break;
|
case 20: value = (long)process.UserExceptionContextAddress; break;
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
{
|
{
|
||||||
SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64();
|
SaveSpaceId saveSpaceId = (SaveSpaceId)context.RequestData.ReadInt64();
|
||||||
|
|
||||||
long titleId = context.RequestData.ReadInt64();
|
ulong titleId = context.RequestData.ReadUInt64();
|
||||||
|
|
||||||
UInt128 userId = context.RequestData.ReadStruct<UInt128>();
|
UInt128 userId = context.RequestData.ReadStruct<UInt128>();
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||||
{
|
{
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
|
|
||||||
public long TitleId { get; private set; }
|
public ulong TitleId { get; private set; }
|
||||||
|
|
||||||
public int ProcessCategory { get; private set; }
|
public int ProcessCategory { get; private set; }
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||||
|
|
||||||
Name = ReadString(reader, 12);
|
Name = ReadString(reader, 12);
|
||||||
|
|
||||||
TitleId = reader.ReadInt64();
|
TitleId = reader.ReadUInt64();
|
||||||
|
|
||||||
ProcessCategory = reader.ReadInt32();
|
ProcessCategory = reader.ReadInt32();
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,11 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
public class Aci0
|
class Aci0
|
||||||
{
|
{
|
||||||
private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
|
private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
|
||||||
|
|
||||||
public long TitleId { get; private set; }
|
public ulong TitleId { get; set; }
|
||||||
|
|
||||||
public int FsVersion { get; private set; }
|
public int FsVersion { get; private set; }
|
||||||
public ulong FsPermissionsBitmask { get; private set; }
|
public ulong FsPermissionsBitmask { get; private set; }
|
||||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
|
|
||||||
stream.Seek(0xc, SeekOrigin.Current);
|
stream.Seek(0xc, SeekOrigin.Current);
|
||||||
|
|
||||||
TitleId = reader.ReadInt64();
|
TitleId = reader.ReadUInt64();
|
||||||
|
|
||||||
// Reserved.
|
// Reserved.
|
||||||
stream.Seek(8, SeekOrigin.Current);
|
stream.Seek(8, SeekOrigin.Current);
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
public class Acid
|
class Acid
|
||||||
{
|
{
|
||||||
private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
|
private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
public class FsAccessControl
|
class FsAccessControl
|
||||||
{
|
{
|
||||||
public int Version { get; private set; }
|
public int Version { get; private set; }
|
||||||
public ulong PermissionsBitmask { get; private set; }
|
public ulong PermissionsBitmask { get; private set; }
|
||||||
|
|
|
@ -4,7 +4,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
public class FsAccessHeader
|
class FsAccessHeader
|
||||||
{
|
{
|
||||||
public int Version { get; private set; }
|
public int Version { get; private set; }
|
||||||
public ulong PermissionsBitmask { get; private set; }
|
public ulong PermissionsBitmask { get; private set; }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
public class KernelAccessControl
|
class KernelAccessControl
|
||||||
{
|
{
|
||||||
public int[] Capabilities { get; private set; }
|
public int[] Capabilities { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
//https://github.com/SciresM/hactool/blob/master/npdm.c
|
//https://github.com/SciresM/hactool/blob/master/npdm.c
|
||||||
//https://github.com/SciresM/hactool/blob/master/npdm.h
|
//https://github.com/SciresM/hactool/blob/master/npdm.h
|
||||||
//http://switchbrew.org/index.php?title=NPDM
|
//http://switchbrew.org/index.php?title=NPDM
|
||||||
public class Npdm
|
class Npdm
|
||||||
{
|
{
|
||||||
private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
|
private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
public int PersonalMmHeapSize { get; private set; }
|
public int PersonalMmHeapSize { get; private set; }
|
||||||
public int ProcessCategory { get; private set; }
|
public int ProcessCategory { get; private set; }
|
||||||
public int MainThreadStackSize { get; private set; }
|
public int MainThreadStackSize { get; private set; }
|
||||||
public string TitleName { get; private set; }
|
public string TitleName { get; set; }
|
||||||
public byte[] ProductCode { get; private set; }
|
public byte[] ProductCode { get; private set; }
|
||||||
|
|
||||||
public Aci0 Aci0 { get; private set; }
|
public Aci0 Aci0 { get; private set; }
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
public class ServiceAccessControl
|
class ServiceAccessControl
|
||||||
{
|
{
|
||||||
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.NcaUtils;
|
using LibHac.Fs.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.Loaders.Npdm;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -14,45 +13,68 @@ namespace Ryujinx
|
||||||
{
|
{
|
||||||
public class ApplicationLibrary
|
public class ApplicationLibrary
|
||||||
{
|
{
|
||||||
public static Gdk.Pixbuf RyujinxNspIcon { get; private set; }
|
private static Keyset KeySet;
|
||||||
public static Gdk.Pixbuf RyujinxXciIcon { get; private set; }
|
|
||||||
public static Gdk.Pixbuf RyujinxNcaIcon { get; private set; }
|
public static byte[] RyujinxNspIcon { get; private set; }
|
||||||
public static Gdk.Pixbuf RyujinxNroIcon { get; private set; }
|
public static byte[] RyujinxXciIcon { get; private set; }
|
||||||
public static Gdk.Pixbuf RyujinxNsoIcon { get; private set; }
|
public static byte[] RyujinxNcaIcon { get; private set; }
|
||||||
|
public static byte[] RyujinxNroIcon { get; private set; }
|
||||||
|
public static byte[] RyujinxNsoIcon { get; private set; }
|
||||||
|
|
||||||
public static List<ApplicationData> ApplicationLibraryData { get; private set; }
|
public static List<ApplicationData> ApplicationLibraryData { get; private set; }
|
||||||
|
|
||||||
public struct ApplicationData
|
public struct ApplicationData
|
||||||
{
|
{
|
||||||
public Gdk.Pixbuf Icon;
|
public byte[] Icon;
|
||||||
public string GameName;
|
public string TitleName;
|
||||||
public string GameId;
|
public string TitleId;
|
||||||
|
public string Developer;
|
||||||
|
public string Version;
|
||||||
public string TimePlayed;
|
public string TimePlayed;
|
||||||
public string LastPlayed;
|
public string LastPlayed;
|
||||||
public string FileSize;
|
public string FileSize;
|
||||||
public string Path;
|
public string Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Init()
|
public static void Init(Keyset keySet, HLE.HOS.SystemState.TitleLanguage DesiredTitleLanguage)
|
||||||
{
|
{
|
||||||
RyujinxNspIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNSPIcon.png", 75, 75);
|
// Load keyset
|
||||||
RyujinxXciIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxXCIIcon.png", 75, 75);
|
KeySet = keySet;
|
||||||
RyujinxNcaIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNCAIcon.png", 75, 75);
|
|
||||||
RyujinxNroIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNROIcon.png", 75, 75);
|
|
||||||
RyujinxNsoIcon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxNSOIcon.png", 75, 75);
|
|
||||||
|
|
||||||
List<string> Games = new List<string>();
|
// Loads the default application Icons
|
||||||
|
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNSPIcon.png"))
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNspIcon = ms.ToArray(); }
|
||||||
|
}
|
||||||
|
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxXCIIcon.png"))
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxXciIcon = ms.ToArray(); }
|
||||||
|
}
|
||||||
|
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNCAIcon.png"))
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNcaIcon = ms.ToArray(); }
|
||||||
|
}
|
||||||
|
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNROIcon.png"))
|
||||||
|
{
|
||||||
|
using(MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNroIcon = ms.ToArray(); }
|
||||||
|
}
|
||||||
|
using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.GUI.assets.ryujinxNSOIcon.png"))
|
||||||
|
{
|
||||||
|
using (MemoryStream ms = new MemoryStream()) { resourceStream.CopyTo(ms); RyujinxNsoIcon = ms.ToArray(); }
|
||||||
|
}
|
||||||
|
|
||||||
foreach (string GameDir in SwitchSettings.SwitchConfig.GameDirs)
|
// Builds the applications list with paths to found applications
|
||||||
|
List<string> applications = new List<string>();
|
||||||
|
foreach (string gameDir in SwitchSettings.SwitchConfig.GameDirs)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(GameDir) == false)
|
if (Directory.Exists(gameDir) == false)
|
||||||
{
|
{
|
||||||
Logger.PrintError(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{GameDir}\"");
|
Logger.PrintError(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{gameDir}\"");
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectoryInfo GameDirInfo = new DirectoryInfo(GameDir);
|
DirectoryInfo GameDirInfo = new DirectoryInfo(gameDir);
|
||||||
foreach (var Game in GameDirInfo.GetFiles())
|
foreach (var Game in GameDirInfo.GetFiles())
|
||||||
{
|
{
|
||||||
if ((Path.GetExtension(Game.ToString()) == ".xci") ||
|
if ((Path.GetExtension(Game.ToString()) == ".xci") ||
|
||||||
|
@ -62,182 +84,204 @@ namespace Ryujinx
|
||||||
(Path.GetExtension(Game.ToString()) == ".nro") ||
|
(Path.GetExtension(Game.ToString()) == ".nro") ||
|
||||||
(Path.GetExtension(Game.ToString()) == ".nso"))
|
(Path.GetExtension(Game.ToString()) == ".nso"))
|
||||||
{
|
{
|
||||||
Games.Add(Game.ToString());
|
applications.Add(Game.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loops through applications list, creating a struct for each application and then adding the struct to a list of structs
|
||||||
ApplicationLibraryData = new List<ApplicationData>();
|
ApplicationLibraryData = new List<ApplicationData>();
|
||||||
foreach (string GamePath in Games)
|
foreach (string applicationPath in applications)
|
||||||
{
|
{
|
||||||
double filesize = new FileInfo(GamePath).Length * 0.000000000931;
|
double filesize = new FileInfo(applicationPath).Length * 0.000000000931;
|
||||||
|
string titleName = null;
|
||||||
|
string titleId = null;
|
||||||
|
string developer = null;
|
||||||
|
string version = null;
|
||||||
|
byte[] applicationIcon = null;
|
||||||
|
|
||||||
using (FileStream file = new FileStream(GamePath, FileMode.Open, FileAccess.Read))
|
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
||||||
|
{
|
||||||
|
if ((Path.GetExtension(applicationPath) == ".nsp") || (Path.GetExtension(applicationPath) == ".pfs0") || (Path.GetExtension(applicationPath) == ".xci"))
|
||||||
{
|
{
|
||||||
Nca mainNca = null;
|
|
||||||
Nca patchNca = null;
|
|
||||||
Nca controlNca = null;
|
|
||||||
PartitionFileSystem pfs = null;
|
|
||||||
IFileSystem controlFs = null;
|
IFileSystem controlFs = null;
|
||||||
Npdm metaData = null;
|
|
||||||
string TitleName = null;
|
|
||||||
string TitleId = "010000000000100D";
|
|
||||||
Gdk.Pixbuf GameIcon = null;
|
|
||||||
|
|
||||||
if ((Path.GetExtension(GamePath) == ".nsp") || (Path.GetExtension(GamePath) == ".pfs0"))
|
// Store the ControlFS in variable called controlFs
|
||||||
{
|
if (Path.GetExtension(applicationPath) == ".xci")
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (Path.GetExtension(GamePath) == ".xci")
|
|
||||||
{
|
{
|
||||||
Xci xci = new Xci(MainWindow._device.System.KeySet, file.AsStorage());
|
Xci xci = new Xci(MainWindow._device.System.KeySet, file.AsStorage());
|
||||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
|
||||||
}
|
}
|
||||||
|
else { controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage())); }
|
||||||
|
|
||||||
if (pfs != null)
|
// Creates NACP class from the NACP file
|
||||||
{
|
|
||||||
foreach (DirectoryEntry ticketEntry in pfs.EnumerateEntries("*.tik"))
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(pfs.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream());
|
|
||||||
|
|
||||||
if (!MainWindow._device.System.KeySet.TitleKeys.ContainsKey(ticket.RightsId))
|
|
||||||
{
|
|
||||||
MainWindow._device.System.KeySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(MainWindow._device.System.KeySet));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntry fileEntry in pfs.EnumerateEntries("*.nca"))
|
|
||||||
{
|
|
||||||
Nca nca = new Nca(MainWindow._device.System.KeySet, pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
|
|
||||||
if (nca.Header.ContentType == ContentType.Program)
|
|
||||||
{
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, ContentType.Program);
|
|
||||||
|
|
||||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
|
||||||
{
|
|
||||||
patchNca = nca;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mainNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (nca.Header.ContentType == ContentType.Control)
|
|
||||||
{
|
|
||||||
controlNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, MainWindow._device.System.FsIntegrityCheckLevel);
|
|
||||||
|
|
||||||
if (patchNca == null)
|
|
||||||
{
|
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
IFileSystem codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, MainWindow._device.System.FsIntegrityCheckLevel);
|
|
||||||
metaData = new Npdm(codeFs.OpenFile("/main.npdm", OpenMode.Read).AsStream());
|
|
||||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
IFileSystem codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, MainWindow._device.System.FsIntegrityCheckLevel);
|
|
||||||
metaData = new Npdm(codeFs.OpenFile("/main.npdm", OpenMode.Read).AsStream());
|
|
||||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((Path.GetExtension(GamePath) == ".nca") || (Path.GetExtension(GamePath) == ".nro") || (Path.GetExtension(GamePath) == ".nso"))
|
|
||||||
{
|
|
||||||
StringBuilder titleName = new StringBuilder();
|
|
||||||
titleName.Append(Path.GetFileName(GamePath));
|
|
||||||
titleName.Remove(Path.GetFileName(GamePath).Length - Path.GetExtension(GamePath).Length, Path.GetExtension(GamePath).Length);
|
|
||||||
|
|
||||||
TitleName = titleName.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IFile controlFile = controlFs.OpenFile("/control.nacp", OpenMode.Read);
|
IFile controlFile = controlFs.OpenFile("/control.nacp", OpenMode.Read);
|
||||||
Nacp controlData = new Nacp(controlFile.AsStream());
|
Nacp controlData = new Nacp(controlFile.AsStream());
|
||||||
|
|
||||||
TitleName = controlData.Descriptions[(int)MainWindow._device.System.State.DesiredTitleLanguage].Title;
|
// Get the title name, title ID, developer name and version number from the NACP
|
||||||
if (string.IsNullOrWhiteSpace(TitleName))
|
version = controlData.DisplayVersion;
|
||||||
|
|
||||||
|
titleName = controlData.Descriptions[(int)MainWindow._device.System.State.DesiredTitleLanguage].Title;
|
||||||
|
if (string.IsNullOrWhiteSpace(titleName))
|
||||||
{
|
{
|
||||||
TitleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Path.GetExtension(GamePath) == ".nca") { GameIcon = RyujinxNcaIcon; }
|
titleId = controlData.PresenceGroupId.ToString("X16");
|
||||||
|
if (string.IsNullOrWhiteSpace(titleId)) { titleId = controlData.SaveDataOwnerId.ToString("X16"); }
|
||||||
|
if (string.IsNullOrWhiteSpace(titleId)) { titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("X16"); }
|
||||||
|
|
||||||
else if ((Path.GetExtension(GamePath) == ".xci") || (Path.GetExtension(GamePath) == ".nsp") || (Path.GetExtension(GamePath) == ".pfs0"))
|
developer = controlData.Descriptions[(int)MainWindow._device.System.State.DesiredTitleLanguage].Developer;
|
||||||
|
if (string.IsNullOrWhiteSpace(developer))
|
||||||
{
|
{
|
||||||
|
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the icon from the ControlFS and store it as a byte array
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IFile logo = controlFs.OpenFile($"/icon_{MainWindow._device.System.State.DesiredTitleLanguage}.dat", OpenMode.Read);
|
IFile logo = controlFs.OpenFile($"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read);
|
||||||
GameIcon = new Gdk.Pixbuf(logo.AsStream(), 75, 75);
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
logo.AsStream().CopyTo(ms);
|
||||||
|
applicationIcon = ms.ToArray();
|
||||||
}
|
}
|
||||||
catch(FileNotFoundException)
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IFile logo = controlFs.OpenFile($"/icon_AmericanEnglish.dat", OpenMode.Read);
|
IFile logo = controlFs.OpenFile($"/icon_AmericanEnglish.dat", OpenMode.Read);
|
||||||
GameIcon = new Gdk.Pixbuf(logo.AsStream(), 75, 75);
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
logo.AsStream().CopyTo(ms);
|
||||||
|
applicationIcon = ms.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(GamePath) == ".xci") { GameIcon = RyujinxXciIcon; }
|
if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; }
|
||||||
else { GameIcon = RyujinxNspIcon; }
|
else { applicationIcon = RyujinxNspIcon; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (Path.GetExtension(GamePath) == ".nso") { GameIcon = RyujinxNsoIcon; }
|
else if (Path.GetExtension(applicationPath) == ".nro")
|
||||||
|
|
||||||
else if (Path.GetExtension(GamePath) == ".nro")
|
|
||||||
{
|
{
|
||||||
BinaryReader reader = new BinaryReader(file);
|
BinaryReader reader = new BinaryReader(file);
|
||||||
|
|
||||||
file.Seek(24, SeekOrigin.Begin);
|
|
||||||
int AssetOffset = reader.ReadInt32();
|
|
||||||
|
|
||||||
byte[] Read(long Position, int Size)
|
byte[] Read(long Position, int Size)
|
||||||
{
|
{
|
||||||
file.Seek(Position, SeekOrigin.Begin);
|
file.Seek(Position, SeekOrigin.Begin);
|
||||||
return reader.ReadBytes(Size);
|
return reader.ReadBytes(Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.Seek(24, SeekOrigin.Begin);
|
||||||
|
int AssetOffset = reader.ReadInt32();
|
||||||
|
|
||||||
if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
|
if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
|
||||||
{
|
{
|
||||||
byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
|
byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
|
||||||
|
|
||||||
long IconOffset = BitConverter.ToInt64(IconSectionInfo, 0);
|
long iconOffset = BitConverter.ToInt64(IconSectionInfo, 0);
|
||||||
long IconSize = BitConverter.ToInt64(IconSectionInfo, 8);
|
long iconSize = BitConverter.ToInt64(IconSectionInfo, 8);
|
||||||
|
|
||||||
byte[] IconData = Read(AssetOffset + IconOffset, (int)IconSize);
|
ulong nacpOffset = reader.ReadUInt64();
|
||||||
|
ulong nacpSize = reader.ReadUInt64();
|
||||||
|
|
||||||
GameIcon = new Gdk.Pixbuf(IconData, 75, 75);
|
// Reads and stores game icon as byte array
|
||||||
|
applicationIcon = Read(AssetOffset + iconOffset, (int)iconSize);
|
||||||
|
|
||||||
|
// Creates memory stream out of byte array which is the NACP
|
||||||
|
using (MemoryStream stream = new MemoryStream(Read(AssetOffset + (int)nacpOffset, (int)nacpSize)))
|
||||||
|
{
|
||||||
|
// Creates NACP class from the memory stream
|
||||||
|
Nacp controlData = new Nacp(stream);
|
||||||
|
|
||||||
|
// Get the title name, title ID, developer name and version number from the NACP
|
||||||
|
version = controlData.DisplayVersion;
|
||||||
|
|
||||||
|
titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
|
||||||
|
if (string.IsNullOrWhiteSpace(titleName))
|
||||||
|
{
|
||||||
|
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
titleId = controlData.PresenceGroupId.ToString("X16");
|
||||||
|
if (string.IsNullOrWhiteSpace(titleId)) { titleId = controlData.SaveDataOwnerId.ToString("X16"); }
|
||||||
|
if (string.IsNullOrWhiteSpace(titleId)) { titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("X16"); }
|
||||||
|
|
||||||
|
developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
|
||||||
|
if (string.IsNullOrWhiteSpace(developer))
|
||||||
|
{
|
||||||
|
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { applicationIcon = RyujinxNroIcon; titleName = "Application"; titleId = "0000000000000000"; developer = "Unknown"; version = "?"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// If its an NCA or NSO we just set defaults
|
||||||
|
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(applicationPath) == ".nca") { applicationIcon = RyujinxNcaIcon; }
|
||||||
|
else if (Path.GetExtension(applicationPath) == ".nso") { applicationIcon = RyujinxNsoIcon; }
|
||||||
|
|
||||||
|
StringBuilder titlename = new StringBuilder();
|
||||||
|
titlename.Append(Path.GetFileName(applicationPath));
|
||||||
|
titlename.Remove(Path.GetFileName(applicationPath).Length - Path.GetExtension(applicationPath).Length, Path.GetExtension(applicationPath).Length);
|
||||||
|
|
||||||
|
titleName = titlename.ToString();
|
||||||
|
titleId = "0000000000000000";
|
||||||
|
version = "?";
|
||||||
|
developer = "Unknown";
|
||||||
}
|
}
|
||||||
else { GameIcon = RyujinxNroIcon; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationData data = new ApplicationData()
|
ApplicationData data = new ApplicationData()
|
||||||
{
|
{
|
||||||
Icon = GameIcon,
|
Icon = applicationIcon,
|
||||||
GameName = TitleName,
|
TitleName = titleName,
|
||||||
GameId = TitleId,
|
TitleId = titleId,
|
||||||
TimePlayed = GetPlayedData(TitleId)[0],
|
Developer = developer,
|
||||||
LastPlayed = GetPlayedData(TitleId)[1],
|
Version = version,
|
||||||
|
TimePlayed = GetPlayedData(titleId)[0],
|
||||||
|
LastPlayed = GetPlayedData(titleId)[1],
|
||||||
FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB",
|
FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB",
|
||||||
Path = GamePath,
|
Path = applicationPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
ApplicationLibraryData.Add(data);
|
ApplicationLibraryData.Add(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IFileSystem GetControlFs(PartitionFileSystem Pfs)
|
||||||
|
{
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
// Add keys to keyset if needed
|
||||||
|
foreach (DirectoryEntry ticketEntry in Pfs.EnumerateEntries("*.tik"))
|
||||||
|
{
|
||||||
|
Ticket ticket = new Ticket(Pfs.OpenFile(ticketEntry.FullPath, OpenMode.Read).AsStream());
|
||||||
|
|
||||||
|
if (!KeySet.TitleKeys.ContainsKey(ticket.RightsId))
|
||||||
|
{
|
||||||
|
KeySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(MainWindow._device.System.KeySet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the Control NCA and store it in variable called controlNca
|
||||||
|
foreach (DirectoryEntry fileEntry in Pfs.EnumerateEntries("*.nca"))
|
||||||
|
{
|
||||||
|
Nca nca = new Nca(MainWindow._device.System.KeySet, Pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
|
||||||
|
if (nca.Header.ContentType == ContentType.Control)
|
||||||
|
{
|
||||||
|
controlNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the ControlFS
|
||||||
|
return controlNca.OpenFileSystem(NcaSectionType.Data, MainWindow._device.System.FsIntegrityCheckLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string[] GetPlayedData(string TitleId)
|
private static string[] GetPlayedData(string TitleId)
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx
|
namespace Ryujinx
|
||||||
|
@ -58,7 +59,7 @@ namespace Ryujinx
|
||||||
Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||||
Configuration.InitialConfigure(_device);
|
Configuration.InitialConfigure(_device);
|
||||||
|
|
||||||
ApplicationLibrary.Init();
|
ApplicationLibrary.Init(_device.System.KeySet, _device.System.State.DesiredTitleLanguage);
|
||||||
|
|
||||||
_gtkapp = gtkapp;
|
_gtkapp = gtkapp;
|
||||||
|
|
||||||
|
@ -97,12 +98,14 @@ namespace Ryujinx
|
||||||
Nfc.Sensitive = false;
|
Nfc.Sensitive = false;
|
||||||
ReturnMain.Sensitive = false;
|
ReturnMain.Sensitive = false;
|
||||||
GameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0);
|
GameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0);
|
||||||
GameTable.AppendColumn("Game", new CellRendererText(), "text", 1);
|
GameTable.AppendColumn("Application", new CellRendererText(), "text", 1);
|
||||||
GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 2);
|
GameTable.AppendColumn("Developer", new CellRendererText(), "text", 2);
|
||||||
GameTable.AppendColumn("Last Played", new CellRendererText(), "text", 3);
|
GameTable.AppendColumn("Version", new CellRendererText(), "text", 3);
|
||||||
GameTable.AppendColumn("File Size", new CellRendererText(), "text", 4);
|
GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4);
|
||||||
GameTable.AppendColumn("Path", new CellRendererText(), "text", 5);
|
GameTable.AppendColumn("Last Played", new CellRendererText(), "text", 5);
|
||||||
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
GameTable.AppendColumn("File Size", new CellRendererText(), "text", 6);
|
||||||
|
GameTable.AppendColumn("Path", new CellRendererText(), "text", 7);
|
||||||
|
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
||||||
GameTable.Model = _TableStore;
|
GameTable.Model = _TableStore;
|
||||||
UpdateGameTable();
|
UpdateGameTable();
|
||||||
//Temporary code section end
|
//Temporary code section end
|
||||||
|
@ -117,13 +120,15 @@ namespace Ryujinx
|
||||||
ReturnMain.Sensitive = false;
|
ReturnMain.Sensitive = false;
|
||||||
|
|
||||||
GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0);
|
GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0);
|
||||||
GameTable.AppendColumn("Game" , new CellRendererText() , "text" , 1);
|
GameTable.AppendColumn("Application", new CellRendererText() , "text" , 1);
|
||||||
GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 2);
|
GameTable.AppendColumn("Developer" , new CellRendererText() , "text" , 2);
|
||||||
GameTable.AppendColumn("Last Played", new CellRendererText() , "text" , 3);
|
GameTable.AppendColumn("Version" , new CellRendererText() , "text" , 3);
|
||||||
GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 4);
|
GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 4);
|
||||||
GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 5);
|
GameTable.AppendColumn("Last Played", new CellRendererText() , "text" , 5);
|
||||||
|
GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 6);
|
||||||
|
GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 7);
|
||||||
|
|
||||||
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
_TableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
||||||
GameTable.Model = _TableStore;
|
GameTable.Model = _TableStore;
|
||||||
|
|
||||||
UpdateGameTable();
|
UpdateGameTable();
|
||||||
|
@ -133,11 +138,11 @@ namespace Ryujinx
|
||||||
public static void UpdateGameTable()
|
public static void UpdateGameTable()
|
||||||
{
|
{
|
||||||
_TableStore.Clear();
|
_TableStore.Clear();
|
||||||
ApplicationLibrary.Init();
|
ApplicationLibrary.Init(_device.System.KeySet, _device.System.State.DesiredTitleLanguage);
|
||||||
|
|
||||||
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
|
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
|
||||||
{
|
{
|
||||||
_TableStore.AppendValues(AppData.Icon, $"{AppData.GameName}\n{AppData.GameId.ToUpper()}", AppData.TimePlayed, AppData.LastPlayed, AppData.FileSize, AppData.Path);
|
_TableStore.AppendValues(new Gdk.Pixbuf(AppData.Icon, 75, 75), $"{AppData.TitleName}\n{AppData.TitleId.ToUpper()}", AppData.Developer, AppData.Version, AppData.TimePlayed, AppData.LastPlayed, AppData.FileSize, AppData.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,9 +266,18 @@ namespace Ryujinx
|
||||||
{
|
{
|
||||||
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
string savePath = System.IO.Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", _device.System.TitleID);
|
string savePath = System.IO.Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", _device.System.TitleID);
|
||||||
using (FileStream fs = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
|
||||||
|
if (File.Exists(System.IO.Path.Combine(savePath, "TimePlayed.dat")) == false)
|
||||||
{
|
{
|
||||||
using (StreamWriter sr = new StreamWriter(fs))
|
Directory.CreateDirectory(savePath);
|
||||||
|
using (FileStream file = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat"))) { file.Write(Encoding.ASCII.GetBytes("0")); }
|
||||||
|
}
|
||||||
|
if (File.Exists(System.IO.Path.Combine(savePath, "LastPlayed.dat")) == false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(savePath);
|
||||||
|
using (FileStream file = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||||
|
{
|
||||||
|
using (StreamWriter sr = new StreamWriter(file))
|
||||||
{
|
{
|
||||||
sr.WriteLine(DateTime.UtcNow);
|
sr.WriteLine(DateTime.UtcNow);
|
||||||
}
|
}
|
||||||
|
@ -271,6 +285,7 @@ namespace Ryujinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void CreateGameWindow()
|
private static void CreateGameWindow()
|
||||||
{
|
{
|
||||||
|
@ -325,7 +340,7 @@ namespace Ryujinx
|
||||||
private void Row_Activated(object obj, RowActivatedArgs args)
|
private void Row_Activated(object obj, RowActivatedArgs args)
|
||||||
{
|
{
|
||||||
_TableStore.GetIter(out TreeIter treeiter, new TreePath(args.Path.ToString()));
|
_TableStore.GetIter(out TreeIter treeiter, new TreePath(args.Path.ToString()));
|
||||||
string path = (string)_TableStore.GetValue(treeiter, 5);
|
string path = (string)_TableStore.GetValue(treeiter, 7);
|
||||||
|
|
||||||
LoadApplication(path);
|
LoadApplication(path);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue