From a3a226599b178174a6bd562859f4abf14a103b7f Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Wed, 10 Jul 2024 18:08:32 -0700 Subject: [PATCH] Implement SaveDataMover --- src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs | 20 + src/LibHac/FsSrv/Impl/SaveDataMover.cs | 376 +++++++++++++++++- .../FsSrv/Impl/SaveDataTransferUtility.cs | 3 +- src/LibHac/FsSrv/Sf/ISaveDataMover.cs | 8 +- 4 files changed, 387 insertions(+), 20 deletions(-) diff --git a/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs b/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs index 6492747a..50cfdcd4 100644 --- a/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs +++ b/src/LibHac/Fs/Impl/SaveDataMetaPolicy.cs @@ -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(); } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/SaveDataMover.cs b/src/LibHac/FsSrv/Impl/SaveDataMover.cs index dcdd47d6..9a8dd216 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataMover.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataMover.cs @@ -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; +/// +/// Bulk moves save data from one save data space to another. +/// +/// To use this class, call for each save data to be moved. After all save data +/// have been registered, repeatedly call until it returns 0 for the remaining size. +/// Based on nnSdk 18.3.0 (FS 18.0.0) public class SaveDataMover : ISaveDataMover { private enum State @@ -25,13 +34,13 @@ public class SaveDataMover : ISaveDataMover private SharedRef _transferInterface; private Optional _duplicator; - private SaveDataSpaceId _sourceSpaceId; - private Array128 _sourceSaveIds; - private SaveDataSpaceId _destinationSpaceId; - private Array128 _destinationSaveIds; + private SaveDataSpaceId _srcSpaceId; + private Array128 _srcSaveIds; + private SaveDataSpaceId _dstSpaceId; + private Array128 _dstSaveIds; // private TransferMemory _transferMemory; private Memory _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.CreateCopy(in transferInterface); + _duplicator = new Optional(); + + _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(); + using (var srcInternalStorageAccessor = new SharedRef()) + { + 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(); + using var dstFs = new SharedRef(); + using (var dstInternalStorageAccessor = new SharedRef()) + { + 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 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()) + { + 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(); + 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(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(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(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 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 saveDataIds, SaveDataState state) + { + using var accessor = new UniqueRef(); + Result r = _transferInterface.Get.OpenSaveDataIndexerAccessor(ref accessor.Ref, spaceId); + if (r.IsFailure()) return r.Miss(); + + ReadOnlySpan 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(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; + } } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/SaveDataTransferUtility.cs b/src/LibHac/FsSrv/Impl/SaveDataTransferUtility.cs index 91763492..6f56580c 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataTransferUtility.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataTransferUtility.cs @@ -329,7 +329,8 @@ public static partial class SaveDataTransferUtilityGlobalMethods public static Result OpenSaveDataInternalStorageAccessor(this FileSystemServer fsSrv, ref SharedRef 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, diff --git a/src/LibHac/FsSrv/Sf/ISaveDataMover.cs b/src/LibHac/FsSrv/Sf/ISaveDataMover.cs index 445c8084..8c5c7077 100644 --- a/src/LibHac/FsSrv/Sf/ISaveDataMover.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataMover.cs @@ -2,9 +2,15 @@ namespace LibHac.FsSrv.Sf; +/// +/// Bulk moves save data from one save data space to another. +/// +/// To use this class, call for each save data to be moved. After all save data +/// have been registered, repeatedly call until it returns 0 for the remaining size. +/// Based on nnSdk 18.3.0 (FS 18.0.0) public interface ISaveDataMover : IDisposable { Result Register(ulong saveDataId); - Result Process(out long remainingSize, long sizeToProcess); + Result Process(out long outRemainingSize, long sizeToProcess); Result Cancel(); } \ No newline at end of file