Merge 8381bc7220
into d97e995e59
This commit is contained in:
commit
ec244e8d3c
23 changed files with 314 additions and 177 deletions
|
@ -44,7 +44,7 @@ namespace Ryujinx.Common.Configuration
|
||||||
|
|
||||||
static AppDataManager()
|
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)
|
public static void Initialize(string baseDirPath)
|
||||||
|
@ -56,23 +56,23 @@ namespace Ryujinx.Common.Configuration
|
||||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
string userProfilePath = FileSystemUtils.ResolveFullPath(Path.Combine(appDataPath, DefaultBaseDir), true);
|
||||||
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
DirectoryInfo portableInfo = FileSystemUtils.GetActualDirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir));
|
||||||
|
|
||||||
// On macOS, check for a portable directory next to the app bundle as well.
|
// 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, "..", ".."));
|
string bundlePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", ".."));
|
||||||
// Make sure we're actually running within an app bundle.
|
// Make sure we're actually running within an app bundle.
|
||||||
if (bundlePath.EndsWith(".app"))
|
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;
|
Mode = LaunchMode.Portable;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -83,24 +83,19 @@ namespace Ryujinx.Common.Configuration
|
||||||
|
|
||||||
if (baseDirPath != null && baseDirPath != userProfilePath)
|
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}...");
|
Logger.Error?.Print(LogClass.Application, $"Custom Data Directory '{baseDirPath}' does not exist. Falling back to {Mode}...");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BaseDirPath = baseDirPath;
|
BaseDirPath = baseDirInfo.FullName;
|
||||||
Mode = LaunchMode.Custom;
|
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();
|
SetupBasePaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,16 +212,16 @@ namespace Ryujinx.Common.Configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return logDir;
|
return FileSystemUtils.ResolveFullPath(logDir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetupBasePaths()
|
private static void SetupBasePaths()
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(BaseDirPath);
|
Directory.CreateDirectory(BaseDirPath);
|
||||||
LogsDirPath = SetUpLogsDir();
|
LogsDirPath = SetUpLogsDir();
|
||||||
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
|
Directory.CreateDirectory(GamesDirPath = FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, GamesDir), true));
|
||||||
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
|
Directory.CreateDirectory(ProfilesDirPath = FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, ProfilesDir), true));
|
||||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
Directory.CreateDirectory(KeysDirPath = FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, KeysDir), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
|
// 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 GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, DefaultModsDir), true)).FullName;
|
||||||
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(FileSystemUtils.ResolveFullPath(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere"), true)).FullName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -52,5 +53,84 @@ namespace Ryujinx.Common.Utilities
|
||||||
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
|
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
|
||||||
return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c));
|
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);
|
||||||
|
|
||||||
|
if (pathInfo.Exists)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (fileInfo.Exists)
|
||||||
|
{
|
||||||
|
return (FileInfo)(fileInfo.ResolveLinkTarget(true) ?? fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfo GetActualFileInfo(string filePath)
|
||||||
|
{
|
||||||
|
FileInfo fileInfo = new(filePath);
|
||||||
|
|
||||||
|
return fileInfo.GetActualFileInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DirectoryInfo GetActualDirectoryInfo(this DirectoryInfo directoryInfo)
|
||||||
|
{
|
||||||
|
if (directoryInfo.Exists)
|
||||||
|
{
|
||||||
|
return (DirectoryInfo)(directoryInfo.ResolveLinkTarget(true) ?? directoryInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return directoryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DirectoryInfo GetActualDirectoryInfo(string directoryPath)
|
||||||
|
{
|
||||||
|
DirectoryInfo directoryInfo = new(directoryPath);
|
||||||
|
|
||||||
|
return directoryInfo.GetActualDirectoryInfo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,6 +256,12 @@ namespace Ryujinx
|
||||||
MainWindow mainWindow = new();
|
MainWindow mainWindow = new();
|
||||||
mainWindow.Show();
|
mainWindow.Show();
|
||||||
|
|
||||||
|
// Load the game table if no application was requested by the command line
|
||||||
|
if (CommandLineState.LaunchPathArg == null)
|
||||||
|
{
|
||||||
|
mainWindow.UpdateGameTable();
|
||||||
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
if (OperatingSystem.IsLinux())
|
||||||
{
|
{
|
||||||
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
|
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
|
||||||
|
@ -325,15 +331,13 @@ namespace Ryujinx
|
||||||
{
|
{
|
||||||
if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List<ApplicationData> applications))
|
if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List<ApplicationData> applications))
|
||||||
{
|
{
|
||||||
ApplicationData applicationData;
|
|
||||||
|
|
||||||
if (CommandLineState.LaunchApplicationId != null)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -343,8 +347,7 @@ namespace Ryujinx
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
applicationData = applications[0];
|
mainWindow.RunApplication(applications[0], CommandLineState.StartFullscreenArg);
|
||||||
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -15,6 +15,7 @@ using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Multiplayer;
|
using Ryujinx.Common.Configuration.Multiplayer;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.SystemInterop;
|
using Ryujinx.Common.SystemInterop;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading;
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
|
@ -187,7 +188,10 @@ namespace Ryujinx.UI
|
||||||
: IntegrityCheckLevel.None;
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
// Instantiate GUI objects.
|
// Instantiate GUI objects.
|
||||||
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
|
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
|
||||||
|
{
|
||||||
|
DesiredTitleLanguage = ConfigurationState.Instance.System.Language,
|
||||||
|
};
|
||||||
_uiHandler = new GtkHostUIHandler(this);
|
_uiHandler = new GtkHostUIHandler(this);
|
||||||
_deviceExitStatus = new AutoResetEvent(false);
|
_deviceExitStatus = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
@ -325,7 +329,6 @@ namespace Ryujinx.UI
|
||||||
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
|
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
|
||||||
|
|
||||||
UpdateColumns();
|
UpdateColumns();
|
||||||
UpdateGameTable();
|
|
||||||
|
|
||||||
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
|
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
|
||||||
{
|
{
|
||||||
|
@ -738,7 +741,8 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
Thread applicationLibraryThread = new(() =>
|
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;
|
_updatingGameTable = false;
|
||||||
})
|
})
|
||||||
|
@ -917,7 +921,7 @@ namespace Ryujinx.UI
|
||||||
|
|
||||||
if (application.Path.StartsWith("@SystemContent"))
|
if (application.Path.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
|
application.Path = FileSystemUtils.ResolveFullPath(VirtualFileSystem.SwitchPathToSystemPath(application.Path), false);
|
||||||
|
|
||||||
isFirmwareTitle = true;
|
isFirmwareTitle = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
@ -119,10 +120,10 @@ namespace Ryujinx.UI.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data);
|
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);
|
using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open);
|
||||||
|
|
||||||
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
||||||
|
|
|
@ -349,18 +349,15 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
return false;
|
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(installedInfo.FullName, FileMode.Open, FileAccess.Read);
|
||||||
{
|
Nca nca = new(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
using FileStream file = new(installedPath, FileMode.Open, FileAccess.Read);
|
bool contentCheck = nca.Header.ContentType == contentType;
|
||||||
Nca nca = new(_virtualFileSystem.KeySet, file.AsStorage());
|
|
||||||
bool contentCheck = nca.Header.ContentType == contentType;
|
|
||||||
|
|
||||||
return contentCheck;
|
return contentCheck;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -941,9 +938,9 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
if (entry.ContentType == NcaContentType.Data)
|
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());
|
Nca nca = new(_virtualFileSystem.KeySet, fileStream.AsStorage());
|
||||||
|
|
||||||
if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
|
if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using static Ryujinx.HLE.FileSystem.VirtualFileSystem;
|
using static Ryujinx.HLE.FileSystem.VirtualFileSystem;
|
||||||
using Path = System.IO.Path;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
|
@ -30,11 +30,11 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
realPath = switchContentPath switch
|
realPath = switchContentPath switch
|
||||||
{
|
{
|
||||||
SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents),
|
SystemContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "system", Contents),
|
||||||
UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents),
|
UserContent => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user", Contents),
|
||||||
SdCardContent => Path.Combine(GetSdCardPath(), Nintendo, Contents),
|
SdCardContent => FileSystemUtils.CombineAndResolveFullPath(true, GetSdCardPath(), Nintendo, Contents),
|
||||||
System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath),
|
System => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "system"),
|
||||||
User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath),
|
User => FileSystemUtils.CombineAndResolveFullPath(true, AppDataManager.BaseDirPath, AppDataManager.DefaultNandDir, "user"),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Text;
|
using System.Buffers.Text;
|
||||||
|
@ -22,11 +23,13 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
public class VirtualFileSystem : IDisposable
|
public class VirtualFileSystem : IDisposable
|
||||||
{
|
{
|
||||||
|
// NOTE: These are relative paths.
|
||||||
public static readonly string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe");
|
public static readonly string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe");
|
||||||
public static readonly string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system");
|
public static readonly string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system");
|
||||||
public static readonly string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user");
|
public static readonly string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user");
|
||||||
|
@ -61,7 +64,9 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
public void LoadRomFs(ulong pid, string fileName)
|
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) =>
|
_romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) =>
|
||||||
{
|
{
|
||||||
|
@ -98,21 +103,21 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return null;
|
throw new ArgumentException($"The filename does not start with '/': {fileName}", nameof(fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName));
|
string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName));
|
||||||
|
|
||||||
if (!fullPath.StartsWith(AppDataManager.BaseDirPath))
|
if (!fullPath.StartsWith(AppDataManager.BaseDirPath))
|
||||||
{
|
{
|
||||||
return null;
|
Logger.Warning?.Print(LogClass.ServiceFs, $"The path is not located inside the Ryujinx directory: {fullPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetSdCardPath() => MakeFullPath(AppDataManager.DefaultSdcardDir);
|
internal static string GetSdCardPath() => FileSystemUtils.ResolveFullPath(MakeFullPath(AppDataManager.DefaultSdcardDir), true);
|
||||||
public static string GetNandPath() => MakeFullPath(AppDataManager.DefaultNandDir);
|
public static string GetNandPath() => FileSystemUtils.ResolveFullPath(MakeFullPath(AppDataManager.DefaultNandDir), true);
|
||||||
|
|
||||||
public static string SwitchPathToSystemPath(string switchPath)
|
public static string SwitchPathToSystemPath(string switchPath)
|
||||||
{
|
{
|
||||||
|
@ -120,7 +125,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
if (parts.Length != 2)
|
if (parts.Length != 2)
|
||||||
{
|
{
|
||||||
return null;
|
throw new ArgumentException($"Invalid switch fs path provided: {switchPath}", nameof(switchPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetFullPath(MakeFullPath(parts[0]), parts[1]);
|
return GetFullPath(MakeFullPath(parts[0]), parts[1]);
|
||||||
|
@ -174,11 +179,11 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string fullPath = Path.Combine(AppDataManager.BaseDirPath, path);
|
string fullPath = FileSystemUtils.CombineAndResolveFullPath(isDirectory, AppDataManager.BaseDirPath, path);
|
||||||
|
|
||||||
if (isDirectory && !Directory.Exists(fullPath))
|
if (isDirectory && !Directory.Exists(fullPath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(fullPath);
|
Directory.CreateDirectory(fullPath!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullPath;
|
return fullPath;
|
||||||
|
@ -236,23 +241,23 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
void LoadSetAtPath(string basePath)
|
void LoadSetAtPath(string basePath)
|
||||||
{
|
{
|
||||||
string localKeyFile = Path.Combine(basePath, "prod.keys");
|
FileInfo localKeyFile = FileSystemUtils.GetActualFileInfo(Path.Combine(basePath, "prod.keys"));
|
||||||
string localTitleKeyFile = Path.Combine(basePath, "title.keys");
|
FileInfo localTitleKeyFile = FileSystemUtils.GetActualFileInfo(Path.Combine(basePath, "title.keys"));
|
||||||
string localConsoleKeyFile = Path.Combine(basePath, "console.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using System;
|
using System;
|
||||||
|
@ -107,14 +108,20 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||||
|
|
||||||
private static string CleanText(string value)
|
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)
|
private string GetMessageText(uint module, uint description, string key)
|
||||||
{
|
{
|
||||||
string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data);
|
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);
|
Nca nca = new(_horizon.Device.FileSystem.KeySet, ncaFileStream);
|
||||||
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel);
|
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel);
|
||||||
string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage);
|
string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage);
|
||||||
|
@ -131,7 +138,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return "";
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +146,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||||
{
|
{
|
||||||
string buttonsText = GetMessageText(module, description, key);
|
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()
|
private void ParseErrorCommonArg()
|
||||||
|
@ -156,7 +163,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||||
|
|
||||||
string message = GetMessageText(module, description, "DlgMsg");
|
string message = GetMessageText(module, description, "DlgMsg");
|
||||||
|
|
||||||
if (message == "")
|
if (message == string.Empty)
|
||||||
{
|
{
|
||||||
message = "An error has occured.\n\n"
|
message = "An error has occured.\n\n"
|
||||||
+ "Please try again later.\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".
|
// TODO: Handle the LanguageCode to return the translated "OK" and "Details".
|
||||||
|
|
||||||
if (detailsText.Trim() != "")
|
if (detailsText.Trim() != string.Empty)
|
||||||
{
|
{
|
||||||
buttons.Add("Details");
|
buttons.Add("Details");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.Horizon;
|
using Ryujinx.Horizon;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
|
@ -39,19 +40,18 @@ namespace Ryujinx.HLE.HOS
|
||||||
public Result MountSystemData(string mountName, ulong dataId)
|
public Result MountSystemData(string mountName, ulong dataId)
|
||||||
{
|
{
|
||||||
string contentPath = _system.ContentManager.GetInstalledContentPath(dataId, StorageId.BuiltInSystem, NcaContentType.PublicData);
|
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;
|
LocalStorage ncaStorage = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open);
|
ncaStorage = new LocalStorage(ncaInfo.FullName, FileAccess.Read, FileMode.Open);
|
||||||
|
|
||||||
Nca nca = new(_system.KeySet, ncaStorage);
|
Nca nca = new(_system.KeySet, ncaStorage);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
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)
|
if (Interlocked.Exchange(ref _jitLoaded, 1) == 0)
|
||||||
{
|
{
|
||||||
string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program);
|
const ulong JitApplicationId = 0x010000000000003B;
|
||||||
string filePath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(jitPath);
|
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);
|
context.Device.LoadNca(filePath);
|
||||||
|
|
||||||
// FIXME: Most likely not how this should be done?
|
// FIXME: Most likely not how this should be done?
|
||||||
|
|
|
@ -10,6 +10,7 @@ using LibHac.Tools.Es;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -78,17 +79,21 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||||
|
|
||||||
DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent;
|
DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent;
|
||||||
|
|
||||||
while (string.IsNullOrWhiteSpace(archivePath.Extension))
|
while (archivePath != null && string.IsNullOrWhiteSpace(archivePath.Extension))
|
||||||
{
|
{
|
||||||
archivePath = archivePath.Parent;
|
archivePath = archivePath.Parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName))
|
if (archivePath == null)
|
||||||
{
|
{
|
||||||
FileStream pfsFile = new(
|
return ResultCode.PathDoesNotExist;
|
||||||
archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar),
|
}
|
||||||
FileMode.Open,
|
|
||||||
FileAccess.Read);
|
FileInfo archiveInfo = FileSystemUtils.GetActualFileInfo(Path.TrimEndingDirectorySeparator(archivePath.FullName));
|
||||||
|
|
||||||
|
if (archivePath.Extension.ToLower() == ".nsp" && archiveInfo.Exists)
|
||||||
|
{
|
||||||
|
FileStream pfsFile = archiveInfo.Open(FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -97,7 +102,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||||
|
|
||||||
ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet);
|
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<LibHac.Fs.Fsa.IFile>();
|
using var ncaFile = new UniqueRef<LibHac.Fs.Fsa.IFile>();
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy;
|
using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -18,6 +19,7 @@ using static Ryujinx.HLE.Utilities.StringUtils;
|
||||||
using GameCardHandle = System.UInt32;
|
using GameCardHandle = System.UInt32;
|
||||||
using IFileSystem = LibHac.FsSrv.Sf.IFileSystem;
|
using IFileSystem = LibHac.FsSrv.Sf.IFileSystem;
|
||||||
using IStorage = LibHac.FsSrv.Sf.IStorage;
|
using IStorage = LibHac.FsSrv.Sf.IStorage;
|
||||||
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Fs
|
namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
{
|
{
|
||||||
|
@ -52,13 +54,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
ulong titleId = context.RequestData.ReadUInt64();
|
ulong titleId = context.RequestData.ReadUInt64();
|
||||||
#pragma warning restore IDE0059
|
#pragma warning restore IDE0059
|
||||||
string switchPath = ReadUtf8String(context);
|
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)
|
if (result == ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
@ -71,12 +73,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
return ResultCode.PathDoesNotExist;
|
return ResultCode.PathDoesNotExist;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileStream fileStream = new(fullPath, FileMode.Open, FileAccess.Read);
|
FileStream fileStream = realPathInfo.Open(FileMode.Open, FileAccess.Read);
|
||||||
string extension = System.IO.Path.GetExtension(fullPath);
|
string extension = realPathInfo.Extension.ToLower();
|
||||||
|
|
||||||
if (extension == ".nca")
|
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)
|
if (result == ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
@ -87,7 +89,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
}
|
}
|
||||||
else if (extension == ".nsp")
|
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)
|
if (result == ResultCode.Success)
|
||||||
{
|
{
|
||||||
|
@ -832,17 +834,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
if (installedStorage != StorageId.None)
|
if (installedStorage != StorageId.None)
|
||||||
{
|
{
|
||||||
string contentPath = context.Device.System.ContentManager.GetInstalledContentPath(titleId, storageId, contentType);
|
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
|
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);
|
Nca nca = new(context.Device.System.KeySet, ncaStorage);
|
||||||
LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel);
|
LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel);
|
||||||
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(romfsStorage);
|
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(romfsStorage);
|
||||||
|
@ -859,12 +860,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException($"No Nca found in Path `{ncaPath}`.");
|
throw new FileNotFoundException($"No Nca found at path `{ncaInfo.FullName}`.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
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))
|
if (contentManager.TryGetFontTitle(name, out ulong fontTitle) && contentManager.TryGetFontFilename(name, out string fontFilename))
|
||||||
{
|
{
|
||||||
string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.BuiltInSystem, NcaContentType.Data);
|
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;
|
byte[] data;
|
||||||
|
|
||||||
using (IStorage ncaFileStream = new LocalStorage(fontPath, FileAccess.Read, FileMode.Open))
|
using (IStorage ncaFileStream = new LocalStorage(fontPath, FileAccess.Read, FileMode.Open))
|
||||||
|
|
|
@ -7,6 +7,7 @@ using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -305,7 +306,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
|
||||||
return null;
|
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);
|
using IStorage firmwareStorage = new LocalStorage(firmwareTitlePath, FileAccess.Read);
|
||||||
Nca firmwareContent = new(device.System.KeySet, firmwareStorage);
|
Nca firmwareContent = new(device.System.KeySet, firmwareStorage);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.Sockets.Nsd;
|
using Ryujinx.HLE.HOS.Services.Sockets.Nsd;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -21,12 +22,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy
|
||||||
{
|
{
|
||||||
string sdPath = FileSystem.VirtualFileSystem.GetSdCardPath();
|
string sdPath = FileSystem.VirtualFileSystem.GetSdCardPath();
|
||||||
string filePath = FileSystem.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath);
|
string filePath = FileSystem.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath);
|
||||||
|
FileInfo fileInfo = new FileInfo(filePath).GetActualFileInfo();
|
||||||
|
|
||||||
_mitmHostEntries.Clear();
|
_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);
|
using StreamReader reader = new(fileStream);
|
||||||
|
|
||||||
while (!reader.EndOfStream)
|
while (!reader.EndOfStream)
|
||||||
|
@ -92,9 +94,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy
|
||||||
|
|
||||||
return new IPHostEntry
|
return new IPHostEntry
|
||||||
{
|
{
|
||||||
AddressList = new[] { hostEntry.Value },
|
AddressList = [hostEntry.Value],
|
||||||
HostName = hostEntry.Key,
|
HostName = hostEntry.Key,
|
||||||
Aliases = Array.Empty<string>(),
|
Aliases = [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Ssl.Types;
|
using Ryujinx.HLE.HOS.Services.Ssl.Types;
|
||||||
|
@ -122,7 +123,8 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
|
||||||
|
|
||||||
if (HasCertStoreTitle())
|
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);
|
Nca nca = new(_virtualFileSystem.KeySet, ncaFile);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
@ -89,7 +90,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
{
|
{
|
||||||
if (HasTimeZoneBinaryTitle())
|
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);
|
Nca nca = new(_virtualFileSystem.KeySet, ncaFileStream);
|
||||||
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel);
|
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();
|
List<(int Offset, string Location, string Abbr)> outList = new();
|
||||||
var now = DateTimeOffset.Now.ToUnixTimeSeconds();
|
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))
|
using (IFileSystem romfs = new Nca(_virtualFileSystem.KeySet, ncaStorage).OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel))
|
||||||
{
|
{
|
||||||
foreach (string locName in LocationNameCache)
|
foreach (string locName in LocationNameCache)
|
||||||
|
@ -264,7 +267,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
return ResultCode.TimeZoneNotFound;
|
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);
|
Nca nca = new(_virtualFileSystem.KeySet, ncaFile);
|
||||||
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel);
|
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel);
|
||||||
|
|
|
@ -71,10 +71,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
|
|
||||||
if (applicationId == 0)
|
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);
|
mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
|
||||||
controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
|
controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
applicationId = id;
|
||||||
break;
|
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}\"";
|
errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\"";
|
||||||
|
|
|
@ -34,6 +34,7 @@ namespace Ryujinx.UI.App.Common
|
||||||
{
|
{
|
||||||
public class ApplicationLibrary
|
public class ApplicationLibrary
|
||||||
{
|
{
|
||||||
|
public Language DesiredTitleLanguage { get; set; }
|
||||||
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
public event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||||
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||||
|
|
||||||
|
@ -45,7 +46,6 @@ namespace Ryujinx.UI.App.Common
|
||||||
|
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly IntegrityCheckLevel _checkLevel;
|
private readonly IntegrityCheckLevel _checkLevel;
|
||||||
private Language _desiredTitleLanguage;
|
|
||||||
private CancellationTokenSource _cancellationToken;
|
private CancellationTokenSource _cancellationToken;
|
||||||
|
|
||||||
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
@ -221,7 +221,7 @@ namespace Ryujinx.UI.App.Common
|
||||||
{
|
{
|
||||||
using UniqueRef<IFile> icon = new();
|
using UniqueRef<IFile> 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();
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
@ -422,35 +422,40 @@ namespace Ryujinx.UI.App.Common
|
||||||
|
|
||||||
foreach (var data in applications)
|
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;
|
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
|
||||||
|
|
||||||
// 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.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
|
appMetadata.Title = data.Name;
|
||||||
appMetadata.TimePlayedOld = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
|
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
|
||||||
if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
|
if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
|
||||||
{
|
|
||||||
// Migrate from string-based last_played to DateTime-based last_played_utc.
|
|
||||||
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
|
|
||||||
{
|
{
|
||||||
appMetadata.LastPlayed = lastPlayedOldParsed;
|
appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
|
||||||
|
appMetadata.TimePlayedOld = default;
|
||||||
// Migration successful: deleting last_played from the metadata file.
|
|
||||||
appMetadata.LastPlayedOld = 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.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
||||||
data.FileSize = fileSize;
|
data.FileSize = fileSize;
|
||||||
data.Path = applicationPath;
|
data.Path = applicationPath;
|
||||||
|
@ -472,13 +477,11 @@ namespace Ryujinx.UI.App.Common
|
||||||
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadApplications(List<string> appDirs, Language desiredTitleLanguage)
|
public void LoadApplications(List<string> appDirs)
|
||||||
{
|
{
|
||||||
int numApplicationsFound = 0;
|
int numApplicationsFound = 0;
|
||||||
int numApplicationsLoaded = 0;
|
int numApplicationsLoaded = 0;
|
||||||
|
|
||||||
_desiredTitleLanguage = desiredTitleLanguage;
|
|
||||||
|
|
||||||
_cancellationToken = new CancellationTokenSource();
|
_cancellationToken = new CancellationTokenSource();
|
||||||
|
|
||||||
// Builds the applications list with paths to found applications
|
// Builds the applications list with paths to found applications
|
||||||
|
@ -520,13 +523,12 @@ namespace Ryujinx.UI.App.Common
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileInfo = new FileInfo(app);
|
var fileInfo = FileSystemUtils.GetActualFileInfo(app);
|
||||||
string extension = fileInfo.Extension.ToLower();
|
string extension = fileInfo.Extension.ToLower();
|
||||||
|
|
||||||
if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso")
|
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(fileInfo.FullName);
|
||||||
applicationPaths.Add(fullPath);
|
|
||||||
numApplicationsFound++;
|
numApplicationsFound++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -553,19 +555,13 @@ namespace Ryujinx.UI.App.Common
|
||||||
{
|
{
|
||||||
AppData = application,
|
AppData = application,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (applications.Count > 1)
|
numApplicationsFound++;
|
||||||
{
|
numApplicationsLoaded++;
|
||||||
numApplicationsFound += applications.Count - 1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
numApplicationsLoaded += applications.Count;
|
numApplicationsFound--;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
numApplicationsFound--;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
|
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs
|
||||||
{
|
{
|
||||||
|
@ -827,7 +823,7 @@ namespace Ryujinx.UI.App.Common
|
||||||
|
|
||||||
private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data)
|
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)
|
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
|
||||||
{
|
{
|
||||||
|
|
|
@ -395,7 +395,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
configurationFileFormat = JsonHelper.DeserializeFromFile(FileSystemUtils.ResolveFullPath(path, false), ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
||||||
|
|
||||||
return configurationFileFormat.Version != 0;
|
return configurationFileFormat.Version != 0;
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||||
/// <param name="path">The path to the JSON configuration file</param>
|
/// <param name="path">The path to the JSON configuration file</param>
|
||||||
public void SaveConfig(string path)
|
public void SaveConfig(string path)
|
||||||
{
|
{
|
||||||
JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
JsonHelper.SerializeToFile(FileSystemUtils.ResolveFullPath(path, false), this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System;
|
using System;
|
||||||
|
@ -105,10 +106,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data);
|
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);
|
using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open);
|
||||||
|
|
||||||
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
|
||||||
|
|
|
@ -37,6 +37,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||||
|
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
|
private bool _applicationsLoadedOnce;
|
||||||
|
|
||||||
private UserChannelPersistence _userChannelPersistence;
|
private UserChannelPersistence _userChannelPersistence;
|
||||||
private static bool _deferLoad;
|
private static bool _deferLoad;
|
||||||
|
@ -224,7 +225,10 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
? IntegrityCheckLevel.ErrorOnInvalid
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
: IntegrityCheckLevel.None;
|
: 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
|
// 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
|
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
||||||
|
@ -321,15 +325,13 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
|
|
||||||
if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications))
|
if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications))
|
||||||
{
|
{
|
||||||
ApplicationData applicationData;
|
|
||||||
|
|
||||||
if (_launchApplicationId != null)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -339,8 +341,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
applicationData = applications[0];
|
await ViewModel.LoadApplication(applications[0], _startFullscreen);
|
||||||
await ViewModel.LoadApplication(applicationData, _startFullscreen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -472,11 +473,13 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
|
|
||||||
ViewModel.RefreshFirmwareStatus();
|
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().Wait();
|
||||||
CheckLaunchState();
|
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetMainContent(Control content = null)
|
private void SetMainContent(Control content = null)
|
||||||
|
@ -485,6 +488,12 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
|
|
||||||
if (MainContent.Content != content)
|
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;
|
MainContent.Content = content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -581,6 +590,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
|
|
||||||
public void LoadApplications()
|
public void LoadApplications()
|
||||||
{
|
{
|
||||||
|
_applicationsLoadedOnce = true;
|
||||||
ViewModel.Applications.Clear();
|
ViewModel.Applications.Clear();
|
||||||
|
|
||||||
StatusBarView.LoadProgressBar.IsVisible = true;
|
StatusBarView.LoadProgressBar.IsVisible = true;
|
||||||
|
@ -622,7 +632,8 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
|
|
||||||
Thread applicationLibraryThread = new(() =>
|
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;
|
_isLoading = false;
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue