From 3705c206688c69d3348f5cec84dc480d8d7c578e Mon Sep 17 00:00:00 2001
From: Alex Barney <thealexbarney@gmail.com>
Date: Sat, 26 Feb 2022 16:52:25 -0700
Subject: [PATCH] Update LibHac to v0.16.0 (#3159)

---
 Ryujinx.HLE/HOS/ApplicationLoader.cs          | 20 ++++++-------
 Ryujinx.HLE/HOS/Horizon.cs                    |  2 +-
 Ryujinx.HLE/HOS/LibHacHorizonManager.cs       |  2 ++
 Ryujinx.HLE/HOS/ModLoader.cs                  |  2 +-
 Ryujinx.HLE/HOS/ProgramLoader.cs              |  2 +-
 .../Services/Account/Acc/AccountManager.cs    |  4 +--
 .../Acc/IAccountServiceForApplication.cs      |  2 +-
 .../ApplicationProxy/IApplicationFunctions.cs |  7 ++---
 Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs |  4 +--
 .../FileSystemProxy/FileSystemProxyHelper.cs  | 20 +++----------
 .../HOS/Services/Fs/IDeviceOperator.cs        |  2 +-
 .../IParentalControlService.cs                |  9 +++---
 .../QueryPlayStatisticsManager.cs             |  6 ++--
 .../Loaders/Executables/NsoExecutable.cs      |  6 ++--
 Ryujinx.HLE/Ryujinx.HLE.csproj                |  2 +-
 Ryujinx/Ui/App/ApplicationLibrary.cs          | 22 +++++++-------
 Ryujinx/Ui/Widgets/GameTableContextMenu.cs    | 29 +++++++------------
 Ryujinx/Ui/Windows/TitleUpdateWindow.cs       |  2 +-
 18 files changed, 64 insertions(+), 79 deletions(-)

diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
index 65448d5dae..cf92abda26 100644
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs
@@ -481,14 +481,14 @@ namespace Ryujinx.HLE.HOS
 
                 if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
                 {
-                    titleName = controlData.Value.Titles[(int)device.System.State.DesiredTitleLanguage].Name.ToString();
+                    titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
 
                     if (string.IsNullOrWhiteSpace(titleName))
                     {
-                        titleName = controlData.Value.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
+                        titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
                     }
 
-                    displayVersion = controlData.Value.DisplayVersion.ToString();
+                    displayVersion = controlData.Value.DisplayVersionString.ToString();
                 }
             }
             else
@@ -615,20 +615,20 @@ namespace Ryujinx.HLE.HOS
 
                                 ref ApplicationControlProperty nacp = ref ControlData.Value;
 
-                                programInfo.Name = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
+                                programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
 
                                 if (string.IsNullOrWhiteSpace(programInfo.Name))
                                 {
-                                    programInfo.Name = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
+                                    programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
                                 }
 
                                 if (nacp.PresenceGroupId != 0)
                                 {
                                     programInfo.ProgramId = nacp.PresenceGroupId;
                                 }
-                                else if (nacp.SaveDataOwnerId.Value != 0)
+                                else if (nacp.SaveDataOwnerId != 0)
                                 {
-                                    programInfo.ProgramId = nacp.SaveDataOwnerId.Value;
+                                    programInfo.ProgramId = nacp.SaveDataOwnerId;
                                 }
                                 else if (nacp.AddOnContentBaseId != 0)
                                 {
@@ -776,14 +776,14 @@ namespace Ryujinx.HLE.HOS
                 // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
                 control.UserAccountSaveDataSize = 0x4000;
                 control.UserAccountSaveDataJournalSize = 0x4000;
-                control.SaveDataOwnerId = applicationId;
+                control.SaveDataOwnerId = applicationId.Value;
 
                 Logger.Warning?.Print(LogClass.Application,
                     "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
             }
 
             HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
-            Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, ref control);
+            Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
 
             if (resultCode.IsFailure())
             {
@@ -792,7 +792,7 @@ namespace Ryujinx.HLE.HOS
                 return resultCode;
             }
 
-            resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user);
+            resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
 
             if (resultCode.IsFailure())
             {
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 56c9985901..cadd43ffff 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -466,7 +466,7 @@ namespace Ryujinx.HLE.HOS
 
                 AudioRendererManager.Dispose();
 
-                LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
+                LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
                 
                 KernelContext.Dispose();
             }
