From f0aac13fab9b70bb98cde8902bd0fe964de1e3e6 Mon Sep 17 00:00:00 2001
From: Alex Barney <thealexbarney@gmail.com>
Date: Tue, 22 Oct 2019 17:52:55 -0500
Subject: [PATCH] Add some SaveDataInfoReader functions

---
 src/LibHac/Fs/FileSystemClient.cs             |   4 +-
 src/LibHac/Fs/ResultFs.cs                     |   2 +
 src/LibHac/Fs/SaveDataStructs.cs              |  24 ---
 src/LibHac/Fs/Shim/SaveDataManagement.cs      |  96 ++++++++++-
 src/LibHac/FsService/FileSystemProxy.cs       |  71 +++++++-
 src/LibHac/FsService/ISaveDataIndexer.cs      |   1 +
 src/LibHac/FsService/SaveDataIndexer.cs       |  99 ++++++++++-
 .../FsService/SaveDataIndexerManager.cs       |   2 +-
 .../FsService/SaveDataInfoFilterReader.cs     | 156 ++++++++++++++++++
 src/LibHac/Kvdb/KeyValueDatabase.cs           |   7 +
 10 files changed, 426 insertions(+), 36 deletions(-)
 create mode 100644 src/LibHac/FsService/SaveDataInfoFilterReader.cs

diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs
index d4957282..8e03fc26 100644
--- a/src/LibHac/Fs/FileSystemClient.cs
+++ b/src/LibHac/Fs/FileSystemClient.cs
@@ -108,7 +108,7 @@ namespace LibHac.Fs
             int mountLen = 0;
             int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
 
-            for (int i = 0; i < maxMountLen; i++)
+            for (int i = 0; i <= maxMountLen; i++)
             {
                 if (path[i] == PathTools.MountSeparator)
                 {
@@ -122,7 +122,7 @@ namespace LibHac.Fs
                 mountName = default;
                 subPath = default;
 
-                return ResultFs.InvalidMountName;
+                return ResultFs.InvalidMountName.Log();
             }
 
             mountName = path.Slice(0, mountLen);
diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs
index cbd07a15..d2381684 100644
--- a/src/LibHac/Fs/ResultFs.cs
+++ b/src/LibHac/Fs/ResultFs.cs
@@ -97,6 +97,8 @@
         public static Result InvalidMountName => new Result(ModuleFs, 6065);
         public static Result ExtensionSizeTooLarge => new Result(ModuleFs, 6066);
         public static Result ExtensionSizeInvalid => new Result(ModuleFs, 6067);
+        public static Result ReadOldSaveDataInfoReader => new Result(ModuleFs, 6068);
+        public static Result InvalidSaveDataSpaceId => new Result(ModuleFs, 6082);
 
         public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
         public static Result FileExtensionWithoutOpenModeAllowAppend => new Result(ModuleFs, 6201);
diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs
index adea7949..06888447 100644
--- a/src/LibHac/Fs/SaveDataStructs.cs
+++ b/src/LibHac/Fs/SaveDataStructs.cs
@@ -78,30 +78,6 @@ namespace LibHac.Fs
         [FieldOffset(0x2A)] public short Index;
     }
 
-    [StructLayout(LayoutKind.Explicit, Size = 0x50)]
-    public struct SaveDataFilterInternal
-    {
-        [FieldOffset(0x00)] public bool FilterBySaveDataSpaceId;
-        [FieldOffset(0x01)] public SaveDataSpaceId SpaceId;
-
-        [FieldOffset(0x08)] public bool FilterByTitleId;
-        [FieldOffset(0x10)] public TitleId TitleID;
-
-        [FieldOffset(0x18)] public bool FilterBySaveDataType;
-        [FieldOffset(0x19)] public SaveDataType SaveDataType;
-
-        [FieldOffset(0x20)] public bool FilterByUserId;
-        [FieldOffset(0x28)] public UserId UserId;
-
-        [FieldOffset(0x38)] public bool FilterBySaveDataId;
-        [FieldOffset(0x40)] public ulong SaveDataId;
-
-        [FieldOffset(0x48)] public bool FilterByIndex;
-        [FieldOffset(0x4A)] public short Index;
-
-        [FieldOffset(0x4C)] public int Rank;
-    }
-
     [StructLayout(LayoutKind.Explicit, Size = HashLength)]
     public struct HashSalt
     {
diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs
index 26f00d39..eb5ed709 100644
--- a/src/LibHac/Fs/Shim/SaveDataManagement.cs
+++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs
@@ -1,4 +1,7 @@
-using LibHac.FsService;
+using System;
+using System.Runtime.InteropServices;
+using LibHac.Common;
+using LibHac.FsService;
 using LibHac.FsSystem.Save;
 using LibHac.Ncm;
 
@@ -159,6 +162,63 @@ namespace LibHac.Fs.Shim
                 () => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}");
         }
 
