/// <summary> /// Updates the commit context and writes it to a file, signifying that all /// the file systems have been provisionally committed. /// </summary> /// <returns>The <see cref="Result"/> of the operation.</returns> public Result CommitProvisionallyDone() { IFile contextFile = null; try { Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite); if (rc.IsFailure()) { return(rc); } _context.State = CommitState.ProvisionallyCommitted; 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()); }
/// <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()); }
/// <summary> /// Recovers a multi-commit that was interrupted after all file systems had been provisionally committed. /// The recovery will finish committing any file systems that are still provisionally committed. /// </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><see cref="Result.Success"/>: The operation was successful.<br/> /// <see cref="ResultFs.InvalidMultiCommitContextVersion"/>: The version of the commit context /// file isn't supported.<br/> /// <see cref="ResultFs.InvalidMultiCommitContextState"/>: The multi-commit hadn't finished /// provisionally committing all the file systems.</returns> private static Result RecoverCommit(ISaveDataMultiCommitCoreInterface multiCommitInterface, IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService) { IFile contextFile = null; try { // Read the multi-commit context Result rc = contextFs.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite); if (rc.IsFailure()) { return(rc); } Unsafe.SkipInit(out Context context); rc = contextFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref context), ReadOption.None); if (rc.IsFailure()) { return(rc); } // Note: Nintendo doesn't check if the proper amount of bytes were read, but it // doesn't really matter since the context is validated. if (context.Version > CurrentCommitContextVersion) { return(ResultFs.InvalidMultiCommitContextVersion.Log()); } // All the file systems in the multi-commit must have been at least provisionally committed // before we can try to recover the commit. if (context.State != CommitState.ProvisionallyCommitted) { return(ResultFs.InvalidMultiCommitContextState.Log()); } // Keep track of the first error that occurs during the recovery Result recoveryResult = Result.Success; 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 finishing their commits. // 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], false); if (recoveryResult.IsSuccess() && rc.IsFailure()) { recoveryResult = rc; } } return(recoveryResult); } finally { contextFile?.Dispose(); } }