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..c4363f711 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -1,40 +1,46 @@ 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 { public class VirtualFileSystem : IDisposable { - public const string NandPath = AppDataManager.DefaultNandDir; + public const string NandPath = AppDataManager.DefaultNandDir; public const string SdCardPath = AppDataManager.DefaultSdcardDir; - public static string SafeNandPath = Path.Combine(NandPath, "safe"); + public static string SafeNandPath = Path.Combine(NandPath, "safe"); public static string SystemNandPath = Path.Combine(NandPath, "system"); - public static string UserNandPath = Path.Combine(NandPath, "user"); - + public static string UserNandPath = Path.Combine(NandPath, "user"); + 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 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,35 +169,36 @@ 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() { - string keyFile = null; - string titleKeyFile = null; + KeySet ??= KeySet.CreateDefaultKeySet(); + + string keyFile = null; + string titleKeyFile = null; string consoleKeyFile = null; if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile) @@ -236,8 +210,8 @@ namespace Ryujinx.HLE.FileSystem void LoadSetAtPath(string basePath) { - string localKeyFile = Path.Combine(basePath, "prod.keys"); - string localTitleKeyFile = Path.Combine(basePath, "title.keys"); + string localKeyFile = Path.Combine(basePath, "prod.keys"); + string localTitleKeyFile = Path.Combine(basePath, "title.keys"); string localConsoleKeyFile = Path.Combine(basePath, "console.keys"); if (File.Exists(localKeyFile)) @@ -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,324 @@ 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 (ResultFs.TargetNotFound.Includes(rc)) + { + // If the save wasn't found, try to create the directory for its save data ID + rc = CreateSaveDataDirectory(hos, in info[i]); + + if (rc.IsFailure()) + { + Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when creating save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + + // Don't bother fixing the extra data if we couldn't create the directory + continue; + } + + Logger.Info?.Print(LogClass.Application, $"Recreated directory for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + + // Try to fix the extra data in the new directory + rc = FixExtraData(out 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, $"Fixed extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + } + } + } + } + + private static Result CreateSaveDataDirectory(HorizonClient hos, in SaveDataInfo info) + { + if (info.SpaceId != SaveDataSpaceId.User && info.SpaceId != SaveDataSpaceId.System) + return Result.Success; + + const string mountName = "SaveDir"; + var mountNameU8 = mountName.ToU8Span(); + + BisPartitionId partitionId = info.SpaceId switch + { + SaveDataSpaceId.System => BisPartitionId.System, + SaveDataSpaceId.User => BisPartitionId.User, + _ => throw new ArgumentOutOfRangeException() + }; + + Result rc = hos.Fs.MountBis(mountNameU8, partitionId); + if (rc.IsFailure()) return rc; + try + { + var path = $"{mountName}:/save/{info.SaveDataId:x16}".ToU8Span(); + + rc = hos.Fs.GetEntryType(out _, path); + + if (ResultFs.PathNotFound.Includes(rc)) + { + rc = hos.Fs.CreateDirectory(path); + } + + return rc; + } + finally + { + hos.Fs.Unmount(mountNameU8); + } + } + + // 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 +591,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 9cea42a43..f2f135f42 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 385b4af50..5605ab011 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..ca89d4133 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -20,7 +20,7 @@ - + diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index c3a10929d..c5e914eb5 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -43,6 +43,7 @@ namespace Ryujinx.Headless.SDL2 private static VirtualFileSystem _virtualFileSystem; private static ContentManager _contentManager; private static AccountManager _accountManager; + private static LibHacHorizonManager _libHacHorizonManager; private static UserChannelPersistence _userChannelPersistence; private static InputManager _inputManager; private static Switch _emulationContext; @@ -61,8 +62,15 @@ namespace Ryujinx.Headless.SDL2 AppDataManager.Initialize(null); _virtualFileSystem = VirtualFileSystem.CreateInstance(); + _libHacHorizonManager = new LibHacHorizonManager(); + + _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); + _libHacHorizonManager.InitializeArpServer(); + _libHacHorizonManager.InitializeBcatServer(); + _libHacHorizonManager.InitializeSystemClients(); + _contentManager = new ContentManager(_virtualFileSystem); - _accountManager = new AccountManager(_virtualFileSystem); + _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient); _userChannelPersistence = new UserChannelPersistence(); _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver()); @@ -426,6 +434,7 @@ namespace Ryujinx.Headless.SDL2 private static Switch InitializeEmulationContext(WindowBase window, Options options) { HLEConfiguration configuration = new HLEConfiguration(_virtualFileSystem, + _libHacHorizonManager, _contentManager, _accountManager, _userChannelPersistence, diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 4ac857450..c4c161c0e 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; @@ -157,13 +158,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); @@ -373,7 +388,7 @@ namespace Ryujinx.Ui private void InitializeSwitchInstance() { - _virtualFileSystem.Reload(); + _virtualFileSystem.ReloadKeySet(); IRenderer renderer; @@ -443,6 +458,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, @@ -1095,7 +1111,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) @@ -1211,15 +1227,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; } }