+        public static Result FindSaveDataWithFilter(this FileSystemClient fs, out SaveDataInfo info, SaveDataSpaceId spaceId,
+            ref SaveDataFilter filter)
+        {
+            info = default;
+
+            SaveDataFilter tempFilter = filter;
+            var tempInfo = new SaveDataInfo();
+
+            Result result = fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
+                () =>
+                {
+                    IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
+
+                    tempInfo = new SaveDataInfo();
+
+                    Result rc = fsProxy.FindSaveDataWithFilter(out long count, SpanHelpers.AsByteSpan(ref tempInfo),
+                        spaceId, ref tempFilter);
+                    if (rc.IsFailure()) return rc;
+
+                    if (count == 0)
+                        return ResultFs.TargetNotFound.Log();
+
+                    return Result.Success;
+                },
+                () => $", savedataspaceid: {spaceId}");
+
+            if (result.IsSuccess())
+            {
+                info = tempInfo;
+            }
+
+            return result;
+        }
+
+        public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId)
+        {
+            var tempIterator = new SaveDataIterator();
+
+            Result result = fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
+                () =>
+                {
+                    IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
+
+                    Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader reader, spaceId);
+                    if (rc.IsFailure()) return rc;
+
+                    tempIterator = new SaveDataIterator(fs, reader);
+
+                    return Result.Success;
+                },
+                () => $", savedataspaceid: {spaceId}");
+
+            iterator = result.IsSuccess() ? tempIterator : default;
+
+            return result;
+        }
+
         public static Result DisableAutoSaveDataCreation(this FileSystemClient fsClient)
         {
             IFileSystemProxy fsProxy = fsClient.GetFileSystemProxyServiceObject();
@@ -166,4 +226,38 @@ namespace LibHac.Fs.Shim
             return fsProxy.DisableAutoSaveDataCreation();
         }
     }
+
+    public struct SaveDataIterator
+    {
+        private FileSystemClient FsClient { get; }
+        private ISaveDataInfoReader Reader { get; }
+
+        internal SaveDataIterator(FileSystemClient fsClient, ISaveDataInfoReader reader)
+        {
+            FsClient = fsClient;
+            Reader = reader;
+        }
+
+        public Result ReadSaveDataInfo(out long readCount, Span<SaveDataInfo> buffer)
+        {
+            Result rc;
+
+            Span<byte> byteBuffer = MemoryMarshal.Cast<SaveDataInfo, byte>(buffer);
+
+            if (FsClient.IsEnabledAccessLog(LocalAccessLogMode.System))
+            {
+                TimeSpan startTime = FsClient.Time.GetCurrent();
+                rc = Reader.ReadSaveDataInfo(out readCount, byteBuffer);
+                TimeSpan endTime = FsClient.Time.GetCurrent();
+
+                FsClient.OutputAccessLog(rc, startTime, endTime, $", size: {buffer.Length}");
+            }
+            else
+            {
+                rc = Reader.ReadSaveDataInfo(out readCount, byteBuffer);
+            }
+
+            return rc;
+        }
+    }
 }
diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs
index 1b554b18..b4a59cea 100644
--- a/src/LibHac/FsService/FileSystemProxy.cs
+++ b/src/LibHac/FsService/FileSystemProxy.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.CompilerServices;
 using LibHac.Common;
 using LibHac.Fs;
 using LibHac.FsSystem;
@@ -707,7 +708,34 @@ namespace LibHac.FsService
 
         public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId)
         {
-            throw new NotImplementedException();
+            infoReader = default;
+
+            // Missing permission check
+
+            SaveDataIndexerReader indexReader = default;
+
+            try
+            {
+                Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
+                if (rc.IsFailure()) return rc;
+
+                rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
+                if (rc.IsFailure()) return rc;
+
+                var filter = new SaveDataFilterInternal
+                {
+                    FilterBySaveDataSpaceId = true,
+                    SpaceId = GetSpaceIdForIndexer(spaceId)
+                };
+
+                infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
+
+                return Result.Success;
+            }
+            finally
+            {
+                indexReader.Dispose();
+            }
         }
 
         public Result OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId,
