Kaynağa Gözat

Update to LibHac 0.13.1 (#2328)

Update the LibHac dependency to version 0.13.1. This brings a ton of improvements and changes such as:
- Refactor `FsSrv` to match the official refactoring done in FS.
- Change how the `Horizon` and `HorizonClient` classes are handled. Each client created represents a different process with its own process ID and client state.
- Add FS access control to handle permissions for FS service method calls.
- Add FS program registry to keep track of the program ID, location and permissions of each process.
- Add FS program index map info manager to track the program IDs and indexes of multi-application programs.
- Add all FS IPC interfaces.
- Rewrite `Fs.Fsa` code to be more accurate.
- Rewrite a lot of `FsSrv` code to be more accurate.
- Extend directory save data to store `SaveDataExtraData`
- Extend directory save data to lock the save directory to allow only one accessor at a time.
- Improve waiting and retrying when encountering access issues in `LocalFileSystem` and `DirectorySaveDataFileSystem`.
- More `IFileSystemProxy` methods should work now.
- Probably a bunch more stuff.

On the Ryujinx side:
- Forward most `IFileSystemProxy` methods to LibHac.
- Register programs and program index map info when launching an application.
- Remove hacks and workarounds for missing LibHac functionality.
- Recreate missing save data extra data found on emulator startup.
- Create system save data that wasn't indexed correctly on an older LibHac version.

`FsSrv` now enforces access control for each process. When a process tries to open a save data file system, FS reads the save's extra data to determine who the save owner is and if the caller has permission to open the save data. Previously-created save data did not have extra data created when the save was created.
With access control checks in place, this means that processes with no permissions (most games) wouldn't be able to access their own save data. The extra data can be partially created from data in the save data indexer, which should be enough for access control purposes.
Alex Barney 4 yıl önce
ebeveyn
işleme
19afb3209c
31 değiştirilmiş dosya ile 1695 ekleme ve 566 silme
  1. 2 2
      Ryujinx.HLE/FileSystem/Content/ContentManager.cs
  2. 0 1
      Ryujinx.HLE/FileSystem/Content/SystemVersion.cs
  3. 0 45
      Ryujinx.HLE/FileSystem/SaveHelper.cs
  4. 291 54
      Ryujinx.HLE/FileSystem/VirtualFileSystem.cs
  5. 10 5
      Ryujinx.HLE/HLEConfiguration.cs
  6. 150 48
      Ryujinx.HLE/HOS/ApplicationLoader.cs
  7. 13 25
      Ryujinx.HLE/HOS/Horizon.cs
  8. 124 0
      Ryujinx.HLE/HOS/LibHacHorizonManager.cs
  9. 5 4
      Ryujinx.HLE/HOS/ModLoader.cs
  10. 40 12
      Ryujinx.HLE/HOS/ProgramLoader.cs
  11. 19 27
      Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
  12. 39 7
      Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
  13. 24 7
      Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs
  14. 5 4
      Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs
  15. 2 2
      Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs
  16. 2 2
      Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs
  17. 4 4
      Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs
  18. 35 6
      Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs
  19. 17 12
      Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs
  20. 13 11
      Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs
  21. 47 41
      Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs
  22. 5 4
      Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
  23. 14 6
      Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs
  24. 726 144
      Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs
  25. 13 5
      Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs
  26. 5 5
      Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs
  27. 4 3
      Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs
  28. 45 57
      Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
  29. 1 1
      Ryujinx.HLE/Ryujinx.HLE.csproj
  30. 28 12
      Ryujinx/Ui/MainWindow.cs
  31. 12 10
      Ryujinx/Ui/Widgets/GameTableContextMenu.cs

+ 2 - 2
Ryujinx.HLE/FileSystem/Content/ContentManager.cs

@@ -653,11 +653,11 @@ namespace Ryujinx.HLE.FileSystem.Content
 
 
         public SystemVersion VerifyFirmwarePackage(string firmwarePackage)
         public SystemVersion VerifyFirmwarePackage(string firmwarePackage)
         {
         {
-            _virtualFileSystem.Reload();
+            _virtualFileSystem.ReloadKeySet();
 
 
             // LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead
             // 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.
             // 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.");
                 throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
             }
             }

+ 0 - 1
Ryujinx.HLE/FileSystem/Content/SystemVersion.cs

@@ -1,4 +1,3 @@
-using System;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
 
 

+ 0 - 45
Ryujinx.HLE/FileSystem/SaveHelper.cs

@@ -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);
-        }
-    }
-}

+ 291 - 54
Ryujinx.HLE/FileSystem/VirtualFileSystem.cs

@@ -1,15 +1,23 @@
 using LibHac;
 using LibHac;
 using LibHac.Common;
 using LibHac.Common;
+using LibHac.Common.Keys;
 using LibHac.Fs;
 using LibHac.Fs;
 using LibHac.Fs.Fsa;
 using LibHac.Fs.Fsa;
+using LibHac.Fs.Shim;
 using LibHac.FsSrv;
 using LibHac.FsSrv;
 using LibHac.FsSystem;
 using LibHac.FsSystem;
+using LibHac.Ncm;
 using LibHac.Spl;
 using LibHac.Spl;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS;
 using System;
 using System;
+using System.Buffers.Text;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
+using System.Runtime.CompilerServices;
+using RightsId = LibHac.Fs.RightsId;
 
 
 namespace Ryujinx.HLE.FileSystem
 namespace Ryujinx.HLE.FileSystem
 {
 {
@@ -24,17 +32,15 @@ namespace Ryujinx.HLE.FileSystem
         
         
         private static bool _isInitialized = false;
         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 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()
         private VirtualFileSystem()
         {
         {
-            Reload();
+            ReloadKeySet();
             ModLoader = new ModLoader(); // Should only be created once
             ModLoader = new ModLoader(); // Should only be created once
         }
         }
 
 
@@ -80,39 +86,6 @@ namespace Ryujinx.HLE.FileSystem
         internal string GetSdCardPath() => MakeFullPath(SdCardPath);
         internal string GetSdCardPath() => MakeFullPath(SdCardPath);
         public string GetNandPath() => MakeFullPath(NandPath);
         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)
         public string GetFullPartitionPath(string partitionPath)
         {
         {
             return MakeFullPath(partitionPath);
             return MakeFullPath(partitionPath);
@@ -136,8 +109,8 @@ namespace Ryujinx.HLE.FileSystem
 
 
             if (systemPath.StartsWith(baseSystemPath))
             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)
                 if (firstSeparatorOffset == -1)
                 {
                 {
@@ -196,33 +169,34 @@ namespace Ryujinx.HLE.FileSystem
             return new DriveInfo(Path.GetPathRoot(GetBasePath()));
             return new DriveInfo(Path.GetPathRoot(GetBasePath()));
         }
         }
 
 
-        public void Reload()
+        public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient)
         {
         {
-            ReloadKeySet();
-
             LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath());
             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;
             GameCard = fsServerObjects.GameCard;
-            SdCard   = fsServerObjects.SdCard;
+            SdCard = fsServerObjects.SdCard;
 
 
             SdCard.SetSdCardInsertionStatus(true);
             SdCard.SetSdCardInsertionStatus(true);
 
 
-            FileSystemServerConfig fsServerConfig = new FileSystemServerConfig
+            var fsServerConfig = new FileSystemServerConfig
             {
             {
-                FsCreators     = fsServerObjects.FsCreators,
                 DeviceOperator = fsServerObjects.DeviceOperator,
                 DeviceOperator = fsServerObjects.DeviceOperator,
-                ExternalKeySet = KeySet.ExternalKeySet
+                ExternalKeySet = KeySet.ExternalKeySet,
+                FsCreators = fsServerObjects.FsCreators
             };
             };
 
 
-            FsServer = new FileSystemServer(fsServerConfig);
-            FsClient = FsServer.CreateFileSystemClient();
+            FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
         }
         }
 
 
-
-        private void ReloadKeySet()
+        public void ReloadKeySet()
         {
         {
+            KeySet ??= KeySet.CreateDefaultKeySet();
+
             string keyFile        = null;
             string keyFile        = null;
             string titleKeyFile   = null;
             string titleKeyFile   = null;
             string consoleKeyFile = null;
             string consoleKeyFile = null;
@@ -256,7 +230,7 @@ namespace Ryujinx.HLE.FileSystem
                 }
                 }
             }
             }
 
 
-            KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile);
+            ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null);
         }
         }
 
 
         public void ImportTickets(IFileSystem fs)
         public void ImportTickets(IFileSystem fs)
@@ -277,6 +251,269 @@ namespace Ryujinx.HLE.FileSystem
             }
             }
         }
         }
 
 
+        // Save data created before we supported extra data in directory save data will not work properly if
+        // given empty extra data. Luckily some of that extra data can be created using the data from the
+        // save data indexer, which should be enough to check access permissions for user saves.
+        // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
+        // Consider removing this at some point in the future when we don't need to worry about old saves.
+        public static Result FixExtraData(HorizonClient hos)
+        {
+            Result rc = GetSystemSaveList(hos, out List<ulong> 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<SaveDataInfo> info = stackalloc SaveDataInfo[8];
+
+            Result rc = hos.Fs.OpenSaveDataIterator(out var iterator, spaceId);
+            if (rc.IsFailure()) return rc;
+
+            while (true)
+            {
+                rc = iterator.ReadSaveDataInfo(out long count, info);
+                if (rc.IsFailure()) return rc;
+
+                if (count == 0)
+                    return Result.Success;
+
+                for (int i = 0; i < count; i++)
+                {
+                    rc = FixExtraData(out bool wasFixNeeded, hos, in info[i]);
+
+                    if (rc.IsFailure())
+                    {
+                        Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when fixing extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space");
+                    }
+                    else if (wasFixNeeded)
+                    {
+                        Logger.Info?.Print(LogClass.Application, $"Tried to rebuild extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space");
+                    }
+                }
+            }
+        }
+
+        // Gets a list of all the save data files or directories in the system partition.
+        private static Result GetSystemSaveList(HorizonClient hos, out List<ulong> list)
+        {
+            list = null;
+
+            var mountName = "system".ToU8Span();
+            DirectoryHandle handle = default;
+            List<ulong> localList = new List<ulong>();
+
+            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<ulong> 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()
         public void Unload()
         {
         {
             RomFs?.Dispose();
             RomFs?.Dispose();
@@ -299,7 +536,7 @@ namespace Ryujinx.HLE.FileSystem
         {
         {
             if (_isInitialized)
             if (_isInitialized)
             {
             {
-                throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!");
+                throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!");
             }
             }
 
 
             _isInitialized = true;
             _isInitialized = true;

+ 10 - 5
Ryujinx.HLE/HLEConfiguration.cs

@@ -1,8 +1,6 @@
 using LibHac.FsSystem;
 using LibHac.FsSystem;
 using Ryujinx.Audio.Integration;
 using Ryujinx.Audio.Integration;
-using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Configuration.Hid;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.FileSystem.Content;
@@ -10,7 +8,6 @@ using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.HOS.SystemState;
 using System;
 using System;
-using System.Collections.Generic;
 
 
 namespace Ryujinx.HLE
 namespace Ryujinx.HLE
 {
 {
@@ -25,6 +22,12 @@ namespace Ryujinx.HLE
         /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
         /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
         internal readonly VirtualFileSystem VirtualFileSystem;
         internal readonly VirtualFileSystem VirtualFileSystem;
 
 
+        /// <summary>
+        /// The manager for handling a LibHac Horizon instance.
+        /// </summary>
+        /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
+        internal readonly LibHacHorizonManager LibHacHorizonManager;
+
         /// <summary>
         /// <summary>
         /// The account manager used by the account service.
         /// The account manager used by the account service.
         /// </summary>
         /// </summary>
@@ -38,7 +41,7 @@ namespace Ryujinx.HLE
         internal readonly ContentManager ContentManager;
         internal readonly ContentManager ContentManager;
 
 
         /// <summary>
         /// <summary>
-        /// The persistant information between run for multi-application capabilities.
+        /// The persistent information between run for multi-application capabilities.
         /// </summary>
         /// </summary>
         /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
         /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
         public readonly UserChannelPersistence UserChannelPersistence;
         public readonly UserChannelPersistence UserChannelPersistence;
@@ -124,7 +127,7 @@ namespace Ryujinx.HLE
         public MemoryManagerMode MemoryManagerMode { internal get; set; }
         public MemoryManagerMode MemoryManagerMode { internal get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// 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.
         /// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception.
         /// </summary>
         /// </summary>
         /// TODO: Update this again.
         /// TODO: Update this again.
@@ -141,6 +144,7 @@ namespace Ryujinx.HLE
         public Action RefreshInputConfig { internal get; set; }
         public Action RefreshInputConfig { internal get; set; }
 
 
         public HLEConfiguration(VirtualFileSystem virtualFileSystem,
         public HLEConfiguration(VirtualFileSystem virtualFileSystem,
+                                LibHacHorizonManager libHacHorizonManager,
                                 ContentManager contentManager,
                                 ContentManager contentManager,
                                 AccountManager accountManager,
                                 AccountManager accountManager,
                                 UserChannelPersistence userChannelPersistence,
                                 UserChannelPersistence userChannelPersistence,
@@ -162,6 +166,7 @@ namespace Ryujinx.HLE
                                 AspectRatio aspectRatio)
                                 AspectRatio aspectRatio)
         {
         {
             VirtualFileSystem = virtualFileSystem;
             VirtualFileSystem = virtualFileSystem;
+            LibHacHorizonManager = libHacHorizonManager;
             AccountManager = accountManager;
             AccountManager = accountManager;
             ContentManager = contentManager;
             ContentManager = contentManager;
             UserChannelPersistence = userChannelPersistence;
             UserChannelPersistence = userChannelPersistence;

+ 150 - 48
Ryujinx.HLE/HOS/ApplicationLoader.cs

@@ -4,15 +4,17 @@ using LibHac.Account;
 using LibHac.Common;
 using LibHac.Common;
 using LibHac.Fs;
 using LibHac.Fs;
 using LibHac.Fs.Fsa;
 using LibHac.Fs.Fsa;
+using LibHac.Fs.Shim;
 using LibHac.FsSystem;
 using LibHac.FsSystem;
 using LibHac.FsSystem.NcaUtils;
 using LibHac.FsSystem.NcaUtils;
+using LibHac.Loader;
+using LibHac.Ncm;
 using LibHac.Ns;
 using LibHac.Ns;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.HLE.Loaders.Executables;
-using Ryujinx.HLE.Loaders.Npdm;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
@@ -57,14 +59,14 @@ namespace Ryujinx.HLE.HOS
         public string TitleName => _titleName;
         public string TitleName => _titleName;
         public string DisplayVersion => _displayVersion;
         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 string TitleIdText => TitleId.ToString("x16");
 
 
         public ApplicationLoader(Switch device)
         public ApplicationLoader(Switch device)
         {
         {
-            _device      = device;
+            _device = device;
             _controlData = new BlitStruct<ApplicationControlProperty>(1);
             _controlData = new BlitStruct<ApplicationControlProperty>(1);
         }
         }
 
 
@@ -77,7 +79,7 @@ namespace Ryujinx.HLE.HOS
 
 
             LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
             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());
             _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)
         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;
             Nca controlNca = null;
 
 
             fileSystem.ImportTickets(pfs);
             fileSystem.ImportTickets(pfs);
@@ -202,7 +204,7 @@ namespace Ryujinx.HLE.HOS
         public void LoadXci(string xciFile)
         public void LoadXci(string xciFile)
         {
         {
             FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
             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))
             if (!xci.HasPartition(XciPartitionType.Secure))
             {
             {
@@ -220,6 +222,8 @@ namespace Ryujinx.HLE.HOS
             try
             try
             {
             {
                 (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
                 (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
+
+                RegisterProgramMapInfo(securePartition).ThrowIfFailure();
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
@@ -244,8 +248,8 @@ namespace Ryujinx.HLE.HOS
 
 
         public void LoadNsp(string nspFile)
         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 mainNca;
             Nca patchNca;
             Nca patchNca;
@@ -254,6 +258,8 @@ namespace Ryujinx.HLE.HOS
             try
             try
             {
             {
                 (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
                 (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
+
+                RegisterProgramMapInfo(nsp).ThrowIfFailure();
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
@@ -286,7 +292,7 @@ namespace Ryujinx.HLE.HOS
         public void LoadNca(string ncaFile)
         public void LoadNca(string ncaFile)
         {
         {
             FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
             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);
             LoadNca(nca, null, null);
         }
         }
@@ -300,8 +306,8 @@ namespace Ryujinx.HLE.HOS
                 return;
                 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 _);
             (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;
                 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());
             _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));
                 _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);
             LoadExeFs(codeFs, metaData);
@@ -411,11 +420,11 @@ namespace Ryujinx.HLE.HOS
         }
         }
 
 
         // Sets TitleId, so be sure to call before using it
         // 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);
             Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
 
 
-            Npdm metaData;
+            MetaLoader metaData;
 
 
             if (ResultFs.PathNotFound.Includes(result))
             if (ResultFs.PathNotFound.Includes(result))
             {
             {
@@ -425,11 +434,20 @@ namespace Ryujinx.HLE.HOS
             }
             }
             else
             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;
             return metaData;
         }
         }
@@ -437,7 +455,7 @@ namespace Ryujinx.HLE.HOS
         private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
         private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
         {
         {
             IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
             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())
             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))
             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);
             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);
             _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
         }
         }
 
 
         public void LoadProgram(string filePath)
         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;
             IExecutable executable;
 
 
             if (isNro)
             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;
                 executable = obj;
 
 
@@ -552,13 +574,13 @@ namespace Ryujinx.HLE.HOS
                         if (asetVersion == 0)
                         if (asetVersion == 0)
                         {
                         {
                             ulong iconOffset = reader.ReadUInt64();
                             ulong iconOffset = reader.ReadUInt64();
-                            ulong iconSize   = reader.ReadUInt64();
+                            ulong iconSize = reader.ReadUInt64();
 
 
                             ulong nacpOffset = reader.ReadUInt64();
                             ulong nacpOffset = reader.ReadUInt64();
-                            ulong nacpSize   = reader.ReadUInt64();
+                            ulong nacpSize = reader.ReadUInt64();
 
 
                             ulong romfsOffset = reader.ReadUInt64();
                             ulong romfsOffset = reader.ReadUInt64();
-                            ulong romfsSize   = reader.ReadUInt64();
+                            ulong romfsSize = reader.ReadUInt64();
 
 
                             if (romfsSize != 0)
                             if (romfsSize != 0)
                             {
                             {
@@ -573,28 +595,28 @@ namespace Ryujinx.HLE.HOS
 
 
                                 ref ApplicationControlProperty nacp = ref ControlData.Value;
                                 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)
                                 if (nacp.PresenceGroupId != 0)
                                 {
                                 {
-                                    metaData.Aci0.TitleId = nacp.PresenceGroupId;
+                                    programInfo.ProgramId = nacp.PresenceGroupId;
                                 }
                                 }
                                 else if (nacp.SaveDataOwnerId.Value != 0)
                                 else if (nacp.SaveDataOwnerId.Value != 0)
                                 {
                                 {
-                                    metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
+                                    programInfo.ProgramId = nacp.SaveDataOwnerId.Value;
                                 }
                                 }
                                 else if (nacp.AddOnContentBaseId != 0)
                                 else if (nacp.AddOnContentBaseId != 0)
                                 {
                                 {
-                                    metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
+                                    programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
                                 }
                                 }
                                 else
                                 else
                                 {
                                 {
-                                    metaData.Aci0.TitleId = 0000000000000000;
+                                    programInfo.ProgramId = 0000000000000000;
                                 }
                                 }
                             }
                             }
                         }
                         }
@@ -612,27 +634,107 @@ namespace Ryujinx.HLE.HOS
 
 
             _device.Configuration.ContentManager.LoadEntries(_device);
             _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
             // Explicitly null titleid to disable the shader cache
             Graphics.Gpu.GraphicsConfig.TitleId = null;
             Graphics.Gpu.GraphicsConfig.TitleId = null;
             _device.Gpu.HostInitalized.Set();
             _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);
             _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
         }
         }
 
 
-        private Npdm GetDefaultNpdm()
+        private MetaLoader GetDefaultNpdm()
         {
         {
             Assembly asm = Assembly.GetCallingAssembly();
             Assembly asm = Assembly.GetCallingAssembly();
 
 
             using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
             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<bool> 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<ProgramIndexMapInfo> 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)
         private Result EnsureSaveData(ApplicationId applicationId)
@@ -643,7 +745,7 @@ namespace Ryujinx.HLE.HOS
 
 
             ref ApplicationControlProperty control = ref ControlData.Value;
             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
                 // 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.
                 // 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.");
                     "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())
             if (resultCode.IsFailure())
             {
             {
@@ -667,7 +769,7 @@ namespace Ryujinx.HLE.HOS
                 return resultCode;
                 return resultCode;
             }
             }
 
 
-            resultCode = EnsureApplicationSaveData(fileSystem, out _, applicationId, ref control, ref user);
+            resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user);
 
 
             if (resultCode.IsFailure())
             if (resultCode.IsFailure())
             {
             {

+ 13 - 25
Ryujinx.HLE/HOS/Horizon.cs

@@ -1,6 +1,6 @@
-using LibHac;
-using LibHac.Bcat;
+using LibHac.Common.Keys;
 using LibHac.Fs;
 using LibHac.Fs;
+using LibHac.Fs.Shim;
 using LibHac.FsSystem;
 using LibHac.FsSystem;
 using Ryujinx.Audio;
 using Ryujinx.Audio;
 using Ryujinx.Audio.Input;
 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.Account.Acc;
 using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
 using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
 using Ryujinx.HLE.HOS.Services.Apm;
 using Ryujinx.HLE.HOS.Services.Apm;
-using Ryujinx.HLE.HOS.Services.Arp;
 using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
 using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
 using Ryujinx.HLE.HOS.Services.Caps;
 using Ryujinx.HLE.HOS.Services.Caps;
 using Ryujinx.HLE.HOS.Services.Mii;
 using Ryujinx.HLE.HOS.Services.Mii;
@@ -38,6 +37,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
+using TimeSpanType = Ryujinx.HLE.HOS.Services.Time.Clock.TimeSpanType;
 
 
 namespace Ryujinx.HLE.HOS
 namespace Ryujinx.HLE.HOS
 {
 {
@@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS
 
 
         internal KEvent DisplayResolutionChangeEvent { get; private set; }
         internal KEvent DisplayResolutionChangeEvent { get; private set; }
 
 
-        public Keyset KeySet => Device.FileSystem.KeySet;
+        public KeySet KeySet => Device.FileSystem.KeySet;
 
 
         private bool _isDisposed;
         private bool _isDisposed;
 
 
@@ -111,8 +111,7 @@ namespace Ryujinx.HLE.HOS
 
 
         internal NvHostSyncpt HostSyncpoint { get; private set; }
         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)
         public Horizon(Switch device)
         {
         {
@@ -184,6 +183,8 @@ namespace Ryujinx.HLE.HOS
             ContentManager = device.Configuration.ContentManager;
             ContentManager = device.Configuration.ContentManager;
             CaptureManager = new CaptureManager(device);
             CaptureManager = new CaptureManager(device);
 
 
+            LibHacHorizonManager = device.Configuration.LibHacHorizonManager;
+
             // TODO: use set:sys (and get external clock source id from settings)
             // 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.
             // 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());
             UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
@@ -223,17 +224,16 @@ namespace Ryujinx.HLE.HOS
 
 
             TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom());
             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();
             TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
 
 
-            DatabaseImpl.Instance.InitializeDatabase(device);
+            DatabaseImpl.Instance.InitializeDatabase(LibHacHorizonManager.SdbClient);
 
 
             HostSyncpoint = new NvHostSyncpt(device);
             HostSyncpoint = new NvHostSyncpt(device);
 
 
             SurfaceFlinger = new SurfaceFlinger(device);
             SurfaceFlinger = new SurfaceFlinger(device);
 
 
-            InitLibHacHorizon();
             InitializeAudioRenderer();
             InitializeAudioRenderer();
         }
         }
 
 
@@ -309,20 +309,6 @@ namespace Ryujinx.HLE.HOS
             ProgramLoader.LoadKip(KernelContext, new KipExecutable(kipFile));
             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)
         public void ChangeDockedModeState(bool newState)
         {
         {
             if (newState != State.DockedMode)
             if (newState != State.DockedMode)
@@ -355,8 +341,8 @@ namespace Ryujinx.HLE.HOS
         {
         {
             if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag)
             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;
                 NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid;
             }
             }
         }
         }
@@ -453,6 +439,8 @@ namespace Ryujinx.HLE.HOS
 
 
                 AudioRendererManager.Dispose();
                 AudioRendererManager.Dispose();
 
 
+                LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
+                
                 KernelContext.Dispose();
                 KernelContext.Dispose();
             }
             }
         }
         }