diff --git a/Ryujinx.HLE/HOS/LibHacHorizonManager.cs b/Ryujinx.HLE/HOS/LibHacHorizonManager.cs
index 4ad8446eb5..35e5c6e910 100644
--- a/Ryujinx.HLE/HOS/LibHacHorizonManager.cs
+++ b/Ryujinx.HLE/HOS/LibHacHorizonManager.cs
@@ -24,6 +24,7 @@ namespace Ryujinx.HLE.HOS
         public HorizonClient BcatClient        { get; private set; }
         public HorizonClient FsClient          { get; private set; }
         public HorizonClient NsClient          { get; private set; }
+        public HorizonClient PmClient          { get; private set; }
         public HorizonClient SdbClient         { get; private set; }
 
         private SharedRef<LibHacIReader> _arpIReader;
@@ -65,6 +66,7 @@ namespace Ryujinx.HLE.HOS
 
         public void InitializeSystemClients()
         {
+            PmClient      = Server.CreatePrivilegedHorizonClient();
             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);
diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs
index e2037924bd..27c0022fa7 100644
--- a/Ryujinx.HLE/HOS/ModLoader.cs
+++ b/Ryujinx.HLE/HOS/ModLoader.cs
@@ -695,7 +695,7 @@ namespace Ryujinx.HLE.HOS
 
             var buildIds = programs.Select(p => p switch
             {
-                NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()).Replace("-", "").TrimEnd('0'),
+                NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()).Replace("-", "").TrimEnd('0'),
                 NroExecutable nro => BitConverter.ToString(nro.Header.BuildId).Replace("-", "").TrimEnd('0'),
                 _ => string.Empty
             }).ToList();
diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs
index 5605ab0114..403e0227c5 100644
--- a/Ryujinx.HLE/HOS/ProgramLoader.cs
+++ b/Ryujinx.HLE/HOS/ProgramLoader.cs
@@ -160,7 +160,7 @@ namespace Ryujinx.HLE.HOS
 
             var buildIds = executables.Select(e => (e switch
             {
-                NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()),
+                NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()),
                 NroExecutable nro => BitConverter.ToString(nro.Header.BuildId),
                 _ => ""
             }).Replace("-", "").ToUpper());
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
index 3f504b2eaf..e28fe1061e 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
@@ -168,8 +168,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
 
         private void DeleteSaveData(UserId userId)
         {
-            SaveDataFilter saveDataFilter = new SaveDataFilter();
-            saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
+            var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default,
+                new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default);
 
             using var saveDataIterator = new UniqueRef<SaveDataIterator>();
 
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
index c29ed570c4..4de0b34bbb 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
@@ -169,7 +169,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
             //       But since we use LibHac and we load one Application at a time, it's not necessary.
 
-            context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock);
+            context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
 
             Logger.Stub?.PrintStub(LogClass.ServiceAcc);
 
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
index 11b2f41ba2..efc1884f77 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
@@ -131,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             }
 
             HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
-            Result result = EnsureApplicationSaveData(hos.Fs, out long requiredSize, applicationId, ref control, ref userId);
+            Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
 
             context.ResponseData.Write(requiredSize);
 
@@ -148,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             // TODO: When above calls are implemented, switch to using ns:am
 
             long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
-            int  supportedLanguages  = (int)context.Device.Application.ControlData.Value.SupportedLanguages;
+            int  supportedLanguages  = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
             int  firstSupported      = BitOperations.TrailingZeroCount(supportedLanguages);
 
             if (firstSupported > (int)SystemState.TitleLanguage.BrazilianPortuguese)
@@ -190,7 +190,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
         // GetDisplayVersion() -> nn::oe::DisplayVersion
         public ResultCode GetDisplayVersion(ServiceCtx context)
         {
-            // This should work as DisplayVersion U8Span always gives a 0x10 size byte array.
             // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
             context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
 
@@ -252,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
             BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
 
             Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
-                out CacheStorageTargetMedia storageTarget, applicationId, ref controlHolder.Value, index, saveSize,
+                out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
                 journalSize);
 
             if (result.IsFailure()) return (ResultCode)result.Value;
diff --git a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs
index 985dcdec4a..d7686871a8 100644
--- a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs
+++ b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
         {
             launchProperty = new LibHac.Arp.ApplicationLaunchProperty
             {
-                BaseStorageId = StorageId.BuiltInUser,
+                StorageId = StorageId.BuiltInUser,
                 ApplicationId = ApplicationId
             };
 
@@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
         {
             launchProperty = new LibHac.Arp.ApplicationLaunchProperty
             {
-                BaseStorageId = StorageId.BuiltInUser,
+                StorageId = StorageId.BuiltInUser,
                 ApplicationId = applicationId
             };
 
diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs
index 6e9994867a..2afa348071 100644
--- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs
+++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs
@@ -132,22 +132,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
             }
         }
 