@@ -719,7 +747,46 @@ namespace LibHac.FsService
         public Result FindSaveDataWithFilter(out long count, Span<byte> saveDataInfoBuffer, SaveDataSpaceId spaceId,
             ref SaveDataFilter filter)
         {
-            throw new NotImplementedException();
+            count = default;
+
+            if (saveDataInfoBuffer.Length != Unsafe.SizeOf<SaveDataInfo>())
+            {
+                return ResultFs.InvalidArgument.Log();
+            }
+
+            // Missing permission check
+
+            var internalFilter = new SaveDataFilterInternal(ref filter, GetSpaceIdForIndexer(spaceId));
+
+            ref SaveDataInfo saveDataInfo = ref Unsafe.As<byte, SaveDataInfo>(ref saveDataInfoBuffer[0]);
+
+            return FindSaveDataWithFilterImpl(out count, out saveDataInfo, spaceId, ref internalFilter);
+        }
+
+        private Result FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, SaveDataSpaceId spaceId,
+            ref SaveDataFilterInternal filter)
+        {
+            count = default;
+            info = default;
+
+            SaveDataIndexerReader indexReader = default;
+
+            try
+            {
+                Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
+                if (rc.IsFailure()) return rc;
+
+                rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
+                if (rc.IsFailure()) return rc;
+
+                var infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
+
+                return infoReader.ReadSaveDataInfo(out count, SpanHelpers.AsByteSpan(ref info));
+            }
+            finally
+            {
+                indexReader.Dispose();
+            }
         }
 
         public Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId)
diff --git a/src/LibHac/FsService/ISaveDataIndexer.cs b/src/LibHac/FsService/ISaveDataIndexer.cs
index 1ccfc9d2..0a314694 100644
--- a/src/LibHac/FsService/ISaveDataIndexer.cs
+++ b/src/LibHac/FsService/ISaveDataIndexer.cs
@@ -15,5 +15,6 @@ namespace LibHac.FsService
         Result SetState(ulong saveDataId, SaveDataState state);
         Result GetKey(out SaveDataAttribute key, ulong saveDataId);
         Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId);
+        Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader);
     }
 }
\ No newline at end of file
diff --git a/src/LibHac/FsService/SaveDataIndexer.cs b/src/LibHac/FsService/SaveDataIndexer.cs
index 7e1ac8dc..909f57fb 100644
--- a/src/LibHac/FsService/SaveDataIndexer.cs
+++ b/src/LibHac/FsService/SaveDataIndexer.cs
@@ -1,6 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using LibHac.Common;
 using LibHac.Fs;
 using LibHac.Fs.Shim;
@@ -23,6 +25,7 @@ namespace LibHac.FsService
         private bool IsInitialized { get; set; }
         private bool IsKvdbLoaded { get; set; }
         private ulong LastPublishedId { get; set; }
+        private int Version { get; set; }
 
         public SaveDataIndexer(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId, ulong saveDataId)
         {
@@ -30,6 +33,7 @@ namespace LibHac.FsService
             MountName = mountName;
             SaveDataId = saveDataId;
             SpaceId = spaceId;
+            Version = 1;
         }
 
         public static void GetSaveDataInfo(out SaveDataInfo info, ref SaveDataAttribute key, ref SaveDataIndexerValue value)
@@ -140,6 +144,9 @@ namespace LibHac.FsService
                     return rc;
                 }
 
+                rc = AdjustOpenedInfoReaders(ref key);
+                if (rc.IsFailure()) return rc;
+
                 saveDataId = newSaveDataId;
                 return Result.Success;
             }
@@ -194,11 +201,10 @@ namespace LibHac.FsService
                 };
 
                 rc = KvDatabase.Set(ref key, SpanHelpers.AsByteSpan(ref newValue));
+                if (rc.IsFailure()) return rc;
 
-                if (rc.IsFailure())
-                {
-                    // todo: Missing some function call here
-                }
+                rc = AdjustOpenedInfoReaders(ref key);
+                if (rc.IsFailure()) return rc;
 
                 return rc;
             }
@@ -224,7 +230,10 @@ namespace LibHac.FsService
                     return ResultFs.TargetNotFound.Log();
                 }
 
-                return KvDatabase.Delete(ref key);
+                rc = KvDatabase.Delete(ref key);
+                if (rc.IsFailure()) return rc;
+
+                return AdjustOpenedInfoReaders(ref key);
             }
         }
 
