Implement SaveDataMover

This commit is contained in:
Alex Barney 2024-07-10 18:08:32 -07:00
parent c3981d0e16
commit a3a226599b
4 changed files with 387 additions and 20 deletions

View file

@ -40,4 +40,24 @@ internal readonly struct SaveDataMetaPolicy
GenerateMetaInfo(out SaveDataMetaInfo metaInfo);
return metaInfo.Type;
}
}
internal readonly struct SaveDataMetaPolicyForSaveDataTransfer
{
private readonly SaveDataMetaPolicy _base;
public SaveDataMetaPolicyForSaveDataTransfer(SaveDataType saveType) => _base = new SaveDataMetaPolicy(saveType);
public void GenerateMetaInfo(out SaveDataMetaInfo metaInfo) => _base.GenerateMetaInfo(out metaInfo);
public long GetSaveDataMetaSize() => _base.GetSaveDataMetaSize();
public SaveDataMetaType GetSaveDataMetaType() => _base.GetSaveDataMetaType();
}
internal readonly struct SaveDataMetaPolicyForSaveDataTransferVersion2
{
private readonly SaveDataMetaPolicy _base;
public SaveDataMetaPolicyForSaveDataTransferVersion2(SaveDataType saveType) => _base = new SaveDataMetaPolicy(saveType);
public void GenerateMetaInfo(out SaveDataMetaInfo metaInfo) => _base.GenerateMetaInfo(out metaInfo);
public long GetSaveDataMetaSize() => _base.GetSaveDataMetaSize();
public SaveDataMetaType GetSaveDataMetaType() => _base.GetSaveDataMetaType();
}

View file

