Use FlatMapKeyValueStore in SaveDataIndexer

FS 10.0 tweaks how the SaveDataIndexerManager is accessed. SaveDataFileSystemServiceImpl's configuration now includes an ISaveDataIndexerManager instead of using global state.

FS 10.0 uses the new nn::fssrv::storage namespace for abstracting the interface to nn::gc and nn::sdmmc. A tiny bit of the namespace has been added so we can create mock SD card handles for SaveDataIndexerManager.

ReferenceCountedDisposable<T> is now being used for returning ISaveDataInfoReader objects from IFileSystemProxy. I've tried coming up with a pattern to cleanly return a ReferenceCountedDisposable<T>, but the lack of features like RAII or move semantics makes this difficult without either lots of allocations or lots of boilerplate code.
This commit is contained in:
Alex Barney 2020-08-03 18:34:04 -07:00
parent 37251968c0
commit a551360da0
22 changed files with 986 additions and 488 deletions

View File

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace LibHac.Diag
{
@ -22,5 +23,10 @@ namespace LibHac.Diag
DoAbort(message);
}
public static void UnexpectedDefault([CallerMemberName] string caller = "")
{
throw new LibHacException($"Unexpected value passed to switch statement in {caller}");
}
}
}

View File

@ -0,0 +1,20 @@
using LibHac.FsService;
using LibHac.FsService.Storage;
namespace LibHac.Fs.Impl
{
internal class SdHandleManager : IDeviceHandleManager
{
public Result GetHandle(out StorageDeviceHandle handle)
{
return SdCardManagement.GetCurrentSdCardHandle(out handle);
}
public bool IsValid(in StorageDeviceHandle handle)
{
// Note: Nintendo ignores the result here.
SdCardManagement.IsSdCardHandleValid(out bool isValid, in handle).IgnoreResult();
return isValid;
}
}
}

View File