@@ -333,6 +342,26 @@ namespace LibHac.FsService
             }
         }
 
+        public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)
+        {
+            infoReader = default;
+
+            lock (Locker)
+            {
+                Result rc = Initialize();
+                if (rc.IsFailure()) return rc;
+
+                rc = EnsureKvDatabaseLoaded(false);
+                if (rc.IsFailure()) return rc;
+
+                var reader = new SaveDataInfoReader(this);
+
+                infoReader = reader;
+
+                return Result.Success;
+            }
+        }
+
         private bool TryGetBySaveDataIdInternal(out SaveDataAttribute key, out SaveDataIndexerValue value, ulong saveDataId)
         {
             foreach (KeyValuePair<SaveDataAttribute, byte[]> kvp in KvDatabase)
@@ -459,6 +488,12 @@ namespace LibHac.FsService
             }
         }
 
+        private Result AdjustOpenedInfoReaders(ref SaveDataAttribute key)
+        {
+            // todo
+            return Result.Success;
+        }
+
         private ref struct Mounter
         {
             private FileSystemClient FsClient { get; set; }
@@ -516,5 +551,57 @@ namespace LibHac.FsService
                 }
             }
         }
+
+        private class SaveDataInfoReader : ISaveDataInfoReader
+        {
+            private SaveDataIndexer Indexer { get; }
+            private int Version { get; }
+            public int Position { get; set; }
+
+            public SaveDataInfoReader(SaveDataIndexer indexer)
+            {
+                Indexer = indexer;
+                Version = indexer.Version;
+            }
+
+            public Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer)
+            {
+                readCount = default;
+
+                lock (Indexer.Locker)
+                {
+                    // Indexer has been reloaded since this info reader was created
+                    if (Version != Indexer.Version)
+                    {
+                        return ResultFs.ReadOldSaveDataInfoReader.Log();
+                    }
+
+                    // No more to iterate
+                    if (Position == Indexer.KvDatabase.Count)
+                    {
+                        readCount = 0;
+                        return Result.Success;
+                    }
+
+                    Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
+
+                    // Todo: A more efficient way of doing this
+                    List<(SaveDataAttribute key, byte[] value)> list = Indexer.KvDatabase.ToList();
+
+                    int i;
+                    for (i = 0; i < outInfo.Length && Position < list.Count; i++, Position++)
+                    {
+                        SaveDataAttribute key = list[Position].key;
+                        ref SaveDataIndexerValue value = ref Unsafe.As<byte, SaveDataIndexerValue>(ref list[Position].value[0]);
+
+                        GetSaveDataInfo(out outInfo[i], ref key, ref value);
+                    }
+
+                    readCount = i;
+
+                    return Result.Success;
+                }
+            }
+        }
     }
 }
diff --git a/src/LibHac/FsService/SaveDataIndexerManager.cs b/src/LibHac/FsService/SaveDataIndexerManager.cs
index eb788e86..21f07da0 100644
--- a/src/LibHac/FsService/SaveDataIndexerManager.cs
+++ b/src/LibHac/FsService/SaveDataIndexerManager.cs
@@ -47,7 +47,7 @@ namespace LibHac.FsService
                         _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbSd", SaveDataSpaceId.SdSystem, SaveDataId);
                     }
 
-                    reader = new SaveDataIndexerReader(_bisIndexer.Indexer, _bisIndexer.Locker);
+                    reader = new SaveDataIndexerReader(_sdCardIndexer.Indexer, _sdCardIndexer.Locker);
                     return Result.Success;
 
                 case SaveDataSpaceId.TemporaryStorage:
