From bcfe4f6b8a0f0dc374e21c3bcbb0ef63d70c7e57 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 27 Jul 2024 01:26:05 +0200 Subject: [PATCH 01/16] optimization: Load application metadata only for applications with IDs --- .../App/ApplicationLibrary.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index e7c48162aa..610c69dde4 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -422,35 +422,40 @@ namespace Ryujinx.UI.App.Common foreach (var data in applications) { - ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata => + // Only load metadata for applications with an ID + if (data.Id != 0) { - appMetadata.Title = data.Name; - - // Only do the migration if time_played has a value and timespan_played hasn't been updated yet. - if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero) + ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata => { - appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld); - appMetadata.TimePlayedOld = default; - } + appMetadata.Title = data.Name; - // Only do the migration if last_played has a value and last_played_utc doesn't exist yet. - if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue) - { - // Migrate from string-based last_played to DateTime-based last_played_utc. - if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed)) + // Only do the migration if time_played has a value and timespan_played hasn't been updated yet. + if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero) { - appMetadata.LastPlayed = lastPlayedOldParsed; - - // Migration successful: deleting last_played from the metadata file. - appMetadata.LastPlayedOld = default; + appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld); + appMetadata.TimePlayedOld = default; } - } - }); + // Only do the migration if last_played has a value and last_played_utc doesn't exist yet. + if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue) + { + // Migrate from string-based last_played to DateTime-based last_played_utc. + if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed)) + { + appMetadata.LastPlayed = lastPlayedOldParsed; + + // Migration successful: deleting last_played from the metadata file. + appMetadata.LastPlayedOld = default; + } + + } + }); + + data.Favorite = appMetadata.Favorite; + data.TimePlayed = appMetadata.TimePlayed; + data.LastPlayed = appMetadata.LastPlayed; + } - data.Favorite = appMetadata.Favorite; - data.TimePlayed = appMetadata.TimePlayed; - data.LastPlayed = appMetadata.LastPlayed; data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); data.FileSize = fileSize; data.Path = applicationPath; From 49e390b1d51d9959df5be8b579afdd09baf50cc2 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 27 Jul 2024 01:29:53 +0200 Subject: [PATCH 02/16] Load applications when necessary This prevents loading applications when launching an application directly from the command line (or a shortcut). Instead, applications will be loaded after the emulation was stopped by the user. --- src/Ryujinx.Gtk3/Program.cs | 6 ++++++ src/Ryujinx.Gtk3/UI/MainWindow.cs | 1 - src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 14 +++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs index 0fb712885b..8bb6516409 100644 --- a/src/Ryujinx.Gtk3/Program.cs +++ b/src/Ryujinx.Gtk3/Program.cs @@ -256,6 +256,12 @@ namespace Ryujinx MainWindow mainWindow = new(); mainWindow.Show(); + // Load the game table if no application was requested by the command line + if (CommandLineState.LaunchPathArg == null) + { + mainWindow.UpdateGameTable(); + } + if (OperatingSystem.IsLinux()) { int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs index 7f9eceb38a..309690bc42 100644 --- a/src/Ryujinx.Gtk3/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -325,7 +325,6 @@ namespace Ryujinx.UI _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); UpdateColumns(); - UpdateGameTable(); ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => { diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index dc5336ab3f..b0065e9d14 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.Windows internal static MainWindowViewModel MainWindowViewModel { get; private set; } private bool _isLoading; + private bool _applicationsLoadedOnce; private UserChannelPersistence _userChannelPersistence; private static bool _deferLoad; @@ -472,7 +473,11 @@ namespace Ryujinx.Ava.UI.Windows ViewModel.RefreshFirmwareStatus(); - LoadApplications(); + // Load applications if no application was requested by the command line + if (!_deferLoad) + { + LoadApplications(); + } #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed CheckLaunchState(); @@ -485,6 +490,12 @@ namespace Ryujinx.Ava.UI.Windows if (MainContent.Content != content) { + // Load applications while switching to the GameLibrary if we haven't done that yet + if (!_applicationsLoadedOnce && content == GameLibrary) + { + LoadApplications(); + } + MainContent.Content = content; } } @@ -581,6 +592,7 @@ namespace Ryujinx.Ava.UI.Windows public void LoadApplications() { + _applicationsLoadedOnce = true; ViewModel.Applications.Clear(); StatusBarView.LoadProgressBar.IsVisible = true; From 360f98c577eff86723cf238600c815a4c32f3d82 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 27 Jul 2024 01:30:47 +0200 Subject: [PATCH 03/16] Show the title in the configured language when launching an application --- src/Ryujinx.Gtk3/UI/MainWindow.cs | 8 ++++++-- src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 10 ++++------ src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 8 ++++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs index 309690bc42..cba2d239fa 100644 --- a/src/Ryujinx.Gtk3/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -187,7 +187,10 @@ namespace Ryujinx.UI : IntegrityCheckLevel.None; // Instantiate GUI objects. - ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel); + ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel) + { + DesiredTitleLanguage = ConfigurationState.Instance.System.Language, + }; _uiHandler = new GtkHostUIHandler(this); _deviceExitStatus = new AutoResetEvent(false); @@ -737,7 +740,8 @@ namespace Ryujinx.UI Thread applicationLibraryThread = new(() => { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); + ApplicationLibrary.DesiredTitleLanguage = ConfigurationState.Instance.System.Language; + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); _updatingGameTable = false; }) diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 610c69dde4..6dd8d8548a 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -34,6 +34,7 @@ namespace Ryujinx.UI.App.Common { public class ApplicationLibrary { + public Language DesiredTitleLanguage { get; set; } public event EventHandler ApplicationAdded; public event EventHandler ApplicationCountUpdated; @@ -45,7 +46,6 @@ namespace Ryujinx.UI.App.Common private readonly VirtualFileSystem _virtualFileSystem; private readonly IntegrityCheckLevel _checkLevel; - private Language _desiredTitleLanguage; private CancellationTokenSource _cancellationToken; private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); @@ -221,7 +221,7 @@ namespace Ryujinx.UI.App.Common { using UniqueRef icon = new(); - controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); using MemoryStream stream = new(); @@ -477,13 +477,11 @@ namespace Ryujinx.UI.App.Common controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); } - public void LoadApplications(List appDirs, Language desiredTitleLanguage) + public void LoadApplications(List appDirs) { int numApplicationsFound = 0; int numApplicationsLoaded = 0; - _desiredTitleLanguage = desiredTitleLanguage; - _cancellationToken = new CancellationTokenSource(); // Builds the applications list with paths to found applications @@ -832,7 +830,7 @@ namespace Ryujinx.UI.App.Common private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data) { - _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); + _ = Enum.TryParse(DesiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage) { diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index b0065e9d14..6cec3edacc 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -225,7 +225,10 @@ namespace Ryujinx.Ava.UI.Windows ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; - ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel); + ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel) + { + DesiredTitleLanguage = ConfigurationState.Instance.System.Language, + }; // Save data created before we supported extra data in directory save data will not work properly if // given empty extra data. Luckily some of that extra data can be created using the data from the @@ -634,7 +637,8 @@ namespace Ryujinx.Ava.UI.Windows Thread applicationLibraryThread = new(() => { - ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language); + ApplicationLibrary.DesiredTitleLanguage = ConfigurationState.Instance.System.Language; + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); _isLoading = false; }) From 9f85d20f86420106f55daee2b95ce008d139aa64 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:16:21 +0200 Subject: [PATCH 04/16] Wait for async task to complete This way exceptions thrown during the execution of CheckLaunchState() will correctly invoke the unhandled exception handler and cause Ryujinx to crash. --- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 6cec3edacc..564f1fc555 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -482,9 +482,7 @@ namespace Ryujinx.Ava.UI.Windows LoadApplications(); } -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - CheckLaunchState(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + CheckLaunchState().Wait(); } private void SetMainContent(Control content = null) From 7b67342922049a61062b9b8e5056152211fd82ca Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:05:10 +0200 Subject: [PATCH 05/16] optimization: Avoid enumerating applications multiple times --- src/Ryujinx.UI.Common/App/ApplicationLibrary.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 6dd8d8548a..540e90a25e 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -556,19 +556,13 @@ namespace Ryujinx.UI.App.Common { AppData = application, }); - } - if (applications.Count > 1) - { - numApplicationsFound += applications.Count - 1; + numApplicationsFound++; + numApplicationsLoaded++; } + } - numApplicationsLoaded += applications.Count; - } - else - { - numApplicationsFound--; - } + numApplicationsFound--; OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs { From f0ba2e87f633a725f60595d871f8dc8b6028ab08 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:05:58 +0200 Subject: [PATCH 06/16] Fix check for application id when launching directly from the command line --- src/Ryujinx.Gtk3/Program.cs | 11 ++++------- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs index 8bb6516409..1b7cd3cc18 100644 --- a/src/Ryujinx.Gtk3/Program.cs +++ b/src/Ryujinx.Gtk3/Program.cs @@ -331,15 +331,13 @@ namespace Ryujinx { if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List applications)) { - ApplicationData applicationData; - if (CommandLineState.LaunchApplicationId != null) { - applicationData = applications.Find(application => application.IdString == CommandLineState.LaunchApplicationId); + int applicationIndex = applications.FindIndex(application => application.IdString.Equals(CommandLineState.LaunchApplicationId, StringComparison.InvariantCultureIgnoreCase)); - if (applicationData != null) + if (applicationIndex != -1) { - mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg); + mainWindow.RunApplication(applications[applicationIndex], CommandLineState.StartFullscreenArg); } else { @@ -349,8 +347,7 @@ namespace Ryujinx } else { - applicationData = applications[0]; - mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg); + mainWindow.RunApplication(applications[0], CommandLineState.StartFullscreenArg); } } else diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 564f1fc555..05cbca6c50 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -325,15 +325,13 @@ namespace Ryujinx.Ava.UI.Windows if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List applications)) { - ApplicationData applicationData; - if (_launchApplicationId != null) { - applicationData = applications.Find(application => application.IdString == _launchApplicationId); + int applicationIndex = applications.FindIndex(application => application.IdString.Equals(_launchApplicationId, StringComparison.InvariantCultureIgnoreCase)); - if (applicationData != null) + if (applicationIndex != -1) { - await ViewModel.LoadApplication(applicationData, _startFullscreen); + await ViewModel.LoadApplication(applications[applicationIndex], _startFullscreen); } else { @@ -343,8 +341,7 @@ namespace Ryujinx.Ava.UI.Windows } else { - applicationData = applications[0]; - await ViewModel.LoadApplication(applicationData, _startFullscreen); + await ViewModel.LoadApplication(applications[0], _startFullscreen); } } else From 81300d9ce58f624f3737c7b41fabb62e3ba9b11d Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:13:10 +0200 Subject: [PATCH 07/16] Catch HorizonResultException in TryLoad --- .../PartitionFileSystemExtensions.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs index b3590d9bd7..b3d409e082 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -71,10 +71,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions if (applicationId == 0) { - foreach ((ulong _, ContentMetaData content) in applications) + foreach ((ulong id, ContentMetaData content) in applications) { mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); + applicationId = id; break; } } @@ -142,7 +143,24 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions } } - return (true, mainNca.Load(device, patchNca, controlNca)); + try + { + return (true, mainNca.Load(device, patchNca, controlNca)); + } + catch (HorizonResultException ex) + { + // The exception message already contains enough information here. + errorMessage = $"Failed to load: {ex.Message}"; + + return (false, ProcessResult.Failed); + } + catch (Exception ex) + { + // Add the stacktrace in addition to the exception message. + errorMessage = $"Failed to load: {ex}"; + + return (false, ProcessResult.Failed); + } } errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\""; From 2e3bd311022c2a08ec81275c842c8159ae61e978 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 11 Jul 2024 00:37:28 +0200 Subject: [PATCH 08/16] Add utility methods to resolve actual file paths --- .../Utilities/FileSystemUtils.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs index a57fa8a788..06bcbff760 100644 --- a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -52,5 +52,36 @@ namespace Ryujinx.Common.Utilities var reservedChars = new HashSet(Path.GetInvalidFileNameChars()); return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c)); } + + public static string ResolveFullPath(string path, bool isDirectory) + { + FileSystemInfo pathInfo = isDirectory ? new DirectoryInfo(path) : new FileInfo(path); + + return pathInfo.ResolveLinkTarget(true)?.FullName ?? pathInfo.FullName; + } + + public static FileInfo GetActualFileInfo(this FileInfo fileInfo) + { + return (FileInfo)(fileInfo.ResolveLinkTarget(true) ?? fileInfo); + } + + public static FileInfo GetActualFileInfo(string filePath) + { + FileInfo fileInfo = new(filePath); + + return (FileInfo)(fileInfo.ResolveLinkTarget(true) ?? fileInfo); + } + + public static DirectoryInfo GetActualDirectoryInfo(this DirectoryInfo directoryInfo) + { + return (DirectoryInfo)(directoryInfo.ResolveLinkTarget(true) ?? directoryInfo); + } + + public static DirectoryInfo GetActualDirectoryInfo(string directoryPath) + { + DirectoryInfo directoryInfo = new(directoryPath); + + return (DirectoryInfo)(directoryInfo.ResolveLinkTarget(true) ?? directoryInfo); + } } } From eab85667e161e9d90a980b05feaea919eae6ef7e Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 11 Jul 2024 00:39:28 +0200 Subject: [PATCH 09/16] Fix filename trimming and other path related issues in OpenFileSystemFromInternalFile --- .../FileSystemProxy/FileSystemProxyHelper.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs index 20ffb996d2..8cb2da33e8 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs @@ -10,6 +10,7 @@ using LibHac.Tools.Es; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Utilities; using System; using System.IO; using System.Runtime.InteropServices; @@ -78,17 +79,21 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent; - while (string.IsNullOrWhiteSpace(archivePath.Extension)) + while (archivePath != null && string.IsNullOrWhiteSpace(archivePath.Extension)) { archivePath = archivePath.Parent; } - if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName)) + if (archivePath == null) { - FileStream pfsFile = new( - archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar), - FileMode.Open, - FileAccess.Read); + return ResultCode.PathDoesNotExist; + } + + FileInfo archiveInfo = FileSystemUtils.GetActualFileInfo(Path.TrimEndingDirectorySeparator(archivePath.FullName)); + + if (archivePath.Extension.ToLower() == ".nsp" && archiveInfo.Exists) + { + FileStream pfsFile = archiveInfo.Open(FileMode.Open, FileAccess.Read); try { @@ -97,7 +102,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); - string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart('\\'); + string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart(Path.DirectorySeparatorChar); using var ncaFile = new UniqueRef(); From d3d08ae13611cd48da13ca98687c6ccc833a1420 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 11 Jul 2024 00:43:27 +0200 Subject: [PATCH 10/16] Throw an exception instead of returning null for SwitchPathToSystemPath and GetFullPath --- src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 0827266a12..d98ba182f7 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -98,14 +98,14 @@ namespace Ryujinx.HLE.FileSystem } else { - return null; + throw new ArgumentException($"The filename does not start with '/': {fileName}", nameof(fileName)); } string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName)); if (!fullPath.StartsWith(AppDataManager.BaseDirPath)) { - return null; + throw new ArgumentException($"The path is not located inside the Ryujinx directory: {fullPath}", nameof(basePath)); } return fullPath; @@ -120,7 +120,7 @@ namespace Ryujinx.HLE.FileSystem if (parts.Length != 2) { - return null; + throw new ArgumentException($"Invalid switch fs path provided: {switchPath}", nameof(switchPath)); } return GetFullPath(MakeFullPath(parts[0]), parts[1]); From eb7fb9da747208a6ed5c31ae49ef706a72f7b0c5 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 11 Jul 2024 00:47:56 +0200 Subject: [PATCH 11/16] Handle symlinks correctly when resolving file paths --- .../Configuration/AppDataManager.cs | 39 ++++++++----------- src/Ryujinx.Gtk3/UI/MainWindow.cs | 3 +- src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs | 5 ++- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 19 ++++----- .../FileSystem/VirtualFileSystem.cs | 34 +++++++++------- .../HOS/Applets/Error/ErrorApplet.cs | 19 ++++++--- src/Ryujinx.HLE/HOS/HorizonFsClient.cs | 10 ++--- .../ApplicationProxy/IApplicationFunctions.cs | 11 ++++-- .../HOS/Services/Fs/IFileSystemProxy.cs | 31 ++++++++------- .../HOS/Services/Sdb/Pl/SharedFontManager.cs | 5 ++- .../Settings/ISystemSettingsServer.cs | 3 +- .../Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs | 10 +++-- .../Services/Ssl/BuiltInCertificateManager.cs | 4 +- .../Time/TimeZone/TimeZoneContentManager.cs | 10 +++-- .../App/ApplicationLibrary.cs | 5 +-- .../UserFirmwareAvatarSelectorViewModel.cs | 5 ++- 16 files changed, 116 insertions(+), 97 deletions(-) diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs index deaa03def1..34a14b632d 100644 --- a/src/Ryujinx.Common/Configuration/AppDataManager.cs +++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs @@ -44,7 +44,7 @@ namespace Ryujinx.Common.Configuration static AppDataManager() { - KeysDirPathUser = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch"); + KeysDirPathUser = FileSystemUtils.ResolveFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch"), true); } public static void Initialize(string baseDirPath) @@ -56,23 +56,23 @@ namespace Ryujinx.Common.Configuration appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); } - string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir); - string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir); + string userProfilePath = FileSystemUtils.ResolveFullPath(Path.Combine(appDataPath, DefaultBaseDir), true); + DirectoryInfo portableInfo = FileSystemUtils.GetActualDirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir)); // On macOS, check for a portable directory next to the app bundle as well. - if (OperatingSystem.IsMacOS() && !Directory.Exists(portablePath)) + if (OperatingSystem.IsMacOS() && !portableInfo.Exists) { string bundlePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..")); // Make sure we're actually running within an app bundle. if (bundlePath.EndsWith(".app")) { - portablePath = Path.GetFullPath(Path.Combine(bundlePath, "..", DefaultPortableDir)); + portableInfo = FileSystemUtils.GetActualDirectoryInfo(Path.Combine(bundlePath, "..", DefaultPortableDir)); } } - if (Directory.Exists(portablePath)) + if (portableInfo.Exists) { - BaseDirPath = portablePath; + BaseDirPath = portableInfo.FullName; Mode = LaunchMode.Portable; } else @@ -83,24 +83,19 @@ namespace Ryujinx.Common.Configuration if (baseDirPath != null && baseDirPath != userProfilePath) { - if (!Directory.Exists(baseDirPath)) + DirectoryInfo baseDirInfo = FileSystemUtils.GetActualDirectoryInfo(baseDirPath); + + if (!baseDirInfo.Exists) { Logger.Error?.Print(LogClass.Application, $"Custom Data Directory '{baseDirPath}' does not exist. Falling back to {Mode}..."); } else { - BaseDirPath = baseDirPath; + BaseDirPath = baseDirInfo.FullName; Mode = LaunchMode.Custom; } } - BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths - - if (IsPathSymlink(BaseDirPath)) - { - Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended."); - } - SetupBasePaths(); } @@ -217,16 +212,16 @@ namespace Ryujinx.Common.Configuration } } - return logDir; + return FileSystemUtils.ResolveFullPath(logDir, true); } private static void SetupBasePaths() { Directory.CreateDirectory(BaseDirPath); LogsDirPath = SetUpLogsDir(); - Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir)); - Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir)); - Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir)); + Directory.CreateDirectory(GamesDirPath = FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, GamesDir), true)); + Directory.CreateDirectory(ProfilesDirPath = FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, ProfilesDir), true)); + Directory.CreateDirectory(KeysDirPath = FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, KeysDir), true)); } // Check if existing old baseDirPath is a symlink, to prevent possible errors. @@ -320,7 +315,7 @@ namespace Ryujinx.Common.Configuration } } - public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName; - public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName; + public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, DefaultModsDir), true)).FullName; + public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere"), true)).FullName; } } diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs index cba2d239fa..8aa5fef7a3 100644 --- a/src/Ryujinx.Gtk3/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -15,6 +15,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; using Ryujinx.Cpu; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; @@ -920,7 +921,7 @@ namespace Ryujinx.UI if (application.Path.StartsWith("@SystemContent")) { - application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path); + application.Path = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(application.Path), false); isFirmwareTitle = true; } diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs index d9ecd47b76..7af87a7114 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs @@ -7,6 +7,7 @@ using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.UI.Common.Configuration; using SixLabors.ImageSharp; @@ -119,10 +120,10 @@ namespace Ryujinx.UI.Windows } string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); - string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); - if (!string.IsNullOrWhiteSpace(avatarPath)) + if (!string.IsNullOrWhiteSpace(contentPath)) { + string avatarPath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(contentPath), false); using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index e6c0fce081..1cb08c4050 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -349,18 +349,15 @@ namespace Ryujinx.HLE.FileSystem return false; } - string installedPath = VirtualFileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); + FileInfo installedInfo = FileSystemUtils.GetActualFileInfo(VirtualFileSystem.SwitchPathToSystemPath(locationEntry.ContentPath)); - if (!string.IsNullOrWhiteSpace(installedPath)) + if (installedInfo.Exists) { - if (File.Exists(installedPath)) - { - using FileStream file = new(installedPath, FileMode.Open, FileAccess.Read); - Nca nca = new(_virtualFileSystem.KeySet, file.AsStorage()); - bool contentCheck = nca.Header.ContentType == contentType; + using FileStream file = new(installedInfo.FullName, FileMode.Open, FileAccess.Read); + Nca nca = new(_virtualFileSystem.KeySet, file.AsStorage()); + bool contentCheck = nca.Header.ContentType == contentType; - return contentCheck; - } + return contentCheck; } return false; @@ -941,9 +938,9 @@ namespace Ryujinx.HLE.FileSystem { if (entry.ContentType == NcaContentType.Data) { - var path = VirtualFileSystem.SwitchPathToSystemPath(entry.ContentPath); + var fileInfo = FileSystemUtils.GetActualFileInfo(VirtualFileSystem.SwitchPathToSystemPath(entry.ContentPath)); - using FileStream fileStream = File.OpenRead(path); + using FileStream fileStream = fileInfo.OpenRead(); Nca nca = new(_virtualFileSystem.KeySet, fileStream.AsStorage()); if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index d98ba182f7..ecdd6ea83e 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -14,6 +14,7 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS; using System; using System.Buffers.Text; @@ -22,14 +23,15 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using Path = System.IO.Path; +using SpanHelpers = LibHac.Common.SpanHelpers; namespace Ryujinx.HLE.FileSystem { public class VirtualFileSystem : IDisposable { - public static readonly string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe"); - public static readonly string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system"); - public static readonly string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user"); + public static readonly string SafeNandPath = FileSystemUtils.ResolveFullPath(Path.Combine(AppDataManager.DefaultNandDir, "safe"), true); + public static readonly string SystemNandPath = FileSystemUtils.ResolveFullPath(Path.Combine(AppDataManager.DefaultNandDir, "system"), true); + public static readonly string UserNandPath = FileSystemUtils.ResolveFullPath(Path.Combine(AppDataManager.DefaultNandDir, "user"), true); public KeySet KeySet { get; private set; } public EmulatedGameCard GameCard { get; private set; } @@ -61,7 +63,9 @@ namespace Ryujinx.HLE.FileSystem public void LoadRomFs(ulong pid, string fileName) { - var romfsStream = new FileStream(fileName, FileMode.Open, FileAccess.Read); + FileInfo fileInfo = FileSystemUtils.GetActualFileInfo(fileName); + + var romfsStream = fileInfo.Open(FileMode.Open, FileAccess.Read); _romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) => { @@ -111,8 +115,8 @@ namespace Ryujinx.HLE.FileSystem return fullPath; } - internal static string GetSdCardPath() => MakeFullPath(AppDataManager.DefaultSdcardDir); - public static string GetNandPath() => MakeFullPath(AppDataManager.DefaultNandDir); + internal static string GetSdCardPath() => FileSystemUtils.ResolveFullPath(MakeFullPath(AppDataManager.DefaultSdcardDir), true); + public static string GetNandPath() => FileSystemUtils.ResolveFullPath(MakeFullPath(AppDataManager.DefaultNandDir), true); public static string SwitchPathToSystemPath(string switchPath) { @@ -236,23 +240,23 @@ namespace Ryujinx.HLE.FileSystem void LoadSetAtPath(string basePath) { - string localKeyFile = Path.Combine(basePath, "prod.keys"); - string localTitleKeyFile = Path.Combine(basePath, "title.keys"); - string localConsoleKeyFile = Path.Combine(basePath, "console.keys"); + FileInfo localKeyFile = FileSystemUtils.GetActualFileInfo(Path.Combine(basePath, "prod.keys")); + FileInfo localTitleKeyFile = FileSystemUtils.GetActualFileInfo(Path.Combine(basePath, "title.keys")); + FileInfo localConsoleKeyFile = FileSystemUtils.GetActualFileInfo(Path.Combine(basePath, "console.keys")); - if (File.Exists(localKeyFile)) + if (localKeyFile.Exists) { - keyFile = localKeyFile; + keyFile = localKeyFile.FullName; } - if (File.Exists(localTitleKeyFile)) + if (localTitleKeyFile.Exists) { - titleKeyFile = localTitleKeyFile; + titleKeyFile = localTitleKeyFile.FullName; } - if (File.Exists(localConsoleKeyFile)) + if (localConsoleKeyFile.Exists) { - consoleKeyFile = localConsoleKeyFile; + consoleKeyFile = localConsoleKeyFile.FullName; } } diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs index 7ee9b9e907..eeec1c0fc4 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -6,6 +6,7 @@ using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.SystemState; using System; @@ -107,14 +108,20 @@ namespace Ryujinx.HLE.HOS.Applets.Error private static string CleanText(string value) { - return CleanTextRegex().Replace(value, "").Replace("\0", ""); + return CleanTextRegex().Replace(value, string.Empty).Replace("\0", string.Empty); } private string GetMessageText(uint module, uint description, string key) { string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data); - using LibHac.Fs.IStorage ncaFileStream = new LocalStorage(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open); + if (string.IsNullOrWhiteSpace(binaryTitleContentPath)) + { + return string.Empty; + } + + string binaryTitleFullPath = FileSystemUtils.ResolveFullPath(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(binaryTitleContentPath), false); + using LibHac.Fs.IStorage ncaFileStream = new LocalStorage(binaryTitleFullPath, FileAccess.Read, FileMode.Open); Nca nca = new(_horizon.Device.FileSystem.KeySet, ncaFileStream); IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel); string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage); @@ -131,7 +138,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error } else { - return ""; + return string.Empty; } } @@ -139,7 +146,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error { string buttonsText = GetMessageText(module, description, key); - return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + return (buttonsText == string.Empty) ? null : buttonsText.Split(["\r\n", "\r", "\n"], StringSplitOptions.None); } private void ParseErrorCommonArg() @@ -156,7 +163,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error string message = GetMessageText(module, description, "DlgMsg"); - if (message == "") + if (message == string.Empty) { message = "An error has occured.\n\n" + "Please try again later.\n\n" @@ -193,7 +200,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error // TODO: Handle the LanguageCode to return the translated "OK" and "Details". - if (detailsText.Trim() != "") + if (detailsText.Trim() != string.Empty) { buttons.Add("Details"); } diff --git a/src/Ryujinx.HLE/HOS/HorizonFsClient.cs b/src/Ryujinx.HLE/HOS/HorizonFsClient.cs index 3dbafa88b1..353baa747c 100644 --- a/src/Ryujinx.HLE/HOS/HorizonFsClient.cs +++ b/src/Ryujinx.HLE/HOS/HorizonFsClient.cs @@ -3,6 +3,7 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.Ncm; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.Horizon; using Ryujinx.Horizon.Common; @@ -39,19 +40,18 @@ namespace Ryujinx.HLE.HOS public Result MountSystemData(string mountName, ulong dataId) { string contentPath = _system.ContentManager.GetInstalledContentPath(dataId, StorageId.BuiltInSystem, NcaContentType.PublicData); - string installPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); - if (!string.IsNullOrWhiteSpace(installPath)) + if (!string.IsNullOrWhiteSpace(contentPath)) { - string ncaPath = installPath; + FileInfo ncaInfo = FileSystemUtils.GetActualFileInfo(VirtualFileSystem.SwitchPathToSystemPath(contentPath)); - if (File.Exists(ncaPath)) + if (ncaInfo.Exists) { LocalStorage ncaStorage = null; try { - ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); + ncaStorage = new LocalStorage(ncaInfo.FullName, FileAccess.Read, FileMode.Open); Nca nca = new(_system.KeySet, ncaStorage); diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index 9a7fdcc16d..cf96b0705a 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -5,6 +5,7 @@ using LibHac.Ns; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Memory; @@ -654,14 +655,16 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati { if (Interlocked.Exchange(ref _jitLoaded, 1) == 0) { - string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program); - string filePath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(jitPath); + const ulong JitApplicationId = 0x010000000000003B; + string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(JitApplicationId, StorageId.BuiltInSystem, NcaContentType.Program); - if (string.IsNullOrWhiteSpace(filePath)) + if (string.IsNullOrWhiteSpace(jitPath)) { - throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); + throw new InvalidSystemResourceException($"JIT ({JitApplicationId:X16}) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); } + string filePath = FileSystemUtils.ResolveFullPath(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(jitPath), false); + context.Device.LoadNca(filePath); // FIXME: Most likely not how this should be done? diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index 24dd1e9be8..a131c36688 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -11,6 +11,7 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; using System; using System.IO; @@ -18,6 +19,7 @@ using static Ryujinx.HLE.Utilities.StringUtils; using GameCardHandle = System.UInt32; using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; using IStorage = LibHac.FsSrv.Sf.IStorage; +using SpanHelpers = LibHac.Common.SpanHelpers; namespace Ryujinx.HLE.HOS.Services.Fs { @@ -52,13 +54,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs ulong titleId = context.RequestData.ReadUInt64(); #pragma warning restore IDE0059 string switchPath = ReadUtf8String(context); - string fullPath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(switchPath); + FileInfo realPathInfo = FileSystemUtils.GetActualFileInfo(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(switchPath)); - if (!File.Exists(fullPath)) + if (!realPathInfo.Exists) { - if (fullPath.Contains('.')) + if (realPathInfo.FullName.Contains('.')) { - ResultCode result = FileSystemProxyHelper.OpenFileSystemFromInternalFile(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + ResultCode result = FileSystemProxyHelper.OpenFileSystemFromInternalFile(context, realPathInfo.FullName, out FileSystemProxy.IFileSystem fileSystem); if (result == ResultCode.Success) { @@ -71,12 +73,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs return ResultCode.PathDoesNotExist; } - FileStream fileStream = new(fullPath, FileMode.Open, FileAccess.Read); - string extension = System.IO.Path.GetExtension(fullPath); + FileStream fileStream = realPathInfo.Open(FileMode.Open, FileAccess.Read); + string extension = realPathInfo.Extension.ToLower(); if (extension == ".nca") { - ResultCode result = FileSystemProxyHelper.OpenNcaFs(context, fullPath, fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + ResultCode result = FileSystemProxyHelper.OpenNcaFs(context, realPathInfo.FullName, fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); if (result == ResultCode.Success) { @@ -87,7 +89,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs } else if (extension == ".nsp") { - ResultCode result = FileSystemProxyHelper.OpenNsp(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + ResultCode result = FileSystemProxyHelper.OpenNsp(context, realPathInfo.FullName, out FileSystemProxy.IFileSystem fileSystem); if (result == ResultCode.Success) { @@ -832,17 +834,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs if (installedStorage != StorageId.None) { string contentPath = context.Device.System.ContentManager.GetInstalledContentPath(titleId, storageId, contentType); - string installPath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath); - if (!string.IsNullOrWhiteSpace(installPath)) + if (!string.IsNullOrWhiteSpace(contentPath)) { - string ncaPath = installPath; + FileInfo ncaInfo = FileSystemUtils.GetActualFileInfo(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath)); - if (File.Exists(ncaPath)) + if (ncaInfo.Exists) { try { - LibHac.Fs.IStorage ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); + LibHac.Fs.IStorage ncaStorage = new LocalStorage(ncaInfo.FullName, FileAccess.Read, FileMode.Open); Nca nca = new(context.Device.System.KeySet, ncaStorage); LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); using var sharedStorage = new SharedRef(romfsStorage); @@ -859,12 +860,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs } else { - throw new FileNotFoundException($"No Nca found in Path `{ncaPath}`."); + throw new FileNotFoundException($"No Nca found at path `{ncaInfo.FullName}`."); } } else { - throw new DirectoryNotFoundException($"Path for title id {titleId:x16} on Storage {storageId} was not found in Path {installPath}."); + throw new DirectoryNotFoundException($"Path for title id {titleId:x16} on Storage {storageId} was not found at path {contentPath}."); } } diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs index 641795890f..57b3d65ed5 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs @@ -6,6 +6,7 @@ using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel.Memory; @@ -65,10 +66,10 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl if (contentManager.TryGetFontTitle(name, out ulong fontTitle) && contentManager.TryGetFontFilename(name, out string fontFilename)) { string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.BuiltInSystem, NcaContentType.Data); - string fontPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); - if (!string.IsNullOrWhiteSpace(fontPath)) + if (!string.IsNullOrWhiteSpace(contentPath)) { + string fontPath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(contentPath), false); byte[] data; using (IStorage ncaFileStream = new LocalStorage(fontPath, FileAccess.Read, FileMode.Open)) diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs index 65748be332..29db67d858 100644 --- a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -7,6 +7,7 @@ using LibHac.Ncm; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.SystemState; using System; using System.IO; @@ -305,7 +306,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings return null; } - string firmwareTitlePath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath); + string firmwareTitlePath = FileSystemUtils.ResolveFullPath(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath), false); using IStorage firmwareStorage = new LocalStorage(firmwareTitlePath, FileAccess.Read); Nca firmwareContent = new(device.System.KeySet, firmwareStorage); diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs index d17a999dc9..d7995c779f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Services.Sockets.Nsd; using System; using System.Collections.Generic; @@ -21,12 +22,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy { string sdPath = FileSystem.VirtualFileSystem.GetSdCardPath(); string filePath = FileSystem.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath); + FileInfo fileInfo = new FileInfo(filePath).GetActualFileInfo(); _mitmHostEntries.Clear(); - if (File.Exists(filePath)) + if (fileInfo.Exists) { - using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using FileStream fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read); using StreamReader reader = new(fileStream); while (!reader.EndOfStream) @@ -92,9 +94,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy return new IPHostEntry { - AddressList = new[] { hostEntry.Value }, + AddressList = [hostEntry.Value], HostName = hostEntry.Key, - Aliases = Array.Empty(), + Aliases = [], }; } } diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs index 5d2e06a4fc..04055b5643 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs @@ -8,6 +8,7 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Ssl.Types; @@ -122,7 +123,8 @@ namespace Ryujinx.HLE.HOS.Services.Ssl if (HasCertStoreTitle()) { - using LocalStorage ncaFile = new(VirtualFileSystem.SwitchPathToSystemPath(GetCertStoreTitleContentPath()), FileAccess.Read, FileMode.Open); + string certStorePath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(GetCertStoreTitleContentPath()), false); + using LocalStorage ncaFile = new(certStorePath, FileAccess.Read, FileMode.Open); Nca nca = new(_virtualFileSystem.KeySet, ncaFile); diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs index abf3cd7d63..4fb7d4b75c 100644 --- a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs @@ -7,6 +7,7 @@ using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.Cpu; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; @@ -89,7 +90,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone { if (HasTimeZoneBinaryTitle()) { - using IStorage ncaFileStream = new LocalStorage(VirtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open); + string timeZoneBinPath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), false); + using IStorage ncaFileStream = new LocalStorage(timeZoneBinPath, FileAccess.Read, FileMode.Open); Nca nca = new(_virtualFileSystem.KeySet, ncaFileStream); IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); @@ -129,7 +131,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone List<(int Offset, string Location, string Abbr)> outList = new(); var now = DateTimeOffset.Now.ToUnixTimeSeconds(); - using (IStorage ncaStorage = new LocalStorage(VirtualFileSystem.SwitchPathToSystemPath(tzBinaryContentPath), FileAccess.Read, FileMode.Open)) + string tzBinaryPath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(tzBinaryContentPath), false); + using (IStorage ncaStorage = new LocalStorage(tzBinaryPath, FileAccess.Read, FileMode.Open)) using (IFileSystem romfs = new Nca(_virtualFileSystem.KeySet, ncaStorage).OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel)) { foreach (string locName in LocationNameCache) @@ -264,7 +267,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone return ResultCode.TimeZoneNotFound; } - ncaFile = new LocalStorage(VirtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open); + string timeZoneBinPath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), false); + ncaFile = new LocalStorage(timeZoneBinPath, FileAccess.Read, FileMode.Open); Nca nca = new(_virtualFileSystem.KeySet, ncaFile); IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 540e90a25e..32cedf88c3 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -523,13 +523,12 @@ namespace Ryujinx.UI.App.Common return; } - var fileInfo = new FileInfo(app); + var fileInfo = FileSystemUtils.GetActualFileInfo(app); string extension = fileInfo.Extension.ToLower(); if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso") { - var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName; - applicationPaths.Add(fullPath); + applicationPaths.Add(fileInfo.FullName); numApplicationsFound++; } } diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs index b07bf78b94..f7bd1c6522 100644 --- a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -8,6 +8,7 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using SkiaSharp; using System; @@ -105,10 +106,10 @@ namespace Ryujinx.Ava.UI.ViewModels } string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); - string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); - if (!string.IsNullOrWhiteSpace(avatarPath)) + if (!string.IsNullOrWhiteSpace(contentPath)) { + string avatarPath = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(contentPath), false); using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); From abaa560ed5af56b095a388351a9bd61a4e4e4fd1 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 11 Jul 2024 01:01:08 +0200 Subject: [PATCH 12/16] Fix FileNotFoundException when resolving non-existent paths in FileSystemUtils --- .../Utilities/FileSystemUtils.cs | 25 +++++++++++++++---- .../FileSystem/VirtualFileSystem.cs | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs index 06bcbff760..2628efc74f 100644 --- a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -57,31 +57,46 @@ namespace Ryujinx.Common.Utilities { FileSystemInfo pathInfo = isDirectory ? new DirectoryInfo(path) : new FileInfo(path); - return pathInfo.ResolveLinkTarget(true)?.FullName ?? pathInfo.FullName; + if (pathInfo.Exists) + { + return pathInfo.ResolveLinkTarget(true)?.FullName ?? pathInfo.FullName; + } + + return pathInfo.FullName; } public static FileInfo GetActualFileInfo(this FileInfo fileInfo) { - return (FileInfo)(fileInfo.ResolveLinkTarget(true) ?? fileInfo); + if (fileInfo.Exists) + { + return (FileInfo)(fileInfo.ResolveLinkTarget(true) ?? fileInfo); + } + + return fileInfo; } public static FileInfo GetActualFileInfo(string filePath) { FileInfo fileInfo = new(filePath); - return (FileInfo)(fileInfo.ResolveLinkTarget(true) ?? fileInfo); + return fileInfo.GetActualFileInfo(); } public static DirectoryInfo GetActualDirectoryInfo(this DirectoryInfo directoryInfo) { - return (DirectoryInfo)(directoryInfo.ResolveLinkTarget(true) ?? directoryInfo); + if (directoryInfo.Exists) + { + return (DirectoryInfo)(directoryInfo.ResolveLinkTarget(true) ?? directoryInfo); + } + + return directoryInfo; } public static DirectoryInfo GetActualDirectoryInfo(string directoryPath) { DirectoryInfo directoryInfo = new(directoryPath); - return (DirectoryInfo)(directoryInfo.ResolveLinkTarget(true) ?? directoryInfo); + return directoryInfo.GetActualDirectoryInfo(); } } } diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index ecdd6ea83e..19b793008c 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -65,7 +65,7 @@ namespace Ryujinx.HLE.FileSystem { FileInfo fileInfo = FileSystemUtils.GetActualFileInfo(fileName); - var romfsStream = fileInfo.Open(FileMode.Open, FileAccess.Read); + var romfsStream = fileInfo.Open(FileMode.Open, FileAccess.Read); _romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) => { From 647276b82445232c7be2f6c9b95409ce1b18d5a5 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:00:23 +0200 Subject: [PATCH 13/16] Log a warning if the converted path is not inside the Ryujinx directory --- src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 19b793008c..6ecc2d0be3 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -109,7 +109,7 @@ namespace Ryujinx.HLE.FileSystem if (!fullPath.StartsWith(AppDataManager.BaseDirPath)) { - throw new ArgumentException($"The path is not located inside the Ryujinx directory: {fullPath}", nameof(basePath)); + Logger.Warning?.Print(LogClass.ServiceFs, $"The path is not located inside the Ryujinx directory: {fullPath}"); } return fullPath; From b4515bd210514bf9280666d13bca432904c790b7 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:43:47 +0200 Subject: [PATCH 14/16] Don't resolve relative paths --- src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 6ecc2d0be3..a1ff54c068 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -29,9 +29,10 @@ namespace Ryujinx.HLE.FileSystem { public class VirtualFileSystem : IDisposable { - public static readonly string SafeNandPath = FileSystemUtils.ResolveFullPath(Path.Combine(AppDataManager.DefaultNandDir, "safe"), true); - public static readonly string SystemNandPath = FileSystemUtils.ResolveFullPath(Path.Combine(AppDataManager.DefaultNandDir, "system"), true); - public static readonly string UserNandPath = FileSystemUtils.ResolveFullPath(Path.Combine(AppDataManager.DefaultNandDir, "user"), true); + // NOTE: These are relative paths. + public static readonly string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe"); + public static readonly string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system"); + public static readonly string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user"); public KeySet KeySet { get; private set; } public EmulatedGameCard GameCard { get; private set; } From 3f0ab45b9c4efe13964ec858f9c84a6d0462e99f Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:01:31 +0200 Subject: [PATCH 15/16] Resolve symlinks for TryGetRealPath() as well --- .../Utilities/FileSystemUtils.cs | 23 +++++++++++++++++++ src/Ryujinx.HLE/FileSystem/ContentPath.cs | 12 +++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs index 2628efc74f..50c2a2b879 100644 --- a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -65,6 +65,29 @@ namespace Ryujinx.Common.Utilities return pathInfo.FullName; } + // TODO: This is bad. Resolve all data paths on startup instead. + public static string CombineAndResolveFullPath(bool isDirectory, params string[] paths) + { + if (paths.Length == 0) + { + return null; + } + + if (paths.Length == 1) + { + return ResolveFullPath(paths[0], isDirectory); + } + + string fullPath = ResolveFullPath(paths[0], true); + + for (int i = 1; i < paths.Length - 1; i++) + { + fullPath = ResolveFullPath(Path.Combine(fullPath, paths[i]), true); + } + + return ResolveFullPath(Path.Combine(fullPath, paths[^1]), isDirectory); + } + public static FileInfo GetActualFileInfo(this FileInfo fileInfo) { if (fileInfo.Exists) diff --git a/src/Ryujinx.HLE/FileSystem/ContentPath.cs b/src/Ryujinx.HLE/FileSystem/ContentPath.cs index ffc212f771..5e612eb1fe 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentPath.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentPath.cs @@ -1,9 +1,9 @@ using LibHac.Fs; using LibHac.Ncm; using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; using System; using static Ryujinx.HLE.FileSystem.VirtualFileSystem; -using Path = System.IO.Path; namespace Ryujinx.HLE.FileSystem { @@ -30,11 +30,11 @@ namespace Ryujinx.HLE.FileSystem { realPath = switchContentPath switch { - SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents), - UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents), - SdCardContent => Path.Combine(GetSdCardPath(), Nintendo, Contents), - System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath), - User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath), + SystemContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, SystemNandPath, Contents), + UserContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, UserNandPath, Contents), + SdCardContent => FileSystemUtils.CombineAndResolveFullPath(true, GetSdCardPath(), Nintendo, Contents), + System => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, SystemNandPath), + User => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, UserNandPath), _ => null, }; From 8381bc7220598bbb69dbd56f4304ae1add7bc884 Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:40:30 +0200 Subject: [PATCH 16/16] temp: Attempt to solve missing paths + add logging --- src/Ryujinx.Common/Utilities/FileSystemUtils.cs | 15 +++++++++++++-- src/Ryujinx.HLE/FileSystem/ContentPath.cs | 8 ++++---- src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 4 ++-- .../Configuration/ConfigurationFileFormat.cs | 4 ++-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs index 50c2a2b879..fb487e0a2d 100644 --- a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using System.Collections.Generic; using System.IO; using System.Linq; @@ -59,9 +60,15 @@ namespace Ryujinx.Common.Utilities if (pathInfo.Exists) { - return pathInfo.ResolveLinkTarget(true)?.FullName ?? pathInfo.FullName; + var fullPath = pathInfo.ResolveLinkTarget(true)?.FullName ?? pathInfo.FullName; + + Logger.Warning?.Print(LogClass.Application, $"Resolved: {path} -> {pathInfo.FullName}"); + + return fullPath; } + Logger.Warning?.Print(LogClass.Application, $"Can't resolve non-existent path: {path} -> {pathInfo.FullName}"); + return pathInfo.FullName; } @@ -85,7 +92,11 @@ namespace Ryujinx.Common.Utilities fullPath = ResolveFullPath(Path.Combine(fullPath, paths[i]), true); } - return ResolveFullPath(Path.Combine(fullPath, paths[^1]), isDirectory); + fullPath = ResolveFullPath(Path.Combine(fullPath, paths[^1]), isDirectory); + + Logger.Warning?.Print(LogClass.Application, $"Combined and resolved: {fullPath}"); + + return fullPath; } public static FileInfo GetActualFileInfo(this FileInfo fileInfo) diff --git a/src/Ryujinx.HLE/FileSystem/ContentPath.cs b/src/Ryujinx.HLE/FileSystem/ContentPath.cs index 5e612eb1fe..3cd1ad0acd 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentPath.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentPath.cs @@ -30,11 +30,11 @@ namespace Ryujinx.HLE.FileSystem { realPath = switchContentPath switch { - SystemContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, SystemNandPath, Contents), - UserContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, UserNandPath, Contents), + SystemContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "system", Contents), + UserContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", Contents), SdCardContent => FileSystemUtils.CombineAndResolveFullPath(true, GetSdCardPath(), Nintendo, Contents), - System => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, SystemNandPath), - User => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, UserNandPath), + System => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "system"), + User => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user"), _ => null, }; diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index a1ff54c068..5db94de298 100644 --- a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -179,11 +179,11 @@ namespace Ryujinx.HLE.FileSystem break; } - string fullPath = Path.Combine(AppDataManager.BaseDirPath, path); + string fullPath = FileSystemUtils.CombineAndResolveFullPath(isDirectory, AppDataManager.BaseDirPath, path); if (isDirectory && !Directory.Exists(fullPath)) { - Directory.CreateDirectory(fullPath); + Directory.CreateDirectory(fullPath!); } return fullPath; diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index af3ad0a1da..7e7bf38d4e 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -395,7 +395,7 @@ namespace Ryujinx.UI.Common.Configuration { try { - configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); + configurationFileFormat = JsonHelper.DeserializeFromFile(FileSystemUtils.ResolveFullPath(path, false), ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); return configurationFileFormat.Version != 0; } @@ -413,7 +413,7 @@ namespace Ryujinx.UI.Common.Configuration /// The path to the JSON configuration file public void SaveConfig(string path) { - JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); + JsonHelper.SerializeToFile(FileSystemUtils.ResolveFullPath(path, false), this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); } } }