@ -10,7 +10,7 @@ namespace LibHac.Fs
{
[FieldOffset(0x00)] public ProgramId ProgramId;
[FieldOffset(0x08)] public UserId UserId;
[FieldOffset(0x18)] public ulong SaveDataId;
[FieldOffset(0x18)] public ulong StaticSaveDataId;
[FieldOffset(0x20)] public SaveDataType Type;
[FieldOffset(0x21)] public SaveDataRank Rank;
[FieldOffset(0x22)] public short Index;
@ -25,15 +25,18 @@ namespace LibHac.Fs
return ProgramId == other.ProgramId &&
Type == other.Type &&
UserId.Equals(other.UserId) &&
SaveDataId == other.SaveDataId &&
StaticSaveDataId == other.StaticSaveDataId &&
Rank == other.Rank &&
Index == other.Index;
}
public static bool operator ==(SaveDataAttribute left, SaveDataAttribute right) => left.Equals(right);
public static bool operator !=(SaveDataAttribute left, SaveDataAttribute right) => !(left == right);
public override int GetHashCode()
{
// ReSharper disable NonReadonlyMemberInGetHashCode
return HashCode.Combine(ProgramId, Type, UserId, SaveDataId, Rank, Index);
return HashCode.Combine(ProgramId, Type, UserId, StaticSaveDataId, Rank, Index);
// ReSharper restore NonReadonlyMemberInGetHashCode
}
@ -45,7 +48,7 @@ namespace LibHac.Fs
if (typeComparison != 0) return typeComparison;
int userIdComparison = UserId.CompareTo(other.UserId);
if (userIdComparison != 0) return userIdComparison;
int saveDataIdComparison = SaveDataId.CompareTo(other.SaveDataId);
int saveDataIdComparison = StaticSaveDataId.CompareTo(other.StaticSaveDataId);
if (saveDataIdComparison != 0) return saveDataIdComparison;
int rankComparison = Rank.CompareTo(other.Rank);
if (rankComparison != 0) return rankComparison;
@ -143,7 +146,7 @@ namespace LibHac.Fs
[FieldOffset(0x08)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x09)] public SaveDataType Type;
[FieldOffset(0x10)] public UserId UserId;
[FieldOffset(0x20)] public ulong SaveDataIdFromKey;
[FieldOffset(0x20)] public ulong StaticSaveDataId;
[FieldOffset(0x28)] public ProgramId ProgramId;
[FieldOffset(0x30)] public long Size;
[FieldOffset(0x38)] public short Index;

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.FsService;
using LibHac.Ncm;
@ -228,7 +229,7 @@ namespace LibHac.Fs.Shim
var attribute = new SaveDataAttribute
{
UserId = userId,
SaveDataId = saveDataId
StaticSaveDataId = saveDataId
};
var createInfo = new SaveDataCreationInfo
@ -364,7 +365,8 @@ namespace LibHac.Fs.Shim
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader reader, spaceId);
Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId(
out ReferenceCountedDisposable<ISaveDataInfoReader> reader, spaceId);
if (rc.IsFailure()) return rc;
tempIterator = new SaveDataIterator(fs, reader);
@ -388,7 +390,8 @@ namespace LibHac.Fs.Shim
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader reader, spaceId, ref tempFilter);
Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter(
out ReferenceCountedDisposable<ISaveDataInfoReader> reader, spaceId, ref tempFilter);
if (rc.IsFailure()) return rc;
tempIterator = new SaveDataIterator(fs, reader);
@ -402,20 +405,25 @@ namespace LibHac.Fs.Shim
return result;
}
public static Result DisableAutoSaveDataCreation(this FileSystemClient fsClient)
public static void DisableAutoSaveDataCreation(this FileSystemClient fsClient)
{
IFileSystemProxy fsProxy = fsClient.GetFileSystemProxyServiceObject();
return fsProxy.DisableAutoSaveDataCreation();
Result rc = fsProxy.DisableAutoSaveDataCreation();
if (rc.IsFailure())
{
Abort.DoAbort();
}
}
}
public struct SaveDataIterator : IDisposable
{
private FileSystemClient FsClient { get; }
private ISaveDataInfoReader Reader { get; }
private ReferenceCountedDisposable<ISaveDataInfoReader> Reader { get; }
internal SaveDataIterator(FileSystemClient fsClient, ISaveDataInfoReader reader)
internal SaveDataIterator(FileSystemClient fsClient, ReferenceCountedDisposable<ISaveDataInfoReader> reader)
{
FsClient = fsClient;
Reader = reader;
@ -430,14 +438,14 @@ namespace LibHac.Fs.Shim
if (FsClient.IsEnabledAccessLog(AccessLogTarget.System))
{
TimeSpan startTime = FsClient.Time.GetCurrent();
rc = Reader.ReadSaveDataInfo(out readCount, byteBuffer);
rc = Reader.Target.Read(out readCount, byteBuffer);
TimeSpan endTime = FsClient.Time.GetCurrent();
FsClient.OutputAccessLog(rc, startTime, endTime, $", size: {buffer.Length}");
}
else
{
rc = Reader.ReadSaveDataInfo(out readCount, byteBuffer);
rc = Reader.Target.Read(out readCount, byteBuffer);
}
return rc;

View File

@ -21,7 +21,7 @@ namespace LibHac.Fs.Shim
SaveDataAttribute attribute = default;
attribute.UserId = userId;
attribute.SaveDataId = saveDataId;
attribute.StaticSaveDataId = saveDataId;
rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, spaceId, ref attribute);
if (rc.IsFailure()) return rc;

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsService.Impl;
@ -129,13 +130,11 @@ namespace LibHac.FsService
private Result DeleteSaveDataFileSystemImpl(SaveDataSpaceId spaceId, ulong saveDataId)
{
SaveDataIndexerReader reader = default;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId);
if (rc.IsFailure()) return rc;
try
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, spaceId);
if (rc.IsFailure()) return rc;
if (saveDataId == FileSystemServer.SaveIndexerId)
{
// missing: This save can only be deleted by the FS process itself
@ -144,13 +143,13 @@ namespace LibHac.FsService
{
if (spaceId != SaveDataSpaceId.ProperSystem && spaceId != SaveDataSpaceId.SafeMode)
{
rc = reader.Indexer.GetBySaveDataId(out SaveDataIndexerValue value, saveDataId);
rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId);
if (rc.IsFailure()) return rc;
spaceId = value.SpaceId;
}
rc = reader.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId);
if (rc.IsFailure()) return rc;
if (key.Type == SaveDataType.System || key.Type == SaveDataType.SystemBcat)
@ -162,10 +161,10 @@ namespace LibHac.FsService
// Check if permissions allow deleting save data
}
rc = reader.Indexer.SetState(saveDataId, SaveDataState.Creating);
rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Creating);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
rc = accessor.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
@ -174,19 +173,15 @@ namespace LibHac.FsService
if (saveDataId != FileSystemServer.SaveIndexerId)
{
rc = reader.Indexer.Delete(saveDataId);
rc = accessor.Indexer.Delete(saveDataId);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
rc = accessor.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
finally
{
reader.Dispose();
}
}
private Result DeleteSaveDataFileSystemImpl2(SaveDataSpaceId spaceId, ulong saveDataId)
@ -215,23 +210,18 @@ namespace LibHac.FsService
{
if (saveDataId != FileSystemServer.SaveIndexerId)
{
SaveDataIndexerReader reader = default;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId);
if (rc.IsFailure()) return rc;
try
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, spaceId);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.GetBySaveDataId(out SaveDataIndexerValue value, saveDataId);
rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId);
if (rc.IsFailure()) return rc;
if (value.SpaceId != GetSpaceIdForIndexer(spaceId))
return ResultFs.TargetNotFound.Log();
}
finally
{
reader.Dispose();
}
}
return DeleteSaveDataFileSystemImpl(spaceId, saveDataId);
@ -239,25 +229,19 @@ namespace LibHac.FsService
private Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute)
{
info = default;
Unsafe.SkipInit(out info);
SaveDataIndexerReader reader = default;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId);
if (rc.IsFailure()) return rc;
try
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, spaceId);
rc = accessor.Indexer.Get(out SaveDataIndexerValue value, ref attribute);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Get(out SaveDataIndexerValue value, ref attribute);
if (rc.IsFailure()) return rc;
SaveDataIndexer.GetSaveDataInfo(out info, ref attribute, ref value);
SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value);
return Result.Success;
}
finally
{
reader.Dispose();
}
}
public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute attribute)
@ -280,11 +264,11 @@ namespace LibHac.FsService
bool isDeleteNeeded = false;
Result rc;
SaveDataIndexerReader reader = default;
SaveDataIndexerAccessor accessor = null;
try
{
if (attribute.SaveDataId == FileSystemServer.SaveIndexerId)
if (attribute.StaticSaveDataId == FileSystemServer.SaveIndexerId)
{
saveDataId = FileSystemServer.SaveIndexerId;
rc = FsProxyCore.DoesSaveDataExist(out bool saveExists, creationInfo.SpaceId, saveDataId);
@ -298,29 +282,29 @@ namespace LibHac.FsService
}
else
{
rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, creationInfo.SpaceId);
rc = OpenSaveDataIndexerAccessor(out accessor, creationInfo.SpaceId);
if (rc.IsFailure()) return rc;
SaveDataAttribute indexerKey = attribute;
if (attribute.SaveDataId != 0 && attribute.UserId == UserId.Zero)
if (attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.Zero)
{
saveDataId = attribute.SaveDataId;
saveDataId = attribute.StaticSaveDataId;
rc = reader.Indexer.AddSystemSaveData(ref indexerKey);
rc = accessor.Indexer.PutStaticSaveDataIdIndex(ref indexerKey);
}
else
{
if (attribute.Type != SaveDataType.System &&
attribute.Type != SaveDataType.SystemBcat)
{
if (reader.Indexer.IsFull())
if (accessor.Indexer.IsRemainedReservedOnly())
{
return ResultKvdb.OutOfKeyResource.Log();
}
}
rc = reader.Indexer.Add(out saveDataId, ref indexerKey);
rc = accessor.Indexer.Publish(out saveDataId, ref indexerKey);
}
if (ResultFs.SaveDataPathAlreadyExists.Includes(rc))
@ -330,21 +314,21 @@ namespace LibHac.FsService
isDeleteNeeded = true;
rc = reader.Indexer.SetState(saveDataId, SaveDataState.Creating);
rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Creating);
if (rc.IsFailure()) return rc;
SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(creationInfo.SpaceId);
rc = reader.Indexer.SetSpaceId(saveDataId, indexerSpaceId);
rc = accessor.Indexer.SetSpaceId(saveDataId, indexerSpaceId);
if (rc.IsFailure()) return rc;
// todo: calculate size
long size = 0;
rc = reader.Indexer.SetSize(saveDataId, size);
rc = accessor.Indexer.SetSize(saveDataId, size);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
rc = accessor.Indexer.Commit();
if (rc.IsFailure()) return rc;
}
@ -386,17 +370,20 @@ namespace LibHac.FsService
}
}
if (attribute.SaveDataId == FileSystemServer.SaveIndexerId || something)
if (attribute.StaticSaveDataId == FileSystemServer.SaveIndexerId || something)
{
isDeleteNeeded = false;
return Result.Success;
}
rc = reader.Indexer.SetState(saveDataId, SaveDataState.Normal);
// accessor shouldn't ever be null, but checking makes the analyzers happy
Abort.DoAbortUnless(accessor != null);
rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Normal);
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Commit();
rc = accessor.Indexer.Commit();
if (rc.IsFailure()) return rc;
isDeleteNeeded = false;
@ -410,19 +397,19 @@ namespace LibHac.FsService
{
DeleteSaveDataFileSystemImpl2(creationInfo.SpaceId, saveDataId).IgnoreResult();
if (reader.IsInitialized && saveDataId != FileSystemServer.SaveIndexerId)
if (accessor != null && saveDataId != FileSystemServer.SaveIndexerId)
{
rc = reader.Indexer.GetBySaveDataId(out SaveDataIndexerValue value, saveDataId);
rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId);
if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId)
{
reader.Indexer.Delete(saveDataId).IgnoreResult();
reader.Indexer.Commit().IgnoreResult();
accessor.Indexer.Delete(saveDataId).IgnoreResult();
accessor.Indexer.Commit().IgnoreResult();
}
}
}
reader.Dispose();
accessor?.Dispose();
}
}
@ -454,7 +441,7 @@ namespace LibHac.FsService
public Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo)
{
if (!IsSystemSaveDataId(attribute.SaveDataId))
if (!IsSystemSaveDataId(attribute.StaticSaveDataId))
return ResultFs.InvalidArgument.Log();
SaveDataCreationInfo newCreationInfo = creationInfo;
@ -490,21 +477,21 @@ namespace LibHac.FsService
fileSystem = default;
saveDataId = default;
bool hasFixedId = attribute.SaveDataId != 0 && attribute.UserId == UserId.Zero;
bool hasFixedId = attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.Zero;
if (hasFixedId)
{
saveDataId = attribute.SaveDataId;
saveDataId = attribute.StaticSaveDataId;
}
else
{
SaveDataAttribute indexerKey = attribute;
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader tmpReader, spaceId);
using SaveDataIndexerReader reader = tmpReader;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor tempAccessor, spaceId);
using SaveDataIndexerAccessor accessor = tempAccessor;
if (rc.IsFailure()) return rc;
rc = reader.Indexer.Get(out SaveDataIndexerValue indexerValue, ref indexerKey);
rc = accessor.Indexer.Get(out SaveDataIndexerValue indexerValue, ref indexerKey);
if (rc.IsFailure()) return rc;
SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(spaceId);
@ -605,7 +592,7 @@ namespace LibHac.FsService
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
fileSystem = default;
if (!IsSystemSaveDataId(attribute.SaveDataId)) return ResultFs.InvalidArgument.Log();
if (!IsSystemSaveDataId(attribute.StaticSaveDataId)) return ResultFs.InvalidArgument.Log();
Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, spaceId,
ref attribute, false, true);
@ -732,41 +719,34 @@ namespace LibHac.FsService
return FsProxyCore.OpenDeviceOperator(out deviceOperator);
}
public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
{
infoReader = default;
// Missing permission check
SaveDataIndexerReader indexReader = default;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId.System);
if (rc.IsFailure()) return rc;
try
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, SaveDataSpaceId.System);
if (rc.IsFailure()) return rc;
return indexReader.Indexer.OpenSaveDataInfoReader(out infoReader);
}
finally
{
indexReader.Dispose();
return accessor.Indexer.OpenSaveDataInfoReader(out infoReader);
}
}
public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId)
public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId)
{
infoReader = default;
// Missing permission check
SaveDataIndexerReader indexReader = default;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId);
if (rc.IsFailure()) return rc;
try
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
if (rc.IsFailure()) return rc;
rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
rc = accessor.Indexer.OpenSaveDataInfoReader(
out ReferenceCountedDisposable<ISaveDataInfoReader> baseInfoReader);
if (rc.IsFailure()) return rc;
var filter = new SaveDataFilterInternal
@ -775,43 +755,36 @@ namespace LibHac.FsService
SpaceId = GetSpaceIdForIndexer(spaceId)
};
infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
var filterReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
infoReader = new ReferenceCountedDisposable<ISaveDataInfoReader>(filterReader);
return Result.Success;
}
finally
{
indexReader.Dispose();
}
}
public Result OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId,
public Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId,
ref SaveDataFilter filter)
{
infoReader = default;
// Missing permission check
SaveDataIndexerReader indexReader = default;
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId);
if (rc.IsFailure()) return rc;
try
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
if (rc.IsFailure()) return rc;
rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
rc = accessor.Indexer.OpenSaveDataInfoReader(
out ReferenceCountedDisposable<ISaveDataInfoReader> baseInfoReader);
if (rc.IsFailure()) return rc;
var filterInternal = new SaveDataFilterInternal(ref filter, spaceId);
infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filterInternal);
var filterReader = new SaveDataInfoFilterReader(baseInfoReader, ref filterInternal);
infoReader = new ReferenceCountedDisposable<ISaveDataInfoReader>(filterReader);
return Result.Success;
}
finally
{
indexReader.Dispose();
}
}
public Result FindSaveDataWithFilter(out long count, Span<byte> saveDataInfoBuffer, SaveDataSpaceId spaceId,
@ -839,23 +812,20 @@ namespace LibHac.FsService
count = default;
info = default;
SaveDataIndexerReader indexReader = default;
try
Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId);
if (rc.IsFailure()) return rc;
using (accessor)
{
Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out indexReader, spaceId);
rc = accessor.Indexer.OpenSaveDataInfoReader(
out ReferenceCountedDisposable<ISaveDataInfoReader> baseInfoReader);
if (rc.IsFailure()) return rc;
rc = indexReader.Indexer.OpenSaveDataInfoReader(out ISaveDataInfoReader baseInfoReader);
if (rc.IsFailure()) return rc;
var infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter);
return infoReader.ReadSaveDataInfo(out count, SpanHelpers.AsByteSpan(ref info));
}
finally
{
indexReader.Dispose();
using (var infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter))
{
return infoReader.Read(out count, SpanHelpers.AsByteSpan(ref info));
}
}
}
@ -874,7 +844,7 @@ namespace LibHac.FsService
throw new NotImplementedException();
}
public Result OpenSaveDataInfoReaderOnlyCacheStorage(out ISaveDataInfoReader infoReader)
public Result OpenSaveDataInfoReaderOnlyCacheStorage(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
{
throw new NotImplementedException();
}
@ -1053,8 +1023,6 @@ namespace LibHac.FsService
Result rc = FsProxyCore.SetSdCardEncryptionSeed(ref seed);
if (rc.IsFailure()) return rc;
FsServer.SaveDataIndexerManager.ResetSdCardIndexer();
return Result.Success;
}
@ -1146,21 +1114,9 @@ namespace LibHac.FsService
rc = saveDirFs.CleanDirectoryRecursively("/".ToU8Span());
if (rc.IsFailure()) return rc;
SaveDataIndexerReader reader = default;
FsProxyCore.SaveDataIndexerManager.ResetTemporaryStorageIndexer(SaveDataSpaceId.Temporary);
try
{
rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out reader, SaveDataSpaceId.Temporary);
if (rc.IsFailure()) return rc;
reader.Indexer.Reset().IgnoreResult();
return Result.Success;
}
finally
{
reader.Dispose();
}
return Result.Success;
}
public Result SetSdCardAccessibility(bool isAccessible)
@ -1189,7 +1145,7 @@ namespace LibHac.FsService
SaveDataAttribute attribute = default;
attribute.ProgramId = new ProgramId(MultiCommitManager.ProgramId);
attribute.SaveDataId = MultiCommitManager.SaveDataId;
attribute.StaticSaveDataId = MultiCommitManager.SaveDataId;
Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, SaveDataSpaceId.System, ref attribute,
false, true);
@ -1199,6 +1155,35 @@ namespace LibHac.FsService
return Result.Success;
}
// todo: split the FileSystemProxy classes
// nn::fssrv::SaveDataFileSystemService::GetSaveDataIndexerAccessor
private Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId spaceId)
{
accessor = default;
Result rc = FsProxyCore.OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessorTemp,
out bool neededInit, spaceId);
if (rc.IsFailure()) return rc;
try
{
if (neededInit)
{
// todo: nn::fssrv::SaveDataFileSystemService::CleanUpSaveDataCore
// nn::fssrv::SaveDataFileSystemService::CompleteSaveDataExtensionCore
}
accessor = accessorTemp;
accessorTemp = null;
return Result.Success;
}
finally
{
accessorTemp?.Dispose();
}
}
private static bool IsSystemSaveDataId(ulong id)
{
return (long)id < 0;

View File

@ -27,6 +27,8 @@ namespace LibHac.FsService
private GlobalAccessLogMode LogMode { get; set; }
public bool IsSdCardAccessible { get; set; }
internal ISaveDataIndexerManager SaveDataIndexerManager { get; private set; }
public FileSystemProxyCore(FileSystemCreators fsCreators, ExternalKeySet externalKeys, IDeviceOperator deviceOperator)
{
FsCreators = fsCreators;
@ -813,6 +815,9 @@ namespace LibHac.FsService
seed.Value.CopyTo(SdEncryptionSeed);
// todo: FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed);
SaveDataIndexerManager.InvalidateSdCardIndexer(SaveDataSpaceId.SdSystem);
SaveDataIndexerManager.InvalidateSdCardIndexer(SaveDataSpaceId.SdCache);
return Result.Success;
}
@ -1058,6 +1063,16 @@ namespace LibHac.FsService
return Result.Success;
}
internal void SetSaveDataIndexerManager(ISaveDataIndexerManager manager)
{
SaveDataIndexerManager = manager;
}
internal Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId)
{
return SaveDataIndexerManager.OpenAccessor(out accessor, out neededInit, spaceId);
}
private string GetSaveDataIdPath(ulong saveDataId)
{
return $"/{saveDataId:x16}";

View File

@ -1,5 +1,6 @@
using System;
using LibHac.Fs;
using LibHac.Fs.Impl;
using LibHac.Fs.Shim;
using LibHac.FsService.Creators;
@ -15,8 +16,6 @@ namespace LibHac.FsService
public FileSystemClient FsClient { get; }
private ITimeSpanGenerator Timer { get; }
internal SaveDataIndexerManager SaveDataIndexerManager { get; }
/// <summary>
/// Creates a new <see cref="FileSystemServer"/>.
/// </summary>
@ -40,7 +39,8 @@ namespace LibHac.FsService
if (FsClient.IsSdCardInserted())
FsClient.SetSdCardAccessibility(true);
SaveDataIndexerManager = new SaveDataIndexerManager(FsClient, SaveIndexerId);
FsProxyCore.SetSaveDataIndexerManager(new SaveDataIndexerManager(FsClient, SaveIndexerId,
new ArrayPoolMemoryResource(), new SdHandleManager(), false));
fsProxy.CleanUpTemporaryStorage().IgnoreResult();
}