+ 124 - 0
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<byte> 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<byte> 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
+        };
+    }
+}

+ 5 - 4
Ryujinx.HLE/HOS/ModLoader.cs

@@ -3,6 +3,7 @@ using LibHac.Fs;
 using LibHac.Fs.Fsa;
 using LibHac.Fs.Fsa;
 using LibHac.FsSystem;
 using LibHac.FsSystem;
 using LibHac.FsSystem.RomFs;
 using LibHac.FsSystem.RomFs;
+using LibHac.Loader;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.Loaders.Mods;
 using Ryujinx.HLE.Loaders.Mods;
@@ -12,7 +13,6 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.Linq;
 using System.Linq;
 using System.IO;
 using System.IO;
-using Ryujinx.HLE.Loaders.Npdm;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using System.Globalization;
 using System.Globalization;
 
 
@@ -522,7 +522,7 @@ namespace Ryujinx.HLE.HOS
         {
         {
             public BitVector32 Stubs;
             public BitVector32 Stubs;
             public BitVector32 Replaces;
             public BitVector32 Replaces;
-            public Npdm Npdm;
+            public MetaLoader Npdm;
 
 
             public bool Modified => (Stubs.Data | Replaces.Data) != 0;
             public bool Modified => (Stubs.Data | Replaces.Data) != 0;
         }
         }