-        public static Result ReadFsPath(out FsPath path, ServiceCtx context, int index = 0)
-        {
-            ulong position = context.Request.PtrBuff[index].Position;
-            ulong size     = context.Request.PtrBuff[index].Size;
-
-            byte[] pathBytes = new byte[size];
-
-            context.Memory.Read(position, 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;
+            ulong position = context.Request.PtrBuff[index].Position;
+            ulong size = context.Request.PtrBuff[index].Size;
 
             ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
             ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer);
@@ -157,8 +145,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 
         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;
+            ulong position = context.Request.PtrBuff[index].Position;
+            ulong size = 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);
diff --git a/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs b/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs
index 8be0bbcf70..07ade0c65b 100644
--- a/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs
+++ b/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs
@@ -1,6 +1,6 @@
 using LibHac;
 using LibHac.Common;
-using LibHac.FsSrv;
+using LibHac.Fs;
 
 namespace Ryujinx.HLE.HOS.Services.Fs
 {
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
index 15b998f2a7..1ccf0fb423 100644
--- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
+++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
@@ -1,8 +1,9 @@
-using LibHac.Ns;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Services.Arp;
 using System;
 
+using static LibHac.Ns.ApplicationControlProperty;
+
 namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
 {
     class IParentalControlService : IpcService
@@ -52,8 +53,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
                         _titleId = titleId;
 
                         // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
-                        _ratingAge           = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
-                        _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControl;
+                        _ratingAge           = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
+                        _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag;
                     }
                 }
 
@@ -224,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
         private ResultCode IsStereoVisionPermittedImpl()
         {
             /*
-                // TODO: Application Exemptions are readed from file "appExemptions.dat" in the service savedata.
+                // TODO: Application Exemptions are read from file "appExemptions.dat" in the service savedata.
                 //       Since we don't support the pctl savedata for now, this can be implemented later.
 
                 if (appExemption)
diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
index 3449e1082c..fc6635fa75 100644
--- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs
@@ -15,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
 
         internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
         {
+            ref readonly var controlProperty = ref context.Device.Application.ControlData.Value;
+
             ulong inputPosition = context.Request.SendBuff[0].Position;
             ulong inputSize     = context.Request.SendBuff[0].Size;
 
@@ -31,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
                 }
             }
 
-            PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability;
+            PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability;
 
             List<ulong> titleIds = new List<ulong>();
 
@@ -45,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
                 // Check if input title ids are in the whitelist.
                 foreach (ulong titleId in titleIds)
                 {
-                    if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
+                    if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
                     {
                         return (ResultCode)Am.ResultCode.ObjectInvalid;
                     }
diff --git a/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs b/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs
index f983536c7b..d3c0d1bf44 100644
--- a/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs
+++ b/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs
@@ -1,4 +1,4 @@
-using LibHac.Common;
+using LibHac.Common.FixedArrays;
 using LibHac.Fs;
 using LibHac.Loader;
 using LibHac.Tools.FsSystem;
@@ -26,8 +26,8 @@ namespace Ryujinx.HLE.Loaders.Executables
         public uint DataSize { get; }
         public uint BssSize  { get; }
 
-        public string   Name;
-        public Buffer32 BuildId;
+        public string        Name;
+        public Array32<byte> BuildId;
 
         public NsoExecutable(IStorage inStorage, string name = null)
         {
diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj
index 0d7b577c65..cf1c734fc2 100644
--- a/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -19,7 +19,7 @@
 
   <ItemGroup>
     <PackageReference Include="Concentus" Version="1.1.7" />
-    <PackageReference Include="LibHac" Version="0.15.0" />
+    <PackageReference Include="LibHac" Version="0.16.0" />
     <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
     <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
     <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
diff --git a/Ryujinx/Ui/App/ApplicationLibrary.cs b/Ryujinx/Ui/App/ApplicationLibrary.cs
index 519dd72704..3658dfe28f 100644
--- a/Ryujinx/Ui/App/ApplicationLibrary.cs
+++ b/Ryujinx/Ui/App/ApplicationLibrary.cs
@@ -558,10 +558,10 @@ namespace Ryujinx.Ui.App
         {
             _ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
 
-            if (controlData.Titles.Length > (int)desiredTitleLanguage)
+            if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
             {
-                titleName = controlData.Titles[(int)desiredTitleLanguage].Name.ToString();
-                publisher = controlData.Titles[(int)desiredTitleLanguage].Publisher.ToString();
+                titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
+                publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
             }
             else
             {
@@ -571,11 +571,11 @@ namespace Ryujinx.Ui.App
 
             if (string.IsNullOrWhiteSpace(titleName))
             {
-                foreach (ApplicationControlTitle controlTitle in controlData.Titles)
+                foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
                 {
-                    if (!((U8Span)controlTitle.Name).IsEmpty())
+                    if (!controlTitle.NameString.IsEmpty())
                     {
-                        titleName = controlTitle.Name.ToString();
+                        titleName = controlTitle.NameString.ToString();
 
                         break;
                     }
@@ -584,11 +584,11 @@ namespace Ryujinx.Ui.App
 
             if (string.IsNullOrWhiteSpace(publisher))
             {
-                foreach (ApplicationControlTitle controlTitle in controlData.Titles)
+                foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
                 {
-                    if (!((U8Span)controlTitle.Publisher).IsEmpty())
+                    if (!controlTitle.PublisherString.IsEmpty())
                     {
-                        publisher = controlTitle.Publisher.ToString();
+                        publisher = controlTitle.PublisherString.ToString();
 
                         break;
                     }
@@ -599,7 +599,7 @@ namespace Ryujinx.Ui.App
             {
                 titleId = controlData.PresenceGroupId.ToString("x16");
             }
-            else if (controlData.SaveDataOwnerId.Value != 0)
+            else if (controlData.SaveDataOwnerId != 0)
             {
                 titleId = controlData.SaveDataOwnerId.ToString();
             }
@@ -612,7 +612,7 @@ namespace Ryujinx.Ui.App
                 titleId = "0000000000000000";
             }
 
-            version = controlData.DisplayVersion.ToString();
+            version = controlData.DisplayVersionString.ToString();
         }
 
         private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index be9fc7b418..ef8fca341f 100644
--- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -6,7 +6,6 @@ using LibHac.Fs;
 using LibHac.Fs.Fsa;
 using LibHac.Fs.Shim;
 using LibHac.FsSystem;
-using LibHac.Ncm;
 using LibHac.Ns;
 using LibHac.Tools.Fs;
 using LibHac.Tools.FsSystem;
@@ -26,8 +25,6 @@ using System.IO;
 using System.Reflection;
 using System.Threading;
 
-using static LibHac.Fs.ApplicationSaveDataManagement;
-
 namespace Ryujinx.Ui.Widgets
 {
     public partial class GameTableContextMenu : Menu
@@ -81,7 +78,7 @@ namespace Ryujinx.Ui.Widgets
             PopupAtPointer(null);
         }
 
-        private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
+        private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
         {
             saveDataId = default;
 
@@ -121,7 +118,7 @@ namespace Ryujinx.Ui.Widgets
 
                 Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
 
-                result = EnsureApplicationSaveData(_horizonClient.Fs, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
+                result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
 
                 if (result.IsFailure())
                 {
@@ -146,11 +143,9 @@ namespace Ryujinx.Ui.Widgets
             return false;
         }
 
-        private void OpenSaveDir(SaveDataFilter saveDataFilter)
+        private void OpenSaveDir(in SaveDataFilter saveDataFilter)
         {
-            saveDataFilter.SetProgramId(new ProgramId(_titleId));
-
-            if (!TryFindSaveData(_titleName, _titleId, _controlData, saveDataFilter, out ulong saveDataId))
+            if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
             {
                 return;
             }
@@ -439,26 +434,24 @@ namespace Ryujinx.Ui.Widgets
         //
         private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
         {
-            SaveDataFilter saveDataFilter = new SaveDataFilter();
-            saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low));
+            var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
+            var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
 
-            OpenSaveDir(saveDataFilter);
+            OpenSaveDir(in saveDataFilter);
         }
 
         private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
         {
-            SaveDataFilter saveDataFilter = new SaveDataFilter();
-            saveDataFilter.SetSaveDataType(SaveDataType.Device);
+            var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
 
-            OpenSaveDir(saveDataFilter);
+            OpenSaveDir(in saveDataFilter);
         }
 
         private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
         {
-            SaveDataFilter saveDataFilter = new SaveDataFilter();
-            saveDataFilter.SetSaveDataType(SaveDataType.Bcat);
+            var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
 
-            OpenSaveDir(saveDataFilter);
+            OpenSaveDir(in saveDataFilter);
         }
 
         private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
diff --git a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
index f6dbff3073..94bf9e7093 100644
--- a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
+++ b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs
@@ -105,7 +105,7 @@ namespace Ryujinx.Ui.Windows
                             controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
                             nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
 
-                            RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersion.ToString()} - {path}");
+                            RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersionString.ToString()} - {path}");
                             radioButton.JoinGroup(_noUpdateRadioButton);
 
                             _availableUpdatesBox.Add(radioButton);