aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Gtk3/UI
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Gtk3/UI')
-rw-r--r--src/Ryujinx.Gtk3/UI/MainWindow.cs107
-rw-r--r--src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs108
-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
5 files changed, 263 insertions, 181 deletions
diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs
index d1ca6ce6..7f9eceb3 100644
--- a/src/Ryujinx.Gtk3/UI/MainWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs
@@ -37,7 +37,9 @@ using Ryujinx.UI.Windows;
using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using System;
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -60,7 +62,6 @@ namespace Ryujinx.UI
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
- private readonly ApplicationLibrary _applicationLibrary;
private readonly GtkHostUIHandler _uiHandler;
private readonly AutoResetEvent _deviceExitStatus;
private readonly ListStore _tableStore;
@@ -69,11 +70,12 @@ namespace Ryujinx.UI
private bool _gameLoaded;
private bool _ending;
- private string _currentEmulatedGamePath = null;
+ private ApplicationData _currentApplicationData = null;
private string _lastScannedAmiiboId = "";
private bool _lastScannedAmiiboShowAll = false;
+ public readonly ApplicationLibrary ApplicationLibrary;
public RendererWidgetBase RendererWidget;
public InputManager InputManager;
@@ -180,8 +182,12 @@ namespace Ryujinx.UI
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
_userChannelPersistence = new UserChannelPersistence();
+ IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
+ ? IntegrityCheckLevel.ErrorOnInvalid
+ : IntegrityCheckLevel.None;
+
// Instantiate GUI objects.
- _applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
+ ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
_uiHandler = new GtkHostUIHandler(this);
_deviceExitStatus = new AutoResetEvent(false);
@@ -190,8 +196,8 @@ namespace Ryujinx.UI
FocusInEvent += MainWindow_FocusInEvent;
FocusOutEvent += MainWindow_FocusOutEvent;
- _applicationLibrary.ApplicationAdded += Application_Added;
- _applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
+ ApplicationLibrary.ApplicationAdded += Application_Added;
+ ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
_fileMenu.StateChanged += FileMenu_StateChanged;
_actionMenu.StateChanged += ActionMenu_StateChanged;
@@ -732,7 +738,7 @@ namespace Ryujinx.UI
Thread applicationLibraryThread = new(() =>
{
- _applicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
+ ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
_updatingGameTable = false;
})
@@ -783,7 +789,7 @@ namespace Ryujinx.UI
}
}
- private bool LoadApplication(string path, bool isFirmwareTitle)
+ private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
{
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
@@ -857,7 +863,7 @@ namespace Ryujinx.UI
case ".xci":
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
- return _emulationContext.LoadXci(path);
+ return _emulationContext.LoadXci(path, applicationId);
case ".nca":
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
@@ -866,7 +872,7 @@ namespace Ryujinx.UI
case ".pfs0":
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
- return _emulationContext.LoadNsp(path);
+ return _emulationContext.LoadNsp(path, applicationId);
default:
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
try
@@ -887,7 +893,7 @@ namespace Ryujinx.UI
return false;
}
- public void RunApplication(string path, bool startFullscreen = false)
+ public void RunApplication(ApplicationData application, bool startFullscreen = false)
{
if (_gameLoaded)
{
@@ -909,14 +915,14 @@ namespace Ryujinx.UI
bool isFirmwareTitle = false;
- if (path.StartsWith("@SystemContent"))
+ if (application.Path.StartsWith("@SystemContent"))
{
- path = VirtualFileSystem.SwitchPathToSystemPath(path);
+ application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
isFirmwareTitle = true;
}
- if (!LoadApplication(path, isFirmwareTitle))
+ if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
{
_emulationContext.Dispose();
SwitchToGameTable();
@@ -926,7 +932,7 @@ namespace Ryujinx.UI
SetupProgressUIHandlers();
- _currentEmulatedGamePath = path;
+ _currentApplicationData = application;
_deviceExitStatus.Reset();
@@ -1165,7 +1171,7 @@ namespace Ryujinx.UI
_tableStore.AppendValues(
args.AppData.Favorite,
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
- $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
+ $"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}",
args.AppData.Developer,
args.AppData.Version,
args.AppData.TimePlayedString,
@@ -1253,9 +1259,22 @@ namespace Ryujinx.UI
{
_gameTableSelection.GetSelected(out TreeIter treeIter);
- string path = (string)_tableStore.GetValue(treeIter, 9);
+ ApplicationData application = new()
+ {
+ Favorite = (bool)_tableStore.GetValue(treeIter, 0),
+ Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
+ Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
+ Developer = (string)_tableStore.GetValue(treeIter, 3),
+ Version = (string)_tableStore.GetValue(treeIter, 4),
+ TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
+ LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
+ FileExtension = (string)_tableStore.GetValue(treeIter, 7),
+ FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
+ Path = (string)_tableStore.GetValue(treeIter, 9),
+ ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
+ };
- RunApplication(path);
+ RunApplication(application);
}
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
@@ -1313,13 +1332,22 @@ namespace Ryujinx.UI
return;
}
- string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
- string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
- string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
-
- BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
+ ApplicationData application = new()
+ {
+ Favorite = (bool)_tableStore.GetValue(treeIter, 0),
+ Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
+ Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
+ Developer = (string)_tableStore.GetValue(treeIter, 3),
+ Version = (string)_tableStore.GetValue(treeIter, 4),
+ TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
+ LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
+ FileExtension = (string)_tableStore.GetValue(treeIter, 7),
+ FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
+ Path = (string)_tableStore.GetValue(treeIter, 9),
+ ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
+ };
- _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
+ _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
}
private void Load_Application_File(object sender, EventArgs args)
@@ -1341,7 +1369,15 @@ namespace Ryujinx.UI
if (fileChooser.Run() == (int)ResponseType.Accept)
{
- RunApplication(fileChooser.Filename);
+ if (ApplicationLibrary.TryGetApplicationsFromFile(fileChooser.Filename,
+ out List<ApplicationData> applications))
+ {
+ RunApplication(applications[0]);
+ }
+ else
+ {
+ GtkDialog.CreateErrorDialog("No applications found in selected file.");
+ }
}
}
@@ -1351,7 +1387,13 @@ namespace Ryujinx.UI
if (fileChooser.Run() == (int)ResponseType.Accept)
{
- RunApplication(fileChooser.Filename);
+ ApplicationData applicationData = new()
+ {
+ Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename),
+ Path = fileChooser.Filename,
+ };
+
+ RunApplication(applicationData);
}
}
@@ -1366,7 +1408,14 @@ namespace Ryujinx.UI
{
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
- RunApplication(contentPath);
+ ApplicationData applicationData = new()
+ {
+ Name = "miiEdit",
+ Id = 0x0100000000001009ul,
+ Path = contentPath,
+ };
+
+ RunApplication(applicationData);
}
private void Open_Ryu_Folder(object sender, EventArgs args)
@@ -1646,13 +1695,13 @@ namespace Ryujinx.UI
{
_userChannelPersistence.ShouldRestart = false;
- RunApplication(_currentEmulatedGamePath);
+ RunApplication(_currentApplicationData);
}
else
{
// otherwise, clear state.
_userChannelPersistence = new UserChannelPersistence();
- _currentEmulatedGamePath = null;
+ _currentApplicationData = null;
_actionMenu.Sensitive = false;
_firmwareInstallFile.Sensitive = true;
_firmwareInstallDirectory.Sensitive = true;
@@ -1714,7 +1763,7 @@ namespace Ryujinx.UI
_emulationContext.Processes.ActiveApplication.ProgramId,
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
- _currentEmulatedGamePath);
+ _currentApplicationData.Path);
window.Destroyed += CheatWindow_Destroyed;
window.Show();
diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs
index c8236223..e37906d5 100644
--- a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs
+++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs
@@ -16,6 +16,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.HLE.Loaders.Processes.Extensions;
+using Ryujinx.HLE.Utilities;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@@ -23,7 +25,6 @@ using Ryujinx.UI.Windows;
using System;
using System.Buffers;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -36,17 +37,13 @@ namespace Ryujinx.UI.Widgets
private readonly VirtualFileSystem _virtualFileSystem;
private readonly AccountManager _accountManager;
private readonly HorizonClient _horizonClient;
- private readonly BlitStruct<ApplicationControlProperty> _controlData;
- private readonly string _titleFilePath;
- private readonly string _titleName;
- private readonly string _titleIdText;
- private readonly ulong _titleId;
+ private readonly ApplicationData _applicationData;
private MessageDialog _dialog;
private bool _cancel;
- public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
+ public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, ApplicationData applicationData)
{
_parent = parent;
@@ -55,23 +52,22 @@ namespace Ryujinx.UI.Widgets
_virtualFileSystem = virtualFileSystem;
_accountManager = accountManager;
_horizonClient = horizonClient;
- _titleFilePath = titleFilePath;
- _titleName = titleName;
- _titleIdText = titleId;
- _controlData = controlData;
+ _applicationData = applicationData;
- if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
+ if (!_applicationData.ControlHolder.ByteSpan.IsZeros())
{
- GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
-
- return;
+ _openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0;
+ _openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0;
+ _openSaveBcatDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
+ }
+ else
+ {
+ _openSaveUserDirMenuItem.Sensitive = false;
+ _openSaveDeviceDirMenuItem.Sensitive = false;
+ _openSaveBcatDirMenuItem.Sensitive = false;
}
- _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
- _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
- _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
-
- string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
+ string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower();
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
_extractRomFsMenuItem.Sensitive = hasNca;
@@ -137,7 +133,7 @@ namespace Ryujinx.UI.Widgets
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
{
- if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
+ if (!TryFindSaveData(_applicationData.Name, _applicationData.Id, _applicationData.ControlHolder, in saveDataFilter, out ulong saveDataId))
{
return;
}
@@ -190,7 +186,7 @@ namespace Ryujinx.UI.Widgets
{
Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
- SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
+ SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_applicationData.Path)}...",
WindowPosition = WindowPosition.Center,
};
@@ -202,29 +198,16 @@ namespace Ryujinx.UI.Widgets
}
});
- using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read);
+ using FileStream file = new(_applicationData.Path, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
Nca patchNca = null;
- if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
- (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
- (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
+ if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") ||
+ (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") ||
+ (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci"))
{
- IFileSystem pfs;
-
- if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
- {
- Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
-
- pfs = xci.OpenPartition(XciPartitionType.Secure);
- }
- else
- {
- var pfsTemp = new PartitionFileSystem();
- pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
- pfs = pfsTemp;
- }
+ IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
@@ -249,7 +232,7 @@ namespace Ryujinx.UI.Widgets
}
}
}
- else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
+ else if (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nca")
{
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
}
@@ -266,7 +249,11 @@ namespace Ryujinx.UI.Widgets
return;
}
- (Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
+ IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
+ ? IntegrityCheckLevel.ErrorOnInvalid
+ : IntegrityCheckLevel.None;
+
+ (Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
if (updatePatchNca != null)
{
@@ -460,44 +447,44 @@ namespace Ryujinx.UI.Widgets
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
{
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
- var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
+ var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDir(in saveDataFilter);
}
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
{
- var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
+ var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Device, userId: default, saveDataId: default, index: default);
OpenSaveDir(in saveDataFilter);
}
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
{
- var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
+ var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
OpenSaveDir(in saveDataFilter);
}
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
{
- new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
+ new TitleUpdateWindow(_parent, _virtualFileSystem, _applicationData).Show();
}
private void ManageDlc_Clicked(object sender, EventArgs args)
{
- new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
+ new DlcWindow(_virtualFileSystem, _applicationData.IdString, _applicationData).Show();
}
private void ManageCheats_Clicked(object sender, EventArgs args)
{
- new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show();
+ new CheatWindow(_virtualFileSystem, _applicationData.Id, _applicationData.Name, _applicationData.Path).Show();
}
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
{
string modsBasePath = ModLoader.GetModsBasePath();
- string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText);
+ string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -505,7 +492,7 @@ namespace Ryujinx.UI.Widgets
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
- string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText);
+ string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -527,7 +514,7 @@ namespace Ryujinx.UI.Widgets
private void OpenPtcDir_Clicked(object sender, EventArgs args)
{
- string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
+ string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu");
string mainPath = System.IO.Path.Combine(ptcDir, "0");
string backupPath = System.IO.Path.Combine(ptcDir, "1");
@@ -544,7 +531,7 @@ namespace Ryujinx.UI.Widgets
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
{
- string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
+ string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
@@ -556,10 +543,10 @@ namespace Ryujinx.UI.Widgets
private void PurgePtcCache_Clicked(object sender, EventArgs args)
{
- DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
- DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
+ DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0"));
+ DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "1"));
- MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
+ MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
List<FileInfo> cacheFiles = new();
@@ -593,9 +580,9 @@ namespace Ryujinx.UI.Widgets
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
{
- DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
+ DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader"));
- using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
+ using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
@@ -637,8 +624,11 @@ namespace Ryujinx.UI.Widgets
private void CreateShortcut_Clicked(object sender, EventArgs args)
{
- byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
- ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
+ IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
+ ? IntegrityCheckLevel.ErrorOnInvalid
+ : IntegrityCheckLevel.None;
+ byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_applicationData.Path, ConfigurationState.Instance.System.Language, _applicationData.Id);
+ ShortcutHelper.CreateAppShortcut(_applicationData.Path, _applicationData.Name, _applicationData.IdString, appIcon);
}
}
}
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)