| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- using LibHac;
- using LibHac.Fs;
- using LibHac.Fs.NcaUtils;
- using Ryujinx.Common.Logging;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using SystemState = Ryujinx.HLE.HOS.SystemState;
- namespace Ryujinx.UI
- {
- public class ApplicationLibrary
- {
- private static Keyset KeySet;
- private static SystemState.TitleLanguage DesiredTitleLanguage;
- private const double SecondsPerMinute = 60.0;
- private const double SecondsPerHour = SecondsPerMinute * 60;
- private const double SecondsPerDay = SecondsPerHour * 24;
- 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 byte[] Icon;
- public string TitleName;
- public string TitleId;
- public string Developer;
- public string Version;
- public string TimePlayed;
- public string LastPlayed;
- public string FileExt;
- public string FileSize;
- public string Path;
- }
- public static void Init(List<string> AppDirs, Keyset keySet, SystemState.TitleLanguage desiredTitleLanguage)
- {
- KeySet = keySet;
- DesiredTitleLanguage = desiredTitleLanguage;
- // Loads the default application Icons
- RyujinxNspIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSPIcon.png");
- RyujinxXciIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxXCIIcon.png");
- RyujinxNcaIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNCAIcon.png");
- RyujinxNroIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNROIcon.png");
- RyujinxNsoIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSOIcon.png");
- // Builds the applications list with paths to found applications
- List<string> applications = new List<string>();
- foreach (string appDir in AppDirs)
- {
- if (Directory.Exists(appDir) == false)
- {
- Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
- continue;
- }
- DirectoryInfo AppDirInfo = new DirectoryInfo(appDir);
- foreach (FileInfo App in AppDirInfo.GetFiles())
- {
- if ((Path.GetExtension(App.ToString()) == ".xci") ||
- (Path.GetExtension(App.ToString()) == ".nca") ||
- (Path.GetExtension(App.ToString()) == ".nsp") ||
- (Path.GetExtension(App.ToString()) == ".pfs0") ||
- (Path.GetExtension(App.ToString()) == ".nro") ||
- (Path.GetExtension(App.ToString()) == ".nso"))
- {
- applications.Add(App.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 applicationPath in applications)
- {
- 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(applicationPath, FileMode.Open, FileAccess.Read))
- {
- if ((Path.GetExtension(applicationPath) == ".nsp") ||
- (Path.GetExtension(applicationPath) == ".pfs0") ||
- (Path.GetExtension(applicationPath) == ".xci"))
- {
- try
- {
- IFileSystem controlFs = null;
- // Store the ControlFS in variable called controlFs
- if (Path.GetExtension(applicationPath) == ".xci")
- {
- Xci xci = new Xci(KeySet, file.AsStorage());
- controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
- }
- else
- {
- controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage()));
- }
- // Creates NACP class from the NACP file
- IFile controlNacp = controlFs.OpenFile("/control.nacp", OpenMode.Read);
- Nacp controlData = new Nacp(controlNacp.AsStream());
- // 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;
- }
- // Read the icon from the ControlFS and store it as a byte array
- try
- {
- IFile icon = controlFs.OpenFile($"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read);
- using (MemoryStream stream = new MemoryStream())
- {
- icon.AsStream().CopyTo(stream);
- applicationIcon = stream.ToArray();
- }
- }
- catch (HorizonResultException)
- {
- IDirectory controlDir = controlFs.OpenDirectory("./", OpenDirectoryMode.All);
- foreach (DirectoryEntry entry in controlDir.Read())
- {
- if (entry.Name == "control.nacp")
- {
- continue;
- }
- IFile icon = controlFs.OpenFile(entry.FullPath, OpenMode.Read);
- using (MemoryStream stream = new MemoryStream())
- {
- icon.AsStream().CopyTo(stream);
- applicationIcon = stream.ToArray();
- }
- if (applicationIcon != null)
- {
- break;
- }
- }
- if (applicationIcon == null)
- {
- applicationIcon = NspOrXciIcon(applicationPath);
- }
- }
- }
- catch (MissingKeyException exception)
- {
- titleName = "Unknown";
- titleId = "Unknown";
- developer = "Unknown";
- version = "?";
- applicationIcon = NspOrXciIcon(applicationPath);
- Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
- }
- catch (InvalidDataException)
- {
- titleName = "Unknown";
- titleId = "Unknown";
- developer = "Unknown";
- version = "?";
- applicationIcon = NspOrXciIcon(applicationPath);
- Logger.PrintWarning(LogClass.Application, $"The file is not an NCA file or the header key is incorrect. Errored File: {applicationPath}");
- }
- catch (Exception exception)
- {
- Logger.PrintWarning(LogClass.Application, $"This warning usualy means that you have a DLC in one of you game directories\n{exception}");
- continue;
- }
- }
- else if (Path.GetExtension(applicationPath) == ".nro")
- {
- BinaryReader reader = new BinaryReader(file);
- 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);
- ulong nacpOffset = reader.ReadUInt64();
- ulong nacpSize = reader.ReadUInt64();
- // 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;
- }
- string fileName = Path.GetFileName(applicationPath);
- string fileExt = Path.GetExtension(applicationPath);
- StringBuilder titlename = new StringBuilder();
- titlename.Append(fileName);
- titlename.Remove(fileName.Length - fileExt.Length, fileExt.Length);
- titleName = titlename.ToString();
- titleId = "0000000000000000";
- version = "?";
- developer = "Unknown";
- }
- }
- string[] playedData = GetPlayedData(titleId, "00000000000000000000000000000001");
- ApplicationData data = new ApplicationData()
- {
- Icon = applicationIcon,
- TitleName = titleName,
- TitleId = titleId,
- Developer = developer,
- Version = version,
- TimePlayed = playedData[0],
- LastPlayed = playedData[1],
- FileExt = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
- FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB",
- Path = applicationPath,
- };
- ApplicationLibraryData.Add(data);
- }
- }
- private static byte[] GetResourceBytes(string resourceName)
- {
- Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
- byte[] resourceByteArray = new byte[resourceStream.Length];
- resourceStream.Read(resourceByteArray);
- return resourceByteArray;
- }
- 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(KeySet));
- }
- }
- // Find the Control NCA and store it in variable called controlNca
- foreach (DirectoryEntry fileEntry in Pfs.EnumerateEntries("*.nca"))
- {
- Nca nca = new Nca(KeySet, Pfs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage());
- if (nca.Header.ContentType == ContentType.Control)
- {
- controlNca = nca;
- }
- }
- // Return the ControlFS
- return controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
- }
- private static string[] GetPlayedData(string TitleId, string UserId)
- {
- try
- {
- string[] playedData = new string[2];
- string savePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", UserId, 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 < SecondsPerMinute)
- {
- playedData[0] = $"{timePlayed}s";
- }
- else if (timePlayed < SecondsPerHour)
- {
- playedData[0] = $"{Math.Round(timePlayed / SecondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
- }
- else if (timePlayed < SecondsPerDay)
- {
- playedData[0] = $"{Math.Round(timePlayed / SecondsPerHour , 2, MidpointRounding.AwayFromZero)} hrs";
- }
- else
- {
- playedData[0] = $"{Math.Round(timePlayed / SecondsPerDay , 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;
- }
- catch
- {
- return new string[] { "Unknown", "Unknown" };
- }
- }
- private static byte[] NspOrXciIcon(string applicationPath)
- {
- if (Path.GetExtension(applicationPath) == ".xci")
- {
- return RyujinxXciIcon;
- }
- else
- {
- return RyujinxNspIcon;
- }
- }
- }
- }
|