diff --git a/Ryujinx.HLE/ApplicationLibrary.cs b/Ryujinx.HLE/ApplicationLibrary.cs index 9785371d1c..61880d8f5b 100644 --- a/Ryujinx.HLE/ApplicationLibrary.cs +++ b/Ryujinx.HLE/ApplicationLibrary.cs @@ -33,6 +33,7 @@ namespace Ryujinx public string Version; public string TimePlayed; public string LastPlayed; + public string FileExt; public string FileSize; public string Path; } @@ -105,54 +106,45 @@ namespace Ryujinx { if ((Path.GetExtension(applicationPath) == ".nsp") || (Path.GetExtension(applicationPath) == ".pfs0") || (Path.GetExtension(applicationPath) == ".xci")) { - 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 controlFile = controlFs.OpenFile("/control.nacp", OpenMode.Read); - Nacp controlData = new Nacp(controlFile.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 ms = new MemoryStream()) + IFileSystem controlFs = null; + + // Store the ControlFS in variable called controlFs + if (Path.GetExtension(applicationPath) == ".xci") { - icon.AsStream().CopyTo(ms); - applicationIcon = ms.ToArray(); + Xci xci = new Xci(KeySet, file.AsStorage()); + controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure)); } - } - catch (FileNotFoundException) - { + 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_AmericanEnglish.dat", OpenMode.Read); + IFile icon = controlFs.OpenFile($"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read); using (MemoryStream ms = new MemoryStream()) { icon.AsStream().CopyTo(ms); @@ -161,10 +153,52 @@ namespace Ryujinx } catch (FileNotFoundException) { - if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; } - else { applicationIcon = RyujinxNspIcon; } + 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 ms = new MemoryStream()) + { + icon.AsStream().CopyTo(ms); + applicationIcon = ms.ToArray(); + } + + if (applicationIcon != null) { break; } + } + + if (applicationIcon == null) + { + if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; } + else { applicationIcon = RyujinxNspIcon; } + } } } + catch (MissingKeyException exception) + { + titleName = "Unknown"; + titleId = "Unknown"; + developer = "Unknown"; + version = "?"; + + if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; } + else { applicationIcon = RyujinxNspIcon; } + + Logger.PrintError(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); + } + catch (InvalidDataException) + { + titleName = "Unknown"; + titleId = "Unknown"; + developer = "Unknown"; + version = "?"; + + if (Path.GetExtension(applicationPath) == ".xci") { applicationIcon = RyujinxXciIcon; } + else { applicationIcon = RyujinxNspIcon; } + + Logger.PrintError(LogClass.Application, $"Unable to decrypt NCA header. The header key must be incorrect. File: {applicationPath}"); + } } else if (Path.GetExtension(applicationPath) == ".nro") @@ -250,6 +284,7 @@ namespace Ryujinx 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, }; @@ -289,42 +324,46 @@ namespace Ryujinx private static string[] GetPlayedData(string TitleId, string UserId) { - string[] playedData = new string[2]; - string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - string savePath = Path.Combine(appdataPath, "RyuFs", "nand", "user", "save", "0000000000000000", UserId, TitleId); + try + { + string[] playedData = new string[2]; + string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + string savePath = Path.Combine(appdataPath, "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)) + if (File.Exists(Path.Combine(savePath, "TimePlayed.dat")) == false) { - 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"; } + Directory.CreateDirectory(savePath); + using (FileStream file = File.OpenWrite(Path.Combine(savePath, "TimePlayed.dat"))) { file.Write(Encoding.ASCII.GetBytes("0")); } } - } - - 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)) + using (FileStream fs = File.OpenRead(Path.Combine(savePath, "TimePlayed.dat"))) { - playedData[1] = sr.ReadLine(); - } - } + using (StreamReader sr = new StreamReader(fs)) + { + float timePlayed = float.Parse(sr.ReadLine()); - return playedData; + 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; + } + catch { return new string[] { "Unknown", "Unknown" }; } } } } diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 65327c9d10..21f12f95b4 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -591,11 +591,11 @@ namespace Ryujinx.HLE.HOS staticObject = new NxStaticObject(input); } + ContentManager.LoadEntries(); + TitleName = CurrentTitle = metaData.TitleName; TitleID = metaData.Aci0.TitleId.ToString("x16"); - ContentManager.LoadEntries(); - ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject }); } diff --git a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs index b0ca689518..169e68daf3 100644 --- a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +++ b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -4,8 +4,8 @@ using System.Text; namespace Ryujinx.HLE.Loaders.Npdm { - // 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.c + // https://github.com/SciresM/hactool/blob/master/npdm.h // http://switchbrew.org/index.php?title=NPDM class Npdm { @@ -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; set; } + public string TitleName { get; set; } public byte[] ProductCode { get; private set; } public Aci0 Aci0 { get; private set; } diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json index f7ce6039c3..d6ed189adc 100644 --- a/Ryujinx/Config.json +++ b/Ryujinx/Config.json @@ -18,6 +18,7 @@ "enable_legacy_jit": false, "ignore_missing_services": false, "controller_type": "Handheld", + "gui_columns": [ true, true, true, true, true, true, true, true, true ], "game_dirs": [], "enable_custom_theme": false, "custom_theme_path": "", @@ -55,7 +56,7 @@ } }, "joystick_controls": { - "enabled": false, + "enabled": true, "index": 0, "deadzone": 0.05, "trigger_threshold": 0.5, diff --git a/Ryujinx/Configuration.cs b/Ryujinx/Configuration.cs index ef9aab42b8..0297554ed0 100644 --- a/Ryujinx/Configuration.cs +++ b/Ryujinx/Configuration.cs @@ -60,17 +60,17 @@ namespace Ryujinx /// /// Enables printing guest log messages /// - public bool LoggingEnableGuest { get; private set; } + public bool LoggingEnableGuest { get; set; } /// /// Enables printing FS access log messages /// - public bool LoggingEnableFsAccessLog { get; private set; } + public bool LoggingEnableFsAccessLog { get; set; } /// /// Controls which log messages are written to the log targets /// - public LogClass[] LoggingFilteredClasses { get; private set; } + public LogClass[] LoggingFilteredClasses { get; set; } /// /// Enables or disables logging to a file on disk @@ -125,7 +125,12 @@ namespace Ryujinx /// /// The primary controller's type /// - public ControllerStatus ControllerType { get; private set; } + public ControllerStatus ControllerType { get; set; } + + /// + /// Used to toggle columns in the GUI + /// + public List GuiColumns { get; set; } /// /// A list of directories containing games to be used to load games into the games list diff --git a/Ryujinx/Ui/AboutWindow.cs b/Ryujinx/Ui/AboutWindow.cs index 65a23ba423..62b9a4e8d5 100644 --- a/Ryujinx/Ui/AboutWindow.cs +++ b/Ryujinx/Ui/AboutWindow.cs @@ -11,6 +11,7 @@ namespace Ryujinx.UI { #pragma warning disable 649 [GUI] Window AboutWin; + [GUI] Label VersionText; [GUI] Image RyujinxLogo; [GUI] Image PatreonLogo; [GUI] Image GitHubLogo; @@ -23,12 +24,15 @@ namespace Ryujinx.UI private AboutWindow(Builder builder) : base(builder.GetObject("AboutWin").Handle) { builder.Autoconnect(this); + AboutWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png"); - RyujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png", 220, 220); + RyujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png", 100, 100); PatreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 ); GitHubLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 ); DiscordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 ); TwitterLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.TwitterLogo.png", 30 , 30 ); + + VersionText.Text = "Version x.x.x (Commit Number)"; } public void OpenUrl(string url) diff --git a/Ryujinx/Ui/AboutWindow.glade b/Ryujinx/Ui/AboutWindow.glade index d732890fcd..188a32c3d8 100644 --- a/Ryujinx/Ui/AboutWindow.glade +++ b/Ryujinx/Ui/AboutWindow.glade @@ -7,47 +7,18 @@ False True 800 - 400 + 350 dialog - + False vertical - 2 False - end - - - True - False - - - - True - True - 0 - - - - - Close - True - True - True - - - - True - True - 5 - 1 - - False @@ -63,98 +34,159 @@ True False + 10 + 15 + 10 + 15 vertical True False + start + vertical -