@ -1,16 +1,25 @@
// ReSharper disable UnusedMember.Local UnusedType.Local
#pragma warning disable CS0169 // Field is never used
using System;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using LibHac.Sf;
using LibHac.Util;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IStorage = LibHac.Fs.IStorage;
namespace LibHac.FsSrv.Impl;
/// <summary>
/// Bulk moves save data from one save data space to another.
/// </summary>
/// <remarks><para>To use this class, call <see cref="Register"/> for each save data to be moved. After all save data
/// have been registered, repeatedly call <see cref="Process"/> until it returns 0 for the remaining size.</para>
/// <para>Based on nnSdk 18.3.0 (FS 18.0.0)</para></remarks>
public class SaveDataMover : ISaveDataMover
{
private enum State
@ -25,13 +34,13 @@ public class SaveDataMover : ISaveDataMover
private SharedRef<ISaveDataTransferCoreInterface> _transferInterface;
private Optional<StorageDuplicator> _duplicator;
private SaveDataSpaceId _sourceSpaceId;
private Array128<ulong> _sourceSaveIds;
private SaveDataSpaceId _destinationSpaceId;
private Array128<ulong> _destinationSaveIds;
private SaveDataSpaceId _srcSpaceId;
private Array128<ulong> _srcSaveIds;
private SaveDataSpaceId _dstSpaceId;
private Array128<ulong> _dstSaveIds;
// private TransferMemory _transferMemory;
private Memory<byte> _workBuffer;
// private ulong _transferMemorySize;
private ulong _transferMemorySize;
private int _saveCount;
private int _currentSaveIndex;
private long _remainingSize;
@ -42,46 +51,377 @@ public class SaveDataMover : ISaveDataMover
SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle transferMemoryHandle,
ulong transferMemorySize)
{
throw new NotImplementedException();
_transferInterface = SharedRef<ISaveDataTransferCoreInterface>.CreateCopy(in transferInterface);
_duplicator = new Optional<StorageDuplicator>();
_srcSpaceId = sourceSpaceId;
_srcSaveIds = default;
_dstSpaceId = destinationSpaceId;
_dstSaveIds = default;
// Missing: Attach transfer memory
_workBuffer = default;
_transferMemorySize = transferMemorySize;
_saveCount = 0;
_currentSaveIndex = 0;
_remainingSize = 0;
_state = State.Initial;
_mutex = new SdkMutex();
transferMemoryHandle.Detach();
}
public void Dispose()
{
throw new NotImplementedException();
// Missing: Destroy transfer memory
if (_duplicator.HasValue)
{
_duplicator.Value.Dispose();
_duplicator.Clear();
}
_transferInterface.Destroy();
}
private Result OpenDuplicator()
{
throw new NotImplementedException();
Result res;
using var srcFileStorage = new SharedRef<IStorage>();
using (var srcInternalStorageAccessor = new SharedRef<SaveDataInternalStorageAccessor>())
{
res = SaveDataTransferUtilityGlobalMethods.OpenSaveDataInternalStorageAccessor(null,
ref srcInternalStorageAccessor.Ref, _srcSpaceId, _srcSaveIds[_currentSaveIndex]);
if (res.IsFailure()) return res.Miss();
res = srcInternalStorageAccessor.Get.Initialize(coreInterface: _transferInterface.Get,
isTemporaryTransferSave: false, hashSalt: default);
if (res.IsFailure()) return res.Miss();
res = srcInternalStorageAccessor.Get.OpenConcatenationStorage(ref srcFileStorage.Ref);
if (res.IsFailure()) return res.Miss();
}
using var dstFileStorage = new SharedRef<IStorage>();
using var dstFs = new SharedRef<IFileSystem>();
using (var dstInternalStorageAccessor = new SharedRef<SaveDataInternalStorageAccessor>())
{
res = SaveDataTransferUtilityGlobalMethods.OpenSaveDataInternalStorageAccessor(null,
ref dstInternalStorageAccessor.Ref, _dstSpaceId, _dstSaveIds[_currentSaveIndex]);
if (res.IsFailure()) return res.Miss();
res = dstInternalStorageAccessor.Get.Initialize(coreInterface: _transferInterface.Get,
isTemporaryTransferSave: false, hashSalt: default);
if (res.IsFailure()) return res.Miss();
res = dstInternalStorageAccessor.Get.OpenConcatenationStorage(ref dstFileStorage.Ref);
if (res.IsFailure()) return res.Miss();
using SharedRef<IFileSystem> fs = dstInternalStorageAccessor.Get.GetSaveDataInternalFileSystem();
dstFs.SetByMove(ref fs.Ref);
}
_duplicator.Set(new StorageDuplicator(in srcFileStorage, in dstFileStorage, in dstFs));
res = _duplicator.Value.Initialize();
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
private Result Initialize()
{
throw new NotImplementedException();
// Missing: Map transfer memory
_remainingSize = 0;
for (int i = 0; i < _saveCount; i++)
{
Result res = _transferInterface.Get.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData srcExtraData,
_srcSpaceId, _srcSaveIds[i], isTemporarySaveData: false);
if (res.IsFailure()) return res.Miss();
if (!SaveDataProperties.IsValidSpaceIdForSaveDataMover(srcExtraData.Attribute.Type, _dstSpaceId))
return ResultFs.InvalidArgument.Log();
if (_srcSpaceId == _dstSpaceId)
return ResultFs.InvalidArgument.Log();
Unsafe.SkipInit(out IntegrityParam integrityParam);
using (var srcInternalStorageAccessor = new SharedRef<SaveDataInternalStorageAccessor>())
{
res = SaveDataTransferUtilityGlobalMethods.OpenSaveDataInternalStorageAccessor(null,
ref srcInternalStorageAccessor.Ref, _srcSpaceId, _srcSaveIds[_currentSaveIndex]);
if (res.IsFailure()) return res.Miss();
res = srcInternalStorageAccessor.Get.Initialize(coreInterface: _transferInterface.Get,
isTemporaryTransferSave: false, hashSalt: default);
if (res.IsFailure()) return res.Miss();
res = srcInternalStorageAccessor.Get.GetIntegrityParam(ref integrityParam);
if (res.IsFailure()) return res.Miss();
using var srcFileStorage = new SharedRef<IStorage>();
res = srcInternalStorageAccessor.Get.OpenConcatenationStorage(ref srcFileStorage.Ref);
if (res.IsFailure()) return res.Miss();
res = srcFileStorage.Get.GetSize(out long size);
if (res.IsFailure()) return res.Miss();
_remainingSize += size;
}
SaveDataAttribute attribute = srcExtraData.Attribute;
res = SaveDataCreationInfo.Make(out SaveDataCreationInfo creationInfo, srcExtraData.DataSize,
srcExtraData.JournalSize, srcExtraData.OwnerId, srcExtraData.Flags, _dstSpaceId);
if (res.IsFailure()) return res.Miss();
new SaveDataMetaPolicyForSaveDataTransferVersion2(srcExtraData.Attribute.Type).GenerateMetaInfo(out SaveDataMetaInfo metaInfo);
res = _transferInterface.Get.CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo,
new Optional<HashSalt>(in integrityParam.IntegritySeed), leaveUnfinalized: true);
if (res.IsFailure()) return res.Miss();
res = _transferInterface.Get.GetSaveDataInfo(out SaveDataInfo info, _dstSpaceId, attribute);
if (res.IsFailure()) return res.Miss();
_dstSaveIds[i] = info.SaveDataId;
}
return OpenDuplicator().Ret();
}
public Result Register(ulong saveDataId)
{
throw new NotImplementedException();
bool isSuccess = false;
try
{
using var scopedLock = new ScopedLock<SdkMutex>(ref _mutex);
if (_saveCount >= _srcSaveIds.Length)
return ResultFs.PreconditionViolation.Log();
switch (_state)
{
case State.Initial:
case State.Registered:
_srcSaveIds[_saveCount] = saveDataId;
_saveCount++;
_state = State.Registered;
isSuccess = true;
return Result.Success;
case State.Copying:
case State.Finished:
case State.Fatal:
case State.Canceled:
return ResultFs.PreconditionViolation.Log();
default:
Abort.UnexpectedDefault();
return Result.Success;
}
}
finally
{
if (!isSuccess)
ChangeStateToFatal();
}
}
public Result Process(out long remainingSize, long sizeToProcess)
public Result Process(out long outRemainingSize, long sizeToProcess)
{
throw new NotImplementedException();
UnsafeHelpers.SkipParamInit(out outRemainingSize);
bool isSuccess = false;
try
{
using var scopedLock = new ScopedLock<SdkMutex>(ref _mutex);
if ((long)_transferMemorySize < sizeToProcess)
return ResultFs.InvalidSize.Log();
Result res;
switch (_state)
{
case State.Initial:
return ResultFs.PreconditionViolation.Log();
case State.Registered:
res = Initialize();
if (res.IsFailure()) return res.Miss();
_state = State.Copying;
goto case State.Copying;
case State.Copying:
Assert.SdkAssert(!_workBuffer.IsEmpty);
Span<byte> workBuffer = _workBuffer.Span.Slice(0, (int)_transferMemorySize);
// Call the current duplicator with a size of zero to get the remaining size for this save before processing
res = _duplicator.Value.ProcessDuplication(out long remainingSizeBefore, workBuffer, 0);
if (res.IsFailure()) return res.Miss();
res = _duplicator.Value.ProcessDuplication(out long remainingSize, workBuffer, sizeToProcess);
if (res.IsFailure()) return res.Miss();
// Update the total remaining size with how much data was processed in this iteration
_remainingSize -= remainingSizeBefore - remainingSize;
if (remainingSize == 0)
{
// When finished copying this save, finalize and close the duplicator
res = _duplicator.Value.FinalizeObject();
if (res.IsFailure()) return res.Miss();
_duplicator.Value.Dispose();
_duplicator.Clear();
// Copy the extra data from the old save to the new save
res = _transferInterface.Get.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData,
_srcSpaceId, _srcSaveIds[_currentSaveIndex], isTemporarySaveData: false);
if (res.IsFailure()) return res.Miss();
res = _transferInterface.Get.WriteSaveDataFileSystemExtraDataCore(_dstSpaceId,
_dstSaveIds[_currentSaveIndex], in extraData, extraData.Attribute.Type,
updateTimeStamp: false);
if (res.IsFailure()) return res.Miss();
if (_currentSaveIndex == _saveCount - 1)
{
// If this was the last save to copy, finalize the entire move operation
FinalizeObject().IgnoreResult();
_state = State.Finished;
}
else
{
// If there are still saves to copy, open the duplicator for the next one
_currentSaveIndex++;
res = OpenDuplicator();
if (res.IsFailure()) return res.Miss();
}
}
outRemainingSize = _remainingSize;
isSuccess = true;
return Result.Success;
case State.Finished:
outRemainingSize = 0;
isSuccess = true;
return Result.Success;
case State.Fatal:
return ResultFs.PreconditionViolation.Log();
case State.Canceled:
return ResultFs.PreconditionViolation.Log();
default:
Abort.UnexpectedDefault();
return Result.Success;
}
}
finally
{
if (!isSuccess)
ChangeStateToFatal();
}
}
private Result FinalizeObject()
{
throw new NotImplementedException();
Assert.SdkAssert(_mutex.IsLockedByCurrentThread());
Result res = SetStates(_dstSpaceId, _dstSaveIds, SaveDataState.Normal);
if (res.IsFailure()) return res.Miss();
res = SetStates(_srcSpaceId, _srcSaveIds, SaveDataState.Processing);
if (res.IsFailure()) return res.Miss();
for (int i = 0; i < _saveCount; i++)
{
res = _transferInterface.Get.DeleteSaveDataFileSystemBySaveDataSpaceId(_srcSpaceId, _srcSaveIds[i]);
if (res.IsFailure()) return res.Miss();
}
_state = State.Finished;
return Result.Success;
Result SetStates(SaveDataSpaceId spaceId, ReadOnlySpan<ulong> saveDataIds, SaveDataState state)
{
using var accessor = new UniqueRef<SaveDataIndexerAccessor>();
Result r = _transferInterface.Get.OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId);
if (r.IsFailure()) return r.Miss();
ReadOnlySpan<ulong> ids = saveDataIds.Slice(0, _saveCount);
for (int i = 0; i < ids.Length; i++)
{
r = accessor.Get.GetInterface().SetState(ids[i], state);
if (r.IsFailure()) return r.Miss();
}
r = accessor.Get.GetInterface().Commit();
if (r.IsFailure()) return r.Miss();
return Result.Success;
}
}
public Result Cancel()
{
throw new NotImplementedException();
Result DoCancel()
{
_duplicator.Value.Dispose();
_duplicator.Clear();
Result result = Result.Success;
for (int i = 0; i < _saveCount; i++)
{
Result res = _transferInterface.Get.DeleteSaveDataFileSystemBySaveDataSpaceId(_dstSpaceId, _dstSaveIds[_saveCount]);
if (!res.IsSuccess() && !ResultFs.TargetNotFound.Includes(res))
{
if (result.IsSuccess())
result = res;
}
}
_state = State.Canceled;
return result.Ret();
}
using var scopedLock = new ScopedLock<SdkMutex>(ref _mutex);
switch (_state)
{
case State.Initial:
case State.Registered:
case State.Copying:
case State.Fatal:
return DoCancel().Ret();
case State.Finished:
case State.Canceled:
return ResultFs.PreconditionViolation.Log();
default:
Abort.UnexpectedDefault();
return Result.Success;
}
}
private void ChangeStateToFatal()
{
throw new NotImplementedException();
switch (_state)
{
case State.Initial:
case State.Registered:
case State.Copying:
_state = State.Fatal;
break;
case State.Finished:
case State.Fatal:
case State.Canceled:
break;
default:
Abort.UnexpectedDefault();
break;
}
}
}

View file

@ -329,7 +329,8 @@ public static partial class SaveDataTransferUtilityGlobalMethods
public static Result OpenSaveDataInternalStorageAccessor(this FileSystemServer fsSrv,
ref SharedRef<SaveDataInternalStorageAccessor> outAccessor, SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
outAccessor.Reset(new SaveDataInternalStorageAccessor(spaceId, saveDataId));
return Result.Success;
}
public static Result CommitConcatenatedSaveDataStorage(this FileSystemServer fsSrv, IFileSystem fileSystem,

View file

@ -2,9 +2,15 @@
namespace LibHac.FsSrv.Sf;
/// <summary>
/// Bulk moves save data from one save data space to another.
/// </summary>
/// <remarks><para>To use this class, call <see cref="Register"/> for each save data to be moved. After all save data
/// have been registered, repeatedly call <see cref="Process"/> until it returns 0 for the remaining size.</para>
/// <para>Based on nnSdk 18.3.0 (FS 18.0.0)</para></remarks>
public interface ISaveDataMover : IDisposable
{
Result Register(ulong saveDataId);
Result Process(out long remainingSize, long sizeToProcess);
Result Process(out long outRemainingSize, long sizeToProcess);
Result Cancel();
}