aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Gtk3/UI/Windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Gtk3/UI/Windows')
-rw-r--r--src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs9
-rw-r--r--src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs128
-rw-r--r--src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs92
3 files changed, 136 insertions, 93 deletions
diff --git a/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs
index 96ed0723..d9f01918 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs
@@ -1,7 +1,9 @@
using Gtk;
+using LibHac.Tools.FsSystem;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
@@ -27,8 +29,13 @@ namespace Ryujinx.UI.Windows
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
{
builder.Autoconnect(this);
+
+ IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
+ ? IntegrityCheckLevel.ErrorOnInvalid
+ : IntegrityCheckLevel.None;
+
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
- _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}";
+ _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath)}";
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
diff --git a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs
index 388f1108..b69cc003 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs
@@ -2,17 +2,21 @@ using Gtk;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
-using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
+using Ryujinx.HLE.Utilities;
+using Ryujinx.UI.App.Common;
using Ryujinx.UI.Widgets;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
+using System.Linq;
using GUI = Gtk.Builder.ObjectAttribute;
namespace Ryujinx.UI.Windows
@@ -20,7 +24,7 @@ namespace Ryujinx.UI.Windows
public class DlcWindow : Window
{
private readonly VirtualFileSystem _virtualFileSystem;
- private readonly string _titleId;
+ private readonly string _applicationId;
private readonly string _dlcJsonPath;
private readonly List<DownloadableContentContainer> _dlcContainerList;
@@ -32,16 +36,16 @@ namespace Ryujinx.UI.Windows
[GUI] TreeSelection _dlcTreeSelection;
#pragma warning restore CS0649, IDE0044
- public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
+ public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, applicationData) { }
- private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
+ private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationId, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow"))
{
builder.Autoconnect(this);
- _titleId = titleId;
+ _applicationId = applicationId;
_virtualFileSystem = virtualFileSystem;
- _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
- _baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
+ _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationId, "dlc.json");
+ _baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationId.ToUpper()}]";
try
{
@@ -72,7 +76,7 @@ namespace Ryujinx.UI.Windows
};
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
- _dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
+ _dlcTreeView.AppendColumn("ApplicationId", new CellRendererText(), "text", 1);
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
@@ -86,18 +90,18 @@ namespace Ryujinx.UI.Windows
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
- using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
+ using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(dlcContainer.ContainerPath, _virtualFileSystem, false);
- PartitionFileSystem pfs = new();
- pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
-
- _virtualFileSystem.ImportTickets(pfs);
+ if (partitionFileSystem == null)
+ {
+ continue;
+ }
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
{
using var ncaFile = new UniqueRef<IFile>();
- pfs.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ partitionFileSystem.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
if (nca != null)
@@ -112,6 +116,9 @@ namespace Ryujinx.UI.Windows
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}");
}
}
+
+ // NOTE: Try to load downloadable contents from PFS last to preserve enabled state.
+ AddDlc(applicationData.Path, true);
}
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
@@ -128,6 +135,52 @@ namespace Ryujinx.UI.Windows
return null;
}
+ private void AddDlc(string path, bool ignoreNotFound = false)
+ {
+ if (!File.Exists(path) || _dlcContainerList.Any(x => x.ContainerPath == path))
+ {
+ return;
+ }
+
+ using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
+
+ bool containsDlc = false;
+
+ TreeIter? parentIter = null;
+
+ foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef<IFile>();
+
+ partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
+
+ if (nca == null)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.PublicData)
+ {
+ if (nca.GetProgramIdBase() != (ulong.Parse(_applicationId, NumberStyles.HexNumber) & ~0x1FFFUL))
+ {
+ continue;
+ }
+
+ parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", path);
+
+ ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
+ containsDlc = true;
+ }
+ }
+
+ if (!containsDlc && !ignoreNotFound)
+ {
+ GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
+ }
+ }
+
private void AddButton_Clicked(object sender, EventArgs args)
{
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
@@ -147,52 +200,7 @@ namespace Ryujinx.UI.Windows
{
foreach (string containerPath in fileChooser.Filenames)
{
- if (!File.Exists(containerPath))
- {
- return;
- }
-
- using FileStream containerFile = File.OpenRead(containerPath);
-
- PartitionFileSystem pfs = new();
- pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
- bool containsDlc = false;
-
- _virtualFileSystem.ImportTickets(pfs);
-
- TreeIter? parentIter = null;
-
- foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
- {
- using var ncaFile = new UniqueRef<IFile>();
-
- pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
- Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
-
- if (nca == null)
- {
- continue;
- }
-
- if (nca.Header.ContentType == NcaContentType.PublicData)
- {
- if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
- {
- break;
- }
-
- parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
-
- ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
- containsDlc = true;
- }
- }
-
- if (!containsDlc)
- {
- GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
- }
+ AddDlc(containerPath);
}
}
diff --git a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs
index 74b2330e..3ac972ea 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs
@@ -2,14 +2,17 @@ using Gtk;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
-using LibHac.FsSystem;
+using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
+using Ryujinx.HLE.Utilities;
using Ryujinx.UI.App.Common;
+using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Widgets;
using System;
using System.Collections.Generic;
@@ -24,7 +27,7 @@ namespace Ryujinx.UI.Windows
{
private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem;
- private readonly string _titleId;
+ private readonly ApplicationData _applicationData;
private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData;
@@ -38,17 +41,17 @@ namespace Ryujinx.UI.Windows
[GUI] RadioButton _noUpdateRadioButton;
#pragma warning restore CS0649, IDE0044
- public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
+ public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, applicationData) { }
- private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
+ private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
{
_parent = parent;
builder.Autoconnect(this);
- _titleId = titleId;
+ _applicationData = applicationData;
_virtualFileSystem = virtualFileSystem;
- _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
+ _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "updates.json");
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
try
@@ -64,7 +67,10 @@ namespace Ryujinx.UI.Windows
};
}
- _baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
+ _baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdString}]";
+
+ // Try to get updates from PFS first
+ AddUpdate(_applicationData.Path, true);
foreach (string path in _titleUpdateWindowData.Paths)
{
@@ -84,47 +90,69 @@ namespace Ryujinx.UI.Windows
}
}
- private void AddUpdate(string path)
+ private void AddUpdate(string path, bool ignoreNotFound = false)
{
- if (File.Exists(path))
+ if (!File.Exists(path) || _radioButtonToPathDictionary.ContainsValue(path))
{
- using FileStream file = new(path, FileMode.Open, FileAccess.Read);
+ return;
+ }
- PartitionFileSystem nsp = new();
- nsp.Initialize(file.AsStorage()).ThrowIfFailure();
+ IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
+ ? IntegrityCheckLevel.ErrorOnInvalid
+ : IntegrityCheckLevel.None;
- try
- {
- (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
+ try
+ {
+ using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
- if (controlNca != null && patchNca != null)
- {
- ApplicationControlProperty controlData = new();
+ Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, _virtualFileSystem, checkLevel);
- using var nacpFile = new UniqueRef<IFile>();
+ Nca patchNca = null;
+ Nca controlNca = null;
- controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
- nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
+ if (updates.TryGetValue(_applicationData.Id, out ContentMetaData update))
+ {
+ patchNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Program);
+ controlNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Control);
+ }
- RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}");
- radioButton.JoinGroup(_noUpdateRadioButton);
+ if (controlNca != null && patchNca != null)
+ {
+ ApplicationControlProperty controlData = new();
- _availableUpdatesBox.Add(radioButton);
- _radioButtonToPathDictionary.Add(radioButton, path);
+ using var nacpFile = new UniqueRef<IFile>();
- radioButton.Show();
- radioButton.Active = true;
- }
- else
+ controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
+
+ string radioLabel = $"Version {controlData.DisplayVersionString.ToString()} - {path}";
+
+ if (System.IO.Path.GetExtension(path).ToLower() == ".xci")
{
- GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
+ radioLabel = "Bundled: " + radioLabel;
}
+
+ RadioButton radioButton = new(radioLabel);
+ radioButton.JoinGroup(_noUpdateRadioButton);
+
+ _availableUpdatesBox.Add(radioButton);
+ _radioButtonToPathDictionary.Add(radioButton, path);
+
+ radioButton.Show();
+ radioButton.Active = true;
}
- catch (Exception exception)
+ else
{
- GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
+ if (!ignoreNotFound)
+ {
+ GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
+ }
}
}
+ catch (Exception exception)
+ {
+ GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
+ }
}
private void RemoveUpdates(bool removeSelectedOnly = false)