@@ -582,9 +582,10 @@ namespace Ryujinx.HLE.HOS
                         continue;
                         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");
                 }
                 }
             }
             }
 
 

+ 40 - 12
Ryujinx.HLE/HOS/ProgramLoader.cs

@@ -1,4 +1,7 @@
 using ARMeilleure.Translation.PTC;
 using ARMeilleure.Translation.PTC;
+using LibHac.Loader;
+using LibHac.Ncm;
+using LibHac.Util;
 using Ryujinx.Common;
 using Ryujinx.Common;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Kernel;
 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.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.HLE.Loaders.Executables;
-using Ryujinx.HLE.Loaders.Npdm;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
+using System.Runtime.InteropServices;
+using Npdm = LibHac.Loader.Npdm;
 
 
 namespace Ryujinx.HLE.HOS
 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
     static class ProgramLoader
     {
     {
         private const bool AslrEnabled = true;
         private const bool AslrEnabled = true;
@@ -125,11 +141,21 @@ namespace Ryujinx.HLE.HOS
             return true;
             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;
             ulong argsStart = 0;
             uint  argsSize  = 0;
             uint  argsSize  = 0;
-            ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL;
+            ulong codeStart = (meta.Flags & 1) != 0 ? 0x8000000UL : 0x200000UL;
             uint  codeSize  = 0;
             uint  codeSize  = 0;
 
 
             var buildIds = executables.Select(e => (e switch
             var buildIds = executables.Select(e => (e switch
@@ -182,18 +208,20 @@ namespace Ryujinx.HLE.HOS
 
 
             int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
             int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
 
 
-            int personalMmHeapPagesCount = metaData.PersonalMmHeapSize / KPageTableBase.PageSize;
+            int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize);
 
 
             ProcessCreationInfo creationInfo = new ProcessCreationInfo(
             ProcessCreationInfo creationInfo = new ProcessCreationInfo(
-                metaData.TitleName,
-                metaData.Version,
-                metaData.Aci0.TitleId,
+                programInfo.Name,
+                (int)meta.Version,
+                programInfo.ProgramId,
                 codeStart,
                 codeStart,
                 codePagesCount,
                 codePagesCount,
-                (ProcessCreationFlags)metaData.ProcessFlags | ProcessCreationFlags.IsApplication,
+                (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
                 0,
                 0,
                 personalMmHeapPagesCount);
                 personalMmHeapPagesCount);
 
 
+            context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm);
+
             KernelResult result;
             KernelResult result;
 
 
             KResourceLimit resourceLimit = new KResourceLimit(context);
             KResourceLimit resourceLimit = new KResourceLimit(context);
@@ -217,7 +245,7 @@ namespace Ryujinx.HLE.HOS
 
 
             KProcess process = new KProcess(context);
             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)
             if (memoryRegion > MemoryRegion.NvServices)
             {
             {
@@ -232,7 +260,7 @@ namespace Ryujinx.HLE.HOS
 
 
             result = process.Initialize(
             result = process.Initialize(
                 creationInfo,
                 creationInfo,
-                metaData.Aci0.KernelAccessControl.Capabilities,
+                MemoryMarshal.Cast<byte, int>(npdm.KernelCapabilityData).ToArray(),
                 resourceLimit,
                 resourceLimit,
                 memoryRegion,
                 memoryRegion,
                 processContextFactory);
                 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)
             if (result != KernelResult.Success)
             {
             {

+ 19 - 27
Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs

@@ -2,12 +2,9 @@
 using LibHac.Fs;
 using LibHac.Fs;
 using LibHac.Fs.Shim;
 using LibHac.Fs.Shim;
 using Ryujinx.Common;
 using Ryujinx.Common;
-using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.FileSystem.Content;
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Linq;
 
 
 namespace Ryujinx.HLE.HOS.Services.Account.Acc
 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");
         public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
 
 
-        private readonly VirtualFileSystem      _virtualFileSystem;
         private readonly AccountSaveDataManager _accountSaveDataManager;
         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<string, UserProfile> _profiles;
         private ConcurrentDictionary<string, UserProfile> _profiles;
 
 
         public UserProfile LastOpenedUser { get; private set; }
         public UserProfile LastOpenedUser { get; private set; }
 
 
-        public AccountManager(VirtualFileSystem virtualFileSystem)
+        public AccountManager(HorizonClient horizonClient)
         {
         {
-            _virtualFileSystem = virtualFileSystem;
+            _horizonClient = horizonClient;
 
 
             _profiles = new ConcurrentDictionary<string, UserProfile>();
             _profiles = new ConcurrentDictionary<string, UserProfile>();
 
 
@@ -169,31 +170,22 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             SaveDataFilter saveDataFilter = new SaveDataFilter();
             SaveDataFilter saveDataFilter = new SaveDataFilter();
             saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
             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())
-            {
-                Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
-
-                while (true)
-                {
-                    saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo);
+            _horizonClient.Fs.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
 
 
-                    if (readCount == 0)
-                    {
-                        break;
-                    }
+            Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
 
 
-                    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}");
+            while (true)
+            {
+                saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
 
 
-                        Directory.Delete(savePath, true);
-                        Directory.Delete(saveMetaPath, true);
+                if (readCount == 0)
+                {
+                    break;
+                }
 
 
-                        _virtualFileSystem.FsClient.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId);
-                    }
+                for (int i = 0; i < readCount; i++)
+                {
+                    _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId).ThrowIfFailure();
                 }
                 }
             }
             }
         }
         }

+ 39 - 7
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 _notificationStorageChannelEventHandle;
         private int _healthWarningDisappearedSystemEventHandle;
         private int _healthWarningDisappearedSystemEventHandle;
 
 
+        private HorizonClient _horizon;
+
         public IApplicationFunctions(Horizon system)
         public IApplicationFunctions(Horizon system)
         {
         {
             // TODO: Find where they are signaled.
             // TODO: Find where they are signaled.
@@ -44,6 +46,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             _friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
             _friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
             _notificationStorageChannelEvent     = new KEvent(system.KernelContext);
             _notificationStorageChannelEvent     = new KEvent(system.KernelContext);
             _healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
             _healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
+
+            _horizon = system.LibHacHorizonManager.AmClient;
         }
         }
 
 
         [CommandHipc(1)]
         [CommandHipc(1)]
@@ -103,14 +107,16 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
         // EnsureSaveData(nn::account::Uid) -> u64
         // EnsureSaveData(nn::account::Uid) -> u64
         public ResultCode EnsureSaveData(ServiceCtx context)
         public ResultCode EnsureSaveData(ServiceCtx context)
         {
         {
-            Uid           userId        = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
-            ApplicationId applicationId = new ApplicationId(context.Process.TitleId);
+            Uid userId = context.RequestData.ReadStruct<AccountUid>().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<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
             BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
 
 
             ref ApplicationControlProperty control = ref controlHolder.Value;
             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
                 // 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.
                 // 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.");
                     "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);
             context.ResponseData.Write(requiredSize);
 
 
@@ -195,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
         public ResultCode ExtendSaveData(ServiceCtx context)
         public ResultCode ExtendSaveData(ServiceCtx context)
         {
         {
             SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
             SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
-            Uid          userId       = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
+            Uid          userId       = context.RequestData.ReadStruct<Uid>();
             ulong        saveDataSize = context.RequestData.ReadUInt64();
             ulong        saveDataSize = context.RequestData.ReadUInt64();
             ulong        journalSize  = 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)
         public ResultCode GetSaveDataSize(ServiceCtx context)
         {
         {
             SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
             SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
-            Uid          userId       = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
+            Uid          userId       = context.RequestData.ReadStruct<Uid>();
 
 
             // NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded.
             // NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded.
             //       Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes.
             //       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;
             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<ApplicationControlProperty> 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)]
         [CommandHipc(30)]
         // BeginBlockingHomeButtonShortAndLongPressed()
         // BeginBlockingHomeButtonShortAndLongPressed()
         public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context)
         public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context)
@@ -517,7 +549,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle);
             context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle);
 
 
             // NOTE: This is used by "sdk" NSO during applet-application initialization.
             // 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.
             //       When the Event is signaled, official sw will assert.
 
 
             return ResultCode.Success;
             return ResultCode.Success;

+ 24 - 7
Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs

@@ -9,19 +9,14 @@ namespace Ryujinx.HLE.HOS.Services.Arp
 {
 {
     class LibHacIReader : LibHac.Arp.Impl.IReader
     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)
         public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId)
         {
         {
             launchProperty = new LibHac.Arp.ApplicationLaunchProperty
             launchProperty = new LibHac.Arp.ApplicationLaunchProperty
             {
             {
                 BaseStorageId = StorageId.BuiltInUser,
                 BaseStorageId = StorageId.BuiltInUser,
-                ApplicationId = new ApplicationId(System.Device.Application.TitleId)
+                ApplicationId = ApplicationId
             };
             };
 
 
             return Result.Success;
             return Result.Success;
@@ -47,5 +42,27 @@ namespace Ryujinx.HLE.HOS.Services.Arp
         {
         {
             throw new NotImplementedException();
             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;
+        }
     }
     }
 }
 }

