From 19afb3209c48db5f8e4b5f48f0faee925cd20d9f Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 13 Jul 2021 01:19:28 -0700 Subject: [PATCH] Update to LibHac 0.13.1 (#2328) Update the LibHac dependency to version 0.13.1. This brings a ton of improvements and changes such as: - Refactor `FsSrv` to match the official refactoring done in FS. - Change how the `Horizon` and `HorizonClient` classes are handled. Each client created represents a different process with its own process ID and client state. - Add FS access control to handle permissions for FS service method calls. - Add FS program registry to keep track of the program ID, location and permissions of each process. - Add FS program index map info manager to track the program IDs and indexes of multi-application programs. - Add all FS IPC interfaces. - Rewrite `Fs.Fsa` code to be more accurate. - Rewrite a lot of `FsSrv` code to be more accurate. - Extend directory save data to store `SaveDataExtraData` - Extend directory save data to lock the save directory to allow only one accessor at a time. - Improve waiting and retrying when encountering access issues in `LocalFileSystem` and `DirectorySaveDataFileSystem`. - More `IFileSystemProxy` methods should work now. - Probably a bunch more stuff. On the Ryujinx side: - Forward most `IFileSystemProxy` methods to LibHac. - Register programs and program index map info when launching an application. - Remove hacks and workarounds for missing LibHac functionality. - Recreate missing save data extra data found on emulator startup. - Create system save data that wasn't indexed correctly on an older LibHac version. `FsSrv` now enforces access control for each process. When a process tries to open a save data file system, FS reads the save's extra data to determine who the save owner is and if the caller has permission to open the save data. Previously-created save data did not have extra data created when the save was created. With access control checks in place, this means that processes with no permissions (most games) wouldn't be able to access their own save data. The extra data can be partially created from data in the save data indexer, which should be enough for access control purposes. --- .../FileSystem/Content/ContentManager.cs | 4 +- .../FileSystem/Content/SystemVersion.cs | 1 - Ryujinx.HLE/FileSystem/SaveHelper.cs | 45 - Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 345 ++++++- Ryujinx.HLE/HLEConfiguration.cs | 15 +- Ryujinx.HLE/HOS/ApplicationLoader.cs | 198 +++- Ryujinx.HLE/HOS/Horizon.cs | 38 +- Ryujinx.HLE/HOS/LibHacHorizonManager.cs | 124 +++ Ryujinx.HLE/HOS/ModLoader.cs | 9 +- Ryujinx.HLE/HOS/ProgramLoader.cs | 52 +- .../Services/Account/Acc/AccountManager.cs | 46 +- .../ApplicationProxy/IApplicationFunctions.cs | 46 +- Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs | 31 +- .../HOS/Services/Bcat/IServiceCreator.cs | 9 +- .../IDeliveryCacheDirectoryService.cs | 4 +- .../IDeliveryCacheFileService.cs | 4 +- .../IDeliveryCacheStorageService.cs | 8 +- .../FileSystemProxy/FileSystemProxyHelper.cs | 41 +- .../Services/Fs/FileSystemProxy/IDirectory.cs | 29 +- .../HOS/Services/Fs/FileSystemProxy/IFile.cs | 24 +- .../Fs/FileSystemProxy/IFileSystem.cs | 88 +- .../Services/Fs/FileSystemProxy/IStorage.cs | 9 +- .../HOS/Services/Fs/IDeviceOperator.cs | 20 +- .../HOS/Services/Fs/IFileSystemProxy.cs | 976 +++++++++++++++--- .../HOS/Services/Fs/IMultiCommitManager.cs | 18 +- .../HOS/Services/Fs/ISaveDataInfoReader.cs | 10 +- Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs | 7 +- .../HOS/Services/Mii/MiiDatabaseManager.cs | 104 +- Ryujinx.HLE/Ryujinx.HLE.csproj | 2 +- Ryujinx/Ui/MainWindow.cs | 40 +- Ryujinx/Ui/Widgets/GameTableContextMenu.cs | 22 +- 31 files changed, 1795 insertions(+), 574 deletions(-) delete mode 100644 Ryujinx.HLE/FileSystem/SaveHelper.cs create mode 100644 Ryujinx.HLE/HOS/LibHacHorizonManager.cs diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs index a18838abe..2bffb2a05 100644 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -653,11 +653,11 @@ namespace Ryujinx.HLE.FileSystem.Content public SystemVersion VerifyFirmwarePackage(string firmwarePackage) { - _virtualFileSystem.Reload(); + _virtualFileSystem.ReloadKeySet(); // LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead // So, we check it early for a better user experience. - if (_virtualFileSystem.KeySet.HeaderKey.IsEmpty()) + if (_virtualFileSystem.KeySet.HeaderKey.IsZeros()) { throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); } diff --git a/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs index 08ec35125..6e7e85fdd 100644 --- a/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs +++ b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Text; diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs deleted file mode 100644 index 51a255152..000000000 --- a/Ryujinx.HLE/FileSystem/SaveHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using Ryujinx.HLE.HOS; -using System.IO; - -namespace Ryujinx.HLE.FileSystem -{ - static class SaveHelper - { - public static IFileSystem OpenSystemSaveData(ServiceCtx context, ulong saveId) - { - SaveInfo saveInfo = new SaveInfo(0, (long)saveId, SaveDataType.SystemSaveData, SaveSpaceId.NandSystem); - string savePath = context.Device.FileSystem.GetSavePath(context, saveInfo, false); - - if (File.Exists(savePath)) - { - string tempDirectoryPath = $"{savePath}_temp"; - - Directory.CreateDirectory(tempDirectoryPath); - - IFileSystem outputFolder = new LocalFileSystem(tempDirectoryPath); - - using (LocalStorage systemSaveData = new LocalStorage(savePath, FileAccess.Read, FileMode.Open)) - { - IFileSystem saveFs = new LibHac.FsSystem.Save.SaveDataFileSystem(context.Device.System.KeySet, systemSaveData, IntegrityCheckLevel.None, false); - - saveFs.CopyDirectory(outputFolder, "/", "/"); - } - - File.Delete(savePath); - - Directory.Move(tempDirectoryPath, savePath); - } - else - { - if (!Directory.Exists(savePath)) - { - Directory.CreateDirectory(savePath); - } - } - - return new LocalFileSystem(savePath); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index ff3232c26..fbedd8d4e 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -1,15 +1,23 @@ using LibHac; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; using LibHac.FsSrv; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Spl; using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using System; +using System.Buffers.Text; +using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; +using RightsId = LibHac.Fs.RightsId; namespace Ryujinx.HLE.FileSystem { @@ -24,17 +32,15 @@ namespace Ryujinx.HLE.FileSystem private static bool _isInitialized = false; - public Keyset KeySet { get; private set; } - public FileSystemServer FsServer { get; private set; } - public FileSystemClient FsClient { get; private set; } + public KeySet KeySet { get; private set; } public EmulatedGameCard GameCard { get; private set; } public EmulatedSdCard SdCard { get; private set; } - public ModLoader ModLoader {get; private set;} + public ModLoader ModLoader { get; private set; } private VirtualFileSystem() { - Reload(); + ReloadKeySet(); ModLoader = new ModLoader(); // Should only be created once } @@ -80,39 +86,6 @@ namespace Ryujinx.HLE.FileSystem internal string GetSdCardPath() => MakeFullPath(SdCardPath); public string GetNandPath() => MakeFullPath(NandPath); - internal string GetSavePath(ServiceCtx context, SaveInfo saveInfo, bool isDirectory = true) - { - string saveUserPath = ""; - string baseSavePath = NandPath; - ulong currentTitleId = saveInfo.TitleId; - - switch (saveInfo.SaveSpaceId) - { - case SaveSpaceId.NandUser: baseSavePath = UserNandPath; break; - case SaveSpaceId.NandSystem: baseSavePath = SystemNandPath; break; - case SaveSpaceId.SdCard: baseSavePath = Path.Combine(SdCardPath, "Nintendo"); break; - } - - baseSavePath = Path.Combine(baseSavePath, "save"); - - if (saveInfo.TitleId == 0 && saveInfo.SaveDataType == SaveDataType.SaveData) - { - currentTitleId = context.Process.TitleId; - } - - if (saveInfo.SaveSpaceId == SaveSpaceId.NandUser) - { - saveUserPath = saveInfo.UserId.IsNull ? "savecommon" : saveInfo.UserId.ToString(); - } - - string savePath = Path.Combine(baseSavePath, - saveInfo.SaveId.ToString("x16"), - saveUserPath, - saveInfo.SaveDataType == SaveDataType.SaveData ? currentTitleId.ToString("x16") : string.Empty); - - return MakeFullPath(savePath, isDirectory); - } - public string GetFullPartitionPath(string partitionPath) { return MakeFullPath(partitionPath); @@ -136,8 +109,8 @@ namespace Ryujinx.HLE.FileSystem if (systemPath.StartsWith(baseSystemPath)) { - string rawPath = systemPath.Replace(baseSystemPath, ""); - int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar); + string rawPath = systemPath.Replace(baseSystemPath, ""); + int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar); if (firstSeparatorOffset == -1) { @@ -196,33 +169,34 @@ namespace Ryujinx.HLE.FileSystem return new DriveInfo(Path.GetPathRoot(GetBasePath())); } - public void Reload() + public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient) { - ReloadKeySet(); - LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath()); - DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet); + fsServerClient = horizon.CreatePrivilegedHorizonClient(); + var fsServer = new FileSystemServer(fsServerClient); + + DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer); GameCard = fsServerObjects.GameCard; - SdCard = fsServerObjects.SdCard; + SdCard = fsServerObjects.SdCard; SdCard.SetSdCardInsertionStatus(true); - FileSystemServerConfig fsServerConfig = new FileSystemServerConfig + var fsServerConfig = new FileSystemServerConfig { - FsCreators = fsServerObjects.FsCreators, DeviceOperator = fsServerObjects.DeviceOperator, - ExternalKeySet = KeySet.ExternalKeySet + ExternalKeySet = KeySet.ExternalKeySet, + FsCreators = fsServerObjects.FsCreators }; - FsServer = new FileSystemServer(fsServerConfig); - FsClient = FsServer.CreateFileSystemClient(); + FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); } - - private void ReloadKeySet() + public void ReloadKeySet() { + KeySet ??= KeySet.CreateDefaultKeySet(); + string keyFile = null; string titleKeyFile = null; string consoleKeyFile = null; @@ -256,7 +230,7 @@ namespace Ryujinx.HLE.FileSystem } } - KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile); + ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null); } public void ImportTickets(IFileSystem fs) @@ -277,6 +251,269 @@ namespace Ryujinx.HLE.FileSystem } } + // 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 + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + public static Result FixExtraData(HorizonClient hos) + { + Result rc = GetSystemSaveList(hos, out List systemSaveIds); + if (rc.IsFailure()) return rc; + + rc = FixUnindexedSystemSaves(hos, systemSaveIds); + if (rc.IsFailure()) return rc; + + rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.SdCache); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private static Result FixExtraDataInSpaceId(HorizonClient hos, SaveDataSpaceId spaceId) + { + Span info = stackalloc SaveDataInfo[8]; + + Result rc = hos.Fs.OpenSaveDataIterator(out var iterator, spaceId); + if (rc.IsFailure()) return rc; + + while (true) + { + rc = iterator.ReadSaveDataInfo(out long count, info); + if (rc.IsFailure()) return rc; + + if (count == 0) + return Result.Success; + + for (int i = 0; i < count; i++) + { + rc = FixExtraData(out bool wasFixNeeded, hos, in info[i]); + + if (rc.IsFailure()) + { + Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when fixing extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + } + else if (wasFixNeeded) + { + Logger.Info?.Print(LogClass.Application, $"Tried to rebuild extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + } + } + } + } + + // Gets a list of all the save data files or directories in the system partition. + private static Result GetSystemSaveList(HorizonClient hos, out List list) + { + list = null; + + var mountName = "system".ToU8Span(); + DirectoryHandle handle = default; + List localList = new List(); + + try + { + Result rc = hos.Fs.MountBis(mountName, BisPartitionId.System); + if (rc.IsFailure()) return rc; + + rc = hos.Fs.OpenDirectory(out handle, "system:/save".ToU8Span(), OpenDirectoryMode.All); + if (rc.IsFailure()) return rc; + + DirectoryEntry entry = new DirectoryEntry(); + + while (true) + { + rc = hos.Fs.ReadDirectory(out long readCount, SpanHelpers.AsSpan(ref entry), handle); + if (rc.IsFailure()) return rc; + + if (readCount == 0) + break; + + if (Utf8Parser.TryParse(entry.Name, out ulong saveDataId, out int bytesRead, 'x') && + bytesRead == 16 && (long)saveDataId < 0) + { + localList.Add(saveDataId); + } + } + + list = localList; + + return Result.Success; + } + finally + { + if (handle.IsValid) + { + hos.Fs.CloseDirectory(handle); + } + + if (hos.Fs.IsMounted(mountName)) + { + hos.Fs.Unmount(mountName); + } + } + } + + // Adds system save data that isn't in the save data indexer to the indexer and creates extra data for it. + // Only save data IDs added to SystemExtraDataFixInfo will be fixed. + private static Result FixUnindexedSystemSaves(HorizonClient hos, List existingSaveIds) + { + foreach (var fixInfo in SystemExtraDataFixInfo) + { + if (!existingSaveIds.Contains(fixInfo.StaticSaveDataId)) + { + continue; + } + + Result rc = FixSystemExtraData(out bool wasFixNeeded, hos, in fixInfo); + + if (rc.IsFailure()) + { + Logger.Warning?.Print(LogClass.Application, + $"Error {rc.ToStringWithName()} when fixing extra data for system save data 0x{fixInfo.StaticSaveDataId:x}"); + } + else if (wasFixNeeded) + { + Logger.Info?.Print(LogClass.Application, + $"Tried to rebuild extra data for system save data 0x{fixInfo.StaticSaveDataId:x}"); + } + } + + return Result.Success; + } + + private static Result FixSystemExtraData(out bool wasFixNeeded, HorizonClient hos, in ExtraDataFixInfo info) + { + wasFixNeeded = true; + + Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.StaticSaveDataId); + if (!rc.IsSuccess()) + { + if (!ResultFs.TargetNotFound.Includes(rc)) + return rc; + + // We'll reach this point only if the save data directory exists but it's not in the save data indexer. + // Creating the save will add it to the indexer while leaving its existing contents intact. + return hos.Fs.CreateSystemSaveData(info.StaticSaveDataId, UserId.InvalidId, info.OwnerId, info.DataSize, + info.JournalSize, info.Flags); + } + + if (extraData.Attribute.StaticSaveDataId != 0 && extraData.OwnerId != 0) + { + wasFixNeeded = false; + return Result.Success; + } + + extraData = new SaveDataExtraData + { + Attribute = { StaticSaveDataId = info.StaticSaveDataId }, + OwnerId = info.OwnerId, + Flags = info.Flags, + DataSize = info.DataSize, + JournalSize = info.JournalSize + }; + + // Make a mask for writing the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.System, info.StaticSaveDataId, + in extraData, in extraDataMask); + } + + private static Result FixExtraData(out bool wasFixNeeded, HorizonClient hos, in SaveDataInfo info) + { + wasFixNeeded = true; + + Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.SpaceId, + info.SaveDataId); + if (rc.IsFailure()) return rc; + + // The extra data should have program ID or static save data ID set if it's valid. + // We only try to fix the extra data if the info from the save data indexer has a program ID or static save data ID. + bool canFixByProgramId = extraData.Attribute.ProgramId == ProgramId.InvalidId && + info.ProgramId != ProgramId.InvalidId; + + bool canFixBySaveDataId = extraData.Attribute.StaticSaveDataId == 0 && info.StaticSaveDataId != 0; + + if (!canFixByProgramId && !canFixBySaveDataId) + { + wasFixNeeded = false; + return Result.Success; + } + + // The save data attribute struct can be completely created from the save data info. + extraData.Attribute.ProgramId = info.ProgramId; + extraData.Attribute.UserId = info.UserId; + extraData.Attribute.StaticSaveDataId = info.StaticSaveDataId; + extraData.Attribute.Type = info.Type; + extraData.Attribute.Rank = info.Rank; + extraData.Attribute.Index = info.Index; + + // The rest of the extra data can't be created from the save data info. + // On user saves the owner ID will almost certainly be the same as the program ID. + if (info.Type != LibHac.Fs.SaveDataType.System) + { + extraData.OwnerId = info.ProgramId.Value; + } + else + { + // Try to match the system save with one of the known saves + foreach (ExtraDataFixInfo fixInfo in SystemExtraDataFixInfo) + { + if (extraData.Attribute.StaticSaveDataId == fixInfo.StaticSaveDataId) + { + extraData.OwnerId = fixInfo.OwnerId; + extraData.Flags = fixInfo.Flags; + extraData.DataSize = fixInfo.DataSize; + extraData.JournalSize = fixInfo.JournalSize; + + break; + } + } + } + + // Make a mask for writing the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(info.SpaceId, info.SaveDataId, in extraData, in extraDataMask); + } + + struct ExtraDataFixInfo + { + public ulong StaticSaveDataId; + public ulong OwnerId; + public SaveDataFlags Flags; + public long DataSize; + public long JournalSize; + } + + private static readonly ExtraDataFixInfo[] SystemExtraDataFixInfo = + { + new ExtraDataFixInfo() + { + StaticSaveDataId = 0x8000000000000030, + OwnerId = 0x010000000000001F, + Flags = SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData, + DataSize = 0x10000, + JournalSize = 0x10000 + }, + new ExtraDataFixInfo() + { + StaticSaveDataId = 0x8000000000001040, + OwnerId = 0x0100000000001009, + Flags = SaveDataFlags.None, + DataSize = 0xC000, + JournalSize = 0xC000 + } + }; + public void Unload() { RomFs?.Dispose(); @@ -299,7 +536,7 @@ namespace Ryujinx.HLE.FileSystem { if (_isInitialized) { - throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!"); + throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!"); } _isInitialized = true; diff --git a/Ryujinx.HLE/HLEConfiguration.cs b/Ryujinx.HLE/HLEConfiguration.cs index ba35b92c1..0329ddb7f 100644 --- a/Ryujinx.HLE/HLEConfiguration.cs +++ b/Ryujinx.HLE/HLEConfiguration.cs @@ -1,8 +1,6 @@ using LibHac.FsSystem; using Ryujinx.Audio.Integration; -using Ryujinx.Common; using Ryujinx.Common.Configuration; -using Ryujinx.Common.Configuration.Hid; using Ryujinx.Graphics.GAL; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; @@ -10,7 +8,6 @@ using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.SystemState; using System; -using System.Collections.Generic; namespace Ryujinx.HLE { @@ -25,6 +22,12 @@ namespace Ryujinx.HLE /// This cannot be changed after instantiation. internal readonly VirtualFileSystem VirtualFileSystem; + /// + /// The manager for handling a LibHac Horizon instance. + /// + /// This cannot be changed after instantiation. + internal readonly LibHacHorizonManager LibHacHorizonManager; + /// /// The account manager used by the account service. /// @@ -38,7 +41,7 @@ namespace Ryujinx.HLE internal readonly ContentManager ContentManager; /// - /// The persistant information between run for multi-application capabilities. + /// The persistent information between run for multi-application capabilities. /// /// This cannot be changed after instantiation. public readonly UserChannelPersistence UserChannelPersistence; @@ -124,7 +127,7 @@ namespace Ryujinx.HLE public MemoryManagerMode MemoryManagerMode { internal get; set; } /// - /// Control the inital state of the ignore missing services setting. + /// Control the initial state of the ignore missing services setting. /// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception. /// /// TODO: Update this again. @@ -141,6 +144,7 @@ namespace Ryujinx.HLE public Action RefreshInputConfig { internal get; set; } public HLEConfiguration(VirtualFileSystem virtualFileSystem, + LibHacHorizonManager libHacHorizonManager, ContentManager contentManager, AccountManager accountManager, UserChannelPersistence userChannelPersistence, @@ -162,6 +166,7 @@ namespace Ryujinx.HLE AspectRatio aspectRatio) { VirtualFileSystem = virtualFileSystem; + LibHacHorizonManager = libHacHorizonManager; AccountManager = accountManager; ContentManager = contentManager; UserChannelPersistence = userChannelPersistence; diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index 0d48cc815..f794d199e 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -4,15 +4,17 @@ using LibHac.Account; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; +using LibHac.Loader; +using LibHac.Ncm; using LibHac.Ns; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Loaders.Npdm; using System; using System.Collections.Generic; using System.Globalization; @@ -57,14 +59,14 @@ namespace Ryujinx.HLE.HOS public string TitleName => _titleName; public string DisplayVersion => _displayVersion; - public ulong TitleId { get; private set; } - public bool TitleIs64Bit { get; private set; } + public ulong TitleId { get; private set; } + public bool TitleIs64Bit { get; private set; } public string TitleIdText => TitleId.ToString("x16"); public ApplicationLoader(Switch device) { - _device = device; + _device = device; _controlData = new BlitStruct(1); } @@ -77,7 +79,7 @@ namespace Ryujinx.HLE.HOS LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); - Npdm metaData = ReadNpdm(codeFs); + MetaLoader metaData = ReadNpdm(codeFs); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { TitleId }, _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath()); @@ -91,8 +93,8 @@ namespace Ryujinx.HLE.HOS public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex) { - Nca mainNca = null; - Nca patchNca = null; + Nca mainNca = null; + Nca patchNca = null; Nca controlNca = null; fileSystem.ImportTickets(pfs); @@ -202,7 +204,7 @@ namespace Ryujinx.HLE.HOS public void LoadXci(string xciFile) { FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); - Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage()); + Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage()); if (!xci.HasPartition(XciPartitionType.Secure)) { @@ -220,6 +222,8 @@ namespace Ryujinx.HLE.HOS try { (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index); + + RegisterProgramMapInfo(securePartition).ThrowIfFailure(); } catch (Exception e) { @@ -244,8 +248,8 @@ namespace Ryujinx.HLE.HOS public void LoadNsp(string nspFile) { - FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); - PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); + FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); + PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); Nca mainNca; Nca patchNca; @@ -254,6 +258,8 @@ namespace Ryujinx.HLE.HOS try { (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index); + + RegisterProgramMapInfo(nsp).ThrowIfFailure(); } catch (Exception e) { @@ -286,7 +292,7 @@ namespace Ryujinx.HLE.HOS public void LoadNca(string ncaFile) { FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); - Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); + Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); LoadNca(nca, null, null); } @@ -300,8 +306,8 @@ namespace Ryujinx.HLE.HOS return; } - IStorage dataStorage = null; - IFileSystem codeFs = null; + IStorage dataStorage = null; + IFileSystem codeFs = null; (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _); @@ -366,7 +372,7 @@ namespace Ryujinx.HLE.HOS return; } - Npdm metaData = ReadNpdm(codeFs); + MetaLoader metaData = ReadNpdm(codeFs); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath()); @@ -400,9 +406,12 @@ namespace Ryujinx.HLE.HOS _device.Configuration.VirtualFileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read)); } - if (TitleId != 0) + // Don't create save data for system programs. + if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value)) { - EnsureSaveData(new ApplicationId(TitleId)); + // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. + // We'll know if this changes in the future because stuff will get errors when trying to mount the correct save. + EnsureSaveData(new ApplicationId(TitleId & ~0xFul)); } LoadExeFs(codeFs, metaData); @@ -411,11 +420,11 @@ namespace Ryujinx.HLE.HOS } // Sets TitleId, so be sure to call before using it - private Npdm ReadNpdm(IFileSystem fs) + private MetaLoader ReadNpdm(IFileSystem fs) { Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read); - Npdm metaData; + MetaLoader metaData; if (ResultFs.PathNotFound.Includes(result)) { @@ -425,11 +434,20 @@ namespace Ryujinx.HLE.HOS } else { - metaData = new Npdm(npdmFile.AsStream()); + npdmFile.GetSize(out long fileSize).ThrowIfFailure(); + + var npdmBuffer = new byte[fileSize]; + npdmFile.Read(out _, 0, npdmBuffer).ThrowIfFailure(); + + metaData = new MetaLoader(); + metaData.Load(npdmBuffer).ThrowIfFailure(); } - TitleId = metaData.Aci0.TitleId; - TitleIs64Bit = metaData.Is64Bit; + metaData.GetNpdm(out var npdm).ThrowIfFailure(); + + TitleId = npdm.Aci.Value.ProgramId.Value; + TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0; + _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); return metaData; } @@ -437,7 +455,7 @@ namespace Ryujinx.HLE.HOS private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct controlData, ref string titleName, ref string displayVersion) { IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); - Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read); + Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read); if (result.IsSuccess()) { @@ -461,7 +479,7 @@ namespace Ryujinx.HLE.HOS } } - private void LoadExeFs(IFileSystem codeFs, Npdm metaData = null) + private void LoadExeFs(IFileSystem codeFs, MetaLoader metaData = null) { if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) { @@ -519,22 +537,26 @@ namespace Ryujinx.HLE.HOS Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, _device.Configuration.MemoryManagerMode); - ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, executables: programs); + metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); + ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, new ProgramInfo(in npdm), executables: programs); _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine); } public void LoadProgram(string filePath) { - Npdm metaData = GetDefaultNpdm(); - bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; + MetaLoader metaData = GetDefaultNpdm(); + metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); + ProgramInfo programInfo = new ProgramInfo(in npdm); + + bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; IExecutable executable; if (isNro) { - FileStream input = new FileStream(filePath, FileMode.Open); - NroExecutable obj = new NroExecutable(input.AsStorage()); + FileStream input = new FileStream(filePath, FileMode.Open); + NroExecutable obj = new NroExecutable(input.AsStorage()); executable = obj; @@ -552,13 +574,13 @@ namespace Ryujinx.HLE.HOS if (asetVersion == 0) { ulong iconOffset = reader.ReadUInt64(); - ulong iconSize = reader.ReadUInt64(); + ulong iconSize = reader.ReadUInt64(); ulong nacpOffset = reader.ReadUInt64(); - ulong nacpSize = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); ulong romfsOffset = reader.ReadUInt64(); - ulong romfsSize = reader.ReadUInt64(); + ulong romfsSize = reader.ReadUInt64(); if (romfsSize != 0) { @@ -573,28 +595,28 @@ namespace Ryujinx.HLE.HOS ref ApplicationControlProperty nacp = ref ControlData.Value; - metaData.TitleName = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString(); + programInfo.Name = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString(); - if (string.IsNullOrWhiteSpace(metaData.TitleName)) + if (string.IsNullOrWhiteSpace(programInfo.Name)) { - metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); + programInfo.Name = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); } if (nacp.PresenceGroupId != 0) { - metaData.Aci0.TitleId = nacp.PresenceGroupId; + programInfo.ProgramId = nacp.PresenceGroupId; } else if (nacp.SaveDataOwnerId.Value != 0) { - metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value; + programInfo.ProgramId = nacp.SaveDataOwnerId.Value; } else if (nacp.AddOnContentBaseId != 0) { - metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000; + programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000; } else { - metaData.Aci0.TitleId = 0000000000000000; + programInfo.ProgramId = 0000000000000000; } } } @@ -612,29 +634,109 @@ namespace Ryujinx.HLE.HOS _device.Configuration.ContentManager.LoadEntries(_device); - _titleName = metaData.TitleName; - TitleId = metaData.Aci0.TitleId; - TitleIs64Bit = metaData.Is64Bit; + _titleName = programInfo.Name; + TitleId = programInfo.ProgramId; + TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0; + _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); // Explicitly null titleid to disable the shader cache Graphics.Gpu.GraphicsConfig.TitleId = null; _device.Gpu.HostInitalized.Set(); - ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, executables: executable); + ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: executable); _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine); } - private Npdm GetDefaultNpdm() + private MetaLoader GetDefaultNpdm() { Assembly asm = Assembly.GetCallingAssembly(); using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) { - return new Npdm(npdmStream); + var npdmBuffer = new byte[npdmStream.Length]; + npdmStream.Read(npdmBuffer); + + var metaLoader = new MetaLoader(); + metaLoader.Load(npdmBuffer).ThrowIfFailure(); + + return metaLoader; } } + private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs) + { + ulong mainProgramId = 0; + Span hasIndex = stackalloc bool[0x10]; + + fileSystem.ImportTickets(pfs); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new Nca(fileSystem.KeySet, ncaFile.AsStorage()); + + if (nca.Header.ContentType != NcaContentType.Program) + { + continue; + } + + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + continue; + } + + ulong currentProgramId = nca.Header.TitleId; + ulong currentMainProgramId = currentProgramId & ~0xFFFul; + + if (mainProgramId == 0 && currentMainProgramId != 0) + { + mainProgramId = currentMainProgramId; + } + + if (mainProgramId != currentMainProgramId) + { + // As far as I know there aren't any multi-application game cards containing multi-program applications, + // so because multi-application game cards are the only way we should run into multiple applications + // we'll just return that there's a single program. + return (mainProgramId, 1); + } + + hasIndex[(int)(currentProgramId & 0xF)] = true; + } + + int programCount = 0; + + for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) + { + programCount++; + } + + return (mainProgramId, programCount); + } + + private Result RegisterProgramMapInfo(PartitionFileSystem pfs) + { + (ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs); + + if (programCount <= 0) + return Result.Success; + + Span mapInfo = stackalloc ProgramIndexMapInfo[0x10]; + + for (int i = 0; i < programCount; i++) + { + mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); + mapInfo[i].MainProgramId = new ProgramId(applicationId); + mapInfo[i].ProgramIndex = (byte)i; + } + + return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount)); + } + private Result EnsureSaveData(ApplicationId applicationId) { Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); @@ -643,7 +745,7 @@ namespace Ryujinx.HLE.HOS ref ApplicationControlProperty control = ref ControlData.Value; - if (LibHac.Utilities.IsEmpty(ControlData.ByteSpan)) + if (LibHac.Utilities.IsZeros(ControlData.ByteSpan)) { // If the current application doesn't have a loaded control property, create a dummy one // and set the savedata sizes so a user savedata will be created. @@ -657,8 +759,8 @@ namespace Ryujinx.HLE.HOS "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); } - FileSystemClient fileSystem = _device.Configuration.VirtualFileSystem.FsClient; - Result resultCode = fileSystem.EnsureApplicationCacheStorage(out _, applicationId, ref control); + HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient; + Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, ref control); if (resultCode.IsFailure()) { @@ -667,7 +769,7 @@ namespace Ryujinx.HLE.HOS return resultCode; } - resultCode = EnsureApplicationSaveData(fileSystem, out _, applicationId, ref control, ref user); + resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user); if (resultCode.IsFailure()) { diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 916ed7973..851d7e137 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -1,6 +1,6 @@ -using LibHac; -using LibHac.Bcat; +using LibHac.Common.Keys; using LibHac.Fs; +using LibHac.Fs.Shim; using LibHac.FsSystem; using Ryujinx.Audio; using Ryujinx.Audio.Input; @@ -18,7 +18,6 @@ using Ryujinx.HLE.HOS.Services; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; using Ryujinx.HLE.HOS.Services.Apm; -using Ryujinx.HLE.HOS.Services.Arp; using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Mii; @@ -38,6 +37,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using TimeSpanType = Ryujinx.HLE.HOS.Services.Time.Clock.TimeSpanType; namespace Ryujinx.HLE.HOS { @@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS internal KEvent DisplayResolutionChangeEvent { get; private set; } - public Keyset KeySet => Device.FileSystem.KeySet; + public KeySet KeySet => Device.FileSystem.KeySet; private bool _isDisposed; @@ -111,8 +111,7 @@ namespace Ryujinx.HLE.HOS internal NvHostSyncpt HostSyncpoint { get; private set; } - internal LibHac.Horizon LibHacHorizonServer { get; private set; } - internal HorizonClient LibHacHorizonClient { get; private set; } + internal LibHacHorizonManager LibHacHorizonManager { get; private set; } public Horizon(Switch device) { @@ -184,6 +183,8 @@ namespace Ryujinx.HLE.HOS ContentManager = device.Configuration.ContentManager; CaptureManager = new CaptureManager(device); + LibHacHorizonManager = device.Configuration.LibHacHorizonManager; + // TODO: use set:sys (and get external clock source id from settings) // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); @@ -223,17 +224,16 @@ namespace Ryujinx.HLE.HOS TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom()); - // FIXME: TimeZone shoud be init here but it's actually done in ContentManager + // FIXME: TimeZone should be init here but it's actually done in ContentManager TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); - DatabaseImpl.Instance.InitializeDatabase(device); + DatabaseImpl.Instance.InitializeDatabase(LibHacHorizonManager.SdbClient); HostSyncpoint = new NvHostSyncpt(device); SurfaceFlinger = new SurfaceFlinger(device); - InitLibHacHorizon(); InitializeAudioRenderer(); } @@ -309,20 +309,6 @@ namespace Ryujinx.HLE.HOS ProgramLoader.LoadKip(KernelContext, new KipExecutable(kipFile)); } - private void InitLibHacHorizon() - { - LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer); - - horizon.CreateHorizonClient(out HorizonClient ryujinxClient).ThrowIfFailure(); - horizon.CreateHorizonClient(out HorizonClient bcatClient).ThrowIfFailure(); - - ryujinxClient.Sm.RegisterService(new LibHacIReader(this), "arp:r").ThrowIfFailure(); - new BcatServer(bcatClient); - - LibHacHorizonServer = horizon; - LibHacHorizonClient = ryujinxClient; - } - public void ChangeDockedModeState(bool newState) { if (newState != State.DockedMode) @@ -355,8 +341,8 @@ namespace Ryujinx.HLE.HOS { if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) { - NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; - NfpDevices[nfpDeviceId].AmiiboId = amiiboId; + NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; + NfpDevices[nfpDeviceId].AmiiboId = amiiboId; NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid; } } @@ -453,6 +439,8 @@ namespace Ryujinx.HLE.HOS AudioRendererManager.Dispose(); + LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value); + KernelContext.Dispose(); } } diff --git a/Ryujinx.HLE/HOS/LibHacHorizonManager.cs b/Ryujinx.HLE/HOS/LibHacHorizonManager.cs new file mode 100644 index 000000000..48077aa8a --- /dev/null +++ b/Ryujinx.HLE/HOS/LibHacHorizonManager.cs @@ -0,0 +1,124 @@ +using LibHac; +using LibHac.Bcat; +using LibHac.FsSrv.Impl; +using LibHac.Loader; +using LibHac.Ncm; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Arp; +using System; +using StorageId = LibHac.Ncm.StorageId; + +namespace Ryujinx.HLE.HOS +{ + public class LibHacHorizonManager + { + private LibHac.Horizon Server { get; set; } + public HorizonClient RyujinxClient { get; private set; } + + public HorizonClient ApplicationClient { get; private set; } + + public HorizonClient AccountClient { get; private set; } + public HorizonClient AmClient { get; private set; } + public HorizonClient BcatClient { get; private set; } + public HorizonClient FsClient { get; private set; } + public HorizonClient NsClient { get; private set; } + public HorizonClient SdbClient { get; private set; } + + internal LibHacIReader ArpIReader { get; private set; } + + public LibHacHorizonManager() + { + InitializeServer(); + } + + private void InitializeServer() + { + Server = new LibHac.Horizon(new HorizonConfiguration()); + + RyujinxClient = Server.CreatePrivilegedHorizonClient(); + } + + public void InitializeArpServer() + { + ArpIReader = new LibHacIReader(); + RyujinxClient.Sm.RegisterService(new LibHacArpServiceObject(ArpIReader), "arp:r").ThrowIfFailure(); + } + + public void InitializeBcatServer() + { + BcatClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Bcat, StorageId.BuiltInSystem), + BcatFsPermissions); + + _ = new BcatServer(BcatClient); + } + + public void InitializeFsServer(VirtualFileSystem virtualFileSystem) + { + virtualFileSystem.InitializeFsServer(Server, out var fsClient); + + FsClient = fsClient; + } + + public void InitializeSystemClients() + { + AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem), + AccountFsPermissions); + + AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem), + AmFsPermissions); + + NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem), + NsFsPermissions); + + SdbClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Sdb, StorageId.BuiltInSystem), + SdbFacData, SdbFacDescriptor); + } + + public void InitializeApplicationClient(ProgramId programId, in Npdm npdm) + { + ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), + npdm.FsAccessControlData, npdm.FsAccessControlDescriptor); + } + + private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData | + AccessControlBits.Bits.GameCard | + AccessControlBits.Bits.SaveDataMeta | + AccessControlBits.Bits.GetRightsId; + + private static AccessControlBits.Bits AmFsPermissions => AccessControlBits.Bits.SaveDataManagement | + AccessControlBits.Bits.CreateSaveData | + AccessControlBits.Bits.SystemData; + private static AccessControlBits.Bits BcatFsPermissions => AccessControlBits.Bits.SystemSaveData; + + private static AccessControlBits.Bits NsFsPermissions => AccessControlBits.Bits.ApplicationInfo | + AccessControlBits.Bits.SystemSaveData | + AccessControlBits.Bits.GameCard | + AccessControlBits.Bits.SaveDataManagement | + AccessControlBits.Bits.ContentManager | + AccessControlBits.Bits.ImageManager | + AccessControlBits.Bits.SystemSaveDataManagement | + AccessControlBits.Bits.SystemUpdate | + AccessControlBits.Bits.SdCard | + AccessControlBits.Bits.FormatSdCard | + AccessControlBits.Bits.GetRightsId | + AccessControlBits.Bits.RegisterProgramIndexMapInfo | + AccessControlBits.Bits.MoveCacheStorage; + + // Sdb has save data access control info so we can't store just its access control bits + private static ReadOnlySpan SdbFacData => new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x03, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01 + }; + + private static ReadOnlySpan SdbFacDescriptor => new byte[] + { + 0x01, 0x00, 0x02, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + } +} diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs index a2e9af18a..d09e837d1 100644 --- a/Ryujinx.HLE/HOS/ModLoader.cs +++ b/Ryujinx.HLE/HOS/ModLoader.cs @@ -3,6 +3,7 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.FsSystem.RomFs; +using LibHac.Loader; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.HLE.Loaders.Mods; @@ -12,7 +13,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.IO; -using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.HOS.Kernel.Process; using System.Globalization; @@ -522,7 +522,7 @@ namespace Ryujinx.HLE.HOS { public BitVector32 Stubs; public BitVector32 Replaces; - public Npdm Npdm; + public MetaLoader Npdm; public bool Modified => (Stubs.Data | Replaces.Data) != 0; } @@ -582,9 +582,10 @@ namespace Ryujinx.HLE.HOS continue; } - modLoadResult.Npdm = new Npdm(npdmFile.OpenRead()); + modLoadResult.Npdm = new MetaLoader(); + modLoadResult.Npdm.Load(File.ReadAllBytes(npdmFile.FullName)); - Logger.Info?.Print(LogClass.ModLoader, $"main.npdm replaced"); + Logger.Info?.Print(LogClass.ModLoader, "main.npdm replaced"); } } diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs index 93ddd7ee6..2a49561f8 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -1,4 +1,7 @@ using ARMeilleure.Translation.PTC; +using LibHac.Loader; +using LibHac.Ncm; +using LibHac.Util; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel; @@ -6,12 +9,25 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Loaders.Npdm; using System; using System.Linq; +using System.Runtime.InteropServices; +using Npdm = LibHac.Loader.Npdm; namespace Ryujinx.HLE.HOS { + struct ProgramInfo + { + public string Name; + public ulong ProgramId; + + public ProgramInfo(in Npdm npdm) + { + Name = StringUtils.Utf8ZToString(npdm.Meta.Value.ProgramName); + ProgramId = npdm.Aci.Value.ProgramId.Value; + } + } + static class ProgramLoader { private const bool AslrEnabled = true; @@ -125,11 +141,21 @@ namespace Ryujinx.HLE.HOS return true; } - public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, Npdm metaData, byte[] arguments = null, params IExecutable[] executables) + public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, MetaLoader metaData, ProgramInfo programInfo, byte[] arguments = null, params IExecutable[] executables) { + LibHac.Result rc = metaData.GetNpdm(out var npdm); + + if (rc.IsFailure()) + { + tamperInfo = null; + return false; + } + + ref readonly var meta = ref npdm.Meta.Value; + ulong argsStart = 0; uint argsSize = 0; - ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL; + ulong codeStart = (meta.Flags & 1) != 0 ? 0x8000000UL : 0x200000UL; uint codeSize = 0; var buildIds = executables.Select(e => (e switch @@ -182,18 +208,20 @@ namespace Ryujinx.HLE.HOS int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); - int personalMmHeapPagesCount = metaData.PersonalMmHeapSize / KPageTableBase.PageSize; + int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize); ProcessCreationInfo creationInfo = new ProcessCreationInfo( - metaData.TitleName, - metaData.Version, - metaData.Aci0.TitleId, + programInfo.Name, + (int)meta.Version, + programInfo.ProgramId, codeStart, codePagesCount, - (ProcessCreationFlags)metaData.ProcessFlags | ProcessCreationFlags.IsApplication, + (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, 0, personalMmHeapPagesCount); + context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm); + KernelResult result; KResourceLimit resourceLimit = new KResourceLimit(context); @@ -217,7 +245,7 @@ namespace Ryujinx.HLE.HOS KProcess process = new KProcess(context); - MemoryRegion memoryRegion = (MemoryRegion)((metaData.Acid.Flags >> 2) & 0xf); + MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Value.Flags >> 2) & 0xf); if (memoryRegion > MemoryRegion.NvServices) { @@ -232,7 +260,7 @@ namespace Ryujinx.HLE.HOS result = process.Initialize( creationInfo, - metaData.Aci0.KernelAccessControl.Capabilities, + MemoryMarshal.Cast(npdm.KernelCapabilityData).ToArray(), resourceLimit, memoryRegion, processContextFactory); @@ -262,9 +290,9 @@ namespace Ryujinx.HLE.HOS } } - process.DefaultCpuCore = metaData.DefaultCpuId; + process.DefaultCpuCore = meta.DefaultCpuId; - result = process.Start(metaData.MainThreadPriority, (ulong)metaData.MainThreadStackSize); + result = process.Start(meta.MainThreadPriority, meta.MainThreadStackSize); if (result != KernelResult.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs index 2cea57e9e..454ed1f35 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs @@ -2,12 +2,9 @@ using LibHac.Fs; using LibHac.Fs.Shim; using Ryujinx.Common; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; namespace Ryujinx.HLE.HOS.Services.Account.Acc @@ -16,16 +13,20 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc { public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000"); - private readonly VirtualFileSystem _virtualFileSystem; private readonly AccountSaveDataManager _accountSaveDataManager; + // Todo: The account service doesn't have the permissions to delete save data. Qlaunch takes care of deleting + // save data, so we're currently passing a client with full permissions. Consider moving save data deletion + // outside of the AccountManager. + private readonly HorizonClient _horizonClient; + private ConcurrentDictionary _profiles; public UserProfile LastOpenedUser { get; private set; } - public AccountManager(VirtualFileSystem virtualFileSystem) + public AccountManager(HorizonClient horizonClient) { - _virtualFileSystem = virtualFileSystem; + _horizonClient = horizonClient; _profiles = new ConcurrentDictionary(); @@ -169,31 +170,22 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc SaveDataFilter saveDataFilter = new SaveDataFilter(); saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low)); - Result result = _virtualFileSystem.FsClient.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, ref saveDataFilter); - if (result.IsSuccess()) + _horizonClient.Fs.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span saveDataInfo = stackalloc SaveDataInfo[10]; + + while (true) { - Span saveDataInfo = stackalloc SaveDataInfo[10]; + saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); - while (true) + if (readCount == 0) { - saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo); + break; + } - if (readCount == 0) - { - break; - } - - for (int i = 0; i < readCount; i++) - { - // TODO: We use Directory.Delete workaround because DeleteSaveData softlock without, due to a bug in LibHac 0.12.0. - string savePath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataInfo[i].SaveDataId:x16}"); - string saveMetaPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/saveMeta/{saveDataInfo[i].SaveDataId:x16}"); - - Directory.Delete(savePath, true); - Directory.Delete(saveMetaPath, true); - - _virtualFileSystem.FsClient.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId); - } + for (int i = 0; i < readCount; i++) + { + _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId).ThrowIfFailure(); } } } diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index 3ea956aa0..b358606ca 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -37,6 +37,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati private int _notificationStorageChannelEventHandle; private int _healthWarningDisappearedSystemEventHandle; + private HorizonClient _horizon; + public IApplicationFunctions(Horizon system) { // TODO: Find where they are signaled. @@ -44,6 +46,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati _friendInvitationStorageChannelEvent = new KEvent(system.KernelContext); _notificationStorageChannelEvent = new KEvent(system.KernelContext); _healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext); + + _horizon = system.LibHacHorizonManager.AmClient; } [CommandHipc(1)] @@ -103,14 +107,16 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati // EnsureSaveData(nn::account::Uid) -> u64 public ResultCode EnsureSaveData(ServiceCtx context) { - Uid userId = context.RequestData.ReadStruct().ToLibHacUid(); - ApplicationId applicationId = new ApplicationId(context.Process.TitleId); + Uid userId = context.RequestData.ReadStruct().ToLibHacUid(); + + // Mask out the low nibble of the program ID to get the application ID + ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); BlitStruct controlHolder = context.Device.Application.ControlData; ref ApplicationControlProperty control = ref controlHolder.Value; - if (LibHac.Utilities.IsEmpty(controlHolder.ByteSpan)) + if (LibHac.Utilities.IsZeros(controlHolder.ByteSpan)) { // If the current application doesn't have a loaded control property, create a dummy one // and set the savedata sizes so a user savedata will be created. @@ -124,7 +130,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); } - Result result = EnsureApplicationSaveData(context.Device.FileSystem.FsClient, out long requiredSize, applicationId, ref control, ref userId); + HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; + Result result = EnsureApplicationSaveData(hos.Fs, out long requiredSize, applicationId, ref control, ref userId); context.ResponseData.Write(requiredSize); @@ -195,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati public ResultCode ExtendSaveData(ServiceCtx context) { SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); - Uid userId = context.RequestData.ReadStruct().ToLibHacUid(); + Uid userId = context.RequestData.ReadStruct(); ulong saveDataSize = context.RequestData.ReadUInt64(); ulong journalSize = context.RequestData.ReadUInt64(); @@ -217,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati public ResultCode GetSaveDataSize(ServiceCtx context) { SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); - Uid userId = context.RequestData.ReadStruct().ToLibHacUid(); + Uid userId = context.RequestData.ReadStruct(); // NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded. // Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes. @@ -231,6 +238,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati return ResultCode.Success; } + [CommandHipc(27)] // 5.0.0+ + // CreateCacheStorage(u16 index, s64 save_size, s64 journal_size) -> (u32 storageTarget, u64 requiredSize) + public ResultCode CreateCacheStorage(ServiceCtx context) + { + ushort index = (ushort)context.RequestData.ReadUInt64(); + long saveSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + // Mask out the low nibble of the program ID to get the application ID + ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); + + BlitStruct controlHolder = context.Device.Application.ControlData; + + Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, + out CacheStorageTargetMedia storageTarget, applicationId, ref controlHolder.Value, index, saveSize, + journalSize); + + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write((ulong)storageTarget); + context.ResponseData.Write(requiredSize); + + return ResultCode.Success; + } + [CommandHipc(30)] // BeginBlockingHomeButtonShortAndLongPressed() public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context) @@ -517,7 +549,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle); // NOTE: This is used by "sdk" NSO during applet-application initialization. - // A seperate thread is setup where event-waiting is handled. + // A separate thread is setup where event-waiting is handled. // When the Event is signaled, official sw will assert. return ResultCode.Success; diff --git a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs index dc8ed2e6c..d36ec5900 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs @@ -9,19 +9,14 @@ namespace Ryujinx.HLE.HOS.Services.Arp { class LibHacIReader : LibHac.Arp.Impl.IReader { - private Horizon System { get; } - - public LibHacIReader(Horizon system) - { - System = system; - } + public ApplicationId ApplicationId { get; set; } public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId) { launchProperty = new LibHac.Arp.ApplicationLaunchProperty { BaseStorageId = StorageId.BuiltInUser, - ApplicationId = new ApplicationId(System.Device.Application.TitleId) + ApplicationId = ApplicationId }; return Result.Success; @@ -47,5 +42,27 @@ namespace Ryujinx.HLE.HOS.Services.Arp { throw new NotImplementedException(); } + + public Result GetServiceObject(out object serviceObject) + { + throw new NotImplementedException(); + } + } + + internal class LibHacArpServiceObject : LibHac.Sm.IServiceObject + { + private LibHacIReader _serviceObject; + + public LibHacArpServiceObject(LibHacIReader serviceObject) + { + _serviceObject = serviceObject; + } + + public Result GetServiceObject(out object serviceObject) + { + serviceObject = _serviceObject; + + return Result.Success; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs index 8c408e476..760396b3e 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs @@ -11,11 +11,12 @@ namespace Ryujinx.HLE.HOS.Services.Bcat [Service("bcat:s", "bcat:s")] class IServiceCreator : IpcService { - private LibHac.Bcat.Detail.Ipc.IServiceCreator _base; + private LibHac.Bcat.Impl.Ipc.IServiceCreator _base; public IServiceCreator(ServiceCtx context, string serviceName) { - context.Device.System.LibHacHorizonClient.Sm.GetService(out _base, serviceName).ThrowIfFailure(); + var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient; + applicationClient.Sm.GetService(out _base, serviceName).ThrowIfFailure(); } [CommandHipc(0)] @@ -42,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat { ulong pid = context.RequestData.ReadUInt64(); - Result rc = _base.CreateDeliveryCacheStorageService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv, pid); + Result rc = _base.CreateDeliveryCacheStorageService(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService serv, pid); if (rc.IsSuccess()) { @@ -58,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat { ApplicationId applicationId = context.RequestData.ReadStruct(); - Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv, + Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService serv, applicationId); if (rc.IsSuccess()) diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs index 46c2c09c9..36df6117a 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs @@ -7,9 +7,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator { class IDeliveryCacheDirectoryService : DisposableIpcService { - private LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService _base; + private LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService _base; - public IDeliveryCacheDirectoryService(LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService baseService) + public IDeliveryCacheDirectoryService(LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService baseService) { _base = baseService; } diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs index 55c89a3eb..eada19c22 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs @@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator { class IDeliveryCacheFileService : DisposableIpcService { - private LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService _base; + private LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService _base; - public IDeliveryCacheFileService(LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService baseService) + public IDeliveryCacheFileService(LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService baseService) { _base = baseService; } diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs index 0d2f25213..8fd6a3a87 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs @@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator { class IDeliveryCacheStorageService : DisposableIpcService { - private LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService _base; + private LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService _base; - public IDeliveryCacheStorageService(ServiceCtx context, LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService baseService) + public IDeliveryCacheStorageService(ServiceCtx context, LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService baseService) { _base = baseService; } @@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator // CreateFileService() -> object public ResultCode CreateFileService(ServiceCtx context) { - Result result = _base.CreateFileService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService service); + Result result = _base.CreateFileService(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService service); if (result.IsSuccess()) { @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator // CreateDirectoryService() -> object public ResultCode CreateDirectoryService(ServiceCtx context) { - Result result = _base.CreateDirectoryService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService service); + Result result = _base.CreateDirectoryService(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService service); if (result.IsSuccess()) { diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs index 7774af232..1b6c84c3b 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs @@ -1,10 +1,16 @@ using LibHac; using LibHac.Common; +using LibHac.Common.Keys; using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.Spl; +using System; using System.IO; +using System.Runtime.InteropServices; +using Path = System.IO.Path; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { @@ -16,12 +22,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy try { - LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); - PartitionFileSystem nsp = new PartitionFileSystem(storage); + LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); + ReferenceCountedDisposable nsp = new(new PartitionFileSystem(storage)); - ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + ImportTitleKeysFromNsp(nsp.Target, context.Device.System.KeySet); - openedFileSystem = new IFileSystem(nsp); + openedFileSystem = new IFileSystem(FileSystemInterfaceAdapter.CreateShared(ref nsp)); } catch (HorizonResultException ex) { @@ -45,8 +51,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy } LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + var sharedFs = new ReferenceCountedDisposable(fileSystem); - openedFileSystem = new IFileSystem(fileSystem); + openedFileSystem = new IFileSystem(FileSystemInterfaceAdapter.CreateShared(ref sharedFs)); } catch (HorizonResultException ex) { @@ -99,7 +106,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy return ResultCode.PathDoesNotExist; } - public static void ImportTitleKeysFromNsp(LibHac.Fs.Fsa.IFileSystem nsp, Keyset keySet) + public static void ImportTitleKeysFromNsp(LibHac.Fs.Fsa.IFileSystem nsp, KeySet keySet) { foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) { @@ -125,5 +132,27 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy return FsPath.FromSpan(out path, pathBytes); } + + public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0) + { + ulong position = (ulong)context.Request.PtrBuff[index].Position; + ulong size = (ulong)context.Request.PtrBuff[index].Size; + + ReadOnlySpan buffer = context.Memory.GetSpan(position, (int)size); + ReadOnlySpan fspBuffer = MemoryMarshal.Cast(buffer); + + return ref fspBuffer[0]; + } + + public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0) + { + ulong position = (ulong)context.Request.PtrBuff[index].Position; + ulong size = (ulong)context.Request.PtrBuff[index].Size; + + ReadOnlySpan buffer = context.Memory.GetSpan(position, (int)size); + ReadOnlySpan pathBuffer = MemoryMarshal.Cast(buffer); + + return ref pathBuffer[0]; + } } } diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs index 565ddc4c6..99e545b1e 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs @@ -1,15 +1,13 @@ using LibHac; -using LibHac.Fs; -using System; -using System.Runtime.InteropServices; +using LibHac.Sf; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { - class IDirectory : IpcService + class IDirectory : DisposableIpcService { - private LibHac.Fs.Fsa.IDirectory _baseDirectory; + private ReferenceCountedDisposable _baseDirectory; - public IDirectory(LibHac.Fs.Fsa.IDirectory directory) + public IDirectory(ReferenceCountedDisposable directory) { _baseDirectory = directory; } @@ -19,14 +17,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy public ResultCode Read(ServiceCtx context) { ulong bufferPosition = context.Request.ReceiveBuff[0].Position; - ulong bufferLen = context.Request.ReceiveBuff[0].Size; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; - byte[] entriesBytes = new byte[bufferLen]; - Span entries = MemoryMarshal.Cast(entriesBytes); + byte[] entryBuffer = new byte[bufferLen]; - Result result = _baseDirectory.Read(out long entriesRead, entries); + Result result = _baseDirectory.Target.Read(out long entriesRead, new OutBuffer(entryBuffer)); - context.Memory.Write(bufferPosition, entriesBytes); + context.Memory.Write(bufferPosition, entryBuffer); context.ResponseData.Write(entriesRead); return (ResultCode)result.Value; @@ -36,11 +33,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // GetEntryCount() -> u64 public ResultCode GetEntryCount(ServiceCtx context) { - Result result = _baseDirectory.GetEntryCount(out long entryCount); + Result result = _baseDirectory.Target.GetEntryCount(out long entryCount); context.ResponseData.Write(entryCount); return (ResultCode)result.Value; } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseDirectory?.Dispose(); + } + } } } diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs index cf1611e78..3a94a2a72 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs @@ -1,13 +1,15 @@ using LibHac; using LibHac.Fs; +using LibHac.Sf; +using Ryujinx.Common; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { class IFile : DisposableIpcService { - private LibHac.Fs.Fsa.IFile _baseFile; + private ReferenceCountedDisposable _baseFile; - public IFile(LibHac.Fs.Fsa.IFile baseFile) + public IFile(ReferenceCountedDisposable baseFile) { _baseFile = baseFile; } @@ -18,15 +20,15 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { ulong position = context.Request.ReceiveBuff[0].Position; - ReadOption readOption = new ReadOption(context.RequestData.ReadInt32()); + ReadOption readOption = context.RequestData.ReadStruct(); context.RequestData.BaseStream.Position += 4; long offset = context.RequestData.ReadInt64(); long size = context.RequestData.ReadInt64(); - byte[] data = new byte[size]; + byte[] data = new byte[context.Request.ReceiveBuff[0].Size]; - Result result = _baseFile.Read(out long bytesRead, offset, data, readOption); + Result result = _baseFile.Target.Read(out long bytesRead, offset, new OutBuffer(data), size, readOption); context.Memory.Write(position, data); @@ -41,24 +43,24 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { ulong position = context.Request.SendBuff[0].Position; - WriteOption writeOption = new WriteOption(context.RequestData.ReadInt32()); + WriteOption writeOption = context.RequestData.ReadStruct(); context.RequestData.BaseStream.Position += 4; long offset = context.RequestData.ReadInt64(); long size = context.RequestData.ReadInt64(); - byte[] data = new byte[size]; + byte[] data = new byte[context.Request.SendBuff[0].Size]; context.Memory.Read(position, data); - return (ResultCode)_baseFile.Write(offset, data, writeOption).Value; + return (ResultCode)_baseFile.Target.Write(offset, new InBuffer(data), size, writeOption).Value; } [CommandHipc(2)] // Flush() public ResultCode Flush(ServiceCtx context) { - return (ResultCode)_baseFile.Flush().Value; + return (ResultCode)_baseFile.Target.Flush().Value; } [CommandHipc(3)] @@ -67,14 +69,14 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { long size = context.RequestData.ReadInt64(); - return (ResultCode)_baseFile.SetSize(size).Value; + return (ResultCode)_baseFile.Target.SetSize(size).Value; } [CommandHipc(4)] // GetSize() -> u64 fileSize public ResultCode GetSize(ServiceCtx context) { - Result result = _baseFile.GetSize(out long size); + Result result = _baseFile.Target.GetSize(out long size); context.ResponseData.Write(size); diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs index 5aa26258c..b9b4266d2 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs @@ -1,21 +1,19 @@ using LibHac; -using LibHac.Common; using LibHac.Fs; -using LibHac.Fs.Fsa; -using static Ryujinx.HLE.Utilities.StringUtils; +using LibHac.FsSrv.Sf; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { - class IFileSystem : IpcService + class IFileSystem : DisposableIpcService { - private LibHac.Fs.Fsa.IFileSystem _fileSystem; + private ReferenceCountedDisposable _fileSystem; - public IFileSystem(LibHac.Fs.Fsa.IFileSystem provider) + public IFileSystem(ReferenceCountedDisposable provider) { _fileSystem = provider; } - public LibHac.Fs.Fsa.IFileSystem GetBaseFileSystem() + public ReferenceCountedDisposable GetBaseFileSystem() { return _fileSystem; } @@ -24,79 +22,79 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // CreateFile(u32 createOption, u64 size, buffer, 0x19, 0x301> path) public ResultCode CreateFile(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - CreateFileOptions createOption = (CreateFileOptions)context.RequestData.ReadInt32(); + int createOption = context.RequestData.ReadInt32(); context.RequestData.BaseStream.Position += 4; long size = context.RequestData.ReadInt64(); - return (ResultCode)_fileSystem.CreateFile(name, size, createOption).Value; + return (ResultCode)_fileSystem.Target.CreateFile(in name, size, createOption).Value; } [CommandHipc(1)] // DeleteFile(buffer, 0x19, 0x301> path) public ResultCode DeleteFile(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - return (ResultCode)_fileSystem.DeleteFile(name).Value; + return (ResultCode)_fileSystem.Target.DeleteFile(in name).Value; } [CommandHipc(2)] // CreateDirectory(buffer, 0x19, 0x301> path) public ResultCode CreateDirectory(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - return (ResultCode)_fileSystem.CreateDirectory(name).Value; + return (ResultCode)_fileSystem.Target.CreateDirectory(in name).Value; } [CommandHipc(3)] // DeleteDirectory(buffer, 0x19, 0x301> path) public ResultCode DeleteDirectory(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - return (ResultCode)_fileSystem.DeleteDirectory(name).Value; + return (ResultCode)_fileSystem.Target.DeleteDirectory(in name).Value; } [CommandHipc(4)] // DeleteDirectoryRecursively(buffer, 0x19, 0x301> path) public ResultCode DeleteDirectoryRecursively(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - return (ResultCode)_fileSystem.DeleteDirectoryRecursively(name).Value; + return (ResultCode)_fileSystem.Target.DeleteDirectoryRecursively(in name).Value; } [CommandHipc(5)] // RenameFile(buffer, 0x19, 0x301> oldPath, buffer, 0x19, 0x301> newPath) public ResultCode RenameFile(ServiceCtx context) { - U8Span oldName = ReadUtf8Span(context, 0); - U8Span newName = ReadUtf8Span(context, 1); + ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0); + ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1); - return (ResultCode)_fileSystem.RenameFile(oldName, newName).Value; + return (ResultCode)_fileSystem.Target.RenameFile(in currentName, in newName).Value; } [CommandHipc(6)] // RenameDirectory(buffer, 0x19, 0x301> oldPath, buffer, 0x19, 0x301> newPath) public ResultCode RenameDirectory(ServiceCtx context) { - U8Span oldName = ReadUtf8Span(context, 0); - U8Span newName = ReadUtf8Span(context, 1); + ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0); + ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1); - return (ResultCode)_fileSystem.RenameDirectory(oldName, newName).Value; + return (ResultCode)_fileSystem.Target.RenameDirectory(in currentName, in newName).Value; } [CommandHipc(7)] // GetEntryType(buffer, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType public ResultCode GetEntryType(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - Result result = _fileSystem.GetEntryType(out DirectoryEntryType entryType, name); + Result result = _fileSystem.Target.GetEntryType(out uint entryType, in name); context.ResponseData.Write((int)entryType); @@ -107,11 +105,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // OpenFile(u32 mode, buffer, 0x19, 0x301> path) -> object file public ResultCode OpenFile(ServiceCtx context) { - OpenMode mode = (OpenMode)context.RequestData.ReadInt32(); + uint mode = context.RequestData.ReadUInt32(); - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - Result result = _fileSystem.OpenFile(out LibHac.Fs.Fsa.IFile file, name, mode); + Result result = _fileSystem.Target.OpenFile(out ReferenceCountedDisposable file, in name, mode); if (result.IsSuccess()) { @@ -127,11 +125,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // OpenDirectory(u32 filter_flags, buffer, 0x19, 0x301> path) -> object directory public ResultCode OpenDirectory(ServiceCtx context) { - OpenDirectoryMode mode = (OpenDirectoryMode)context.RequestData.ReadInt32(); + uint mode = context.RequestData.ReadUInt32(); - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - Result result = _fileSystem.OpenDirectory(out LibHac.Fs.Fsa.IDirectory dir, name, mode); + Result result = _fileSystem.Target.OpenDirectory(out ReferenceCountedDisposable dir, name, mode); if (result.IsSuccess()) { @@ -147,16 +145,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // Commit() public ResultCode Commit(ServiceCtx context) { - return (ResultCode)_fileSystem.Commit().Value; + return (ResultCode)_fileSystem.Target.Commit().Value; } [CommandHipc(11)] // GetFreeSpaceSize(buffer, 0x19, 0x301> path) -> u64 totalFreeSpace public ResultCode GetFreeSpaceSize(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - Result result = _fileSystem.GetFreeSpaceSize(out long size, name); + Result result = _fileSystem.Target.GetFreeSpaceSize(out long size, in name); context.ResponseData.Write(size); @@ -167,9 +165,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // GetTotalSpaceSize(buffer, 0x19, 0x301> path) -> u64 totalSize public ResultCode GetTotalSpaceSize(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - Result result = _fileSystem.GetTotalSpaceSize(out long size, name); + Result result = _fileSystem.Target.GetTotalSpaceSize(out long size, in name); context.ResponseData.Write(size); @@ -180,18 +178,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // CleanDirectoryRecursively(buffer, 0x19, 0x301> path) public ResultCode CleanDirectoryRecursively(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - return (ResultCode)_fileSystem.CleanDirectoryRecursively(name).Value; + return (ResultCode)_fileSystem.Target.CleanDirectoryRecursively(in name).Value; } [CommandHipc(14)] // GetFileTimeStampRaw(buffer, 0x19, 0x301> path) -> bytes<0x20> timestamp public ResultCode GetFileTimeStampRaw(ServiceCtx context) { - U8Span name = ReadUtf8Span(context); + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); - Result result = _fileSystem.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, name); + Result result = _fileSystem.Target.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, in name); context.ResponseData.Write(timestamp.Created); context.ResponseData.Write(timestamp.Modified); @@ -206,5 +204,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy return (ResultCode)result.Value; } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _fileSystem?.Dispose(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs index 62a3c62ad..08f5b87b5 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -1,13 +1,14 @@ using LibHac; +using LibHac.Sf; using Ryujinx.HLE.HOS.Ipc; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { class IStorage : DisposableIpcService { - private LibHac.Fs.IStorage _baseStorage; + private ReferenceCountedDisposable _baseStorage; - public IStorage(LibHac.Fs.IStorage baseStorage) + public IStorage(ReferenceCountedDisposable baseStorage) { _baseStorage = baseStorage; } @@ -31,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy byte[] data = new byte[size]; - Result result = _baseStorage.Read((long)offset, data); + Result result = _baseStorage.Target.Read((long)offset, new OutBuffer(data), (long)size); context.Memory.Write(buffDesc.Position, data); @@ -45,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy // GetSize() -> u64 size public ResultCode GetSize(ServiceCtx context) { - Result result = _baseStorage.GetSize(out long size); + Result result = _baseStorage.Target.GetSize(out long size); context.ResponseData.Write(size); diff --git a/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs b/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs index 4e6ee3a49..2968d89c8 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs @@ -3,11 +3,11 @@ using LibHac.FsSrv; namespace Ryujinx.HLE.HOS.Services.Fs { - class IDeviceOperator : IpcService + class IDeviceOperator : DisposableIpcService { - private LibHac.FsSrv.IDeviceOperator _baseOperator; + private ReferenceCountedDisposable _baseOperator; - public IDeviceOperator(LibHac.FsSrv.IDeviceOperator baseOperator) + public IDeviceOperator(ReferenceCountedDisposable baseOperator) { _baseOperator = baseOperator; } @@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs // IsSdCardInserted() -> b8 is_inserted public ResultCode IsSdCardInserted(ServiceCtx context) { - Result result = _baseOperator.IsSdCardInserted(out bool isInserted); + Result result = _baseOperator.Target.IsSdCardInserted(out bool isInserted); context.ResponseData.Write(isInserted); @@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs // IsGameCardInserted() -> b8 is_inserted public ResultCode IsGameCardInserted(ServiceCtx context) { - Result result = _baseOperator.IsGameCardInserted(out bool isInserted); + Result result = _baseOperator.Target.IsGameCardInserted(out bool isInserted); context.ResponseData.Write(isInserted); @@ -38,11 +38,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs // GetGameCardHandle() -> u32 gamecard_handle public ResultCode GetGameCardHandle(ServiceCtx context) { - Result result = _baseOperator.GetGameCardHandle(out GameCardHandle handle); + Result result = _baseOperator.Target.GetGameCardHandle(out GameCardHandle handle); context.ResponseData.Write(handle.Value); return (ResultCode)result.Value; } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseOperator?.Dispose(); + } + } } } diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index bd07c103a..b9205e03e 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -1,33 +1,40 @@ using LibHac; using LibHac.Fs; +using LibHac.Fs.Shim; using LibHac.FsSrv; +using LibHac.FsSrv.Impl; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.Ncm; +using LibHac.Sf; +using LibHac.Spl; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Cpu; using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; using System.IO; using static Ryujinx.HLE.Utilities.StringUtils; +using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; +using IStorage = LibHac.FsSrv.Sf.IStorage; +using RightsId = LibHac.Fs.RightsId; using StorageId = Ryujinx.HLE.FileSystem.StorageId; namespace Ryujinx.HLE.HOS.Services.Fs { [Service("fsp-srv")] - class IFileSystemProxy : IpcService + class IFileSystemProxy : DisposableIpcService { - private LibHac.FsSrv.IFileSystemProxy _baseFileSystemProxy; + private ReferenceCountedDisposable _baseFileSystemProxy; public IFileSystemProxy(ServiceCtx context) { - _baseFileSystemProxy = context.Device.FileSystem.FsServer.CreateFileSystemProxyService(); + var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient; + _baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject(); } [CommandHipc(1)] - // Initialize(u64, pid) - public ResultCode Initialize(ServiceCtx context) + // SetCurrentProcess(u64, pid) + public ResultCode SetCurrentProcess(ServiceCtx context) { return ResultCode.Success; } @@ -94,241 +101,382 @@ namespace Ryujinx.HLE.HOS.Services.Fs { BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); - Result rc = FileSystemProxyHelper.ReadFsPath(out FsPath path, context); - if (rc.IsFailure()) return (ResultCode)rc.Value; + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); - rc = _baseFileSystemProxy.OpenBisFileSystem(out LibHac.Fs.Fsa.IFileSystem fileSystem, ref path, bisPartitionId); - if (rc.IsFailure()) return (ResultCode)rc.Value; + Result result = _baseFileSystemProxy.Target.OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, in path, bisPartitionId); + if (result.IsFailure()) return (ResultCode)result.Value; MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); return ResultCode.Success; } + [CommandHipc(12)] + // OpenBisStorage(u32 partitionId) -> object bisStorage + public ResultCode OpenBisStorage(ServiceCtx context) + { + BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenBisStorage(out ReferenceCountedDisposable storage, bisPartitionId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IStorage(storage)); + + return ResultCode.Success; + } + + [CommandHipc(13)] + // InvalidateBisCache() -> () + public ResultCode InvalidateBisCache(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.InvalidateBisCache().Value; + } + [CommandHipc(18)] // OpenSdCardFileSystem() -> object public ResultCode OpenSdCardFileSystem(ServiceCtx context) { - Result rc = _baseFileSystemProxy.OpenSdCardFileSystem(out LibHac.Fs.Fsa.IFileSystem fileSystem); - if (rc.IsFailure()) return (ResultCode)rc.Value; + Result result = _baseFileSystemProxy.Target.OpenSdCardFileSystem(out var fileSystem); + if (result.IsFailure()) return (ResultCode)result.Value; MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); return ResultCode.Success; } + [CommandHipc(19)] + // FormatSdCardFileSystem() -> () + public ResultCode FormatSdCardFileSystem(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.FormatSdCardFileSystem().Value; + } + [CommandHipc(21)] + // DeleteSaveDataFileSystem(u64 saveDataId) -> () public ResultCode DeleteSaveDataFileSystem(ServiceCtx context) { ulong saveDataId = context.RequestData.ReadUInt64(); - Result result = _baseFileSystemProxy.DeleteSaveDataFileSystem(saveDataId); - - return (ResultCode)result.Value; + return (ResultCode)_baseFileSystemProxy.Target.DeleteSaveDataFileSystem(saveDataId).Value; } [CommandHipc(22)] + // CreateSaveDataFileSystem(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo, nn::fs::SaveDataMetaInfo metaInfo) -> () public ResultCode CreateSaveDataFileSystem(ServiceCtx context) { SaveDataAttribute attribute = context.RequestData.ReadStruct(); SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct(); - SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct(); + SaveDataMetaInfo metaInfo = context.RequestData.ReadStruct(); - // TODO: There's currently no program registry for FS to reference. - // Workaround that by setting the application ID and owner ID if they're not already set - if (attribute.ProgramId == ProgramId.InvalidId) - { - attribute.ProgramId = new ProgramId(context.Device.Application.TitleId); - } - - Logger.Info?.Print(LogClass.ServiceFs, $"Creating save with title ID {attribute.ProgramId.Value:x16}"); - - Result result = _baseFileSystemProxy.CreateSaveDataFileSystem(ref attribute, ref creationInfo, ref metaCreateInfo); - - return (ResultCode)result.Value; + return (ResultCode)_baseFileSystemProxy.Target.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo).Value; } [CommandHipc(23)] + // CreateSaveDataFileSystemBySystemSaveDataId(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo) -> () public ResultCode CreateSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) { SaveDataAttribute attribute = context.RequestData.ReadStruct(); SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct(); - Result result = _baseFileSystemProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref creationInfo); + return (ResultCode)_baseFileSystemProxy.Target.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo).Value; + } - return (ResultCode)result.Value; + [CommandHipc(24)] + // RegisterSaveDataFileSystemAtomicDeletion(buffer saveDataIds) -> () + public ResultCode RegisterSaveDataFileSystemAtomicDeletion(ServiceCtx context) + { + byte[] saveIdBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, saveIdBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.RegisterSaveDataFileSystemAtomicDeletion(new InBuffer(saveIdBuffer)).Value; } [CommandHipc(25)] + // DeleteSaveDataFileSystemBySaveDataSpaceId(u8 spaceId, u64 saveDataId) -> () public ResultCode DeleteSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) { SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); ulong saveDataId = context.RequestData.ReadUInt64(); - Result result = _baseFileSystemProxy.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + return (ResultCode)_baseFileSystemProxy.Target.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId).Value; + } - return (ResultCode)result.Value; + [CommandHipc(26)] + // FormatSdCardDryRun() -> () + public ResultCode FormatSdCardDryRun(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.FormatSdCardDryRun().Value; + } + + [CommandHipc(27)] + // IsExFatSupported() -> (u8 isSupported) + public ResultCode IsExFatSupported(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Target.IsExFatSupported(out bool isSupported); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(isSupported); + + return ResultCode.Success; } [CommandHipc(28)] + // DeleteSaveDataFileSystemBySaveDataAttribute(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> () public ResultCode DeleteSaveDataFileSystemBySaveDataAttribute(ServiceCtx context) { SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataAttribute attribute = context.RequestData.ReadStruct(); - Result result = _baseFileSystemProxy.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, ref attribute); - - return (ResultCode)result.Value; + return (ResultCode)_baseFileSystemProxy.Target.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute).Value; } [CommandHipc(30)] - // OpenGameCardStorage(u32, u32) -> object + // OpenGameCardStorage(u32 handle, u32 partitionId) -> object public ResultCode OpenGameCardStorage(ServiceCtx context) { GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32(); - Result result = _baseFileSystemProxy.OpenGameCardStorage(out LibHac.Fs.IStorage storage, handle, partitionId); + Result result = _baseFileSystemProxy.Target.OpenGameCardStorage(out ReferenceCountedDisposable storage, handle, partitionId); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) - { - MakeObject(context, new FileSystemProxy.IStorage(storage)); - } + MakeObject(context, new FileSystemProxy.IStorage(storage)); - return (ResultCode)result.Value; + return ResultCode.Success; + } + + [CommandHipc(31)] + // OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object + public ResultCode OpenGameCardFileSystem(ServiceCtx context) + { + GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32()); + GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenGameCardFileSystem(out ReferenceCountedDisposable fileSystem, handle, partitionId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(32)] + // ExtendSaveDataFileSystem(u8 spaceId, u64 saveDataId, s64 dataSize, s64 journalSize) -> () + public ResultCode ExtendSaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.ExtendSaveDataFileSystem(spaceId, saveDataId, dataSize, journalSize).Value; + } + + [CommandHipc(33)] + // DeleteCacheStorage(u16 index) -> () + public ResultCode DeleteCacheStorage(ServiceCtx context) + { + ushort index = context.RequestData.ReadUInt16(); + + return (ResultCode)_baseFileSystemProxy.Target.DeleteCacheStorage(index).Value; + } + + [CommandHipc(34)] + // GetCacheStorageSize(u16 index) -> (s64 dataSize, s64 journalSize) + public ResultCode GetCacheStorageSize(ServiceCtx context) + { + ushort index = context.RequestData.ReadUInt16(); + + Result result = _baseFileSystemProxy.Target.GetCacheStorageSize(out long dataSize, out long journalSize, index); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(dataSize); + context.ResponseData.Write(journalSize); + + return ResultCode.Success; } [CommandHipc(35)] + // CreateSaveDataFileSystemWithHashSalt(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo, nn::fs::SaveDataMetaInfo metaInfo nn::fs::HashSalt hashSalt) -> () public ResultCode CreateSaveDataFileSystemWithHashSalt(ServiceCtx context) { SaveDataAttribute attribute = context.RequestData.ReadStruct(); SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct(); - SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct(); + SaveDataMetaInfo metaCreateInfo = context.RequestData.ReadStruct(); HashSalt hashSalt = context.RequestData.ReadStruct(); - // TODO: There's currently no program registry for FS to reference. - // Workaround that by setting the application ID and owner ID if they're not already set - if (attribute.ProgramId == ProgramId.InvalidId) - { - attribute.ProgramId = new ProgramId(context.Device.Application.TitleId); - } - - Result result = _baseFileSystemProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref creationInfo, ref metaCreateInfo, ref hashSalt); - - return (ResultCode)result.Value; + return (ResultCode)_baseFileSystemProxy.Target.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value; } [CommandHipc(51)] - // OpenSaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object saveDataFs + // OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object saveDataFs public ResultCode OpenSaveDataFileSystem(ServiceCtx context) { SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataAttribute attribute = context.RequestData.ReadStruct(); - // TODO: There's currently no program registry for FS to reference. - // Workaround that by setting the application ID if it's not already set - if (attribute.ProgramId == ProgramId.InvalidId) - { - attribute.ProgramId = new ProgramId(context.Device.Application.TitleId); - } + Result result = _baseFileSystemProxy.Target.OpenSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; - Result result = _baseFileSystemProxy.OpenSaveDataFileSystem(out LibHac.Fs.Fsa.IFileSystem fileSystem, spaceId, ref attribute); + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); - if (result.IsSuccess()) - { - MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); - } - - return (ResultCode)result.Value; + return ResultCode.Success; } [CommandHipc(52)] - // OpenSaveDataFileSystemBySystemSaveDataId(u8 save_data_space_id, nn::fssrv::sf::SaveStruct saveStruct) -> object systemSaveDataFs + // OpenSaveDataFileSystemBySystemSaveDataId(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object systemSaveDataFs public ResultCode OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) { SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataAttribute attribute = context.RequestData.ReadStruct(); - Result result = _baseFileSystemProxy.OpenSaveDataFileSystemBySystemSaveDataId(out LibHac.Fs.Fsa.IFileSystem fileSystem, spaceId, ref attribute); + Result result = _baseFileSystemProxy.Target.OpenSaveDataFileSystemBySystemSaveDataId(out ReferenceCountedDisposable fileSystem, spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) - { - MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); - } + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); - return (ResultCode)result.Value; + return ResultCode.Success; } [CommandHipc(53)] - // OpenReadOnlySaveDataFileSystem(u8 save_data_space_id, nn::fssrv::sf::SaveStruct save_struct) -> object + // OpenReadOnlySaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object public ResultCode OpenReadOnlySaveDataFileSystem(ServiceCtx context) { SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataAttribute attribute = context.RequestData.ReadStruct(); - // TODO: There's currently no program registry for FS to reference. - // Workaround that by setting the application ID if it's not already set - if (attribute.ProgramId == ProgramId.InvalidId) - { - attribute.ProgramId = new ProgramId(context.Device.Application.TitleId); - } + Result result = _baseFileSystemProxy.Target.OpenReadOnlySaveDataFileSystem(out ReferenceCountedDisposable fileSystem, spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; - Result result = _baseFileSystemProxy.OpenReadOnlySaveDataFileSystem(out LibHac.Fs.Fsa.IFileSystem fileSystem, spaceId, ref attribute); + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); - if (result.IsSuccess()) - { - MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); - } + return ResultCode.Success; + } - return (ResultCode)result.Value; + [CommandHipc(57)] + // ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(u8 spaceId, u64 saveDataId) -> (buffer extraData) + public ResultCode ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + Result result = _baseFileSystemProxy.Target.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(new OutBuffer(extraDataBuffer), spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + return ResultCode.Success; + } + + [CommandHipc(58)] + // ReadSaveDataFileSystemExtraData(u64 saveDataId) -> (buffer extraData) + public ResultCode ReadSaveDataFileSystemExtraData(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + Result result = _baseFileSystemProxy.Target.ReadSaveDataFileSystemExtraData(new OutBuffer(extraDataBuffer), saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + return ResultCode.Success; + } + + [CommandHipc(59)] + // WriteSaveDataFileSystemExtraData(u8 spaceId, u64 saveDataId, buffer extraData) -> () + public ResultCode WriteSaveDataFileSystemExtraData(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, new InBuffer(extraDataBuffer)).Value; } [CommandHipc(60)] + // OpenSaveDataInfoReader() -> object public ResultCode OpenSaveDataInfoReader(ServiceCtx context) { - Result result = _baseFileSystemProxy.OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader); + Result result = _baseFileSystemProxy.Target.OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) - { - MakeObject(context, new ISaveDataInfoReader(infoReader)); - } + MakeObject(context, new ISaveDataInfoReader(infoReader)); - return (ResultCode)result.Value; + return ResultCode.Success; } [CommandHipc(61)] + // OpenSaveDataInfoReaderBySaveDataSpaceId(u8 spaceId) -> object public ResultCode OpenSaveDataInfoReaderBySaveDataSpaceId(ServiceCtx context) { SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadByte(); - Result result = _baseFileSystemProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable infoReader, spaceId); + Result result = _baseFileSystemProxy.Target.OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable infoReader, spaceId); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) - { - MakeObject(context, new ISaveDataInfoReader(infoReader)); - } + MakeObject(context, new ISaveDataInfoReader(infoReader)); - return (ResultCode)result.Value; + return ResultCode.Success; } [CommandHipc(62)] + // OpenSaveDataInfoReaderOnlyCacheStorage() -> object public ResultCode OpenSaveDataInfoReaderOnlyCacheStorage(ServiceCtx context) { - SaveDataFilter filter = new SaveDataFilter(); - filter.SetSaveDataType(SaveDataType.Cache); - filter.SetProgramId(new ProgramId(context.Process.TitleId)); + Result result = _baseFileSystemProxy.Target.OpenSaveDataInfoReaderOnlyCacheStorage(out ReferenceCountedDisposable infoReader); + if (result.IsFailure()) return (ResultCode)result.Value; - // FS would query the User and SdCache space IDs to find where the existing cache is (if any). - // We always have the SD card inserted, so we can always use SdCache for now. - Result result = _baseFileSystemProxy.OpenSaveDataInfoReaderBySaveDataSpaceId( - out ReferenceCountedDisposable infoReader, SaveDataSpaceId.SdCache); + MakeObject(context, new ISaveDataInfoReader(infoReader)); - if (result.IsSuccess()) - { - MakeObject(context, new ISaveDataInfoReader(infoReader)); - } + return ResultCode.Success; + } - return (ResultCode)result.Value; + [CommandHipc(64)] + // OpenSaveDataInternalStorageFileSystem(u8 spaceId, u64 saveDataId) -> object + public ResultCode OpenSaveDataInternalStorageFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Target.OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable fileSystem, spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(65)] + // UpdateSaveDataMacForDebug(u8 spaceId, u64 saveDataId) -> () + public ResultCode UpdateSaveDataMacForDebug(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.UpdateSaveDataMacForDebug(spaceId, saveDataId).Value; + } + + [CommandHipc(66)] + public ResultCode WriteSaveDataFileSystemExtraDataWithMask(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + byte[] maskBuffer = new byte[context.Request.SendBuff[1].Size]; + context.Memory.Read(context.Request.SendBuff[1].Position, maskBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, new InBuffer(extraDataBuffer), new InBuffer(maskBuffer)).Value; } [CommandHipc(67)] @@ -342,12 +490,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs byte[] infoBuffer = new byte[bufferLen]; - Result result = _baseFileSystemProxy.FindSaveDataWithFilter(out long count, infoBuffer, spaceId, ref filter); + Result result = _baseFileSystemProxy.Target.FindSaveDataWithFilter(out long count, new OutBuffer(infoBuffer), spaceId, in filter); + if (result.IsFailure()) return (ResultCode)result.Value; context.Memory.Write(bufferPosition, infoBuffer); context.ResponseData.Write(count); - return (ResultCode)result.Value; + return ResultCode.Success; } [CommandHipc(68)] @@ -356,23 +505,160 @@ namespace Ryujinx.HLE.HOS.Services.Fs SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataFilter filter = context.RequestData.ReadStruct(); - Result result = _baseFileSystemProxy.OpenSaveDataInfoReaderWithFilter( - out ReferenceCountedDisposable infoReader, spaceId, ref filter); + Result result = _baseFileSystemProxy.Target.OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable infoReader, spaceId, in filter); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) - { - MakeObject(context, new ISaveDataInfoReader(infoReader)); - } + MakeObject(context, new ISaveDataInfoReader(infoReader)); - return (ResultCode)result.Value; + return ResultCode.Success; + } + + [CommandHipc(69)] + public ResultCode ReadSaveDataFileSystemExtraDataBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Target.ReadSaveDataFileSystemExtraDataBySaveDataAttribute(new OutBuffer(outputBuffer), spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, outputBuffer); + + return ResultCode.Success; + } + + [CommandHipc(70)] + public ResultCode WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + byte[] maskBuffer = new byte[context.Request.SendBuff[1].Size]; + context.Memory.Read(context.Request.SendBuff[1].Position, maskBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, spaceId, new InBuffer(extraDataBuffer), new InBuffer(maskBuffer)).Value; } [CommandHipc(71)] public ResultCode ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(ServiceCtx context) { - Logger.Stub?.PrintStub(LogClass.ServiceFs); + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); - MemoryHelper.FillWithZeros(context.Memory, context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size); + byte[] maskBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, maskBuffer); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Target.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(new OutBuffer(outputBuffer), spaceId, in attribute, new InBuffer(maskBuffer)); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, outputBuffer); + + return ResultCode.Success; + } + + [CommandHipc(80)] + public ResultCode OpenSaveDataMetaFile(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt32(); + SaveDataMetaType metaType = (SaveDataMetaType)context.RequestData.ReadInt32(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Target.OpenSaveDataMetaFile(out ReferenceCountedDisposable file, spaceId, in attribute, metaType); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new IFile(file)); + + return ResultCode.Success; + } + + [CommandHipc(84)] + public ResultCode ListAccessibleSaveDataOwnerId(ServiceCtx context) + { + int startIndex = context.RequestData.ReadInt32(); + int bufferCount = context.RequestData.ReadInt32(); + ProgramId programId = context.RequestData.ReadStruct(); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Target.ListAccessibleSaveDataOwnerId(out int readCount, new OutBuffer(outputBuffer), programId, startIndex, bufferCount); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(readCount); + + return ResultCode.Success; + } + + [CommandHipc(100)] + public ResultCode OpenImageDirectoryFileSystem(ServiceCtx context) + { + ImageDirectoryId directoryId = (ImageDirectoryId)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenImageDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, directoryId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(101)] + public ResultCode OpenBaseFileSystem(ServiceCtx context) + { + BaseFileSystemId fileSystemId = (BaseFileSystemId)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenBaseFileSystem(out ReferenceCountedDisposable fileSystem, fileSystemId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(110)] + public ResultCode OpenContentStorageFileSystem(ServiceCtx context) + { + ContentStorageId contentStorageId = (ContentStorageId)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenContentStorageFileSystem(out ReferenceCountedDisposable fileSystem, contentStorageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(120)] + public ResultCode OpenCloudBackupWorkStorageFileSystem(ServiceCtx context) + { + CloudBackupWorkStorageId storageId = (CloudBackupWorkStorageId)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenCloudBackupWorkStorageFileSystem(out ReferenceCountedDisposable fileSystem, storageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(130)] + public ResultCode OpenCustomStorageFileSystem(ServiceCtx context) + { + CustomStorageId customStorageId = (CustomStorageId)context.RequestData.ReadInt32(); + + Result result = _baseFileSystemProxy.Target.OpenCustomStorageFileSystem(out ReferenceCountedDisposable fileSystem, customStorageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); return ResultCode.Success; } @@ -381,13 +667,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs // OpenDataStorageByCurrentProcess() -> object dataStorage public ResultCode OpenDataStorageByCurrentProcess(ServiceCtx context) { - MakeObject(context, new FileSystemProxy.IStorage(context.Device.FileSystem.RomFs.AsStorage())); + var storage = context.Device.FileSystem.RomFs.AsStorage(true); + var sharedStorage = new ReferenceCountedDisposable(storage); + ReferenceCountedDisposable sfStorage = StorageInterfaceAdapter.CreateShared(ref sharedStorage); - return 0; + MakeObject(context, new FileSystemProxy.IStorage(sfStorage)); + + return ResultCode.Success; } [CommandHipc(202)] - // OpenDataStorageByDataId(u8 storageId, nn::ApplicationId tid) -> object dataStorage + // OpenDataStorageByDataId(u8 storageId, nn::ncm::DataId dataId) -> object dataStorage public ResultCode OpenDataStorageByDataId(ServiceCtx context) { StorageId storageId = (StorageId)context.RequestData.ReadByte(); @@ -396,11 +686,15 @@ namespace Ryujinx.HLE.HOS.Services.Fs // We do a mitm here to find if the request is for an AOC. // This is because AOC can be distributed over multiple containers in the emulator. - if (context.Device.System.ContentManager.GetAocDataStorage((ulong)titleId, out LibHac.Fs.IStorage aocStorage, context.Device.Configuration.FsIntegrityCheckLevel)) + if (context.Device.System.ContentManager.GetAocDataStorage(titleId, out LibHac.Fs.IStorage aocStorage, context.Device.Configuration.FsIntegrityCheckLevel)) { Logger.Info?.Print(LogClass.Loader, $"Opened AddOnContent Data TitleID={titleId:X16}"); - MakeObject(context, new FileSystemProxy.IStorage(context.Device.FileSystem.ModLoader.ApplyRomFsMods((ulong)titleId, aocStorage))); + var storage = context.Device.FileSystem.ModLoader.ApplyRomFsMods(titleId, aocStorage); + var sharedStorage = new ReferenceCountedDisposable(storage); + ReferenceCountedDisposable sfStorage = StorageInterfaceAdapter.CreateShared(ref sharedStorage); + + MakeObject(context, new FileSystemProxy.IStorage(sfStorage)); return ResultCode.Success; } @@ -432,8 +726,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs LibHac.Fs.IStorage ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + var sharedStorage = new ReferenceCountedDisposable(romfsStorage); + ReferenceCountedDisposable sfStorage = StorageInterfaceAdapter.CreateShared(ref sharedStorage); - MakeObject(context, new FileSystemProxy.IStorage(romfsStorage)); + MakeObject(context, new FileSystemProxy.IStorage(sfStorage)); } catch (HorizonResultException ex) { @@ -460,7 +756,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs // OpenPatchDataStorageByCurrentProcess() -> object public ResultCode OpenPatchDataStorageByCurrentProcess(ServiceCtx context) { - MakeObject(context, new FileSystemProxy.IStorage(context.Device.FileSystem.RomFs.AsStorage())); + var storage = context.Device.FileSystem.RomFs.AsStorage(true); + var sharedStorage = new ReferenceCountedDisposable(storage); + ReferenceCountedDisposable sfStorage = StorageInterfaceAdapter.CreateShared(ref sharedStorage); + + MakeObject(context, new FileSystemProxy.IStorage(sfStorage)); return ResultCode.Success; } @@ -469,43 +769,325 @@ namespace Ryujinx.HLE.HOS.Services.Fs // OpenDataStorageByCurrentProcess() -> object dataStorage public ResultCode OpenDeviceOperator(ServiceCtx context) { - Result result = _baseFileSystemProxy.OpenDeviceOperator(out LibHac.FsSrv.IDeviceOperator deviceOperator); + Result result = _baseFileSystemProxy.Target.OpenDeviceOperator(out ReferenceCountedDisposable deviceOperator); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) - { - MakeObject(context, new IDeviceOperator(deviceOperator)); - } + MakeObject(context, new IDeviceOperator(deviceOperator)); - return (ResultCode)result.Value; + return ResultCode.Success; + } + + [CommandHipc(601)] + public ResultCode QuerySaveDataTotalSize(ServiceCtx context) + { + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + Result result = _baseFileSystemProxy.Target.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + [CommandHipc(511)] + public ResultCode NotifySystemDataUpdateEvent(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.NotifySystemDataUpdateEvent().Value; + } + + [CommandHipc(523)] + public ResultCode SimulateDeviceDetectionEvent(ServiceCtx context) + { + bool signalEvent = context.RequestData.ReadBoolean(); + context.RequestData.BaseStream.Seek(3, SeekOrigin.Current); + SdmmcPort port = context.RequestData.ReadStruct(); + SimulatingDeviceDetectionMode mode = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Target.SimulateDeviceDetectionEvent(port, mode, signalEvent).Value; + } + + [CommandHipc(602)] + public ResultCode VerifySaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] readBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, readBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.VerifySaveDataFileSystem(saveDataId, new OutBuffer(readBuffer)).Value; + } + + [CommandHipc(603)] + public ResultCode CorruptSaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.CorruptSaveDataFileSystem(saveDataId).Value; + } + + [CommandHipc(604)] + public ResultCode CreatePaddingFile(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.CreatePaddingFile(size).Value; + } + + [CommandHipc(605)] + public ResultCode DeleteAllPaddingFiles(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.DeleteAllPaddingFiles().Value; + } + + [CommandHipc(606)] + public ResultCode GetRightsId(ServiceCtx context) + { + LibHac.Ncm.StorageId storageId = (LibHac.Ncm.StorageId)context.RequestData.ReadInt64(); + ProgramId programId = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Target.GetRightsId(out RightsId rightsId, programId, storageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandHipc(607)] + public ResultCode RegisterExternalKey(ServiceCtx context) + { + RightsId rightsId = context.RequestData.ReadStruct(); + AccessKey accessKey = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Target.RegisterExternalKey(in rightsId, in accessKey).Value; + } + + [CommandHipc(608)] + public ResultCode UnregisterAllExternalKey(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.UnregisterAllExternalKey().Value; + } + + [CommandHipc(609)] + public ResultCode GetRightsIdByPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + Result result = _baseFileSystemProxy.Target.GetRightsIdByPath(out RightsId rightsId, in path); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandHipc(610)] + public ResultCode GetRightsIdAndKeyGenerationByPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + Result result = _baseFileSystemProxy.Target.GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in path); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(keyGeneration); + context.ResponseData.BaseStream.Seek(7, SeekOrigin.Current); + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandHipc(611)] + public ResultCode SetCurrentPosixTimeWithTimeDifference(ServiceCtx context) + { + int timeDifference = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); + long time = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.SetCurrentPosixTimeWithTimeDifference(time, timeDifference).Value; + } + + [CommandHipc(612)] + public ResultCode GetFreeSpaceSizeForSaveData(ServiceCtx context) + { + SaveDataSpaceId spaceId = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Target.GetFreeSpaceSizeForSaveData(out long freeSpaceSize, spaceId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(freeSpaceSize); + + return ResultCode.Success; + } + + [CommandHipc(613)] + public ResultCode VerifySaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] readBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, readBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, new OutBuffer(readBuffer)).Value; + } + + [CommandHipc(614)] + public ResultCode CorruptSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.CorruptSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId).Value; + } + + [CommandHipc(615)] + public ResultCode QuerySaveDataInternalStorageTotalSize(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Target.QuerySaveDataInternalStorageTotalSize(out long size, spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(size); + + return ResultCode.Success; + } + + [CommandHipc(616)] + public ResultCode GetSaveDataCommitId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Target.GetSaveDataCommitId(out long commitId, spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(commitId); + + return ResultCode.Success; + } + + [CommandHipc(617)] + public ResultCode UnregisterExternalKey(ServiceCtx context) + { + RightsId rightsId = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Target.UnregisterExternalKey(in rightsId).Value; + } + + [CommandHipc(620)] + public ResultCode SetSdCardEncryptionSeed(ServiceCtx context) + { + EncryptionSeed encryptionSeed = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Target.SetSdCardEncryptionSeed(in encryptionSeed).Value; } [CommandHipc(630)] - // SetSdCardAccessibility(u8) + // SetSdCardAccessibility(u8 isAccessible) public ResultCode SetSdCardAccessibility(ServiceCtx context) { bool isAccessible = context.RequestData.ReadBoolean(); - return (ResultCode)_baseFileSystemProxy.SetSdCardAccessibility(isAccessible).Value; + return (ResultCode)_baseFileSystemProxy.Target.SetSdCardAccessibility(isAccessible).Value; } [CommandHipc(631)] - // IsSdCardAccessible() -> u8 + // IsSdCardAccessible() -> u8 isAccessible public ResultCode IsSdCardAccessible(ServiceCtx context) { - Result result = _baseFileSystemProxy.IsSdCardAccessible(out bool isAccessible); + Result result = _baseFileSystemProxy.Target.IsSdCardAccessible(out bool isAccessible); + if (result.IsFailure()) return (ResultCode)result.Value; context.ResponseData.Write(isAccessible); - return (ResultCode)result.Value; + return ResultCode.Success; + } + + [CommandHipc(702)] + public ResultCode IsAccessFailureDetected(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Target.IsAccessFailureDetected(out bool isDetected, processId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(isDetected); + + return ResultCode.Success; + } + + [CommandHipc(710)] + public ResultCode ResolveAccessFailure(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.ResolveAccessFailure(processId).Value; + } + + [CommandHipc(720)] + public ResultCode AbandonAccessFailure(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.AbandonAccessFailure(processId).Value; + } + + [CommandHipc(800)] + public ResultCode GetAndClearErrorInfo(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Target.GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(errorInfo); + + return ResultCode.Success; + } + + [CommandHipc(810)] + public ResultCode RegisterProgramIndexMapInfo(ServiceCtx context) + { + int programCount = context.RequestData.ReadInt32(); + + byte[] mapInfoBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, mapInfoBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.RegisterProgramIndexMapInfo(new InBuffer(mapInfoBuffer), programCount).Value; + } + + [CommandHipc(1000)] + public ResultCode SetBisRootForHost(ServiceCtx context) + { + BisPartitionId partitionId = (BisPartitionId)context.RequestData.ReadInt32(); + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + return (ResultCode)_baseFileSystemProxy.Target.SetBisRootForHost(partitionId, in path).Value; + } + + [CommandHipc(1001)] + public ResultCode SetSaveDataSize(ServiceCtx context) + { + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.SetSaveDataSize(dataSize, journalSize).Value; + } + + [CommandHipc(1002)] + public ResultCode SetSaveDataRootPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + return (ResultCode)_baseFileSystemProxy.Target.SetSaveDataRootPath(in path).Value; } [CommandHipc(1003)] - // DisableAutoSaveDataCreation() public ResultCode DisableAutoSaveDataCreation(ServiceCtx context) { - // NOTE: This call does nothing in original service. - - return ResultCode.Success; + return (ResultCode)_baseFileSystemProxy.Target.DisableAutoSaveDataCreation().Value; } [CommandHipc(1004)] @@ -542,11 +1124,39 @@ namespace Ryujinx.HLE.HOS.Services.Fs return ResultCode.Success; } + [CommandHipc(1007)] + public ResultCode RegisterUpdatePartition(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.RegisterUpdatePartition().Value; + } + + [CommandHipc(1008)] + public ResultCode OpenRegisteredUpdatePartition(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Target.OpenRegisteredUpdatePartition(out ReferenceCountedDisposable fileSystem); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(fileSystem)); + + return ResultCode.Success; + } + + [CommandHipc(1009)] + public ResultCode GetAndClearMemoryReportInfo(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Target.GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(reportInfo); + + return ResultCode.Success; + } + [CommandHipc(1011)] public ResultCode GetProgramIndexForAccessLog(ServiceCtx context) { - int programIndex = 0; - int programCount = 1; + Result result = _baseFileSystemProxy.Target.GetProgramIndexForAccessLog(out int programIndex, out int programCount); + if (result.IsFailure()) return (ResultCode)result.Value; context.ResponseData.Write(programIndex); context.ResponseData.Write(programCount); @@ -554,18 +1164,82 @@ namespace Ryujinx.HLE.HOS.Services.Fs return ResultCode.Success; } + [CommandHipc(1012)] + public ResultCode GetFsStackUsage(ServiceCtx context) + { + FsStackUsageThreadType threadType = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Target.GetFsStackUsage(out uint usage, threadType); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(usage); + + return ResultCode.Success; + } + + [CommandHipc(1013)] + public ResultCode UnsetSaveDataRootPath(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.UnsetSaveDataRootPath().Value; + } + + [CommandHipc(1014)] + public ResultCode OutputMultiProgramTagAccessLog(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.OutputMultiProgramTagAccessLog().Value; + } + + [CommandHipc(1016)] + public ResultCode FlushAccessLogOnSdCard(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Target.FlushAccessLogOnSdCard().Value; + } + + [CommandHipc(1017)] + public ResultCode OutputApplicationInfoAccessLog(ServiceCtx context) + { + ApplicationInfo info = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Target.OutputApplicationInfoAccessLog(in info).Value; + } + + [CommandHipc(1100)] + public ResultCode OverrideSaveDataTransferTokenSignVerificationKey(ServiceCtx context) + { + byte[] keyBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, keyBuffer); + + return (ResultCode)_baseFileSystemProxy.Target.OverrideSaveDataTransferTokenSignVerificationKey(new InBuffer(keyBuffer)).Value; + } + + [CommandHipc(1110)] + public ResultCode CorruptSaveDataFileSystemByOffset(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + long offset = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Target.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset).Value; + } + [CommandHipc(1200)] // 6.0.0+ // OpenMultiCommitManager() -> object public ResultCode OpenMultiCommitManager(ServiceCtx context) { - Result result = _baseFileSystemProxy.OpenMultiCommitManager(out LibHac.FsSrv.IMultiCommitManager commitManager); + Result result = _baseFileSystemProxy.Target.OpenMultiCommitManager(out ReferenceCountedDisposable commitManager); + if (result.IsFailure()) return (ResultCode)result.Value; - if (result.IsSuccess()) + MakeObject(context, new IMultiCommitManager(commitManager)); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) { - MakeObject(context, new IMultiCommitManager(commitManager)); + _baseFileSystemProxy?.Dispose(); } - - return (ResultCode)result.Value; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs b/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs index 675f71d20..f8245819f 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs @@ -3,11 +3,11 @@ using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; namespace Ryujinx.HLE.HOS.Services.Fs { - class IMultiCommitManager : IpcService // 6.0.0+ + class IMultiCommitManager : DisposableIpcService // 6.0.0+ { - private LibHac.FsSrv.IMultiCommitManager _baseCommitManager; + private ReferenceCountedDisposable _baseCommitManager; - public IMultiCommitManager(LibHac.FsSrv.IMultiCommitManager baseCommitManager) + public IMultiCommitManager(ReferenceCountedDisposable baseCommitManager) { _baseCommitManager = baseCommitManager; } @@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs { IFileSystem fileSystem = GetObject(context, 0); - Result result = _baseCommitManager.Add(fileSystem.GetBaseFileSystem()); + Result result = _baseCommitManager.Target.Add(fileSystem.GetBaseFileSystem()); return (ResultCode)result.Value; } @@ -27,9 +27,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs // Commit() public ResultCode Commit(ServiceCtx context) { - Result result = _baseCommitManager.Commit(); + Result result = _baseCommitManager.Target.Commit(); return (ResultCode)result.Value; } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseCommitManager?.Dispose(); + } + } } } diff --git a/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs b/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs index bc4a2eb95..43e9a85a8 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs @@ -1,13 +1,13 @@ -using System; -using LibHac; +using LibHac; +using LibHac.Sf; namespace Ryujinx.HLE.HOS.Services.Fs { class ISaveDataInfoReader : DisposableIpcService { - private ReferenceCountedDisposable _baseReader; + private ReferenceCountedDisposable _baseReader; - public ISaveDataInfoReader(ReferenceCountedDisposable baseReader) + public ISaveDataInfoReader(ReferenceCountedDisposable baseReader) { _baseReader = baseReader; } @@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs byte[] infoBuffer = new byte[bufferLen]; - Result result = _baseReader.Target.Read(out long readCount, infoBuffer); + Result result = _baseReader.Target.Read(out long readCount, new OutBuffer(infoBuffer)); context.Memory.Write(bufferPosition, infoBuffer); context.ResponseData.Write(readCount); diff --git a/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs b/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs index d5331920d..ee094ddf2 100644 --- a/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs +++ b/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs @@ -1,4 +1,5 @@ -using Ryujinx.HLE.HOS.Services.Mii.Types; +using LibHac; +using Ryujinx.HLE.HOS.Services.Mii.Types; using System; namespace Ryujinx.HLE.HOS.Services.Mii @@ -147,9 +148,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii return GetDefault(flag, ref count, elements); } - public ResultCode InitializeDatabase(Switch device) + public ResultCode InitializeDatabase(HorizonClient horizonClient) { - _miiDatabase.InitializeDatabase(device); + _miiDatabase.InitializeDatabase(horizonClient); _miiDatabase.LoadFromFile(out _isBroken); // Nintendo ignore any error code from before diff --git a/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs b/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs index 0bf15a7f5..682283b04 100644 --- a/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs +++ b/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs @@ -1,7 +1,9 @@ using LibHac; using LibHac.Common; using LibHac.Fs; +using LibHac.Fs.Fsa; using LibHac.Fs.Shim; +using LibHac.Ncm; using Ryujinx.HLE.HOS.Services.Mii.Types; using System.Runtime.CompilerServices; @@ -14,8 +16,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii private const ulong DatabaseTestSaveDataId = 0x8000000000000031; private const ulong DatabaseSaveDataId = 0x8000000000000030; - private const ulong NsTitleId = 0x010000000000001F; - private const ulong SdbTitleId = 0x0100000000000039; private static U8String DatabasePath = new U8String("mii:/MiiDatabase.dat"); private static U8String MountName = new U8String("mii"); @@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii private NintendoFigurineDatabase _database; private bool _isDirty; - private FileSystemClient _filesystemClient; + private HorizonClient _horizonClient; protected ulong UpdateCounter { get; private set; } @@ -94,74 +94,62 @@ namespace Ryujinx.HLE.HOS.Services.Mii return virtualIndex; } - public void InitializeDatabase(Switch device) + public void InitializeDatabase(HorizonClient horizonClient) { - _filesystemClient = device.FileSystem.FsClient; + _horizonClient = horizonClient; // Ensure we have valid data in the database _database.Format(); - // TODO: Unmount is currently not implemented properly at dispose, implement that and decrement MountCounter. - MountCounter = 0; - MountSave(); } private Result MountSave() { - Result result = Result.Success; - - if (MountCounter == 0) + if (MountCounter != 0) { - ulong targetSaveDataId; - ulong targetTitleId; + MountCounter++; + return Result.Success; + } + + ulong saveDataId = IsTestModeEnabled ? DatabaseTestSaveDataId : DatabaseSaveDataId; + + Result result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId); + + if (result.IsFailure()) + { + if (!ResultFs.TargetNotFound.Includes(result)) + return result; if (IsTestModeEnabled) { - targetSaveDataId = DatabaseTestSaveDataId; - targetTitleId = SdbTitleId; + result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, 0x10000, 0x10000, + SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); + if (result.IsFailure()) return result; } else { - targetSaveDataId = DatabaseSaveDataId; - - // Nintendo use NS TitleID when creating the production save even on sdb, let's follow that behaviour. - targetTitleId = NsTitleId; + result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, SystemProgramId.Ns.Value, 0x10000, + 0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); + if (result.IsFailure()) return result; } - U8Span mountName = new U8Span(MountName); - - result = _filesystemClient.MountSystemSaveData(mountName, SaveDataSpaceId.System, targetSaveDataId); - - if (result.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(result)) - { - // TODO: We're currently always specifying the owner ID because FS doesn't have a way of - // knowing which process called it - result = _filesystemClient.CreateSystemSaveData(targetSaveDataId, targetTitleId, 0x10000, - 0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); - if (result.IsFailure()) return result; - - result = _filesystemClient.MountSystemSaveData(mountName, SaveDataSpaceId.System, targetSaveDataId); - if (result.IsFailure()) return result; - } - } - - if (result == Result.Success) - { - MountCounter++; - } + result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId); + if (result.IsFailure()) return result; } + if (result == Result.Success) + { + MountCounter++; + } return result; } public ResultCode DeleteFile() { - ResultCode result = (ResultCode)_filesystemClient.DeleteFile(DatabasePath).Value; + ResultCode result = (ResultCode)_horizonClient.Fs.DeleteFile(DatabasePath).Value; - _filesystemClient.Commit(MountName); + _horizonClient.Fs.Commit(MountName); return result; } @@ -179,17 +167,17 @@ namespace Ryujinx.HLE.HOS.Services.Mii ResetDatabase(); - Result result = _filesystemClient.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read); + Result result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read); if (result.IsSuccess()) { - result = _filesystemClient.GetFileSize(out long fileSize, handle); + result = _horizonClient.Fs.GetFileSize(out long fileSize, handle); if (result.IsSuccess()) { if (fileSize == Unsafe.SizeOf()) { - result = _filesystemClient.ReadFile(handle, 0, _database.AsSpan()); + result = _horizonClient.Fs.ReadFile(handle, 0, _database.AsSpan()); if (result.IsSuccess()) { @@ -211,7 +199,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii } } - _filesystemClient.CloseFile(handle); + _horizonClient.Fs.CloseFile(handle); return (ResultCode)result.Value; } @@ -225,32 +213,32 @@ namespace Ryujinx.HLE.HOS.Services.Mii private Result ForceSaveDatabase() { - Result result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf()); + Result result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf()); if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result)) { - result = _filesystemClient.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write); + result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write); if (result.IsSuccess()) { - result = _filesystemClient.GetFileSize(out long fileSize, handle); + result = _horizonClient.Fs.GetFileSize(out long fileSize, handle); if (result.IsSuccess()) { // If the size doesn't match, recreate the file if (fileSize != Unsafe.SizeOf()) { - _filesystemClient.CloseFile(handle); + _horizonClient.Fs.CloseFile(handle); - result = _filesystemClient.DeleteFile(DatabasePath); + result = _horizonClient.Fs.DeleteFile(DatabasePath); if (result.IsSuccess()) { - result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf()); + result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf()); if (result.IsSuccess()) { - result = _filesystemClient.OpenFile(out handle, DatabasePath, OpenMode.Write); + result = _horizonClient.Fs.OpenFile(out handle, DatabasePath, OpenMode.Write); } } @@ -260,10 +248,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii } } - result = _filesystemClient.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush); + result = _horizonClient.Fs.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush); } - _filesystemClient.CloseFile(handle); + _horizonClient.Fs.CloseFile(handle); } } @@ -271,7 +259,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii { _isDirty = false; - result = _filesystemClient.Commit(MountName); + result = _horizonClient.Fs.Commit(MountName); } return result; diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index 022dc42dc..17fbe2c5c 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -20,7 +20,7 @@ - + diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index c7d86c7e6..9aa0d6f48 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -52,9 +52,10 @@ namespace Ryujinx.Ui { public class MainWindow : Window { - private readonly VirtualFileSystem _virtualFileSystem; - private readonly ContentManager _contentManager; - private readonly AccountManager _accountManager; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ContentManager _contentManager; + private readonly AccountManager _accountManager; + private readonly LibHacHorizonManager _libHacHorizonManager; private UserChannelPersistence _userChannelPersistence; @@ -156,13 +157,27 @@ namespace Ryujinx.Ui // Hide emulation context status bar. _statusBar.Hide(); - // Instanciate HLE objects. - _virtualFileSystem = VirtualFileSystem.CreateInstance(); + // Instantiate HLE objects. + _virtualFileSystem = VirtualFileSystem.CreateInstance(); + _libHacHorizonManager = new LibHacHorizonManager(); + + _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); + _libHacHorizonManager.InitializeArpServer(); + _libHacHorizonManager.InitializeBcatServer(); + _libHacHorizonManager.InitializeSystemClients(); + + // 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 + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient); + _contentManager = new ContentManager(_virtualFileSystem); - _accountManager = new AccountManager(_virtualFileSystem); + _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient); _userChannelPersistence = new UserChannelPersistence(); - // Instanciate GUI objects. + // Instantiate GUI objects. _applicationLibrary = new ApplicationLibrary(_virtualFileSystem); _uiHandler = new GtkHostUiHandler(this); _deviceExitStatus = new AutoResetEvent(false); @@ -370,7 +385,7 @@ namespace Ryujinx.Ui private void InitializeSwitchInstance() { - _virtualFileSystem.Reload(); + _virtualFileSystem.ReloadKeySet(); IRenderer renderer; @@ -440,6 +455,7 @@ namespace Ryujinx.Ui IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(_virtualFileSystem, + _libHacHorizonManager, _contentManager, _accountManager, _userChannelPersistence, @@ -1092,7 +1108,7 @@ namespace Ryujinx.Ui BlitStruct controlData = (BlitStruct)_tableStore.GetValue(treeIter, 10); - _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, titleFilePath, titleName, titleId, controlData); + _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData); } private void Load_Application_File(object sender, EventArgs args) @@ -1208,15 +1224,15 @@ namespace Ryujinx.Ui SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename); - string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; - - if (firmwareVersion == null) + if (firmwareVersion is null) { GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}."); return; } + string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; + SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion(); string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs index eb3150cea..4a8d1096f 100644 --- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs +++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs @@ -33,6 +33,7 @@ namespace Ryujinx.Ui.Widgets private readonly MainWindow _parent; private readonly VirtualFileSystem _virtualFileSystem; private readonly AccountManager _accountManager; + private readonly HorizonClient _horizonClient; private readonly BlitStruct _controlData; private readonly string _titleFilePath; @@ -43,7 +44,7 @@ namespace Ryujinx.Ui.Widgets private MessageDialog _dialog; private bool _cancel; - public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, string titleFilePath, string titleName, string titleId, BlitStruct controlData) + public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct controlData) { _parent = parent; @@ -51,6 +52,7 @@ namespace Ryujinx.Ui.Widgets _virtualFileSystem = virtualFileSystem; _accountManager = accountManager; + _horizonClient = horizonClient; _titleFilePath = titleFilePath; _titleName = titleName; _titleIdText = titleId; @@ -63,9 +65,9 @@ namespace Ryujinx.Ui.Widgets return; } - _openSaveUserDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; - _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; - _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; + _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; + _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; + _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower(); bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; @@ -81,7 +83,7 @@ namespace Ryujinx.Ui.Widgets { saveDataId = default; - Result result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); + Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter); if (ResultFs.TargetNotFound.Includes(result)) { @@ -102,7 +104,7 @@ namespace Ryujinx.Ui.Widgets ref ApplicationControlProperty control = ref controlHolder.Value; - if (Utilities.IsEmpty(controlHolder.ByteSpan)) + if (Utilities.IsZeros(controlHolder.ByteSpan)) { // If the current application doesn't have a loaded control property, create a dummy one // and set the savedata sizes so a user savedata will be created. @@ -117,7 +119,7 @@ namespace Ryujinx.Ui.Widgets Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); - result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user); + result = EnsureApplicationSaveData(_horizonClient.Fs, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user); if (result.IsFailure()) { @@ -127,7 +129,7 @@ namespace Ryujinx.Ui.Widgets } // Try to find the savedata again after creating it - result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, ref filter); + result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter); } if (result.IsSuccess()) @@ -284,7 +286,7 @@ namespace Ryujinx.Ui.Widgets IFileSystem ncaFileSystem = patchNca != null ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); - FileSystemClient fsClient = _virtualFileSystem.FsClient; + FileSystemClient fsClient = _horizonClient.Fs; string source = DateTime.Now.ToFileTime().ToString()[10..]; string output = DateTime.Now.ToFileTime().ToString()[10..]; @@ -409,7 +411,7 @@ namespace Ryujinx.Ui.Widgets rc = fs.ReadFile(out long _, sourceHandle, offset, buf); if (rc.IsFailure()) return rc; - rc = fs.WriteFile(destHandle, offset, buf); + rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); if (rc.IsFailure()) return rc; } }