View File

@ -0,0 +1,10 @@
using LibHac.FsService.Storage;
namespace LibHac.FsService
{
public interface IDeviceHandleManager
{
Result GetHandle(out StorageDeviceHandle handle);
bool IsValid(in StorageDeviceHandle handle);
}
}

View File

@ -42,14 +42,14 @@ namespace LibHac.FsService
Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(Span<byte> extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId);
Result ReadSaveDataFileSystemExtraData(Span<byte> extraDataBuffer, ulong saveDataId);
Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer);
Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader);
Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId);
Result OpenSaveDataInfoReaderOnlyCacheStorage(out ISaveDataInfoReader infoReader);
Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader);
Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId);
Result OpenSaveDataInfoReaderOnlyCacheStorage(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader);
Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId);
Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId);
Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer, ReadOnlySpan<byte> maskBuffer);
Result FindSaveDataWithFilter(out long count, Span<byte> saveDataInfoBuffer, SaveDataSpaceId spaceId, ref SaveDataFilter filter);
Result OpenSaveDataInfoReaderWithFilter(out ISaveDataInfoReader infoReader, SaveDataSpaceId spaceId, ref SaveDataFilter filter);
Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader, SaveDataSpaceId spaceId, ref SaveDataFilter filter);
Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(Span<byte> extraDataBuffer, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result WriteSaveDataFileSystemExtraDataBySaveDataAttribute(ref SaveDataAttribute attribute, SaveDataSpaceId spaceId, ReadOnlySpan<byte> extraDataBuffer, ReadOnlySpan<byte> maskBuffer);
Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, SaveDataMetaType type);