+ 5 - 4
Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs

@@ -11,11 +11,12 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
     [Service("bcat:s", "bcat:s")]
     [Service("bcat:s", "bcat:s")]
     class IServiceCreator : IpcService
     class IServiceCreator : IpcService
     {
     {
-        private LibHac.Bcat.Detail.Ipc.IServiceCreator _base;
+        private LibHac.Bcat.Impl.Ipc.IServiceCreator _base;
 
 
         public IServiceCreator(ServiceCtx context, string serviceName)
         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)]
         [CommandHipc(0)]
@@ -42,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
         {
         {
             ulong pid = context.RequestData.ReadUInt64();
             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())
             if (rc.IsSuccess())
             {
             {
@@ -58,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
         {
         {
             ApplicationId applicationId = context.RequestData.ReadStruct<ApplicationId>();
             ApplicationId applicationId = context.RequestData.ReadStruct<ApplicationId>();
 
 
-            Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv,
+            Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService serv,
                applicationId);
                applicationId);
 
 
             if (rc.IsSuccess())
             if (rc.IsSuccess())

+ 2 - 2
Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs

@@ -7,9 +7,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
 {
 {
     class IDeliveryCacheDirectoryService : DisposableIpcService
     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;
             _base = baseService;
         }
         }

+ 2 - 2
Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs

@@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
 {
 {
     class IDeliveryCacheFileService : DisposableIpcService
     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;
             _base = baseService;
         }
         }

+ 4 - 4
Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs

@@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
 {
 {
     class IDeliveryCacheStorageService : DisposableIpcService
     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;
             _base = baseService;
         }
         }
@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
         // CreateFileService() -> object<nn::bcat::detail::ipc::IDeliveryCacheFileService>
         // CreateFileService() -> object<nn::bcat::detail::ipc::IDeliveryCacheFileService>
         public ResultCode CreateFileService(ServiceCtx context)
         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())
             if (result.IsSuccess())
             {
             {
@@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
         // CreateDirectoryService() -> object<nn::bcat::detail::ipc::IDeliveryCacheDirectoryService>
         // CreateDirectoryService() -> object<nn::bcat::detail::ipc::IDeliveryCacheDirectoryService>
         public ResultCode CreateDirectoryService(ServiceCtx context)
         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())
             if (result.IsSuccess())
             {
             {

+ 35 - 6
Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs

@@ -1,10 +1,16 @@
 using LibHac;
 using LibHac;
 using LibHac.Common;
 using LibHac.Common;
+using LibHac.Common.Keys;
 using LibHac.Fs;
 using LibHac.Fs;
+using LibHac.FsSrv.Impl;
+using LibHac.FsSrv.Sf;
 using LibHac.FsSystem;
 using LibHac.FsSystem;
 using LibHac.FsSystem.NcaUtils;
 using LibHac.FsSystem.NcaUtils;
 using LibHac.Spl;
 using LibHac.Spl;
+using System;
 using System.IO;
 using System.IO;
+using System.Runtime.InteropServices;
+using Path = System.IO.Path;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
 {
@@ -16,12 +22,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 
 
             try
             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<LibHac.Fs.Fsa.IFileSystem> 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)
             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);
                 LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel);
+                var sharedFs = new ReferenceCountedDisposable<LibHac.Fs.Fsa.IFileSystem>(fileSystem);
 
 
-                openedFileSystem = new IFileSystem(fileSystem);
+                openedFileSystem = new IFileSystem(FileSystemInterfaceAdapter.CreateShared(ref sharedFs));
             }
             }
             catch (HorizonResultException ex)
             catch (HorizonResultException ex)
             {
             {
@@ -99,7 +106,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
             return ResultCode.PathDoesNotExist;
             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"))
             foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
             {
             {
@@ -125,5 +132,27 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 
 
             return FsPath.FromSpan(out path, pathBytes);
             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<byte> buffer = context.Memory.GetSpan(position, (int)size);
+            ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(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<byte> buffer = context.Memory.GetSpan(position, (int)size);
+            ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer);
+
+            return ref pathBuffer[0];
+        }
     }
     }
 }
 }

+ 17 - 12
Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs

@@ -1,15 +1,13 @@
 using LibHac;
 using LibHac;
-using LibHac.Fs;
-using System;
-using System.Runtime.InteropServices;
+using LibHac.Sf;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
 {
-    class IDirectory : IpcService
+    class IDirectory : DisposableIpcService
     {
     {
-        private LibHac.Fs.Fsa.IDirectory _baseDirectory;
+        private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDirectory> _baseDirectory;
 
 
-        public IDirectory(LibHac.Fs.Fsa.IDirectory directory)
+        public IDirectory(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDirectory> directory)
         {
         {
             _baseDirectory = directory;
             _baseDirectory = directory;
         }
         }
@@ -19,14 +17,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         public ResultCode Read(ServiceCtx context)
         public ResultCode Read(ServiceCtx context)
         {
         {
             ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
             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<DirectoryEntry> entries      = MemoryMarshal.Cast<byte, DirectoryEntry>(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);
             context.ResponseData.Write(entriesRead);
 
 
             return (ResultCode)result.Value;
             return (ResultCode)result.Value;
@@ -36,11 +33,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // GetEntryCount() -> u64
         // GetEntryCount() -> u64
         public ResultCode GetEntryCount(ServiceCtx context)
         public ResultCode GetEntryCount(ServiceCtx context)
         {
         {
-            Result result = _baseDirectory.GetEntryCount(out long entryCount);
+            Result result = _baseDirectory.Target.GetEntryCount(out long entryCount);
 
 
             context.ResponseData.Write(entryCount);
             context.ResponseData.Write(entryCount);
 
 
             return (ResultCode)result.Value;
             return (ResultCode)result.Value;
         }
         }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            if (isDisposing)
+            {
+                _baseDirectory?.Dispose();
+            }
+        }
     }
     }
 }
 }

+ 13 - 11
Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs

@@ -1,13 +1,15 @@
 using LibHac;
 using LibHac;
 using LibHac.Fs;
 using LibHac.Fs;
