From 085511660d68f3e7b2ff344d0e549f5be921b27a Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 15 Jan 2022 03:02:21 -0700 Subject: [PATCH] Fixup save data indexer classes and update them for 13.1.0 - SaveDataIndexer - SaveDataIndexerLite - SaveDataIndexerLiteInfoReader - SaveDataIndexerManager - SaveDataIndexerAccessor --- src/LibHac/Fs/SaveData.cs | 4 +- src/LibHac/FsSrv/Impl/MultiCommitManager.cs | 4 +- src/LibHac/FsSrv/SaveDataFileSystemService.cs | 70 +- .../FsSrv/SaveDataFileSystemServiceImpl.cs | 2 +- src/LibHac/FsSrv/SaveDataIndexer.cs | 1202 +++++++++-------- src/LibHac/FsSrv/SaveDataIndexerLite.cs | 301 +++-- src/LibHac/FsSrv/SaveDataIndexerManager.cs | 350 ++--- src/LibHac/Os/UniqueLock.cs | 10 + src/LibHac/Util/Optional.cs | 9 +- 9 files changed, 1038 insertions(+), 914 deletions(-) diff --git a/src/LibHac/Fs/SaveData.cs b/src/LibHac/Fs/SaveData.cs index 4f533c4b..2f7cce99 100644 --- a/src/LibHac/Fs/SaveData.cs +++ b/src/LibHac/Fs/SaveData.cs @@ -4,9 +4,9 @@ namespace LibHac.Fs; public static class SaveData { - public const ulong SaveIndexerId = 0x8000000000000000; + public static readonly ulong SaveIndexerId = 0x8000000000000000; public static ProgramId InvalidProgramId => ProgramId.InvalidId; public static ProgramId AutoResolveCallerProgramId => new ProgramId(ulong.MaxValue - 1); public static UserId InvalidUserId => UserId.InvalidId; - + public static readonly ulong InvalidSystemSaveDataId = 0; } diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index 0e0ca117..c2f4ecf2 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -304,7 +304,7 @@ internal class MultiCommitManager : IMultiCommitManager rc = saveService.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out _, SaveDataSpaceId.User); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; // Iterate through all the saves to find any provisionally committed save data @@ -385,7 +385,7 @@ internal class MultiCommitManager : IMultiCommitManager rc = saveService.OpenSaveDataIndexerAccessor(ref accessor.Ref(), out _, SaveDataSpaceId.User); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; // Iterate through all the saves to find any provisionally committed save data diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index fb1287c6..23955958 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -484,7 +484,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsFailure()) return rc; if (value.SpaceId != ConvertToRealSpaceId(spaceId)) @@ -534,14 +534,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } else { - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsFailure()) return rc; actualSpaceId = value.SpaceId; } // Check if the caller has permission to delete this save. - rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); if (rc.IsFailure()) return rc; Result GetExtraData(out SaveDataExtraData data) => @@ -552,10 +552,10 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (rc.IsFailure()) return rc; // Pre-delete checks successful. Put the save in the Processing state until deletion is finished. - rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Processing); + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Processing); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.Commit(); + rc = accessor.Get.GetInterface().Commit(); if (rc.IsFailure()) return rc; } @@ -567,10 +567,10 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // The indexer doesn't track itself, so skip if deleting its save data. if (saveDataId != SaveData.SaveIndexerId) { - rc = accessor.Get.Indexer.Delete(saveDataId); + rc = accessor.Get.GetInterface().Delete(saveDataId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.Commit(); + rc = accessor.Get.GetInterface().Commit(); if (rc.IsFailure()) return rc; } @@ -725,7 +725,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // If a static save data ID is specified that ID is always used saveDataId = attribute.StaticSaveDataId; - rc = accessor.Get.Indexer.PutStaticSaveDataIdIndex(in indexerKey); + rc = accessor.Get.GetInterface().PutStaticSaveDataIdIndex(in indexerKey); } else { @@ -734,14 +734,14 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave // end up in a situation where it can't create a required system save. if (!SaveDataProperties.CanUseIndexerReservedArea(attribute.Type)) { - if (accessor.Get.Indexer.IsRemainedReservedOnly()) + if (accessor.Get.GetInterface().IsRemainedReservedOnly()) { return ResultKvdb.OutOfKeyResource.Log(); } } // If a static save data ID is no specified we're assigned a new save ID - rc = accessor.Get.Indexer.Publish(out saveDataId, in indexerKey); + rc = accessor.Get.GetInterface().Publish(out saveDataId, in indexerKey); } if (rc.IsFailure()) @@ -757,19 +757,19 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave creating = true; // Set the state, space ID and size on the new save indexer entry. - rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Processing); + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Processing); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); + rc = accessor.Get.GetInterface().SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); if (rc.IsFailure()) return rc; rc = QuerySaveDataTotalSize(out long saveDataSize, creationInfo.Size, creationInfo.JournalSize); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.SetSize(saveDataId, saveDataSize); + rc = accessor.Get.GetInterface().SetSize(saveDataId, saveDataSize); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.Commit(); + rc = accessor.Get.GetInterface().Commit(); if (rc.IsFailure()) return rc; } @@ -826,10 +826,10 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (attribute.StaticSaveDataId != SaveData.SaveIndexerId) { // Mark the save data as being successfully created - rc = accessor.Get.Indexer.SetState(saveDataId, SaveDataState.Normal); + rc = accessor.Get.GetInterface().SetState(saveDataId, SaveDataState.Normal); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.Commit(); + rc = accessor.Get.GetInterface().Commit(); if (rc.IsFailure()) return rc; } @@ -845,12 +845,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (accessorInitialized && saveDataId != SaveData.SaveIndexerId) { - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId) { - accessor.Get.Indexer.Delete(saveDataId).IgnoreResult(); - accessor.Get.Indexer.Commit().IgnoreResult(); + accessor.Get.GetInterface().Delete(saveDataId).IgnoreResult(); + accessor.Get.GetInterface().Commit().IgnoreResult(); } } } @@ -867,7 +867,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue value, in attribute); + rc = accessor.Get.GetInterface().Get(out SaveDataIndexerValue value, in attribute); if (rc.IsFailure()) return rc; SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value); @@ -1007,7 +1007,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue indexerValue, in attribute); + rc = accessor.Get.GetInterface().Get(out SaveDataIndexerValue indexerValue, in attribute); if (rc.IsFailure()) return rc; if (indexerValue.SpaceId != ConvertToRealSpaceId(spaceId)) @@ -1062,7 +1062,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (rc.IsFailure()) return rc; // Check the space ID of the save data - rc = accessor.Get.Indexer.Get(out SaveDataIndexerValue value, in key); + rc = accessor.Get.GetInterface().Get(out SaveDataIndexerValue value, in key); if (rc.IsFailure()) return rc; if (value.SpaceId != ConvertToRealSpaceId(spaceId)) @@ -1070,8 +1070,8 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave } // Remove the indexer entry. Nintendo ignores these results - accessor.Get.Indexer.Delete(tempSaveDataId).IgnoreResult(); - accessor.Get.Indexer.Commit().IgnoreResult(); + accessor.Get.GetInterface().Delete(tempSaveDataId).IgnoreResult(); + accessor.Get.GetInterface().Commit().IgnoreResult(); return Result.Success; } @@ -1275,7 +1275,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); if (rc.IsFailure()) return rc; using Path saveDataRootPath = _saveDataRootPath.DangerousGetPath(); @@ -1311,12 +1311,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave if (rc.IsFailure()) return rc; } - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsFailure()) return rc; resolvedSpaceId = value.SpaceId; - rc = accessor.Get.Indexer.GetKey(out key, saveDataId); + rc = accessor.Get.GetInterface().GetKey(out key, saveDataId); if (rc.IsFailure()) return rc; } else @@ -1326,12 +1326,12 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + rc = accessor.Get.GetInterface().GetValue(out SaveDataIndexerValue value, saveDataId); if (rc.IsFailure()) return rc; resolvedSpaceId = value.SpaceId; - rc = accessor.Get.Indexer.GetKey(out key, saveDataId); + rc = accessor.Get.GetInterface().GetKey(out key, saveDataId); if (rc.IsFailure()) return rc; } @@ -1462,7 +1462,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + rc = accessor.Get.GetInterface().GetKey(out SaveDataAttribute key, saveDataId); if (rc.IsFailure()) return rc; Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, @@ -1554,7 +1554,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), SaveDataSpaceId.System); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; } @@ -1583,7 +1583,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var reader = new SharedRef(); - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; var filter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), programId: default, @@ -1620,7 +1620,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave using var reader = new SharedRef(); - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); @@ -1644,7 +1644,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; using var filterReader = @@ -1762,7 +1762,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), spaceId); if (rc.IsFailure()) return rc; - rc = accessor.Get.Indexer.OpenSaveDataInfoReader(ref reader.Ref()); + rc = accessor.Get.GetInterface().OpenSaveDataInfoReader(ref reader.Ref()); if (rc.IsFailure()) return rc; ProgramId resolvedProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 5a6811da..73d83e9f 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -803,7 +803,7 @@ public class SaveDataFileSystemServiceImpl Result rc = OpenSaveDataIndexerAccessor(ref accessor.Ref(), out bool _, SaveDataSpaceId.User); if (rc.IsFailure()) return rc.Miss(); - count = accessor.Get.Indexer.GetIndexCount(); + count = accessor.Get.GetInterface().GetIndexCount(); return Result.Success; } diff --git a/src/LibHac/FsSrv/SaveDataIndexer.cs b/src/LibHac/FsSrv/SaveDataIndexer.cs index a8dc3913..ccc6ee27 100644 --- a/src/LibHac/FsSrv/SaveDataIndexer.cs +++ b/src/LibHac/FsSrv/SaveDataIndexer.cs @@ -1,16 +1,18 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Common.FixedArrays; using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Fs.Shim; using LibHac.Kvdb; +using LibHac.Os; using LibHac.Sf; +using LibHac.Util; +using SaveData = LibHac.Fs.SaveData; namespace LibHac.FsSrv; @@ -22,7 +24,7 @@ namespace LibHac.FsSrv; /// Each manages one to two save data spaces. /// Each save data space is identified by a , /// and has its own unique storage location on disk. -///
Based on FS 10.0.0 (nnSdk 10.4.0) +/// Based on FS 13.1.0 (nnSdk 13.4.0) /// public class SaveDataIndexer : ISaveDataIndexer { @@ -47,40 +49,200 @@ public class SaveDataIndexer : ISaveDataIndexer private delegate void SaveDataValueTransform(ref SaveDataIndexerValue value, ReadOnlySpan updateData); - private FileSystemClient FsClient { get; } - private U8String MountName { get; } - private ulong SaveDataId { get; } - private SaveDataSpaceId SpaceId { get; } - private MemoryResource MemoryResource { get; } - private MemoryResource BufferMemoryResource { get; } - - private FlatMapKeyValueStore KvDatabase { get; } - - private object Locker { get; } = new object(); - private bool IsInitialized { get; set; } - private bool IsKvdbLoaded { get; set; } - private ulong _lastPublishedId; - private int Handle { get; set; } - - private List OpenReaders { get; } = new List(); - - public SaveDataIndexer(FileSystemClient fsClient, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId, MemoryResource memoryResource) + /// + /// Mounts the storage for a , and unmounts the storage + /// when the is disposed; + /// + /// Based on FS 13.1.0 (nnSdk 13.4.0) + [NonCopyableDisposable] + private ref struct ScopedMount { - FsClient = fsClient; - SaveDataId = saveDataId; - SpaceId = spaceId; - MemoryResource = memoryResource; + private Array16 _mountName; + private bool _isMounted; - // note: FS uses a separate PooledBufferMemoryResource here - BufferMemoryResource = memoryResource; + // LibHac addition + private FileSystemClient _fsClient; - KvDatabase = new FlatMapKeyValueStore(); + public ScopedMount(FileSystemClient fsClient) + { + _mountName = default; + _isMounted = false; + _fsClient = fsClient; + } - IsInitialized = false; - IsKvdbLoaded = false; - Handle = 1; + public void Dispose() + { + if (_isMounted) + { + _fsClient.Unmount(new U8Span(_mountName)); + _isMounted = false; + } + } - MountName = mountName.ToU8String(); + public Result Mount(ReadOnlySpan mountName, SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequires(!_isMounted); + + int mountNameLength = StringUtils.Strlcpy(_mountName.Items, mountName, Array16.Length); + Assert.SdkLess(mountNameLength, Array16.Length); + + _fsClient.DisableAutoSaveDataCreation(); + + Result rc = _fsClient.MountSystemSaveData(new U8Span(_mountName), spaceId, saveDataId); + + if (rc.IsFailure()) + { + if (ResultFs.TargetNotFound.Includes(rc)) + { + rc = _fsClient.CreateSystemSaveData(spaceId, saveDataId, 0, SaveDataAvailableSize, + SaveDataJournalSize, 0); + if (rc.IsFailure()) return rc; + + rc = _fsClient.MountSystemSaveData(new U8Span(_mountName), spaceId, saveDataId); + if (rc.IsFailure()) return rc; + } + else if (ResultFs.SignedSystemPartitionDataCorrupted.Includes(rc)) + { + return rc; + } + else if (ResultFs.DataCorrupted.Includes(rc)) + { + if (spaceId == SaveDataSpaceId.SdSystem) + return rc; + + rc = _fsClient.DeleteSaveData(spaceId, saveDataId); + if (rc.IsFailure()) return rc; + + rc = _fsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); + if (rc.IsFailure()) return rc; + + rc = _fsClient.MountSystemSaveData(new U8Span(_mountName), spaceId, saveDataId); + if (rc.IsFailure()) return rc; + } + else + { + return rc; + } + } + + _isMounted = true; + return Result.Success; + } + } + + /// + /// Iterates through all the save data indexed in a . + /// + /// Based on FS 13.1.0 (nnSdk 13.4.0) + private class Reader : SaveDataInfoReaderImpl + { + private readonly SaveDataIndexer _indexer; + private FlatMapKeyValueStore.Iterator _iterator; + private readonly int _handle; + + public Reader(SaveDataIndexer indexer) + { + _indexer = indexer; + _iterator = indexer.GetBeginIterator(); + _handle = indexer.GetHandle(); + } + + public void Dispose() + { + _indexer.UnregisterReader(); + } + + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) + { + UnsafeHelpers.SkipParamInit(out readCount); + + using UniqueLockRef scopedLock = _indexer.GetScopedLock(); + + // Indexer has been reloaded since this info reader was created + if (_handle != _indexer.GetHandle()) + return ResultFs.InvalidHandle.Log(); + + Span outInfos = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); + + int count; + for (count = 0; !_iterator.IsEnd() && count < outInfos.Length; count++) + { + ref SaveDataAttribute key = ref _iterator.Get().Key; + ref SaveDataIndexerValue value = ref _iterator.GetValue(); + + GenerateSaveDataInfo(out outInfos[count], in key, in value); + + _iterator.Next(); + } + + readCount = count; + return Result.Success; + } + + public void Fix(in SaveDataAttribute key) + { + if (_handle == _indexer.GetHandle()) + _indexer.FixIterator(ref _iterator, in key); + } + } + + private class ReaderAccessor : IDisposable + { + private WeakRef _reader; + + public ReaderAccessor(in SharedRef reader) + { + _reader = new WeakRef(in reader); + } + + public void Dispose() => _reader.Destroy(); + public bool IsExpired() => _reader.Expired; + public SharedRef Lock() => _reader.Lock(); + } + + private Array48 _mountName; + private ulong _indexerSaveDataId; + private SaveDataSpaceId _spaceId; + private MemoryResource _memoryResource; + private MemoryResource _bufferMemoryResource; + private FlatMapKeyValueStore _kvDatabase; + private SdkMutexType _mutex; + private bool _isInitialized; + private bool _isLoaded; + private ulong _lastPublishedId; + private int _handle; + private LinkedList _openReaders; + private bool _isDelayedReaderUnregistrationRequired; + + // LibHac addition + private FileSystemClient _fsClient; + + public SaveDataIndexer(FileSystemClient fsClient, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId, + MemoryResource memoryResource) + { + _indexerSaveDataId = saveDataId; + _spaceId = spaceId; + _memoryResource = memoryResource; + + // Todo: FS uses a separate PooledBufferMemoryResource here + _bufferMemoryResource = memoryResource; + _kvDatabase = new FlatMapKeyValueStore(); + _mutex = new SdkMutexType(); + _isInitialized = false; + _isLoaded = false; + _handle = 1; + _openReaders = new LinkedList(); + _isDelayedReaderUnregistrationRequired = false; + StringUtils.Copy(_mountName.Items, mountName); + + _fsClient = fsClient; + } + + public void Dispose() + { + Assert.SdkRequires(!_isDelayedReaderUnregistrationRequired); + + _kvDatabase?.Dispose(); } private static void MakeLastPublishedIdSaveFilePath(Span buffer, ReadOnlySpan mountName) @@ -91,7 +253,7 @@ public class SaveDataIndexer : ISaveDataIndexer sb.Append(MountDelimiter); sb.Append(LastPublishedIdFileName); - Debug.Assert(!sb.Overflowed); + Assert.SdkAssert(!sb.Overflowed); } private static void MakeRootPath(Span buffer, ReadOnlySpan mountName) @@ -101,7 +263,7 @@ public class SaveDataIndexer : ISaveDataIndexer sb.Append(mountName); sb.Append(MountDelimiter); - Debug.Assert(!sb.Overflowed); + Assert.SdkAssert(!sb.Overflowed); } /// @@ -110,108 +272,128 @@ public class SaveDataIndexer : ISaveDataIndexer /// When this method returns, contains the generated . /// The key used to generate the . /// The value used to generate the . - public static void GenerateSaveDataInfo(out SaveDataInfo info, in SaveDataAttribute key, in SaveDataIndexerValue value) + public static void GenerateSaveDataInfo(out SaveDataInfo info, in SaveDataAttribute key, + in SaveDataIndexerValue value) { info = new SaveDataInfo { SaveDataId = value.SaveDataId, SpaceId = value.SpaceId, - Type = key.Type, - UserId = key.UserId, + Size = value.Size, + State = value.State, StaticSaveDataId = key.StaticSaveDataId, ProgramId = key.ProgramId, - Size = value.Size, + Type = key.Type, + UserId = key.UserId, Index = key.Index, - Rank = key.Rank, - State = value.State + Rank = key.Rank }; } - public Result Commit() + public UniqueLockRef GetScopedLock() { - lock (Locker) + return new UniqueLockRef(ref _mutex); + } + + /// + /// Initializes and ensures that the indexer's save data is created. + /// Does nothing if this has already been initialized. + /// + /// The of the operation. + private Result TryInitializeDatabase() + { + if (_isInitialized) + return Result.Success; + + using var scopedMount = new ScopedMount(_fsClient); + + Result rc = scopedMount.Mount(_mountName, _spaceId, _indexerSaveDataId); + if (rc.IsFailure()) return rc; + + Span rootPath = stackalloc byte[MaxPathLength]; + MakeRootPath(rootPath, _mountName); + + rc = _kvDatabase.Initialize(_fsClient, new U8Span(rootPath), KvDatabaseCapacity, _memoryResource, _bufferMemoryResource); + if (rc.IsFailure()) return rc; + + _isInitialized = true; + return Result.Success; + } + + /// + /// Ensures that the database file exists and loads any existing entries. + /// Does nothing if the database has already been loaded and is . + /// + /// If , forces the database to be reloaded, + /// even it it was already loaded previously. + /// The of the operation. + private Result TryLoadDatabase(bool forceLoad) + { + if (forceLoad) + _isLoaded = false; + + if (_isLoaded) + return Result.Success; + + using var scopedMount = new ScopedMount(_fsClient); + Result rc = scopedMount.Mount(_mountName, _spaceId, _indexerSaveDataId); + if (rc.IsFailure()) return rc; + + rc = _kvDatabase.Load(); + if (rc.IsFailure()) return rc; + + bool createdNewFile = false; + + Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; + MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, _mountName); + + try { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; + rc = _fsClient.OpenFile(out FileHandle file, new U8Span(lastPublishedIdPath), OpenMode.Read); - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; + // Create the last published ID file if it doesn't exist. + if (rc.IsFailure()) + { + if (!ResultFs.PathNotFound.Includes(rc)) return rc; - var mount = new Mounter(); + rc = _fsClient.CreateFile(new U8Span(lastPublishedIdPath), LastPublishedIdFileSize); + if (rc.IsFailure()) return rc; + + rc = _fsClient.OpenFile(out file, new U8Span(lastPublishedIdPath), OpenMode.Read); + if (rc.IsFailure()) return rc; + + createdNewFile = true; + } try { - rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); - if (rc.IsFailure()) return rc; - - rc = KvDatabase.Save(); - if (rc.IsFailure()) return rc; - - Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; - MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, MountName); - - rc = FsClient.OpenFile(out FileHandle handle, new U8Span(lastPublishedIdPath), OpenMode.Write); - if (rc.IsFailure()) return rc; - - bool isFileClosed = false; - - try + // If we had to create the file earlier, we don't need to load the value again. + if (!createdNewFile) { - rc = FsClient.WriteFile(handle, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId), WriteOption.None); + rc = _fsClient.ReadFile(file, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId)); if (rc.IsFailure()) return rc; - - rc = FsClient.FlushFile(handle); - if (rc.IsFailure()) return rc; - - FsClient.CloseFile(handle); - isFileClosed = true; - - return FsClient.Commit(MountName); } - finally + else { - if (!isFileClosed) - { - FsClient.CloseFile(handle); - } + _lastPublishedId = 0; } + + _isLoaded = true; + return Result.Success; } finally { - mount.Dispose(); + _fsClient.CloseFile(file); } } - } - - public Result Rollback() - { - lock (Locker) + finally { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(forceLoad: true); - if (rc.IsFailure()) return rc; - - UpdateHandle(); - return Result.Success; - } - } - - public Result Reset() - { - lock (Locker) - { - IsKvdbLoaded = false; - - Result rc = FsClient.DeleteSaveData(SaveDataId); - - if (rc.IsSuccess() || ResultFs.TargetNotFound.Includes(rc)) + // The save data needs to be committed if we created the last published ID file. + if (createdNewFile) { - UpdateHandle(); + // Nintendo does not check this return value. + _fsClient.CommitSaveData(new U8Span(_mountName)).IgnoreResult(); } - - return rc; } } @@ -219,224 +401,278 @@ public class SaveDataIndexer : ISaveDataIndexer { UnsafeHelpers.SkipParamInit(out saveDataId); - lock (Locker) + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; + + Assert.SdkRequires(_isLoaded); + + // Make sure the key isn't in the database already. + SaveDataIndexerValue value = default; + rc = _kvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); + + if (rc.IsSuccess()) { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; + return ResultFs.AlreadyExists.Log(); + } - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; + // Get the next save data ID and write the new key/value to the database + _lastPublishedId++; + ulong newSaveDataId = _lastPublishedId; - Unsafe.SkipInit(out SaveDataIndexerValue value); + value = new SaveDataIndexerValue { SaveDataId = newSaveDataId }; + rc = _kvDatabase.Set(in key, SpanHelpers.AsByteSpan(ref value)); - // Make sure the key isn't in the database already. - rc = KvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); + if (rc.IsFailure()) + { + _lastPublishedId--; + return rc; + } - if (rc.IsSuccess()) + rc = FixReader(in key); + if (rc.IsFailure()) return rc; + + saveDataId = value.SaveDataId; + return Result.Success; + } + + public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; + + Assert.SdkRequires(_isLoaded); + Assert.SdkRequires(key.StaticSaveDataId != SaveData.InvalidSystemSaveDataId); + Assert.SdkRequires(key.UserId == SaveData.InvalidUserId); + + // Iterate through all existing values to check if the save ID is already in use. + FlatMapKeyValueStore.Iterator iterator = _kvDatabase.GetBeginIterator(); + while (!iterator.IsEnd()) + { + if (iterator.GetValue().SaveDataId == key.StaticSaveDataId) { return ResultFs.AlreadyExists.Log(); } - _lastPublishedId++; - ulong newSaveDataId = _lastPublishedId; + iterator.Next(); + } - value = new SaveDataIndexerValue { SaveDataId = newSaveDataId }; + var value = new SaveDataIndexerValue { SaveDataId = key.StaticSaveDataId }; - rc = KvDatabase.Set(in key, SpanHelpers.AsByteSpan(ref value)); + rc = _kvDatabase.Set(in key, SpanHelpers.AsReadOnlyByteSpan(in value)); + if (rc.IsFailure()) return rc; - if (rc.IsFailure()) - { - _lastPublishedId--; - return rc; - } + rc = FixReader(in key); + if (rc.IsFailure()) return rc; - rc = FixReader(in key); + return Result.Success; + } + + public bool IsRemainedReservedOnly() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return _kvDatabase.Count >= KvDatabaseCapacity - KvDatabaseReservedEntryCount; + } + + public Result Delete(ulong saveDataId) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; + + Assert.SdkRequires(_isLoaded); + + FlatMapKeyValueStore.Iterator iterator = _kvDatabase.GetBeginIterator(); + + while (true) + { + if (iterator.IsEnd()) + return ResultFs.TargetNotFound.Log(); + + if (iterator.GetValue().SaveDataId == saveDataId) + break; + + iterator.Next(); + } + + SaveDataAttribute key = iterator.Get().Key; + + rc = _kvDatabase.Delete(in key); + if (rc.IsFailure()) return rc; + + rc = FixReader(in key); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + public Result Commit() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + // Make sure we've loaded the database + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; + + Assert.SdkRequires(_isLoaded); + + // Mount the indexer's save data + using var scopedMount = new ScopedMount(_fsClient); + rc = scopedMount.Mount(_mountName, _spaceId, _indexerSaveDataId); + if (rc.IsFailure()) return rc; + + // Save the actual database + rc = _kvDatabase.Save(); + if (rc.IsFailure()) return rc; + + // Save the last published save data ID + Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; + MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, _mountName); + + rc = _fsClient.OpenFile(out FileHandle file, new U8Span(lastPublishedIdPath), OpenMode.Write); + if (rc.IsFailure()) return rc; + + bool isFileClosed = false; + + try + { + rc = _fsClient.WriteFile(file, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId), WriteOption.None); if (rc.IsFailure()) return rc; - saveDataId = newSaveDataId; - return Result.Success; + rc = _fsClient.FlushFile(file); + if (rc.IsFailure()) return rc; + + _fsClient.CloseFile(file); + isFileClosed = true; + + return _fsClient.CommitSaveData(new U8Span(_mountName)); } + finally + { + if (!isFileClosed) + { + _fsClient.CloseFile(file); + } + } + } + + public Result Rollback() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: true); + if (rc.IsFailure()) return rc; + + UpdateHandle(); + return Result.Success; + } + + public Result Reset() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (_isLoaded) + _isLoaded = false; + + Result rc = _fsClient.DeleteSaveData(_indexerSaveDataId); + + if (rc.IsSuccess() || ResultFs.TargetNotFound.Includes(rc)) + { + UpdateHandle(); + } + + return rc; } public Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key) { UnsafeHelpers.SkipParamInit(out value); - lock (Locker) + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; + + Assert.SdkRequires(_isLoaded); + + rc = _kvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); + + if (rc.IsFailure()) { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - rc = KvDatabase.Get(out _, in key, SpanHelpers.AsByteSpan(ref value)); - - if (rc.IsFailure()) - { - return ResultFs.TargetNotFound.LogConverted(rc); - } - - return Result.Success; + return ResultFs.TargetNotFound.LogConverted(rc); } - } - public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - // Iterate through all existing values to check if the save ID is already in use. - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - while (!iterator.IsEnd()) - { - if (iterator.GetValue().SaveDataId == key.StaticSaveDataId) - { - return ResultFs.AlreadyExists.Log(); - } - - iterator.Next(); - } - - var newValue = new SaveDataIndexerValue - { - SaveDataId = key.StaticSaveDataId - }; - - rc = KvDatabase.Set(in key, SpanHelpers.AsReadOnlyByteSpan(in newValue)); - if (rc.IsFailure()) return rc; - - rc = FixReader(in key); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - } - - public bool IsRemainedReservedOnly() - { - return KvDatabase.Count >= KvDatabaseCapacity - KvDatabaseReservedEntryCount; - } - - public Result Delete(ulong saveDataId) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - - while (true) - { - if (iterator.IsEnd()) - return ResultFs.TargetNotFound.Log(); - - if (iterator.GetValue().SaveDataId == saveDataId) - break; - - iterator.Next(); - } - - SaveDataAttribute key = iterator.Get().Key; - - rc = KvDatabase.Delete(in key); - if (rc.IsFailure()) return rc; - - rc = FixReader(in key); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - } - - private Result UpdateValueBySaveDataId(ulong saveDataId, SaveDataValueTransform func, ReadOnlySpan data) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - SaveDataIndexerValue value; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); - - while (true) - { - if (iterator.IsEnd()) - return ResultFs.TargetNotFound.Log(); - - ref SaveDataIndexerValue val = ref iterator.GetValue(); - - if (val.SaveDataId == saveDataId) - { - value = val; - break; - } - - iterator.Next(); - } - - func(ref value, data); - - rc = KvDatabase.Set(in iterator.Get().Key, SpanHelpers.AsReadOnlyByteSpan(in value)); - if (rc.IsFailure()) return rc; - - return Result.Success; - } + return Result.Success; } public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId) { - return UpdateValueBySaveDataId(saveDataId, SetSpaceIdImpl, SpanHelpers.AsReadOnlyByteSpan(in spaceId)); - - static void SetSpaceIdImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) - { - value.SpaceId = (SaveDataSpaceId)updateData[0]; - } + return UpdateValueBySaveDataId(saveDataId, + static (ref SaveDataIndexerValue value, ReadOnlySpan data) => + { + value.SpaceId = (SaveDataSpaceId)data[0]; + }, + SpanHelpers.AsReadOnlyByteSpan(in spaceId)); } public Result SetSize(ulong saveDataId, long size) { - return UpdateValueBySaveDataId(saveDataId, SetSizeImpl, SpanHelpers.AsReadOnlyByteSpan(in size)); - - static void SetSizeImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) - { - value.Size = BinaryPrimitives.ReadInt64LittleEndian(updateData); - } + return UpdateValueBySaveDataId(saveDataId, + static (ref SaveDataIndexerValue value, ReadOnlySpan data) => + { + value.Size = BinaryPrimitives.ReadInt64LittleEndian(data); + }, + SpanHelpers.AsReadOnlyByteSpan(in size)); } public Result SetState(ulong saveDataId, SaveDataState state) { - return UpdateValueBySaveDataId(saveDataId, SetSizeImpl, SpanHelpers.AsReadOnlyByteSpan(in state)); - - static void SetSizeImpl(ref SaveDataIndexerValue value, ReadOnlySpan updateData) - { - value.State = (SaveDataState)updateData[0]; - } + return UpdateValueBySaveDataId(saveDataId, + static (ref SaveDataIndexerValue value, ReadOnlySpan data) => + { + value.State = (SaveDataState)data[0]; + }, + SpanHelpers.AsReadOnlyByteSpan(in state)); } public Result GetKey(out SaveDataAttribute key, ulong saveDataId) { UnsafeHelpers.SkipParamInit(out key); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + Result rc = TryInitializeDatabase(); if (rc.IsFailure()) return rc; - rc = TryLoadDatabase(false); + rc = TryLoadDatabase(forceLoad: false); if (rc.IsFailure()) return rc; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); + Assert.SdkRequires(_isLoaded); + + FlatMapKeyValueStore.Iterator iterator = _kvDatabase.GetBeginIterator(); while (!iterator.IsEnd()) { @@ -456,13 +692,17 @@ public class SaveDataIndexer : ISaveDataIndexer { UnsafeHelpers.SkipParamInit(out value); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + Result rc = TryInitializeDatabase(); if (rc.IsFailure()) return rc; - rc = TryLoadDatabase(false); + rc = TryLoadDatabase(forceLoad: false); if (rc.IsFailure()) return rc; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetBeginIterator(); + Assert.SdkRequires(_isLoaded); + + FlatMapKeyValueStore.Iterator iterator = _kvDatabase.GetBeginIterator(); while (!iterator.IsEnd()) { @@ -482,13 +722,17 @@ public class SaveDataIndexer : ISaveDataIndexer public Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value) { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + Result rc = TryInitializeDatabase(); if (rc.IsFailure()) return rc; - rc = TryLoadDatabase(false); + rc = TryLoadDatabase(forceLoad: false); if (rc.IsFailure()) return rc; - FlatMapKeyValueStore.Iterator iterator = KvDatabase.GetLowerBoundIterator(in key); + Assert.SdkRequires(_isLoaded); + + FlatMapKeyValueStore.Iterator iterator = _kvDatabase.GetLowerBoundIterator(in key); // Key was not found if (iterator.IsEnd()) @@ -500,197 +744,58 @@ public class SaveDataIndexer : ISaveDataIndexer public int GetIndexCount() { - lock (Locker) - { - return KvDatabase.Count; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return _kvDatabase.Count; } - public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) - { - lock (Locker) - { - Result rc = TryInitializeDatabase(); - if (rc.IsFailure()) return rc; - - rc = TryLoadDatabase(false); - if (rc.IsFailure()) return rc; - - // Create the reader and register it in the opened-reader list - using var reader = new SharedRef(new Reader(this)); - rc = RegisterReader(ref reader.Ref()); - if (rc.IsFailure()) return rc; - - outInfoReader.SetByCopy(in reader.Ref()); - return Result.Success; - } - } - - private FlatMapKeyValueStore.Iterator GetBeginIterator() - { - Assert.SdkRequires(IsKvdbLoaded); - - return KvDatabase.GetBeginIterator(); - } - - private void FixIterator(ref FlatMapKeyValueStore.Iterator iterator, - in SaveDataAttribute key) - { - KvDatabase.FixIterator(ref iterator, in key); - } - - /// - /// Initializes and ensures that the indexer's save data is created. - /// Does nothing if this has already been initialized. - /// - /// The of the operation. - private Result TryInitializeDatabase() - { - if (IsInitialized) return Result.Success; - - var mount = new Mounter(); - - try - { - Result rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); - if (rc.IsFailure()) return rc; - - Span rootPath = stackalloc byte[MaxPathLength]; - MakeRootPath(rootPath, MountName); - - rc = KvDatabase.Initialize(FsClient, new U8Span(rootPath), KvDatabaseCapacity, MemoryResource, BufferMemoryResource); - if (rc.IsFailure()) return rc; - - IsInitialized = true; - return Result.Success; - } - finally - { - mount.Dispose(); - } - } - - /// - /// Ensures that the database file exists and loads any existing entries. - /// Does nothing if the database has already been loaded and is . - /// - /// If , forces the database to be reloaded, - /// even it it was already loaded previously. - /// The of the operation. - private Result TryLoadDatabase(bool forceLoad) - { - if (forceLoad) - { - IsKvdbLoaded = false; - } - else if (IsKvdbLoaded) - { - return Result.Success; - } - - var mount = new Mounter(); - - try - { - Result rc = mount.Mount(FsClient, MountName, SpaceId, SaveDataId); - if (rc.IsFailure()) return rc; - - rc = KvDatabase.Load(); - if (rc.IsFailure()) return rc; - - bool createdNewFile = false; - - Span lastPublishedIdPath = stackalloc byte[MaxPathLength]; - MakeLastPublishedIdSaveFilePath(lastPublishedIdPath, MountName); - - try - { - rc = FsClient.OpenFile(out FileHandle handle, new U8Span(lastPublishedIdPath), OpenMode.Read); - - // Create the last published ID file if it doesn't exist. - if (rc.IsFailure()) - { - if (!ResultFs.PathNotFound.Includes(rc)) return rc; - - rc = FsClient.CreateFile(new U8Span(lastPublishedIdPath), LastPublishedIdFileSize); - if (rc.IsFailure()) return rc; - - rc = FsClient.OpenFile(out handle, new U8Span(lastPublishedIdPath), OpenMode.Read); - if (rc.IsFailure()) return rc; - - createdNewFile = true; - - _lastPublishedId = 0; - IsKvdbLoaded = true; - } - - try - { - // If we had to create the file earlier, we don't need to load the value again. - if (!createdNewFile) - { - rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref _lastPublishedId)); - if (rc.IsFailure()) return rc; - - IsKvdbLoaded = true; - } - - return Result.Success; - } - finally - { - FsClient.CloseFile(handle); - } - } - finally - { - // The save data needs to be committed if we created the last published ID file. - if (createdNewFile) - { - // Note: Nintendo does not check this return value, probably because it's in a scope-exit block. - FsClient.Commit(MountName).IgnoreResult(); - } - } - } - finally - { - mount.Dispose(); - } - } - - private void UpdateHandle() => Handle++; - /// /// Adds a to the list of registered readers. /// /// The reader to add. /// The of the operation. - private Result RegisterReader(ref SharedRef reader) + private Result RegisterReader(in SharedRef reader) { - OpenReaders.Add(new ReaderAccessor(ref reader)); + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + _openReaders.AddLast(new ReaderAccessor(in reader)); return Result.Success; } + public void UnregisterReader() + { + if (_mutex.IsLockedByCurrentThread()) + { + _isDelayedReaderUnregistrationRequired = true; + } + else + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + UnregisterReaderImpl(); + } + } + /// /// Removes any s that are no longer in use from the registered readers. /// - private void UnregisterReader() + private void UnregisterReaderImpl() { - int i = 0; - List readers = OpenReaders; + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - while (i < readers.Count) + _isDelayedReaderUnregistrationRequired = false; + + LinkedListNode node = _openReaders.First; + + while (node is not null) { - // Remove the reader if there are no references to it. There is no need to increment - // i in this case because the next reader in the list will be shifted to index i - if (readers[i].IsExpired()) + // Grab the next node so we can continue iterating if we need to delete the current node + LinkedListNode currentNode = node; + node = node.Next; + + if (currentNode.Value.IsExpired()) { - readers.RemoveAt(i); - } - else - { - i++; + _openReaders.Remove(currentNode); } } } @@ -704,7 +809,10 @@ public class SaveDataIndexer : ISaveDataIndexer /// The of the operation. private Result FixReader(in SaveDataAttribute key) { - foreach (ReaderAccessor accessor in OpenReaders) + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + Assert.SdkRequires(!_isDelayedReaderUnregistrationRequired); + + foreach (ReaderAccessor accessor in _openReaders) { using SharedRef reader = accessor.Lock(); @@ -714,145 +822,101 @@ public class SaveDataIndexer : ISaveDataIndexer } } + if (_isDelayedReaderUnregistrationRequired) + { + UnregisterReaderImpl(); + Assert.SdkRequires(!_isDelayedReaderUnregistrationRequired); + } + return Result.Success; } - /// - /// Mounts the storage for a , and unmounts the storage - /// when the is disposed; - /// - private ref struct Mounter + public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) { - private FileSystemClient FsClient { get; set; } - private U8String MountName { get; set; } - private bool IsMounted { get; set; } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - public Result Mount(FileSystemClient fsClient, U8String mountName, SaveDataSpaceId spaceId, - ulong saveDataId) - { - FsClient = fsClient; - MountName = mountName; + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; - FsClient.DisableAutoSaveDataCreation(); + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; - Result rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); + // Create the reader and register it in the opened-reader list + using var reader = new SharedRef(new Reader(this)); + rc = RegisterReader(in reader); + if (rc.IsFailure()) return rc; - if (rc.IsFailure()) - { - if (ResultFs.TargetNotFound.Includes(rc)) - { - rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, SaveDataAvailableSize, SaveDataJournalSize, 0); - if (rc.IsFailure()) return rc; - - rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); - if (rc.IsFailure()) return rc; - } - else - { - if (ResultFs.SignedSystemPartitionDataCorrupted.Includes(rc)) return rc; - if (!ResultFs.DataCorrupted.Includes(rc)) return rc; - - if (spaceId == SaveDataSpaceId.SdSystem) return rc; - - rc = FsClient.DeleteSaveData(spaceId, saveDataId); - if (rc.IsFailure()) return rc; - - rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); - if (rc.IsFailure()) return rc; - - rc = FsClient.MountSystemSaveData(MountName, spaceId, saveDataId); - if (rc.IsFailure()) return rc; - } - } - - IsMounted = true; - - return Result.Success; - } - - public void Dispose() - { - if (IsMounted) - { - FsClient.Unmount(MountName); - } - } + outInfoReader.SetByMove(ref reader.Ref()); + return Result.Success; } - private class ReaderAccessor : IDisposable + private void FixIterator(ref FlatMapKeyValueStore.Iterator iterator, in SaveDataAttribute key) { - private WeakRef _reader; + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - public ReaderAccessor(ref SharedRef reader) - { - _reader = new WeakRef(in reader); - } - - public void Dispose() => _reader.Destroy(); - public SharedRef Lock() => _reader.Lock(); - public bool IsExpired() => _reader.Expired; + _kvDatabase.FixIterator(ref iterator, in key); } - private class Reader : SaveDataInfoReaderImpl + private FlatMapKeyValueStore.Iterator GetBeginIterator() { - private readonly SaveDataIndexer _indexer; - private FlatMapKeyValueStore.Iterator _iterator; - private readonly int _handle; + Assert.SdkRequires(_isLoaded); + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); - public Reader(SaveDataIndexer indexer) + return _kvDatabase.GetBeginIterator(); + } + + public int GetHandle() + { + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + return _handle; + } + + private void UpdateHandle() + { + Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); + + _handle++; + } + + private Result UpdateValueBySaveDataId(ulong saveDataId, SaveDataValueTransform func, ReadOnlySpan data) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Result rc = TryInitializeDatabase(); + if (rc.IsFailure()) return rc; + + rc = TryLoadDatabase(forceLoad: false); + if (rc.IsFailure()) return rc; + + Assert.SdkRequires(_isLoaded); + + SaveDataIndexerValue value; + FlatMapKeyValueStore.Iterator iterator = _kvDatabase.GetBeginIterator(); + + // Find the save with the specified ID + while (true) { - _indexer = indexer; - _handle = indexer.Handle; + if (iterator.IsEnd()) + return ResultFs.TargetNotFound.Log(); - _iterator = indexer.GetBeginIterator(); - } + ref SaveDataIndexerValue val = ref iterator.GetValue(); - public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) - { - UnsafeHelpers.SkipParamInit(out readCount); - - lock (_indexer.Locker) + if (val.SaveDataId == saveDataId) { - // Indexer has been reloaded since this info reader was created - if (_handle != _indexer.Handle) - { - return ResultFs.InvalidHandle.Log(); - } - - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); - - int i; - for (i = 0; !_iterator.IsEnd() && i < outInfo.Length; i++) - { - ref SaveDataAttribute key = ref _iterator.Get().Key; - ref SaveDataIndexerValue value = ref _iterator.GetValue(); - - GenerateSaveDataInfo(out outInfo[i], in key, in value); - - _iterator.Next(); - } - - readCount = i; - return Result.Success; + value = val; + break; } + + iterator.Next(); } - public void Fix(in SaveDataAttribute attribute) - { - _indexer.FixIterator(ref _iterator, in attribute); - } + // Run the function on the save data's indexer value and update the value in the database + func(ref value, data); - public void Dispose() - { - lock (_indexer.Locker) - { - _indexer.UnregisterReader(); - } - } + rc = _kvDatabase.Set(in iterator.Get().Key, SpanHelpers.AsReadOnlyByteSpan(in value)); + if (rc.IsFailure()) return rc; + + return Result.Success; } - - public void Dispose() - { - KvDatabase?.Dispose(); - } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataIndexerLite.cs b/src/LibHac/FsSrv/SaveDataIndexerLite.cs index 717771a0..887351c1 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerLite.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerLite.cs @@ -1,11 +1,66 @@ -using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; +using LibHac.Os; using LibHac.Sf; +using LibHac.Util; namespace LibHac.FsSrv; +/// +/// Iterates through all the save data indexed in a . +/// +/// Based on FS 13.1.0 (nnSdk 13.4.0) +internal class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl +{ + private bool _finishedIterating; + private SaveDataInfo _info; + + public SaveDataIndexerLiteInfoReader() + { + _finishedIterating = true; + _info = default; + } + + public void Dispose() { } + + public SaveDataIndexerLiteInfoReader(in SaveDataAttribute key, in SaveDataIndexerValue value) + { + _finishedIterating = false; + _info = default; + + // Don't set the State, Index, or Rank of the returned SaveDataInfo + _info.SaveDataId = value.SaveDataId; + _info.SpaceId = value.SpaceId; + _info.Size = value.Size; + _info.StaticSaveDataId = key.StaticSaveDataId; + _info.ProgramId = key.ProgramId; + _info.Type = key.Type; + _info.UserId = key.UserId; + } + + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) + { + UnsafeHelpers.SkipParamInit(out readCount); + + if (_finishedIterating || saveDataInfoBuffer.Size == 0) + { + readCount = 0; + return Result.Success; + } + + if (saveDataInfoBuffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + Unsafe.As(ref MemoryMarshal.GetReference(saveDataInfoBuffer.Buffer)) = _info; + readCount = 1; + _finishedIterating = true; + + return Result.Success; + } +} + /// /// Indexes metadata for temporary save data, holding a key-value pair of types /// and respectively. @@ -13,87 +68,89 @@ namespace LibHac.FsSrv; /// /// Only one temporary save data may exist at a time. When a new /// save data is added to the index, the existing key-value pair is replaced. -///
Based on FS 10.0.0 (nnSdk 10.4.0) +/// Based on FS 13.1.0 (nnSdk 13.4.0) ///
public class SaveDataIndexerLite : ISaveDataIndexer { - private object Locker { get; } = new object(); - private ulong CurrentSaveDataId { get; set; } = 0x4000000000000000; - - // Todo: Use Optional - private bool IsKeyValueSet { get; set; } - - private SaveDataAttribute _key; + private SdkMutex _mutex; + private ulong _nextSaveDataId; + private Optional _key; private SaveDataIndexerValue _value; + public SaveDataIndexerLite() + { + _mutex = new SdkMutex(); + _nextSaveDataId = 0x4000000000000000; + } + + public void Dispose() { } + public Result Commit() { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + return Result.Success; } public Result Rollback() { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + return Result.Success; } public Result Reset() { - lock (Locker) - { - IsKeyValueSet = false; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + _key.Clear(); + return Result.Success; } public Result Publish(out ulong saveDataId, in SaveDataAttribute key) { UnsafeHelpers.SkipParamInit(out saveDataId); - lock (Locker) - { - if (IsKeyValueSet && _key == key) - return ResultFs.AlreadyExists.Log(); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - _key = key; - IsKeyValueSet = true; + if (_key.HasValue && key == _key.ValueRo) + return ResultFs.AlreadyExists.Log(); - _value = new SaveDataIndexerValue { SaveDataId = CurrentSaveDataId }; - saveDataId = CurrentSaveDataId; - CurrentSaveDataId++; + _key.Set(in key); - return Result.Success; - } + saveDataId = _nextSaveDataId; + _value = new SaveDataIndexerValue { SaveDataId = _nextSaveDataId }; + _nextSaveDataId++; + + return Result.Success; } public Result Get(out SaveDataIndexerValue value, in SaveDataAttribute key) { UnsafeHelpers.SkipParamInit(out value); - lock (Locker) - { - if (IsKeyValueSet && _key == key) - { - value = _value; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && key == _key.ValueRo) + { + value = _value; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result PutStaticSaveDataIdIndex(in SaveDataAttribute key) { - lock (Locker) - { - if (IsKeyValueSet && _key == key) - return ResultFs.AlreadyExists.Log(); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - _key = key; - IsKeyValueSet = true; + if (_key.HasValue && key == _key.ValueRo) + return ResultFs.AlreadyExists.Log(); - _value = new SaveDataIndexerValue(); - return Result.Success; - } + _key.Set(in key); + _value = default; + + return Result.Success; } public bool IsRemainedReservedOnly() @@ -103,106 +160,99 @@ public class SaveDataIndexerLite : ISaveDataIndexer public Result Delete(ulong saveDataId) { - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - IsKeyValueSet = false; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && saveDataId == _value.SaveDataId) + { + _key.Clear(); + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId) { - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - _value.SpaceId = spaceId; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && saveDataId == _value.SaveDataId) + { + _value.SpaceId = spaceId; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result SetSize(ulong saveDataId, long size) { - // Nintendo doesn't lock in this function for some reason - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - _value.Size = size; - return Result.Success; - } + // Note: Nintendo doesn't lock in this function for some reason + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && saveDataId == _value.SaveDataId) + { + _value.Size = size; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result SetState(ulong saveDataId, SaveDataState state) { - // Nintendo doesn't lock in this function for some reason - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - _value.State = state; - return Result.Success; - } + // Note: Nintendo doesn't lock in this function for some reason + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && saveDataId == _value.SaveDataId) + { + _value.State = state; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result GetKey(out SaveDataAttribute key, ulong saveDataId) { UnsafeHelpers.SkipParamInit(out key); - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - key = _key; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && saveDataId == _value.SaveDataId) + { + key = _key.ValueRo; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result GetValue(out SaveDataIndexerValue value, ulong saveDataId) { UnsafeHelpers.SkipParamInit(out value); - lock (Locker) - { - if (IsKeyValueSet && _value.SaveDataId == saveDataId) - { - value = _value; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && saveDataId == _value.SaveDataId) + { + value = _value; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public Result SetValue(in SaveDataAttribute key, in SaveDataIndexerValue value) { - lock (Locker) - { - if (IsKeyValueSet && _key == key) - { - _value = value; - return Result.Success; - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - return ResultFs.TargetNotFound.Log(); + if (_key.HasValue && _key.ValueRo == key) + { + _value = value; + return Result.Success; } + + return ResultFs.TargetNotFound.Log(); } public int GetIndexCount() @@ -212,58 +262,17 @@ public class SaveDataIndexerLite : ISaveDataIndexer public Result OpenSaveDataInfoReader(ref SharedRef outInfoReader) { - SaveDataIndexerLiteInfoReader reader; + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - if (IsKeyValueSet) + if (_key.HasValue) { - reader = new SaveDataIndexerLiteInfoReader(in _key, in _value); + outInfoReader.Reset(new SaveDataIndexerLiteInfoReader(in _key.Value, in _value)); } else { - reader = new SaveDataIndexerLiteInfoReader(); + outInfoReader.Reset(new SaveDataIndexerLiteInfoReader()); } - outInfoReader.Reset(reader); - return Result.Success; } - - private class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl - { - private bool _finishedIterating; - private SaveDataInfo _info; - - public SaveDataIndexerLiteInfoReader() - { - _finishedIterating = true; - } - - public SaveDataIndexerLiteInfoReader(in SaveDataAttribute key, in SaveDataIndexerValue value) - { - SaveDataIndexer.GenerateSaveDataInfo(out _info, in key, in value); - } - - public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) - { - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); - - // Note: Nintendo doesn't check if the buffer is large enough here - if (_finishedIterating || outInfo.IsEmpty) - { - readCount = 0; - } - else - { - outInfo[0] = _info; - readCount = 1; - _finishedIterating = true; - } - - return Result.Success; - } - - public void Dispose() { } - } - - public void Dispose() { } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataIndexerManager.cs b/src/LibHac/FsSrv/SaveDataIndexerManager.cs index 3803fae8..a2bb10cc 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerManager.cs @@ -3,6 +3,7 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Fs; using LibHac.FsSrv.Storage; +using LibHac.Os; using LibHac.Util; namespace LibHac.FsSrv; @@ -11,35 +12,106 @@ namespace LibHac.FsSrv; /// Initializes and holds s for each save data space. /// Creates accessors for individual SaveDataIndexers. ///
-/// Based on FS 10.0.0 (nnSdk 10.4.0) -internal class SaveDataIndexerManager : ISaveDataIndexerManager +/// Based on FS 13.1.0 (nnSdk 13.4.0) +internal class SaveDataIndexerManager : ISaveDataIndexerManager, IDisposable { - private FileSystemClient FsClient { get; } - private MemoryResource MemoryResource { get; } - private ulong SaveDataId { get; } - - private IndexerHolder _bisIndexer = new IndexerHolder(new object()); - private IndexerHolder _tempIndexer = new IndexerHolder(new object()); - - private IndexerHolder _sdCardIndexer = new IndexerHolder(new object()); + private MemoryResource _memoryResource; + private readonly ulong _indexerSaveDataId; + private SdkMutex _bisIndexerMutex; + private Optional _bisIndexer; + private SdkMutex _tempIndexerMutex; + private SaveDataIndexerLite _tempIndexer; + private SdkMutex _sdIndexerMutex; + private Optional _sdIndexer; private StorageDeviceHandle _sdCardHandle; - private IDeviceHandleManager _sdCardHandleManager; + private IDeviceHandleManager _sdHandleManager; + private SdkMutex _properBisIndexerMutex; + private Optional _properBisIndexer; + private SdkMutex _safeIndexerMutex; + private Optional _safeIndexer; + private readonly bool _isBisUserRedirectionEnabled; - private IndexerHolder _safeIndexer = new IndexerHolder(new object()); - private IndexerHolder _properSystemIndexer = new IndexerHolder(new object()); - - private bool IsBisUserRedirectionEnabled { get; } + // LibHac addition + private FileSystemClient _fsClient; public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId, MemoryResource memoryResource, IDeviceHandleManager sdCardHandleManager, bool isBisUserRedirectionEnabled) { - FsClient = fsClient; - SaveDataId = saveDataId; - MemoryResource = memoryResource; - _sdCardHandleManager = sdCardHandleManager; - IsBisUserRedirectionEnabled = isBisUserRedirectionEnabled; + _memoryResource = memoryResource; + _indexerSaveDataId = saveDataId; - _tempIndexer.Indexer = new SaveDataIndexerLite(); + _bisIndexerMutex = new SdkMutex(); + _tempIndexerMutex = new SdkMutex(); + _tempIndexer = new SaveDataIndexerLite(); + _sdIndexerMutex = new SdkMutex(); + _sdHandleManager = sdCardHandleManager; + _properBisIndexerMutex = new SdkMutex(); + _safeIndexerMutex = new SdkMutex(); + + _isBisUserRedirectionEnabled = isBisUserRedirectionEnabled; + + _fsClient = fsClient; + } + + public void Dispose() + { + InvalidateIndexerImpl(ref _bisIndexer); + _tempIndexer.Dispose(); + InvalidateIndexerImpl(ref _sdIndexer); + InvalidateIndexerImpl(ref _properBisIndexer); + InvalidateIndexerImpl(ref _safeIndexer); + } + + public void InvalidateAllIndexers() + { + InvalidateIndexerImpl(ref _bisIndexer); + InvalidateIndexerImpl(ref _sdIndexer); + InvalidateIndexerImpl(ref _properBisIndexer); + InvalidateIndexerImpl(ref _safeIndexer); + } + + public void InvalidateIndexer(SaveDataSpaceId spaceId) + { + switch (spaceId) + { + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdUser: + { + // Note: Nintendo doesn't lock when doing this operation + using ScopedLock scopedLock = ScopedLock.Lock(ref _sdIndexerMutex); + InvalidateIndexerImpl(ref _sdIndexer); + break; + } + + default: + Abort.UnexpectedDefault(); + break; + } + } + + // Todo: Figure out how to add generic disposal to Optional + private static void InvalidateIndexerImpl(ref Optional indexer) + { + if (indexer.HasValue) + indexer.Value.Dispose(); + + indexer.Clear(); + } + + public void ResetIndexer(SaveDataSpaceId spaceId) + { + switch (spaceId) + { + case SaveDataSpaceId.Temporary: + // ReSharper disable once RedundantAssignment + Result rc = _tempIndexer.Reset(); + Assert.SdkAssert(rc.IsSuccess()); + break; + + default: + Abort.UnexpectedDefault(); + break; + } } /// @@ -59,172 +131,131 @@ internal class SaveDataIndexerManager : ISaveDataIndexerManager { UnsafeHelpers.SkipParamInit(out neededInit); - if (IsBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User) + if (_isBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User) { spaceId = SaveDataSpaceId.ProperSystem; } - UniqueLock indexerLock = default; - try + ISaveDataIndexer indexer; + using var indexerLock = new UniqueLock(); + bool wasIndexerInitialized = false; + + switch (spaceId) { - ISaveDataIndexer indexer; - switch (spaceId) + case SaveDataSpaceId.System: + case SaveDataSpaceId.User: { - case SaveDataSpaceId.System: - case SaveDataSpaceId.User: - indexerLock = new UniqueLock(_bisIndexer.Locker); + indexerLock.Reset(_bisIndexerMutex); - if (!_bisIndexer.IsInitialized) - { - _bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName), - SaveDataSpaceId.System, SaveDataId, MemoryResource); + if (!_bisIndexer.HasValue) + { + _bisIndexer.Set(new SaveDataIndexer(_fsClient, new U8Span(BisIndexerMountName), + SaveDataSpaceId.System, _indexerSaveDataId, _memoryResource)); + wasIndexerInitialized = true; + } - neededInit = true; - } - - indexer = _bisIndexer.Indexer; - break; - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.SdUser: - // ReSharper doesn't realize that UniqueLock locks the indexer's lock object - // ReSharper disable InconsistentlySynchronizedField - indexerLock = new UniqueLock(_sdCardIndexer.Locker); - - // We need to reinitialize the indexer if the SD card has changed - if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer.Dispose(); - _sdCardIndexer.Indexer = null; - } - - if (!_sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName), - SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource); - - _sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult(); - - neededInit = true; - } - - indexer = _sdCardIndexer.Indexer; - // ReSharper restore InconsistentlySynchronizedField - - break; - case SaveDataSpaceId.Temporary: - indexerLock = new UniqueLock(_tempIndexer.Locker); - - indexer = _tempIndexer.Indexer; - break; - case SaveDataSpaceId.ProperSystem: - indexerLock = new UniqueLock(_properSystemIndexer.Locker); - - if (!_properSystemIndexer.IsInitialized) - { - _properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, - new U8Span(ProperSystemIndexerMountName), - SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource); - - neededInit = true; - } - - indexer = _properSystemIndexer.Indexer; - break; - case SaveDataSpaceId.SafeMode: - indexerLock = new UniqueLock(_safeIndexer.Locker); - - if (!_safeIndexer.IsInitialized) - { - _safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName), - SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource); - - neededInit = true; - } - - indexer = _safeIndexer.Indexer; - break; - - default: - outAccessor = default; + indexer = _bisIndexer.Value; + break; + } + case SaveDataSpaceId.Temporary: + { + indexerLock.Reset(_tempIndexerMutex); + indexer = _tempIndexer; + break; + } + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdUser: + { + if (_sdHandleManager is null) return ResultFs.InvalidArgument.Log(); + + indexerLock.Reset(_sdIndexerMutex); + + // We need to reinitialize the indexer if the SD card has changed + if (!_sdHandleManager.IsValid(in _sdCardHandle) && _sdIndexer.HasValue) + { + _sdIndexer.Value.Dispose(); + _sdIndexer.Clear(); + } + + if (!_sdIndexer.HasValue) + { + _sdIndexer.Set(new SaveDataIndexer(_fsClient, new U8Span(SdCardIndexerMountName), + SaveDataSpaceId.SdSystem, _indexerSaveDataId, _memoryResource)); + + _sdHandleManager.GetHandle(out _sdCardHandle).IgnoreResult(); + wasIndexerInitialized = true; + } + + indexer = _sdIndexer.Value; + break; } - - - outAccessor.Reset(new SaveDataIndexerAccessor(indexer, ref indexerLock)); - return Result.Success; - } - finally - { - indexerLock.Dispose(); - } - } - - public void ResetIndexer(SaveDataSpaceId spaceId) - { - if (spaceId != SaveDataSpaceId.Temporary) - { - Abort.UnexpectedDefault(); - } - - // ReSharper disable once RedundantAssignment - Result rc = _tempIndexer.Indexer.Reset(); - Assert.SdkAssert(rc.IsSuccess()); - } - - public void InvalidateIndexer(SaveDataSpaceId spaceId) - { - // Note: Nintendo doesn't lock when doing this operation - lock (_sdCardIndexer.Locker) - { - if (spaceId != SaveDataSpaceId.SdUser && spaceId != SaveDataSpaceId.SdSystem) + case SaveDataSpaceId.ProperSystem: { - Abort.UnexpectedDefault(); - } + indexerLock.Reset(_properBisIndexerMutex); - if (_sdCardIndexer.IsInitialized) + if (!_properBisIndexer.HasValue) + { + _properBisIndexer.Set(new SaveDataIndexer(_fsClient, new U8Span(ProperBisIndexerMountName), + SaveDataSpaceId.ProperSystem, _indexerSaveDataId, _memoryResource)); + + wasIndexerInitialized = true; + } + + indexer = _properBisIndexer.Value; + break; + } + case SaveDataSpaceId.SafeMode: { - _sdCardIndexer.Indexer.Dispose(); - _sdCardIndexer.Indexer = null; + indexerLock.Reset(_safeIndexerMutex); + + if (!_safeIndexer.HasValue) + { + _safeIndexer.Set(new SaveDataIndexer(_fsClient, new U8Span(SafeModeIndexerMountName), + SaveDataSpaceId.SafeMode, _indexerSaveDataId, _memoryResource)); + + wasIndexerInitialized = true; + } + + indexer = _safeIndexer.Value; + break; } - } - } - private struct IndexerHolder - { - public object Locker { get; } - public ISaveDataIndexer Indexer { get; set; } - - public IndexerHolder(object locker) - { - Locker = locker; - Indexer = null; + default: + return ResultFs.InvalidArgument.Log(); } - public bool IsInitialized => Indexer != null; + outAccessor.Reset(new SaveDataIndexerAccessor(indexer, ref indexerLock.Ref())); + neededInit = wasIndexerInitialized; + return Result.Success; } - private static ReadOnlySpan SystemIndexerMountName => // saveDataIxrDb + /// "saveDataIxrDb" + private static ReadOnlySpan BisIndexerMountName => new[] { (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b' }; - private static ReadOnlySpan SdCardIndexerMountName => // saveDataIxrDbSd + /// "saveDataIxrDbSd" + private static ReadOnlySpan SdCardIndexerMountName => new[] { (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'S', (byte) 'd' }; - private static ReadOnlySpan ProperSystemIndexerMountName => // saveDataIxrDbPr + /// "saveDataIxrDbPr" + private static ReadOnlySpan ProperBisIndexerMountName => new[] { (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', (byte) 'I', (byte) 'x', (byte) 'r', (byte) 'D', (byte) 'b', (byte) 'P', (byte) 'r' }; - private static ReadOnlySpan SafeModeIndexerMountName => // saveDataIxrDbSf + /// "saveDataIxrDbSf" + private static ReadOnlySpan SafeModeIndexerMountName => new[] { (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'D', (byte) 'a', (byte) 't', (byte) 'a', @@ -236,20 +267,25 @@ internal class SaveDataIndexerManager : ISaveDataIndexerManager /// Gives exclusive access to an . /// Releases the lock to the upon disposal. /// -/// Based on FS 10.0.0 (nnSdk 10.4.0) +/// Based on FS 13.1.0 (nnSdk 13.4.0) public class SaveDataIndexerAccessor : IDisposable { - public ISaveDataIndexer Indexer { get; } - private UniqueLock _locker; + private readonly ISaveDataIndexer _indexer; + private UniqueLock _lock; - public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock locker) + public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock indexerLock) { - Indexer = indexer; - _locker = new UniqueLock(ref locker); + _indexer = indexer; + _lock = new UniqueLock(ref indexerLock); } public void Dispose() { - _locker.Dispose(); + _lock.Dispose(); + } + + public ISaveDataIndexer GetInterface() + { + return _indexer; } } \ No newline at end of file diff --git a/src/LibHac/Os/UniqueLock.cs b/src/LibHac/Os/UniqueLock.cs index e63b49dd..714b0ba9 100644 --- a/src/LibHac/Os/UniqueLock.cs +++ b/src/LibHac/Os/UniqueLock.cs @@ -156,6 +156,16 @@ public struct UniqueLock : IDisposable where TMutex : class, ILockable other = default; } + public void Reset(TMutex mutex) + { + if (_ownsLock) + _mutex.Unlock(); + + _mutex = mutex; + mutex.Lock(); + _ownsLock = true; + } + public void Lock() { if (_mutex is null) diff --git a/src/LibHac/Util/Optional.cs b/src/LibHac/Util/Optional.cs index b73bbbef..ea11fb83 100644 --- a/src/LibHac/Util/Optional.cs +++ b/src/LibHac/Util/Optional.cs @@ -1,4 +1,4 @@ -using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; @@ -52,6 +52,11 @@ public struct Optional public void Clear() { _hasValue = false; - _value = default; + + // Clear types with references so the GC doesn't think we still need any contained objects + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + _value = default; + } } } \ No newline at end of file