View File

@ -1,22 +1,25 @@
using LibHac.Fs;
using System;
using LibHac.Fs;
namespace LibHac.FsService
{
public interface ISaveDataIndexer
public interface ISaveDataIndexer : IDisposable
{
Result Commit();
Result Rollback();
Result Reset();
Result Add(out ulong saveDataId, ref SaveDataAttribute key);
Result Publish(out ulong saveDataId, ref SaveDataAttribute key);
Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key);
Result AddSystemSaveData(ref SaveDataAttribute key);
bool IsFull();
Result PutStaticSaveDataIdIndex(ref SaveDataAttribute key);
bool IsRemainedReservedOnly();
Result Delete(ulong saveDataId);
Result SetSpaceId(ulong saveDataId, SaveDataSpaceId spaceId);
Result SetSize(ulong saveDataId, long size);
Result SetState(ulong saveDataId, SaveDataState state);
Result GetKey(out SaveDataAttribute key, ulong saveDataId);
Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId);
int GetCount();
Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader);
Result GetValue(out SaveDataIndexerValue value, ulong saveDataId);
Result SetValue(ref SaveDataAttribute key, ref SaveDataIndexerValue value);
int GetIndexCount();
Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader);
}
}

View File

@ -0,0 +1,11 @@
using LibHac.Fs;
namespace LibHac.FsService
{
public interface ISaveDataIndexerManager
{
Result OpenAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId);
void ResetTemporaryStorageIndexer(SaveDataSpaceId spaceId);
void InvalidateSdCardIndexer(SaveDataSpaceId spaceId);
}
}