+using LibHac.Sf;
+using Ryujinx.Common;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
 {
     class IFile : DisposableIpcService
     class IFile : DisposableIpcService
     {
     {
-        private LibHac.Fs.Fsa.IFile _baseFile;
+        private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFile> _baseFile;
 
 
-        public IFile(LibHac.Fs.Fsa.IFile baseFile)
+        public IFile(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFile> baseFile)
         {
         {
             _baseFile = baseFile;
             _baseFile = baseFile;
         }
         }
@@ -18,15 +20,15 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         {
         {
             ulong position = context.Request.ReceiveBuff[0].Position;
             ulong position = context.Request.ReceiveBuff[0].Position;
 
 
-            ReadOption readOption = new ReadOption(context.RequestData.ReadInt32());
+            ReadOption readOption = context.RequestData.ReadStruct<ReadOption>();
             context.RequestData.BaseStream.Position += 4;
             context.RequestData.BaseStream.Position += 4;
 
 
             long offset = context.RequestData.ReadInt64();
             long offset = context.RequestData.ReadInt64();
             long size   = 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);
             context.Memory.Write(position, data);
 
 
@@ -41,24 +43,24 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         {
         {
             ulong position = context.Request.SendBuff[0].Position;
             ulong position = context.Request.SendBuff[0].Position;
 
 
-            WriteOption writeOption = new WriteOption(context.RequestData.ReadInt32());
+            WriteOption writeOption = context.RequestData.ReadStruct<WriteOption>();
             context.RequestData.BaseStream.Position += 4;
             context.RequestData.BaseStream.Position += 4;
 
 
             long offset = context.RequestData.ReadInt64();
             long offset = context.RequestData.ReadInt64();
             long size   = 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);
             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)]
         [CommandHipc(2)]
         // Flush()
         // Flush()
         public ResultCode Flush(ServiceCtx context)
         public ResultCode Flush(ServiceCtx context)
         {
         {
-            return (ResultCode)_baseFile.Flush().Value;
+            return (ResultCode)_baseFile.Target.Flush().Value;
         }
         }
 
 
         [CommandHipc(3)]
         [CommandHipc(3)]
@@ -67,14 +69,14 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         {
         {
             long size = context.RequestData.ReadInt64();
             long size = context.RequestData.ReadInt64();
 
 
-            return (ResultCode)_baseFile.SetSize(size).Value;
+            return (ResultCode)_baseFile.Target.SetSize(size).Value;
         }
         }
 
 
         [CommandHipc(4)]
         [CommandHipc(4)]
         // GetSize() -> u64 fileSize
         // GetSize() -> u64 fileSize
         public ResultCode GetSize(ServiceCtx context)
         public ResultCode GetSize(ServiceCtx context)
         {
         {
-            Result result = _baseFile.GetSize(out long size);
+            Result result = _baseFile.Target.GetSize(out long size);
 
 
             context.ResponseData.Write(size);
             context.ResponseData.Write(size);
 
 

+ 47 - 41
Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs

@@ -1,21 +1,19 @@
 using LibHac;
 using LibHac;
-using LibHac.Common;
 using LibHac.Fs;
 using LibHac.Fs;
-using LibHac.Fs.Fsa;
-using static Ryujinx.HLE.Utilities.StringUtils;
+using LibHac.FsSrv.Sf;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
 {
-    class IFileSystem : IpcService
+    class IFileSystem : DisposableIpcService
     {
     {
-        private LibHac.Fs.Fsa.IFileSystem _fileSystem;
+        private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFileSystem> _fileSystem;
 
 
-        public IFileSystem(LibHac.Fs.Fsa.IFileSystem provider)
+        public IFileSystem(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFileSystem> provider)
         {
         {
             _fileSystem = provider;
             _fileSystem = provider;
         }
         }
 
 
-        public LibHac.Fs.Fsa.IFileSystem GetBaseFileSystem()
+        public ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFileSystem> GetBaseFileSystem()
         {
         {
             return _fileSystem;
             return _fileSystem;
         }
         }
@@ -24,79 +22,79 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // CreateFile(u32 createOption, u64 size, buffer<bytes<0x301>, 0x19, 0x301> path)
         // CreateFile(u32 createOption, u64 size, buffer<bytes<0x301>, 0x19, 0x301> path)
         public ResultCode CreateFile(ServiceCtx context)
         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;
             context.RequestData.BaseStream.Position += 4;
 
 
             long size = context.RequestData.ReadInt64();
             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)]
         [CommandHipc(1)]
         // DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path)
         // DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path)
         public ResultCode DeleteFile(ServiceCtx context)
         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)]
         [CommandHipc(2)]
         // CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
         // CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
         public ResultCode CreateDirectory(ServiceCtx context)
         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)]
         [CommandHipc(3)]
         // DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
         // DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
         public ResultCode DeleteDirectory(ServiceCtx context)
         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)]
         [CommandHipc(4)]
         // DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
         // DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
         public ResultCode DeleteDirectoryRecursively(ServiceCtx context)
         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)]
         [CommandHipc(5)]
         // RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
         // RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
         public ResultCode RenameFile(ServiceCtx context)
         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)]
         [CommandHipc(6)]
         // RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
         // RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
         public ResultCode RenameDirectory(ServiceCtx context)
         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)]
         [CommandHipc(7)]
         // GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType
         // GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType
         public ResultCode GetEntryType(ServiceCtx context)
         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);
             context.ResponseData.Write((int)entryType);
 
 
@@ -107,11 +105,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file
         // OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file
         public ResultCode OpenFile(ServiceCtx context)
         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<LibHac.FsSrv.Sf.IFile> file, in name, mode);
 
 
             if (result.IsSuccess())
             if (result.IsSuccess())
             {
             {
@@ -127,11 +125,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory
         // OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory
         public ResultCode OpenDirectory(ServiceCtx context)
         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<LibHac.FsSrv.Sf.IDirectory> dir, name, mode);
 
 
             if (result.IsSuccess())
             if (result.IsSuccess())
             {
             {
@@ -147,16 +145,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // Commit()
         // Commit()
         public ResultCode Commit(ServiceCtx context)
         public ResultCode Commit(ServiceCtx context)
         {
         {
-            return (ResultCode)_fileSystem.Commit().Value;
+            return (ResultCode)_fileSystem.Target.Commit().Value;
         }
         }
 
 
         [CommandHipc(11)]
         [CommandHipc(11)]
         // GetFreeSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalFreeSpace
         // GetFreeSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalFreeSpace
         public ResultCode GetFreeSpaceSize(ServiceCtx context)
         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);
             context.ResponseData.Write(size);
 
 
@@ -167,9 +165,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // GetTotalSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalSize
         // GetTotalSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalSize
         public ResultCode GetTotalSpaceSize(ServiceCtx context)
         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);
             context.ResponseData.Write(size);
 
 
@@ -180,18 +178,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
         // CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
         public ResultCode CleanDirectoryRecursively(ServiceCtx context)
         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)]
         [CommandHipc(14)]
         // GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp
         // GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp
         public ResultCode GetFileTimeStampRaw(ServiceCtx context)
         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.Created);
             context.ResponseData.Write(timestamp.Modified);
             context.ResponseData.Write(timestamp.Modified);
@@ -206,5 +204,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 
 
             return (ResultCode)result.Value;
             return (ResultCode)result.Value;
         }
         }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            if (isDisposing)
+            {
+                _fileSystem?.Dispose();
+            }
+        }
     }
     }
 }
 }

+ 5 - 4
Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs

@@ -1,13 +1,14 @@
 using LibHac;
 using LibHac;