diff --git a/src/LibHac/FsService/SaveDataInfoFilterReader.cs b/src/LibHac/FsService/SaveDataInfoFilterReader.cs
new file mode 100644
index 00000000..312c00ad
--- /dev/null
+++ b/src/LibHac/FsService/SaveDataInfoFilterReader.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Runtime.InteropServices;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.FsSystem.Save;
+using LibHac.Ncm;
+
+namespace LibHac.FsService
+{
+    internal class SaveDataInfoFilterReader : ISaveDataInfoReader
+    {
+        private ISaveDataInfoReader Reader { get; }
+        private SaveDataFilterInternal Filter { get; }
+
+        public SaveDataInfoFilterReader(ISaveDataInfoReader reader, ref SaveDataFilterInternal filter)
+        {
+            Reader = reader;
+            Filter = filter;
+        }
+
+        public Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer)
+        {
+            readCount = default;
+
+            Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
+
+            SaveDataInfo tempInfo = default;
+            Span<byte> tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo);
+
+            int count = 0;
+
+            while (count < outInfo.Length)
+            {
+                Result rc = Reader.ReadSaveDataInfo(out long baseReadCount, tempInfoBytes);
+                if (rc.IsFailure()) return rc;
+
+                if (baseReadCount == 0) break;
+
+                if (Filter.Matches(ref tempInfo))
+                {
+                    outInfo[count] = tempInfo;
+
+                    count++;
+                }
+            }
+
+            readCount = count;
+
+            return Result.Success;
+        }
+    }
+
+    [StructLayout(LayoutKind.Explicit, Size = 0x50)]
+    internal struct SaveDataFilterInternal
+    {
+        [FieldOffset(0x00)] public bool FilterBySaveDataSpaceId;
+        [FieldOffset(0x01)] public SaveDataSpaceId SpaceId;
+
+        [FieldOffset(0x08)] public bool FilterByTitleId;
+        [FieldOffset(0x10)] public TitleId TitleId;
+
+        [FieldOffset(0x18)] public bool FilterBySaveDataType;
+        [FieldOffset(0x19)] public SaveDataType SaveDataType;
+
+        [FieldOffset(0x20)] public bool FilterByUserId;
+        [FieldOffset(0x28)] public UserId UserId;
+
+        [FieldOffset(0x38)] public bool FilterBySaveDataId;
+        [FieldOffset(0x40)] public ulong SaveDataId;
+
+        [FieldOffset(0x48)] public bool FilterByIndex;
+        [FieldOffset(0x4A)] public short Index;
+
+        [FieldOffset(0x4C)] public int Rank;
+
+        public SaveDataFilterInternal(ref SaveDataFilter filter, SaveDataSpaceId spaceId)
+        {
+            this = default;
+
+            FilterBySaveDataSpaceId = true;
+            SpaceId = spaceId;
+
+            Rank = filter.Rank;
+
+            if (filter.FilterByTitleId)
+            {
+                FilterByTitleId = true;
+                TitleId = filter.TitleId;
+            }
+
+            if (filter.FilterBySaveDataType)
+            {
+                FilterBySaveDataType = true;
+                SaveDataType = filter.SaveDataType;
+            }
+
+            if (filter.FilterByUserId)
+            {
+                FilterByUserId = true;
+                UserId = filter.UserId;
+            }
+
+            if (filter.FilterBySaveDataId)
+            {
+                FilterBySaveDataId = true;
+                SaveDataId = filter.SaveDataId;
+            }
+
+            if (filter.FilterByIndex)
+            {
+                FilterByIndex = true;
+                Index = filter.Index;
+            }
+        }
+
+        public bool Matches(ref SaveDataInfo info)
+        {
+            if (FilterBySaveDataSpaceId && info.SpaceId != SpaceId)
+            {
+                return false;
+            }
+
+            if (FilterByTitleId && info.TitleId != TitleId)
+            {
+                return false;
+            }
+
+            if (FilterBySaveDataType && info.Type != SaveDataType)
+            {
+                return false;
+            }
+
+            if (FilterByUserId && info.UserId != UserId)
+            {
+                return false;
+            }
+
+            if (FilterBySaveDataId && info.SaveDataId != SaveDataId)
+            {
+                return false;
+            }
+
+            if (FilterByIndex && info.Index != Index)
+            {
+                return false;
+            }
+
+            if ((Rank & 1) == 0 && info.Rank != 0)
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/LibHac/Kvdb/KeyValueDatabase.cs b/src/LibHac/Kvdb/KeyValueDatabase.cs
index 8dfa20af..87136351 100644
--- a/src/LibHac/Kvdb/KeyValueDatabase.cs
+++ b/src/LibHac/Kvdb/KeyValueDatabase.cs
@@ -15,6 +15,8 @@ namespace LibHac.Kvdb
         private FileSystemClient FsClient { get; }
         private string FileName { get; }
 
+        public int Count => KvDict.Count;
+
         public KeyValueDatabase() { }
 
         public KeyValueDatabase(FileSystemClient fsClient, string fileName)
@@ -147,6 +149,11 @@ namespace LibHac.Kvdb
             return size;
         }
 
+        public List<(TKey key, byte[] value)> ToList()
+        {
+            return KvDict.OrderBy(x => x.Key).Select(entry => (entry.Key, entry.Value)).ToList();
+        }
+
         private Result ReadFile(out byte[] data)
         {
             Debug.Assert(FsClient != null);