View File

@ -4,6 +4,6 @@ namespace LibHac.FsService
{
public interface ISaveDataInfoReader : IDisposable
{
Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer);
Result Read(out long readCount, Span<byte> saveDataInfoBuffer);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,11 @@ namespace LibHac.FsService
return Result.Success;
}
public Result Rollback()
{
return Result.Success;
}
public Result Reset()
{
lock (Locker)
@ -27,11 +32,11 @@ namespace LibHac.FsService
}
}
public Result Add(out ulong saveDataId, ref SaveDataAttribute key)
public Result Publish(out ulong saveDataId, ref SaveDataAttribute key)
{
lock (Locker)
{
if (IsKeyValueSet && _key.Equals(key))
if (IsKeyValueSet && _key == key)
{
saveDataId = default;
return ResultFs.SaveDataPathAlreadyExists.Log();
@ -52,7 +57,7 @@ namespace LibHac.FsService
{
lock (Locker)
{
if (IsKeyValueSet && _key.Equals(key))
if (IsKeyValueSet && _key == key)
{
value = _value;
return Result.Success;
@ -63,11 +68,11 @@ namespace LibHac.FsService
}
}
public Result AddSystemSaveData(ref SaveDataAttribute key)
public Result PutStaticSaveDataIdIndex(ref SaveDataAttribute key)
{
lock (Locker)
{
if (IsKeyValueSet && _key.Equals(key))
if (IsKeyValueSet && _key == key)
{
return ResultFs.SaveDataPathAlreadyExists.Log();
}
@ -81,7 +86,7 @@ namespace LibHac.FsService
}
}
public bool IsFull()
public bool IsRemainedReservedOnly()
{
return false;
}
@ -159,7 +164,7 @@ namespace LibHac.FsService
}
}
public Result GetBySaveDataId(out SaveDataIndexerValue value, ulong saveDataId)
public Result GetValue(out SaveDataIndexerValue value, ulong saveDataId)
{
lock (Locker)
{
@ -174,22 +179,40 @@ namespace LibHac.FsService
}
}
public int GetCount()
public Result SetValue(ref SaveDataAttribute key, ref SaveDataIndexerValue value)
{
lock (Locker)
{
if (IsKeyValueSet && _key == key)
{
_value = value;
return Result.Success;
}
return ResultFs.TargetNotFound.Log();
}
}
public int GetIndexCount()
{
return 1;
}
public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
{
SaveDataIndexerLiteInfoReader reader;
if (IsKeyValueSet)
{
infoReader = new SaveDataIndexerLiteInfoReader(ref _key, ref _value);
reader = new SaveDataIndexerLiteInfoReader(ref _key, ref _value);
}
else
{
infoReader = new SaveDataIndexerLiteInfoReader();
reader = new SaveDataIndexerLiteInfoReader();
}
infoReader = new ReferenceCountedDisposable<ISaveDataInfoReader>(reader);
return Result.Success;
}
@ -205,14 +228,14 @@ namespace LibHac.FsService
public SaveDataIndexerLiteInfoReader(ref SaveDataAttribute key, ref SaveDataIndexerValue value)
{
SaveDataIndexer.GetSaveDataInfo(out _info, ref key, ref value);
SaveDataIndexer.GenerateSaveDataInfo(out _info, in key, in value);
}
public Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer)
public Result Read(out long readCount, Span<byte> saveDataInfoBuffer)
{
Span<SaveDataInfo> outInfo = MemoryMarshal.Cast<byte, SaveDataInfo>(saveDataInfoBuffer);
// Nintendo doesn't check if the buffer is too small here
// Note: Nintendo doesn't check if the buffer is too small here
if (_finishedIterating || outInfo.IsEmpty)
{
readCount = 0;
@ -229,5 +252,7 @@ namespace LibHac.FsService
public void Dispose() { }
}
public void Dispose() { }
}
}