+using LibHac.Sf;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Ipc;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
 {
     class IStorage : DisposableIpcService
     class IStorage : DisposableIpcService
     {
     {
-        private LibHac.Fs.IStorage _baseStorage;
+        private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IStorage> _baseStorage;
 
 
-        public IStorage(LibHac.Fs.IStorage baseStorage)
+        public IStorage(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IStorage> baseStorage)
         {
         {
             _baseStorage = baseStorage;
             _baseStorage = baseStorage;
         }
         }
@@ -31,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 
 
                 byte[] data = new byte[size];
                 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);
                 context.Memory.Write(buffDesc.Position, data);
 
 
@@ -45,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
         // GetSize() -> u64 size
         // GetSize() -> u64 size
         public ResultCode GetSize(ServiceCtx context)
         public ResultCode GetSize(ServiceCtx context)
         {
         {
-            Result result = _baseStorage.GetSize(out long size);
+            Result result = _baseStorage.Target.GetSize(out long size);
 
 
             context.ResponseData.Write(size);
             context.ResponseData.Write(size);
 
 

+ 14 - 6
Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs

@@ -3,11 +3,11 @@ using LibHac.FsSrv;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs
 namespace Ryujinx.HLE.HOS.Services.Fs
 {
 {
-    class IDeviceOperator : IpcService
+    class IDeviceOperator : DisposableIpcService
     {
     {
-        private LibHac.FsSrv.IDeviceOperator _baseOperator;
+        private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDeviceOperator> _baseOperator;
 
 
-        public IDeviceOperator(LibHac.FsSrv.IDeviceOperator baseOperator)
+        public IDeviceOperator(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDeviceOperator> baseOperator)
         {
         {
             _baseOperator = baseOperator;
             _baseOperator = baseOperator;
         }
         }
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
         // IsSdCardInserted() -> b8 is_inserted
         // IsSdCardInserted() -> b8 is_inserted
         public ResultCode IsSdCardInserted(ServiceCtx context)
         public ResultCode IsSdCardInserted(ServiceCtx context)
         {
         {
-            Result result = _baseOperator.IsSdCardInserted(out bool isInserted);
+            Result result = _baseOperator.Target.IsSdCardInserted(out bool isInserted);
 
 
             context.ResponseData.Write(isInserted);
             context.ResponseData.Write(isInserted);
 
 
@@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
         // IsGameCardInserted() -> b8 is_inserted
         // IsGameCardInserted() -> b8 is_inserted
         public ResultCode IsGameCardInserted(ServiceCtx context)
         public ResultCode IsGameCardInserted(ServiceCtx context)
         {
         {
-            Result result = _baseOperator.IsGameCardInserted(out bool isInserted);
+            Result result = _baseOperator.Target.IsGameCardInserted(out bool isInserted);
 
 
             context.ResponseData.Write(isInserted);
             context.ResponseData.Write(isInserted);
 
 
@@ -38,11 +38,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs
         // GetGameCardHandle() -> u32 gamecard_handle
         // GetGameCardHandle() -> u32 gamecard_handle
         public ResultCode GetGameCardHandle(ServiceCtx context)
         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);
             context.ResponseData.Write(handle.Value);
 
 
             return (ResultCode)result.Value;
             return (ResultCode)result.Value;
         }
         }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            if (isDisposing)
+            {
+                _baseOperator?.Dispose();
+            }
+        }
     }
     }
 }
 }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 726 - 144
Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs


+ 13 - 5
Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs

@@ -3,11 +3,11 @@ using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy;
 
 
 namespace Ryujinx.HLE.HOS.Services.Fs
 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<LibHac.FsSrv.Sf.IMultiCommitManager> _baseCommitManager;
 
 
-        public IMultiCommitManager(LibHac.FsSrv.IMultiCommitManager baseCommitManager)
+        public IMultiCommitManager(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IMultiCommitManager> baseCommitManager)
         {
         {
             _baseCommitManager = baseCommitManager;
             _baseCommitManager = baseCommitManager;
         }
         }
@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
         {
         {
             IFileSystem fileSystem = GetObject<IFileSystem>(context, 0);
             IFileSystem fileSystem = GetObject<IFileSystem>(context, 0);
 
 
-            Result result = _baseCommitManager.Add(fileSystem.GetBaseFileSystem());
+            Result result = _baseCommitManager.Target.Add(fileSystem.GetBaseFileSystem());
 
 
             return (ResultCode)result.Value;
             return (ResultCode)result.Value;
         }
         }
@@ -27,9 +27,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs
         // Commit()
         // Commit()
         public ResultCode Commit(ServiceCtx context)
         public ResultCode Commit(ServiceCtx context)
         {
         {
-            Result result = _baseCommitManager.Commit();
+            Result result = _baseCommitManager.Target.Commit();
 
 
             return (ResultCode)result.Value;
             return (ResultCode)result.Value;
         }
         }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            if (isDisposing)
+            {
+                _baseCommitManager?.Dispose();
+            }
+        }
     }
     }
 }
 }

+ 5 - 5
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
 namespace Ryujinx.HLE.HOS.Services.Fs
 {
 {
     class ISaveDataInfoReader : DisposableIpcService
     class ISaveDataInfoReader : DisposableIpcService
     {
     {
-        private ReferenceCountedDisposable<LibHac.FsSrv.ISaveDataInfoReader> _baseReader;
+        private ReferenceCountedDisposable<LibHac.FsSrv.Sf.ISaveDataInfoReader> _baseReader;
 
 
-        public ISaveDataInfoReader(ReferenceCountedDisposable<LibHac.FsSrv.ISaveDataInfoReader> baseReader)
+        public ISaveDataInfoReader(ReferenceCountedDisposable<LibHac.FsSrv.Sf.ISaveDataInfoReader> baseReader)
         {
         {
             _baseReader = baseReader;
             _baseReader = baseReader;
         }
         }
@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
 
 
             byte[] infoBuffer = new byte[bufferLen];
             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.Memory.Write(bufferPosition, infoBuffer);
             context.ResponseData.Write(readCount);
             context.ResponseData.Write(readCount);

+ 4 - 3
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;
 using System;
 
 
 namespace Ryujinx.HLE.HOS.Services.Mii
 namespace Ryujinx.HLE.HOS.Services.Mii
@@ -147,9 +148,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii
             return GetDefault(flag, ref count, elements);
             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);
             _miiDatabase.LoadFromFile(out _isBroken);
 
 
             // Nintendo ignore any error code from before
             // Nintendo ignore any error code from before

+ 45 - 57
Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs

@@ -1,7 +1,9 @@
 using LibHac;
 using LibHac;
 using LibHac.Common;
 using LibHac.Common;
 using LibHac.Fs;
 using LibHac.Fs;
+using LibHac.Fs.Fsa;
 using LibHac.Fs.Shim;
 using LibHac.Fs.Shim;
+using LibHac.Ncm;
 using Ryujinx.HLE.HOS.Services.Mii.Types;
 using Ryujinx.HLE.HOS.Services.Mii.Types;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 
 
@@ -14,8 +16,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii
 
 
         private const ulong  DatabaseTestSaveDataId = 0x8000000000000031;
         private const ulong  DatabaseTestSaveDataId = 0x8000000000000031;
         private const ulong  DatabaseSaveDataId     = 0x8000000000000030;
         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 DatabasePath = new U8String("mii:/MiiDatabase.dat");
         private static U8String MountName    = new U8String("mii");
         private static U8String MountName    = new U8String("mii");
@@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
         private NintendoFigurineDatabase _database;
         private NintendoFigurineDatabase _database;
         private bool                     _isDirty;
         private bool                     _isDirty;
 
 
-        private FileSystemClient _filesystemClient;
+        private HorizonClient _horizonClient;
 
 
         protected ulong UpdateCounter { get; private set; }
         protected ulong UpdateCounter { get; private set; }
 
 
@@ -94,74 +94,62 @@ namespace Ryujinx.HLE.HOS.Services.Mii
             return virtualIndex;
             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
             // Ensure we have valid data in the database
             _database.Format();
             _database.Format();
 
 
-            // TODO: Unmount is currently not implemented properly at dispose, implement that and decrement MountCounter.
-            MountCounter = 0;
-
             MountSave();
             MountSave();
         }
         }
 
 
         private Result MountSave()
         private Result MountSave()
         {
         {
-            Result result = Result.Success;
+            if (MountCounter != 0)
+            {
+                MountCounter++;
+                return Result.Success;
+            }
 
 
-            if (MountCounter == 0)
+            ulong saveDataId = IsTestModeEnabled ? DatabaseTestSaveDataId : DatabaseSaveDataId;
+
+            Result result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId);
+
+            if (result.IsFailure())
             {
             {
-                ulong targetSaveDataId;
-                ulong targetTitleId;
+                if (!ResultFs.TargetNotFound.Includes(result))
+                    return result;
 
 
                 if (IsTestModeEnabled)
                 if (IsTestModeEnabled)
                 {
                 {
-                    targetSaveDataId = DatabaseTestSaveDataId;
-                    targetTitleId    = SdbTitleId;
+                    result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, 0x10000, 0x10000,
+                        SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
+                    if (result.IsFailure()) return result;
                 }
                 }
                 else
                 else
                 {
                 {
-                    targetSaveDataId = DatabaseSaveDataId;
-
-                    // Nintendo use NS TitleID when creating the production save even on sdb, let's follow that behaviour.
-                    targetTitleId = NsTitleId;
-                }
-
-                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;
-                    }
+                    result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, SystemProgramId.Ns.Value, 0x10000,
+                        0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
+                    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;
             return result;
         }
         }
 
 
         public ResultCode DeleteFile()
         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;
             return result;
         }
         }
