/// <summary> /// Creates and writes the initial commit context to a file. /// </summary> /// <param name="commitCount">The counter.</param> /// <param name="fileSystemCount">The number of file systems being committed.</param> /// <returns>The <see cref="Result"/> of the operation.</returns> public Result Create(long commitCount, int fileSystemCount) { IFile contextFile = null; try { // Open context file and create if it doesn't exist Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read); if (rc.IsFailure()) { if (!ResultFs.PathNotFound.Includes(rc)) { return(rc); } rc = _fileSystem.CreateFile(CommitContextFileName, CommitContextFileSize, CreateFileOptions.None); if (rc.IsFailure()) { return(rc); } rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read); if (rc.IsFailure()) { return(rc); } } } finally { contextFile?.Dispose(); } try { Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite); if (rc.IsFailure()) { return(rc); } _context.Version = CurrentCommitContextVersion; _context.State = CommitState.NotCommitted; _context.FileSystemCount = fileSystemCount; _context.Counter = commitCount; // Write the initial context to the file rc = contextFile.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None); if (rc.IsFailure()) { return(rc); } rc = contextFile.Flush(); if (rc.IsFailure()) { return(rc); } } finally { contextFile?.Dispose(); } return(_fileSystem.Commit()); }
// Commit() public ResultCode Commit(ServiceCtx context) { return((ResultCode)_fileSystem.Commit().Value); }
/// <summary> /// Tries to recover a multi-commit using the context in the provided file system. /// </summary> /// <param name="multiCommitInterface">The core interface used for multi-commits.</param> /// <param name="contextFs">The file system containing the multi-commit context file.</param> /// <param name="saveService">The save data service.</param> /// <returns></returns> private static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService) { if (multiCommitInterface is null) { return(ResultFs.InvalidArgument.Log()); } if (contextFs is null) { return(ResultFs.InvalidArgument.Log()); } // Keep track of the first error that occurs during the recovery Result recoveryResult = Result.Success; Result rc = RecoverCommit(multiCommitInterface, contextFs, saveService); if (rc.IsFailure()) { // Note: Yes, the next ~50 lines are exactly the same as the code in RecoverCommit except // for a single bool value. No, Nintendo doesn't split it out into its own function. int saveCount = 0; Span <SaveDataInfo> savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount]; SaveDataIndexerAccessor accessor = null; ReferenceCountedDisposable <SaveDataInfoReaderImpl> infoReader = null; try { rc = saveService.OpenSaveDataIndexerAccessor(out accessor, out _, SaveDataSpaceId.User); if (rc.IsFailure()) { return(rc); } rc = accessor.Indexer.OpenSaveDataInfoReader(out infoReader); if (rc.IsFailure()) { return(rc); } // Iterate through all the saves to find any provisionally committed save data while (true) { Unsafe.SkipInit(out SaveDataInfo info); rc = infoReader.Target.Read(out long readCount, OutBuffer.FromStruct(ref info)); if (rc.IsFailure()) { return(rc); } // Break once we're done iterating all save data if (readCount == 0) { break; } rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in info); // Note: Some saves could be missed if there are more than MaxFileSystemCount // provisionally committed saves. Not sure why Nintendo doesn't catch this. if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount) { savesToRecover[saveCount] = info; saveCount++; } } } finally { accessor?.Dispose(); infoReader?.Dispose(); } // Recover the saves by rolling them back to the previous commit. // All file systems will try to be recovered, even if one fails. // If any commits fail, the result from the first failed recovery will be returned. for (int i = 0; i < saveCount; i++) { rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], true); if (recoveryResult.IsSuccess() && rc.IsFailure()) { recoveryResult = rc; } } } // Delete the commit context file rc = contextFs.DeleteFile(CommitContextFileName); if (rc.IsFailure()) { return(rc); } rc = contextFs.Commit(); if (rc.IsFailure()) { return(rc); } return(recoveryResult); }