View File

@ -1,28 +1,51 @@
using System.Threading;
using System;
using System.Threading;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.FsService.Storage;
namespace LibHac.FsService
{
internal class SaveDataIndexerManager
internal class SaveDataIndexerManager : ISaveDataIndexerManager
{
private FileSystemClient FsClient { get; }
private MemoryResource MemoryResource { get; }
private ulong SaveDataId { get; }
private IndexerHolder _bisIndexer = new IndexerHolder(new object());
private IndexerHolder _sdCardIndexer = new IndexerHolder(new object());
private IndexerHolder _tempIndexer = new IndexerHolder(new object());
private IndexerHolder _sdCardIndexer = new IndexerHolder(new object());
private StorageDeviceHandle _sdCardHandle;
private IDeviceHandleManager _sdCardHandleManager;
private IndexerHolder _safeIndexer = new IndexerHolder(new object());
private IndexerHolder _properSystemIndexer = new IndexerHolder(new object());
public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId)
private bool IsBisUserRedirectionEnabled { get; }
public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId, MemoryResource memoryResource,
IDeviceHandleManager sdCardHandleManager, bool isBisUserRedirectionEnabled)
{
FsClient = fsClient;
SaveDataId = saveDataId;
MemoryResource = memoryResource;
_sdCardHandleManager = sdCardHandleManager;
IsBisUserRedirectionEnabled = isBisUserRedirectionEnabled;
_tempIndexer.Indexer = new SaveDataIndexerLite();
}
public Result GetSaveDataIndexer(out SaveDataIndexerReader reader, SaveDataSpaceId spaceId)
public Result OpenAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId)
{
neededInit = false;
if (IsBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User)
{
spaceId = SaveDataSpaceId.ProperSystem;
}
switch (spaceId)
{
case SaveDataSpaceId.System:
@ -31,70 +54,106 @@ namespace LibHac.FsService
if (!_bisIndexer.IsInitialized)
{
_bisIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDb".ToU8Span(), SaveDataSpaceId.System, SaveDataId);
_bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName),
SaveDataSpaceId.System, SaveDataId, MemoryResource);
neededInit = true;
}
reader = new SaveDataIndexerReader(_bisIndexer.Indexer, _bisIndexer.Locker);
accessor = new SaveDataIndexerAccessor(_bisIndexer.Indexer, _bisIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.SdSystem:
case SaveDataSpaceId.SdCache:
Monitor.Enter(_sdCardIndexer.Locker);
// todo: Missing reinitialize if SD handle is old
// 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, "saveDataIxrDbSd".ToU8Span(), SaveDataSpaceId.SdSystem, SaveDataId);
_sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName),
SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource);
_sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult();
neededInit = true;
}
reader = new SaveDataIndexerReader(_sdCardIndexer.Indexer, _sdCardIndexer.Locker);
accessor = new SaveDataIndexerAccessor(_sdCardIndexer.Indexer, _sdCardIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.Temporary:
Monitor.Enter(_tempIndexer.Locker);
if (!_tempIndexer.IsInitialized)
{
_tempIndexer.Indexer = new SaveDataIndexerLite();
}
reader = new SaveDataIndexerReader(_tempIndexer.Indexer, _tempIndexer.Locker);
accessor = new SaveDataIndexerAccessor(_tempIndexer.Indexer, _tempIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.ProperSystem:
Monitor.Enter(_safeIndexer.Locker);
if (!_safeIndexer.IsInitialized)
{
_safeIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbPr".ToU8Span(), SaveDataSpaceId.ProperSystem, SaveDataId);
}
reader = new SaveDataIndexerReader(_safeIndexer.Indexer, _safeIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.SafeMode:
Monitor.Enter(_properSystemIndexer.Locker);
if (!_properSystemIndexer.IsInitialized)
{
_properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbSf".ToU8Span(), SaveDataSpaceId.SafeMode, SaveDataId);
_properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(ProperSystemIndexerMountName),
SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource);
neededInit = true;
}
reader = new SaveDataIndexerReader(_properSystemIndexer.Indexer, _properSystemIndexer.Locker);
accessor = new SaveDataIndexerAccessor(_properSystemIndexer.Indexer, _properSystemIndexer.Locker);
return Result.Success;
case SaveDataSpaceId.SafeMode:
Monitor.Enter(_safeIndexer.Locker);
if (!_safeIndexer.IsInitialized)
{
_safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName),
SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource);
neededInit = true;
}
accessor = new SaveDataIndexerAccessor(_safeIndexer.Indexer, _safeIndexer.Locker);
return Result.Success;
default:
reader = default;
accessor = default;
return ResultFs.InvalidArgument.Log();
}
}
internal void ResetSdCardIndexer()
public void ResetTemporaryStorageIndexer(SaveDataSpaceId spaceId)
{
if (spaceId != SaveDataSpaceId.Temporary)
{
Abort.UnexpectedDefault();
}
// ReSharper disable once RedundantAssignment
Result rc = _tempIndexer.Indexer.Reset();
Assert.AssertTrue(rc.IsSuccess());
}
public void InvalidateSdCardIndexer(SaveDataSpaceId spaceId)
{
// Note: Nintendo doesn't lock when doing this operation
lock (_sdCardIndexer.Locker)
{
_sdCardIndexer.Indexer = null;
if (spaceId != SaveDataSpaceId.SdCache && spaceId != SaveDataSpaceId.SdSystem)
{
Abort.UnexpectedDefault();
}
if (_sdCardIndexer.IsInitialized)
{
_sdCardIndexer.Indexer.Dispose();
_sdCardIndexer.Indexer = null;
}
}
}
@ -111,30 +170,56 @@ namespace LibHac.FsService
public bool IsInitialized => Indexer != null;
}
private static ReadOnlySpan<byte> SystemIndexerMountName => // saveDataIxrDb
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
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
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
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) 'f'
};
}
public ref struct SaveDataIndexerReader
public class SaveDataIndexerAccessor : IDisposable
{
private bool _isInitialized;
private object _locker;
public ISaveDataIndexer Indexer { get; }
private object Locker { get; }
private bool HasLock { get; set; }
public ISaveDataIndexer Indexer;
public bool IsInitialized => _isInitialized;
internal SaveDataIndexerReader(ISaveDataIndexer indexer, object locker)
public SaveDataIndexerAccessor(ISaveDataIndexer indexer, object locker)
{
_isInitialized = true;
_locker = locker;
Indexer = indexer;
Locker = locker;
HasLock = true;
}
public void Dispose()
{
if (_isInitialized)
if (HasLock)
{
Monitor.Exit(_locker);
Monitor.Exit(Locker);
_isInitialized = false;
HasLock = false;
}
}
}