@@ -179,17 +167,17 @@ namespace Ryujinx.HLE.HOS.Services.Mii
 
 
             ResetDatabase();
             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())
             if (result.IsSuccess())
             {
             {
-                result = _filesystemClient.GetFileSize(out long fileSize, handle);
+                result = _horizonClient.Fs.GetFileSize(out long fileSize, handle);
 
 
                 if (result.IsSuccess())
                 if (result.IsSuccess())
                 {
                 {
                     if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>())
                     if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>())
                     {
                     {
-                        result = _filesystemClient.ReadFile(handle, 0, _database.AsSpan());
+                        result = _horizonClient.Fs.ReadFile(handle, 0, _database.AsSpan());
 
 
                         if (result.IsSuccess())
                         if (result.IsSuccess())
                         {
                         {
@@ -211,7 +199,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
                     }
                     }
                 }
                 }
 
 
-                _filesystemClient.CloseFile(handle);
+                _horizonClient.Fs.CloseFile(handle);
 
 
                 return (ResultCode)result.Value;
                 return (ResultCode)result.Value;
             }
             }
@@ -225,32 +213,32 @@ namespace Ryujinx.HLE.HOS.Services.Mii
 
 
         private Result ForceSaveDatabase()
         private Result ForceSaveDatabase()
         {
         {
-            Result result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
+            Result result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
 
 
             if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result))
             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())
                 if (result.IsSuccess())
                 {
                 {
-                    result = _filesystemClient.GetFileSize(out long fileSize, handle);
+                    result = _horizonClient.Fs.GetFileSize(out long fileSize, handle);
 
 
                     if (result.IsSuccess())
                     if (result.IsSuccess())
                     {
                     {
                         // If the size doesn't match, recreate the file
                         // If the size doesn't match, recreate the file
                         if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>())
                         if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>())
                         {
                         {
-                            _filesystemClient.CloseFile(handle);
+                            _horizonClient.Fs.CloseFile(handle);
 
 
-                            result = _filesystemClient.DeleteFile(DatabasePath);
+                            result = _horizonClient.Fs.DeleteFile(DatabasePath);
 
 
                             if (result.IsSuccess())
                             if (result.IsSuccess())
                             {
                             {
-                                result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
+                                result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
 
 
                                 if (result.IsSuccess())
                                 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;
                 _isDirty = false;
 
 
-                result = _filesystemClient.Commit(MountName);
+                result = _horizonClient.Fs.Commit(MountName);
             }
             }
 
 
             return result;
             return result;

+ 1 - 1
Ryujinx.HLE/Ryujinx.HLE.csproj

@@ -20,7 +20,7 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Concentus" Version="1.1.7" />
     <PackageReference Include="Concentus" Version="1.1.7" />
-    <PackageReference Include="LibHac" Version="0.12.0" />
+    <PackageReference Include="LibHac" Version="0.13.1" />
     <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
     <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
     <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
     <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
   </ItemGroup>
   </ItemGroup>

+ 28 - 12
Ryujinx/Ui/MainWindow.cs

@@ -52,9 +52,10 @@ namespace Ryujinx.Ui
 {
 {
     public class MainWindow : Window
     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;
         private UserChannelPersistence _userChannelPersistence;
 
 
@@ -156,13 +157,27 @@ namespace Ryujinx.Ui
             // Hide emulation context status bar.
             // Hide emulation context status bar.
             _statusBar.Hide();
             _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);
             _contentManager         = new ContentManager(_virtualFileSystem);
-            _accountManager         = new AccountManager(_virtualFileSystem);
+            _accountManager         = new AccountManager(_libHacHorizonManager.RyujinxClient);
             _userChannelPersistence = new UserChannelPersistence();
             _userChannelPersistence = new UserChannelPersistence();
 
 
-            // Instanciate GUI objects.
+            // Instantiate GUI objects.
             _applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
             _applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
             _uiHandler          = new GtkHostUiHandler(this);
             _uiHandler          = new GtkHostUiHandler(this);
             _deviceExitStatus   = new AutoResetEvent(false);
             _deviceExitStatus   = new AutoResetEvent(false);
@@ -370,7 +385,7 @@ namespace Ryujinx.Ui
 
 
         private void InitializeSwitchInstance()
         private void InitializeSwitchInstance()
         {
         {
-            _virtualFileSystem.Reload();
+            _virtualFileSystem.ReloadKeySet();
 
 
             IRenderer renderer;
             IRenderer renderer;
 
 
@@ -440,6 +455,7 @@ namespace Ryujinx.Ui
             IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
             IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
 
 
             HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(_virtualFileSystem,
             HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(_virtualFileSystem,
+                                                                          _libHacHorizonManager,
                                                                           _contentManager,
                                                                           _contentManager,
                                                                           _accountManager,
                                                                           _accountManager,
                                                                           _userChannelPersistence,
                                                                           _userChannelPersistence,
@@ -1092,7 +1108,7 @@ namespace Ryujinx.Ui
 
 
             BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
             BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_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)
         private void Load_Application_File(object sender, EventArgs args)
@@ -1208,15 +1224,15 @@ namespace Ryujinx.Ui
 
 
                     SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename);
                     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}.");
                         GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}.");
 
 
                         return;
                         return;
                     }
                     }
 
 
+                    string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}";
+
                     SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion();
                     SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion();
 
 
                     string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";
                     string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";

+ 12 - 10
Ryujinx/Ui/Widgets/GameTableContextMenu.cs

@@ -33,6 +33,7 @@ namespace Ryujinx.Ui.Widgets
         private readonly MainWindow                             _parent;
         private readonly MainWindow                             _parent;
         private readonly VirtualFileSystem                      _virtualFileSystem;
         private readonly VirtualFileSystem                      _virtualFileSystem;
         private readonly AccountManager                         _accountManager;
         private readonly AccountManager                         _accountManager;
+        private readonly HorizonClient                          _horizonClient;
         private readonly BlitStruct<ApplicationControlProperty> _controlData;
         private readonly BlitStruct<ApplicationControlProperty> _controlData;
 
 
         private readonly string _titleFilePath;
         private readonly string _titleFilePath;
@@ -43,7 +44,7 @@ namespace Ryujinx.Ui.Widgets
         private MessageDialog _dialog;
         private MessageDialog _dialog;
         private bool          _cancel;
         private bool          _cancel;
 
 
-        public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
+        public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
         {
         {
             _parent = parent;
             _parent = parent;
 
 
@@ -51,6 +52,7 @@ namespace Ryujinx.Ui.Widgets
 
 
             _virtualFileSystem = virtualFileSystem;
             _virtualFileSystem = virtualFileSystem;
             _accountManager    = accountManager;
             _accountManager    = accountManager;
+            _horizonClient     = horizonClient;
             _titleFilePath     = titleFilePath;
             _titleFilePath     = titleFilePath;
             _titleName         = titleName;
             _titleName         = titleName;
             _titleIdText       = titleId;
             _titleIdText       = titleId;
@@ -63,9 +65,9 @@ namespace Ryujinx.Ui.Widgets
                 return;
                 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();
             string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
             bool   hasNca  = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
             bool   hasNca  = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
@@ -81,7 +83,7 @@ namespace Ryujinx.Ui.Widgets
         {
         {
             saveDataId = default;
             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))
             if (ResultFs.TargetNotFound.Includes(result))
             {
             {
@@ -102,7 +104,7 @@ namespace Ryujinx.Ui.Widgets
 
 
                 ref ApplicationControlProperty control = ref controlHolder.Value;
                 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
                     // 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.
                     // 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);
                 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())
                 if (result.IsFailure())
                 {
                 {
@@ -127,7 +129,7 @@ namespace Ryujinx.Ui.Widgets
                 }
                 }
 
 
                 // Try to find the savedata again after creating it
                 // 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())
             if (result.IsSuccess())
@@ -284,7 +286,7 @@ namespace Ryujinx.Ui.Widgets
                         IFileSystem ncaFileSystem = patchNca != null ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
                         IFileSystem ncaFileSystem = patchNca != null ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
                                                                      : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
                                                                      : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
 
 
-                        FileSystemClient fsClient = _virtualFileSystem.FsClient;
+                        FileSystemClient fsClient = _horizonClient.Fs;
 
 
                         string source = DateTime.Now.ToFileTime().ToString()[10..];
                         string source = DateTime.Now.ToFileTime().ToString()[10..];
                         string output = 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);
                             rc = fs.ReadFile(out long _, sourceHandle, offset, buf);
                             if (rc.IsFailure()) return rc;
                             if (rc.IsFailure()) return rc;
 
 
-                            rc = fs.WriteFile(destHandle, offset, buf);
+                            rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None);
                             if (rc.IsFailure()) return rc;
                             if (rc.IsFailure()) return rc;
                         }
                         }
                     }
                     }

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor