Fixup save data indexer classes and update them for 13.1.0

- SaveDataIndexer
- SaveDataIndexerLite
- SaveDataIndexerLiteInfoReader
- SaveDataIndexerManager
- SaveDataIndexerAccessor
This commit is contained in:
Alex Barney 2022-01-15 03:02:21 -07:00
parent ecb85269eb
commit 085511660d
9 changed files with 1038 additions and 914 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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<SaveDataInfoReaderImpl>();
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<SaveDataInfoReaderImpl>();
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);

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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;
/// <summary>
/// Iterates through all the save data indexed in a <see cref="SaveDataIndexerLite"/>.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
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<SaveDataInfo>())
return ResultFs.InvalidSize.Log();
Unsafe.As<byte, SaveDataInfo>(ref MemoryMarshal.GetReference(saveDataInfoBuffer.Buffer)) = _info;
readCount = 1;
_finishedIterating = true;
return Result.Success;
}
}
/// <summary>
/// Indexes metadata for temporary save data, holding a key-value pair of types
/// <see cref="SaveDataAttribute"/> and <see cref="SaveDataIndexerValue"/> respectively.
@ -13,87 +68,89 @@ namespace LibHac.FsSrv;
/// <remarks>
/// 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.
/// <br/>Based on FS 10.0.0 (nnSdk 10.4.0)
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para>
/// </remarks>
public class SaveDataIndexerLite : ISaveDataIndexer
{
private object Locker { get; } = new object();
private ulong CurrentSaveDataId { get; set; } = 0x4000000000000000;
// Todo: Use Optional<T>
private bool IsKeyValueSet { get; set; }
private SaveDataAttribute _key;
private SdkMutex _mutex;
private ulong _nextSaveDataId;
private Optional<SaveDataAttribute> _key;
private SaveDataIndexerValue _value;
public SaveDataIndexerLite()
{
_mutex = new SdkMutex();
_nextSaveDataId = 0x4000000000000000;
}
public void Dispose() { }
public Result Commit()
{
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
return Result.Success;
}
public Result Rollback()
{
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
return Result.Success;
}
public Result Reset()
{
lock (Locker)
{
IsKeyValueSet = false;
return Result.Success;
}
using ScopedLock<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SdkMutex> 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<SaveDataInfoReaderImpl> outInfoReader)
{
SaveDataIndexerLiteInfoReader reader;
using ScopedLock<SdkMutex> 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<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(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() { }
}
}

View file

@ -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 <see cref="ISaveDataIndexer"/>s for each save data space.
/// Creates accessors for individual SaveDataIndexers.
/// </summary>
/// <remarks>Based on FS 10.0.0 (nnSdk 10.4.0)</remarks>
internal class SaveDataIndexerManager : ISaveDataIndexerManager
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
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<SaveDataIndexer> _bisIndexer;
private SdkMutex _tempIndexerMutex;
private SaveDataIndexerLite _tempIndexer;
private SdkMutex _sdIndexerMutex;
private Optional<SaveDataIndexer> _sdIndexer;
private StorageDeviceHandle _sdCardHandle;
private IDeviceHandleManager _sdCardHandleManager;
private IDeviceHandleManager _sdHandleManager;
private SdkMutex _properBisIndexerMutex;
private Optional<SaveDataIndexer> _properBisIndexer;
private SdkMutex _safeIndexerMutex;
private Optional<SaveDataIndexer> _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<SdkMutex> scopedLock = ScopedLock.Lock(ref _sdIndexerMutex);
InvalidateIndexerImpl(ref _sdIndexer);
break;
}
default:
Abort.UnexpectedDefault();
break;
}
}
// Todo: Figure out how to add generic disposal to Optional<T>
private static void InvalidateIndexerImpl(ref Optional<SaveDataIndexer> 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;
}
}
/// <summary>
@ -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<SdkMutex>();
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<byte> SystemIndexerMountName => // saveDataIxrDb
/// <summary>"<c>saveDataIxrDb</c>"</summary>
private static ReadOnlySpan<byte> 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<byte> SdCardIndexerMountName => // saveDataIxrDbSd
/// <summary>"<c>saveDataIxrDbSd</c>"</summary>
private static ReadOnlySpan<byte> 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<byte> ProperSystemIndexerMountName => // saveDataIxrDbPr
/// <summary>"<c>saveDataIxrDbPr</c>"</summary>
private static ReadOnlySpan<byte> 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<byte> SafeModeIndexerMountName => // saveDataIxrDbSf
/// <summary>"<c>saveDataIxrDbSf</c>"</summary>
private static ReadOnlySpan<byte> 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 <see cref="ISaveDataIndexer"/>.
/// Releases the lock to the <see cref="ISaveDataIndexer"/> upon disposal.
/// </summary>
/// <remarks>Based on FS 10.0.0 (nnSdk 10.4.0)</remarks>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public class SaveDataIndexerAccessor : IDisposable
{
public ISaveDataIndexer Indexer { get; }
private UniqueLock _locker;
private readonly ISaveDataIndexer _indexer;
private UniqueLock<SdkMutex> _lock;
public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock locker)
public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock<SdkMutex> indexerLock)
{
Indexer = indexer;
_locker = new UniqueLock(ref locker);
_indexer = indexer;
_lock = new UniqueLock<SdkMutex>(ref indexerLock);
}
public void Dispose()
{
_locker.Dispose();
_lock.Dispose();
}
public ISaveDataIndexer GetInterface()
{
return _indexer;
}
}

View file

@ -156,6 +156,16 @@ public struct UniqueLock<TMutex> : 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)

View file

@ -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<T>
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<T>())
{
_value = default;
}
}
}