View File

@ -8,16 +8,16 @@ namespace LibHac.FsService
{
internal class SaveDataInfoFilterReader : ISaveDataInfoReader
{
private ISaveDataInfoReader Reader { get; }
private ReferenceCountedDisposable<ISaveDataInfoReader> Reader { get; }
private SaveDataFilterInternal Filter { get; }
public SaveDataInfoFilterReader(ISaveDataInfoReader reader, ref SaveDataFilterInternal filter)
public SaveDataInfoFilterReader(ReferenceCountedDisposable<ISaveDataInfoReader> reader, ref SaveDataFilterInternal filter)
{
Reader = reader;
Filter = filter;
}
public Result ReadSaveDataInfo(out long readCount, Span<byte> saveDataInfoBuffer)
public Result Read(out long readCount, Span<byte> saveDataInfoBuffer)
{
readCount = default;
@ -26,11 +26,12 @@ namespace LibHac.FsService
SaveDataInfo tempInfo = default;
Span<byte> tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo);
ISaveDataInfoReader reader = Reader.Target;
int count = 0;
while (count < outInfo.Length)
{
Result rc = Reader.ReadSaveDataInfo(out long baseReadCount, tempInfoBytes);
Result rc = reader.Read(out long baseReadCount, tempInfoBytes);
if (rc.IsFailure()) return rc;
if (baseReadCount == 0) break;

View File

@ -0,0 +1,20 @@
namespace LibHac.FsService.Storage
{
public static class SdCardManagement
{
public static Result GetCurrentSdCardHandle(out StorageDeviceHandle handle)
{
// todo: StorageDevice interfaces
handle = new StorageDeviceHandle(1, StorageDevicePortId.SdCard);
return Result.Success;
}
public static Result IsSdCardHandleValid(out bool isValid, in StorageDeviceHandle handle)
{
// todo: StorageDevice interfaces
isValid = handle.PortId == StorageDevicePortId.SdCard;
return Result.Success;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
namespace LibHac.FsService.Storage
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public readonly struct StorageDeviceHandle : IEquatable<StorageDeviceHandle>
{
public readonly uint Value;
public readonly StorageDevicePortId PortId;
public StorageDeviceHandle(uint value, StorageDevicePortId portId)
{
Value = value;
PortId = portId;
}
public override bool Equals(object obj) => obj is StorageDeviceHandle other && Equals(other);
public bool Equals(StorageDeviceHandle other) => Value == other.Value && PortId == other.PortId;
public static bool operator ==(StorageDeviceHandle left, StorageDeviceHandle right) => left.Equals(right);
public static bool operator !=(StorageDeviceHandle left, StorageDeviceHandle right) => !(left == right);
public override int GetHashCode() => HashCode.Combine(Value, (int) PortId);
}
}

View File

@ -0,0 +1,10 @@
namespace LibHac.FsService.Storage
{
public enum StorageDevicePortId : byte
{
Invalid = 0,
Mmc = 1,
SdCard = 2,
GameCard = 3
}
}

View File

@ -25,8 +25,8 @@ namespace LibHac.Kvdb
private static ReadOnlySpan<byte> ArchiveFileName => // /imkvdb.arc
new[]
{
(byte) '/', (byte) 'i', (byte) 'm', (byte) 'k', (byte) 'v', (byte) 'd', (byte) 'b', (byte) '.',
(byte) 'a', (byte) 'r', (byte) 'c'
(byte) '/', (byte) 'i', (byte) 'm', (byte) 'k', (byte) 'v', (byte) 'd', (byte) 'b', (byte) '.',
(byte) 'a', (byte) 'r', (byte) 'c'
};
public int Count => _index.Count;
@ -646,6 +646,11 @@ namespace LibHac.Kvdb
public ref KeyValue Get() => ref _entries[_index];
public Span<byte> GetValue() => _entries[_index].Value.Get();
public ref T GetValue<T>() where T : unmanaged
{
return ref SpanHelpers.AsStruct<T>(_entries[_index].Value.Get());
}
public void Next() => _index++;
public bool IsEnd() => _index == _length;

View File

@ -134,6 +134,54 @@ namespace LibHac
=> TryAddReferenceImpl(_instance, _boxedReferenceCount) ??
throw new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>));
/// <summary>
/// Increments the reference count for the disposable object, and returns a new disposable reference to it
/// of type <typeparamref name="TTo"/>. The type of the disposable object must be compatible with type
/// <typeparamref name="TTo"/>.
/// </summary>
/// <remarks>
/// <para>The returned object is an independent reference to the same underlying object. Disposing of the
/// returned value multiple times will only cause the reference count to be decreased once.</para>
/// </remarks>
/// <typeparam name="TTo">The type of the new reference to the disposable object.</typeparam>
/// <returns>A new <see cref="ReferenceCountedDisposable{TTo}"/> pointing to the same underlying object, if it
/// has not yet been disposed; otherwise, <see cref="ObjectDisposedException"/> is thrown if this reference
/// to the underlying object has already been disposed, and <see cref="InvalidCastException"/> is thrown if
/// <typeparamref name="T"/> is not compatible with <typeparamref name="TTo"/>.</returns>
public ReferenceCountedDisposable<TTo> AddReference<TTo>() where TTo : class, IDisposable
{
ReferenceCountedDisposable<TTo>? newReference =
TryAddReferenceImpl<T, TTo>(_instance, _boxedReferenceCount, out CreateResult result);
if (newReference != null)
{
return newReference;
}
throw result switch
{
CreateResult.Disposed => new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>)),
CreateResult.NotCastable => new InvalidCastException(),
_ => new NotSupportedException("This exception should never be thrown.")
};
}
/// <summary>
/// Increments the reference count for the disposable object, and returns a new disposable reference to it
/// of type <typeparamref name="TTo"/>. The type of the disposable object must be compatible with type
/// <typeparamref name="TTo"/>.
/// </summary>
/// <remarks>
/// <para>The returned object is an independent reference to the same underlying object. Disposing of the
/// returned value multiple times will only cause the reference count to be decreased once.</para>
/// </remarks>
/// <returns>A new <see cref="ReferenceCountedDisposable{TTo}"/> pointing to the same underlying object, if it
/// has not yet been disposed, and <typeparamref name="T"/> is compatible with <typeparamref name="TTo"/>;
/// otherwise, <see langword="null"/> if this reference to the underlying object has already been disposed.
/// </returns>
public ReferenceCountedDisposable<TTo>? TryAddReference<TTo>() where TTo : class, IDisposable
=> TryAddReferenceImpl<T, TTo>(_instance, _boxedReferenceCount, out _);
/// <summary>
/// Increments the reference count for the disposable object, and returns a new disposable reference to it.
/// </summary>
@ -179,6 +227,51 @@ namespace LibHac
}
}
/// <summary>
/// Provides the implementation for <see cref="TryAddReference"/> and
/// <see cref="WeakReference.TryAddReference"/> when casting the underlying object to another type.
/// </summary>
private static ReferenceCountedDisposable<TTo>? TryAddReferenceImpl<TFrom, TTo>(TFrom? target, StrongBox<int> referenceCount,
out CreateResult result)
where TFrom : class, IDisposable
where TTo : class, IDisposable
{
lock (referenceCount)
{
if (referenceCount.Value == 0)
{
// The target is already disposed, and cannot be reused
result = CreateResult.Disposed;
return null;
}
if (target == null)
{
// The current reference has been disposed, so even though it isn't disposed yet we don't have a
// reference to the target
result = CreateResult.Disposed;
return null;
}
if (!(target is TTo castedTarget))
{
// The target cannot be casted to type TTo
result = CreateResult.NotCastable;
return null;
}
checked
{
referenceCount.Value++;
}
// Must return a new instance, in order for the Dispose operation on each individual instance to
// be idempotent.
result = CreateResult.Success;
return new ReferenceCountedDisposable<TTo>(castedTarget, referenceCount);
}
}
/// <summary>
/// Releases the current reference, causing the underlying object to be disposed if this was the last
/// reference.
@ -212,6 +305,16 @@ namespace LibHac
instanceToDispose?.Dispose();
}
/// <summary>
/// Represents the result of the TryAddReferenceImpl method.
/// </summary>
private enum CreateResult
{
Success,
Disposed,
NotCastable
}
/// <summary>
/// Represents a weak reference to a <see cref="ReferenceCountedDisposable{T}"/> which is capable of
/// obtaining a new counted reference up until the point when the object is no longer accessible.