Added LastPlayed and TimePlayed columns to the game list
This commit is contained in:
parent
e1ba904108
commit
708a518e10
9 changed files with 149 additions and 36 deletions
|
@ -3,7 +3,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class Aci0
|
public 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;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class Acid
|
public 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
|
||||||
{
|
{
|
||||||
class FsAccessControl
|
public 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
|
||||||
{
|
{
|
||||||
class FsAccessHeader
|
public 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
|
||||||
{
|
{
|
||||||
class KernelAccessControl
|
public 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
|
||||||
class Npdm
|
public 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;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Npdm
|
namespace Ryujinx.HLE.Loaders.Npdm
|
||||||
{
|
{
|
||||||
class ServiceAccessControl
|
public class ServiceAccessControl
|
||||||
{
|
{
|
||||||
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
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;
|
||||||
|
@ -25,6 +26,7 @@ namespace Ryujinx
|
||||||
{
|
{
|
||||||
public Gdk.Pixbuf Icon;
|
public Gdk.Pixbuf Icon;
|
||||||
public string GameName;
|
public string GameName;
|
||||||
|
public string GameId;
|
||||||
public string TimePlayed;
|
public string TimePlayed;
|
||||||
public string LastPlayed;
|
public string LastPlayed;
|
||||||
public string FileSize;
|
public string FileSize;
|
||||||
|
@ -72,10 +74,14 @@ namespace Ryujinx
|
||||||
|
|
||||||
using (FileStream file = new FileStream(GamePath, FileMode.Open, FileAccess.Read))
|
using (FileStream file = new FileStream(GamePath, FileMode.Open, FileAccess.Read))
|
||||||
{
|
{
|
||||||
|
Nca mainNca = null;
|
||||||
|
Nca patchNca = null;
|
||||||
Nca controlNca = null;
|
Nca controlNca = null;
|
||||||
PartitionFileSystem pfs = null;
|
PartitionFileSystem pfs = null;
|
||||||
IFileSystem controlFs = null;
|
IFileSystem controlFs = null;
|
||||||
|
Npdm metaData = null;
|
||||||
string TitleName = null;
|
string TitleName = null;
|
||||||
|
string TitleId = "010000000000100D";
|
||||||
Gdk.Pixbuf GameIcon = null;
|
Gdk.Pixbuf GameIcon = null;
|
||||||
|
|
||||||
if ((Path.GetExtension(GamePath) == ".nsp") || (Path.GetExtension(GamePath) == ".pfs0"))
|
if ((Path.GetExtension(GamePath) == ".nsp") || (Path.GetExtension(GamePath) == ".pfs0"))
|
||||||
|
@ -104,13 +110,45 @@ namespace Ryujinx
|
||||||
foreach (DirectoryEntry fileEntry in pfs.EnumerateEntries("*.nca"))
|
foreach (DirectoryEntry fileEntry in pfs.EnumerateEntries("*.nca"))
|
||||||
{
|
{
|
||||||
Nca nca = new Nca(MainWindow._device.System.KeySet, pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
|
Nca nca = new Nca(MainWindow._device.System.KeySet, pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
|
||||||
if (nca.Header.ContentType == ContentType.Control)
|
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;
|
controlNca = nca;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, MainWindow._device.System.FsIntegrityCheckLevel);
|
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")) { TitleName = Path.GetFileName(GamePath); }
|
if ((Path.GetExtension(GamePath) == ".nca") || (Path.GetExtension(GamePath) == ".nro") || (Path.GetExtension(GamePath) == ".nso")) { TitleName = Path.GetFileName(GamePath); }
|
||||||
|
@ -183,8 +221,9 @@ namespace Ryujinx
|
||||||
{
|
{
|
||||||
Icon = GameIcon,
|
Icon = GameIcon,
|
||||||
GameName = TitleName,
|
GameName = TitleName,
|
||||||
TimePlayed = "",
|
GameId = TitleId,
|
||||||
LastPlayed = "",
|
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 = GamePath,
|
||||||
};
|
};
|
||||||
|
@ -193,5 +232,45 @@ namespace Ryujinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] GetPlayedData(string TitleId)
|
||||||
|
{
|
||||||
|
string[] playedData = new string[2];
|
||||||
|
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
string savePath = Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", TitleId);
|
||||||
|
|
||||||
|
if (File.Exists(Path.Combine(savePath, "TimePlayed.dat")) == false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(savePath);
|
||||||
|
using (FileStream file = File.OpenWrite(Path.Combine(savePath, "TimePlayed.dat"))) { file.Write(Encoding.ASCII.GetBytes("0")); }
|
||||||
|
}
|
||||||
|
using (FileStream fs = File.OpenRead(Path.Combine(savePath, "TimePlayed.dat")))
|
||||||
|
{
|
||||||
|
using (StreamReader sr = new StreamReader(fs))
|
||||||
|
{
|
||||||
|
float timePlayed = float.Parse(sr.ReadLine());
|
||||||
|
|
||||||
|
if (timePlayed <= 60.0) { playedData[0] = $"{timePlayed}s"; }
|
||||||
|
else if(timePlayed <= 3600.0) { playedData[0] = $"{Math.Round(timePlayed / 60 , 2, MidpointRounding.AwayFromZero)}mins"; }
|
||||||
|
else if(timePlayed <= 86400.0) { playedData[0] = $"{Math.Round(timePlayed / 3600 , 2, MidpointRounding.AwayFromZero)}hrs"; }
|
||||||
|
else { playedData[0] = $"{Math.Round(timePlayed / 86400, 2, MidpointRounding.AwayFromZero)}days"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(Path.Combine(savePath, "LastPlayed.dat")) == false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(savePath);
|
||||||
|
using (FileStream file = File.OpenWrite(Path.Combine(savePath, "LastPlayed.dat"))) { file.Write(Encoding.ASCII.GetBytes("Never")); }
|
||||||
|
}
|
||||||
|
using (FileStream fs = File.OpenRead(Path.Combine(savePath, "LastPlayed.dat")))
|
||||||
|
{
|
||||||
|
using (StreamReader sr = new StreamReader(fs))
|
||||||
|
{
|
||||||
|
playedData[1] = sr.ReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return playedData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,8 +101,8 @@ namespace Ryujinx
|
||||||
|
|
||||||
GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0);
|
GameTable.AppendColumn("Icon" , new CellRendererPixbuf(), "pixbuf", 0);
|
||||||
GameTable.AppendColumn("Game" , new CellRendererText() , "text" , 1);
|
GameTable.AppendColumn("Game" , new CellRendererText() , "text" , 1);
|
||||||
//GameTable.AppendColumn("Time Played", new CellRendererText(), "text", 2);
|
GameTable.AppendColumn("Time Played", new CellRendererText() , "text" , 2);
|
||||||
//GameTable.AppendColumn("Last Played", new CellRendererText(), "text", 3);
|
GameTable.AppendColumn("Last Played", new CellRendererText() , "text" , 3);
|
||||||
GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 4);
|
GameTable.AppendColumn("File Size" , new CellRendererText() , "text" , 4);
|
||||||
GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 5);
|
GameTable.AppendColumn("Path" , new CellRendererText() , "text" , 5);
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ namespace Ryujinx
|
||||||
|
|
||||||
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
|
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
|
||||||
{
|
{
|
||||||
_TableStore.AppendValues(AppData.Icon, AppData.GameName, AppData.TimePlayed, AppData.LastPlayed, AppData.FileSize, AppData.Path);
|
_TableStore.AppendValues(AppData.Icon, $"{AppData.GameName}\n{AppData.GameId.ToUpper()}", AppData.TimePlayed, AppData.LastPlayed, AppData.FileSize, AppData.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ namespace Ryujinx
|
||||||
{
|
{
|
||||||
if (_GameLoaded)
|
if (_GameLoaded)
|
||||||
{
|
{
|
||||||
MessageDialog eRrOr = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "A game has already been loaded, please close the game and try again");
|
MessageDialog eRrOr = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, "A game has already been loaded, please unload the game and try again");
|
||||||
eRrOr.SetSizeRequest(100, 20);
|
eRrOr.SetSizeRequest(100, 20);
|
||||||
eRrOr.Title = "Ryujinx - Error";
|
eRrOr.Title = "Ryujinx - Error";
|
||||||
eRrOr.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxIcon.png");
|
eRrOr.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.GUI.assets.ryujinxIcon.png");
|
||||||
|
@ -235,10 +235,20 @@ namespace Ryujinx
|
||||||
DiscordPresence.Assets.LargeImageText = _device.System.TitleName;
|
DiscordPresence.Assets.LargeImageText = _device.System.TitleName;
|
||||||
DiscordPresence.Assets.SmallImageKey = "ryujinx";
|
DiscordPresence.Assets.SmallImageKey = "ryujinx";
|
||||||
DiscordPresence.Assets.SmallImageText = "Ryujinx is an emulator for the Nintendo Switch";
|
DiscordPresence.Assets.SmallImageText = "Ryujinx is an emulator for the Nintendo Switch";
|
||||||
DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow);
|
DiscordPresence.Timestamps = new Timestamps(DateTime.UnixEpoch);
|
||||||
|
|
||||||
DiscordClient.SetPresence(DiscordPresence);
|
DiscordClient.SetPresence(DiscordPresence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")))
|
||||||
|
{
|
||||||
|
using (StreamWriter sr = new StreamWriter(fs))
|
||||||
|
{
|
||||||
|
sr.WriteLine(DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreateGameWindow()
|
private static void CreateGameWindow()
|
||||||
|
@ -255,6 +265,42 @@ namespace Ryujinx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void End()
|
||||||
|
{
|
||||||
|
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
string savePath = System.IO.Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", "savecommon", _device.System.TitleID);
|
||||||
|
double currentPlayTime = 0;
|
||||||
|
|
||||||
|
using (FileStream fs = File.OpenRead(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||||
|
{
|
||||||
|
using (StreamReader sr = new StreamReader(fs))
|
||||||
|
{
|
||||||
|
DateTime startTime = DateTime.Parse(sr.ReadLine());
|
||||||
|
|
||||||
|
using (FileStream lpfs = File.OpenRead(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||||
|
{
|
||||||
|
using (StreamReader lpsr = new StreamReader(lpfs))
|
||||||
|
{
|
||||||
|
currentPlayTime = double.Parse(lpsr.ReadLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream tpfs = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||||
|
{
|
||||||
|
using (StreamWriter tpsr = new StreamWriter(tpfs))
|
||||||
|
{
|
||||||
|
tpsr.WriteLine(currentPlayTime + Math.Round(DateTime.UtcNow.Subtract(startTime).TotalSeconds, MidpointRounding.AwayFromZero));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_audioOut.Dispose();
|
||||||
|
DiscordClient.Dispose();
|
||||||
|
Logger.Shutdown();
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
private void Row_Activated(object obj, RowActivatedArgs args)
|
private void Row_Activated(object obj, RowActivatedArgs args)
|
||||||
{
|
{
|
||||||
|
@ -303,21 +349,9 @@ namespace Ryujinx
|
||||||
fc.Destroy();
|
fc.Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Exit_Pressed(object o, EventArgs args)
|
private void Exit_Pressed(object o, EventArgs args) { End(); }
|
||||||
{
|
|
||||||
_audioOut.Dispose();
|
|
||||||
DiscordClient.Dispose();
|
|
||||||
Logger.Shutdown();
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Window_Close(object obj, DeleteEventArgs args)
|
private void Window_Close(object obj, DeleteEventArgs args) { End(); }
|
||||||
{
|
|
||||||
_audioOut.Dispose();
|
|
||||||
DiscordClient.Dispose();
|
|
||||||
Logger.Shutdown();
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReturnMain_Pressed(object o, EventArgs args)
|
private void ReturnMain_Pressed(object o, EventArgs args)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue