diff --git a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs
new file mode 100644
index 00000000..dabe0f0b
--- /dev/null
+++ b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs
@@ -0,0 +1,134 @@
+using System.Collections.Generic;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.FsSystem;
+using LibHac.Os;
+
+namespace LibHac.FsSrv.Impl
+{
+ ///
+ /// Holds the s for opened save data file systems.
+ ///
+ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorCacheObserver
+ {
+ private struct Cache
+ {
+ private ReferenceCountedDisposable.WeakReference _accessor;
+ private readonly SaveDataSpaceId _spaceId;
+ private readonly ulong _saveDataId;
+
+ public Cache(ReferenceCountedDisposable accessor, SaveDataSpaceId spaceId,
+ ulong saveDataId)
+ {
+ _accessor = new ReferenceCountedDisposable.WeakReference(accessor);
+ _spaceId = spaceId;
+ _saveDataId = saveDataId;
+ }
+
+ public bool Contains(SaveDataSpaceId spaceId, ulong saveDataId)
+ {
+ return _spaceId == spaceId && _saveDataId == saveDataId;
+ }
+
+ public ReferenceCountedDisposable Lock()
+ {
+ return _accessor.TryAddReference();
+ }
+ }
+
+ private readonly LinkedList _accessorList;
+ private SdkRecursiveMutex _mutex;
+
+ public SaveDataExtraDataAccessorCacheManager()
+ {
+ _accessorList = new LinkedList();
+ _mutex = new SdkRecursiveMutex();
+ }
+
+ public void Dispose()
+ {
+ _accessorList.Clear();
+ }
+
+ public Result Register(ReferenceCountedDisposable accessor,
+ SaveDataSpaceId spaceId, ulong saveDataId)
+ {
+ var cache = new Cache(accessor, spaceId, saveDataId);
+
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ _accessorList.AddLast(cache);
+ return Result.Success;
+ }
+
+ public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId)
+ {
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ UnregisterImpl(spaceId, saveDataId);
+ }
+
+ private void UnregisterImpl(SaveDataSpaceId spaceId, ulong saveDataId)
+ {
+ LinkedListNode currentNode = _accessorList.First;
+
+ while (currentNode is not null)
+ {
+ if (currentNode.ValueRef.Contains(spaceId, saveDataId))
+ {
+ _accessorList.Remove(currentNode);
+ return;
+ }
+
+ currentNode = currentNode.Next;
+ }
+ }
+
+ public Result GetCache(out ReferenceCountedDisposable accessor,
+ SaveDataSpaceId spaceId, ulong saveDataId)
+ {
+ UnsafeHelpers.SkipParamInit(out accessor);
+
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ LinkedListNode currentNode = _accessorList.First;
+
+ while (true)
+ {
+ if (currentNode is null)
+ return ResultFs.TargetNotFound.Log();
+
+ if (currentNode.ValueRef.Contains(spaceId, saveDataId))
+ break;
+
+ currentNode = currentNode.Next;
+ }
+
+ ReferenceCountedDisposable tempAccessor = null;
+ try
+ {
+ tempAccessor = currentNode.ValueRef.Lock();
+
+ // Return early if the accessor was already disposed
+ if (tempAccessor is null)
+ {
+ // Note: Nintendo doesn't remove the accessor from the list in this case
+ _accessorList.Remove(currentNode);
+ return ResultFs.TargetNotFound.Log();
+ }
+
+ accessor = SaveDataExtraDataResultConvertAccessor.CreateShared(ref tempAccessor);
+ return Result.Success;
+ }
+ finally
+ {
+ tempAccessor?.Dispose();
+ }
+ }
+
+ public ScopedLock GetScopedLock()
+ {
+ return new ScopedLock(ref _mutex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs
new file mode 100644
index 00000000..5649416f
--- /dev/null
+++ b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs
@@ -0,0 +1,179 @@
+using LibHac.Common;
+using LibHac.Diag;
+using LibHac.Fs;
+using LibHac.FsSystem;
+
+namespace LibHac.FsSrv.Impl
+{
+ public static class SaveDataResultConvert
+ {
+ private static Result ConvertCorruptedResult(Result result)
+ {
+ if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result))
+ {
+ if (ResultFs.IncorrectIntegrityVerificationMagic.Includes(result))
+ return ResultFs.IncorrectSaveDataIntegrityVerificationMagic.Value;
+
+ if (ResultFs.InvalidZeroHash.Includes(result))
+ return ResultFs.InvalidSaveDataZeroHash.Value;
+
+ if (ResultFs.NonRealDataVerificationFailed.Includes(result))
+ return ResultFs.SaveDataNonRealDataVerificationFailed.Value;
+
+ if (ResultFs.ClearedRealDataVerificationFailed.Includes(result))
+ return ResultFs.ClearedSaveDataRealDataVerificationFailed.Value;
+
+ if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result))
+ return ResultFs.UnclearedSaveDataRealDataVerificationFailed.Value;
+
+ Assert.SdkAssert(false);
+ }
+
+ if (ResultFs.HostFileSystemCorrupted.Includes(result))
+ {
+ if (ResultFs.HostEntryCorrupted.Includes(result))
+ return ResultFs.SaveDataHostEntryCorrupted.Value;
+
+ if (ResultFs.HostFileDataCorrupted.Includes(result))
+ return ResultFs.SaveDataHostFileDataCorrupted.Value;
+
+ if (ResultFs.HostFileCorrupted.Includes(result))
+ return ResultFs.SaveDataHostFileCorrupted.Value;
+
+ if (ResultFs.InvalidHostHandle.Includes(result))
+ return ResultFs.InvalidSaveDataHostHandle.Value;
+
+ Assert.SdkAssert(false);
+ }
+
+ if (ResultFs.DatabaseCorrupted.Includes(result))
+ {
+ if (ResultFs.InvalidAllocationTableBlock.Includes(result))
+ return ResultFs.InvalidSaveDataAllocationTableBlock.Value;
+
+ if (ResultFs.InvalidKeyValueListElementIndex.Includes(result))
+ return ResultFs.InvalidSaveDataKeyValueListElementIndex.Value;
+
+ if (ResultFs.AllocationTableIteratedRangeEntry.Includes(result))
+ return ResultFs.SaveDataAllocationTableIteratedRangeEntry.Value;
+
+ if (ResultFs.InvalidAllocationTableOffset.Includes(result))
+ return ResultFs.InvalidSaveDataAllocationTableOffset.Value;
+
+ if (ResultFs.InvalidAllocationTableBlockCount.Includes(result))
+ return ResultFs.InvalidSaveDataAllocationTableBlockCount.Value;
+
+ if (ResultFs.InvalidKeyValueListEntryIndex.Includes(result))
+ return ResultFs.InvalidSaveDataKeyValueListEntryIndex.Value;
+
+ if (ResultFs.InvalidBitmapIndex.Includes(result))
+ return ResultFs.InvalidSaveDataBitmapIndex.Value;
+
+ Assert.SdkAssert(false);
+ }
+
+ if (ResultFs.ZeroBitmapFileCorrupted.Includes(result))
+ {
+ if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result))
+ return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.Value;
+
+ Assert.SdkAssert(false);
+ }
+
+ return result;
+ }
+
+ public static Result ConvertSaveFsDriverPublicResult(Result result)
+ {
+ if (result.IsSuccess())
+ return result;
+
+ if (ResultFs.UnsupportedVersion.Includes(result))
+ return ResultFs.UnsupportedSaveDataVersion.Value;
+
+ if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) ||
+ ResultFs.BuiltInStorageCorrupted.Includes(result) ||
+ ResultFs.HostFileSystemCorrupted.Includes(result) ||
+ ResultFs.DatabaseCorrupted.Includes(result) ||
+ ResultFs.ZeroBitmapFileCorrupted.Includes(result))
+ {
+ return ConvertCorruptedResult(result);
+ }
+
+ if (ResultFs.FatFileSystemCorrupted.Includes(result))
+ return result;
+
+ if (ResultFs.NotFound.Includes(result))
+ return ResultFs.PathNotFound.Value;
+
+ if (ResultFs.AllocationTableFull.Includes(result))
+ return ResultFs.UsableSpaceNotEnough.Value;
+
+ if (ResultFs.AlreadyExists.Includes(result))
+ return ResultFs.PathAlreadyExists.Value;
+
+ if (ResultFs.InvalidOffset.Includes(result))
+ return ResultFs.OutOfRange.Value;
+
+ if (ResultFs.IncompatiblePath.Includes(result) ||
+ ResultFs.FileNotFound.Includes(result))
+ {
+ return ResultFs.PathNotFound.Value;
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// Wraps an , converting its returned s
+ /// to save-data-specific s.
+ ///
+ public class SaveDataExtraDataResultConvertAccessor : ISaveDataExtraDataAccessor
+ {
+ private ReferenceCountedDisposable _accessor;
+
+ public SaveDataExtraDataResultConvertAccessor(
+ ref ReferenceCountedDisposable accessor)
+ {
+ _accessor = Shared.Move(ref accessor);
+ }
+
+ public static ReferenceCountedDisposable CreateShared(
+ ref ReferenceCountedDisposable accessor)
+ {
+ var resultConvertAccessor = new SaveDataExtraDataResultConvertAccessor(ref accessor);
+ return new ReferenceCountedDisposable(resultConvertAccessor);
+ }
+
+ public void Dispose()
+ {
+ _accessor?.Dispose();
+ _accessor = null;
+ }
+
+ public Result WriteExtraData(in SaveDataExtraData extraData)
+ {
+ Result rc = _accessor.Target.WriteExtraData(in extraData);
+ return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc);
+ }
+
+ public Result CommitExtraData(bool updateTimeStamp)
+ {
+ Result rc = _accessor.Target.CommitExtraData(updateTimeStamp);
+ return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc);
+ }
+
+ public Result ReadExtraData(out SaveDataExtraData extraData)
+ {
+ Result rc = _accessor.Target.ReadExtraData(out extraData);
+ return SaveDataResultConvert.ConvertSaveFsDriverPublicResult(rc);
+ }
+
+ public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId,
+ ulong saveDataId)
+ {
+ _accessor.Target.RegisterCacheObserver(observer, spaceId, saveDataId);
+ }
+ }
+}
\ No newline at end of file