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