Example #1
0
            public void Dispose()
            {
                if (_fileSystem is null)
                {
                    return;
                }

                _fileSystem.DeleteFile(CommitContextFileName).IgnoreResult();
                _fileSystem.Commit().IgnoreResult();

                _fileSystem = null;
            }
Example #2
0
        /// <summary>
        /// Commits all added file systems using <paramref name="contextFileSystem"/> to
        /// store the <see cref="Context"/>.
        /// </summary>
        /// <param name="contextFileSystem">The file system where the commit context will be stored.</param>
        /// <returns>The <see cref="Result"/> of the operation.</returns>
        private Result Commit(IFileSystem contextFileSystem)
        {
            ContextUpdater context = default;

            try
            {
                Counter = 1;

                context = new ContextUpdater(contextFileSystem);
                Result rc = context.Create(Counter, FileSystems.Count);
                if (rc.IsFailure())
                {
                    return(rc);
                }

                rc = CommitProvisionallyFileSystem(Counter);
                if (rc.IsFailure())
                {
                    return(rc);
                }

                rc = context.CommitProvisionallyDone();
                if (rc.IsFailure())
                {
                    return(rc);
                }

                rc = CommitFileSystem();
                if (rc.IsFailure())
                {
                    return(rc);
                }

                rc = context.CommitDone();
                if (rc.IsFailure())
                {
                    return(rc);
                }
            }
            finally
            {
                context.Dispose();
            }

            return(Result.Success);
        }
Example #3
0
            /// <summary>
            /// To be called once the multi-commit has been successfully completed. Deletes the commit context file.
            /// </summary>
            /// <returns>The <see cref="Result"/> of the operation.</returns>
            public Result CommitDone()
            {
                Result rc = _fileSystem.DeleteFile(CommitContextFileName);

                if (rc.IsFailure())
                {
                    return(rc);
                }

                rc = _fileSystem.Commit();
                if (rc.IsFailure())
                {
                    return(rc);
                }

                _fileSystem = null;
                return(Result.Success);
            }
Example #4
0
 public IFileSystem(LibHac.Fs.Fsa.IFileSystem provider)
 {
     _fileSystem = provider;
 }
Example #5
0
 /// <summary>
 /// Opens a host file system via <see cref="IFileSystemProxy"/>.
 /// </summary>
 /// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
 /// <param name="fileSystem">If successful, the opened host file system.</param>
 /// <param name="path">The path on the host computer to open. e.g. /C:\Windows\System32/</param>
 /// <param name="option">Options for opening the host file system.</param>
 /// <returns>The <see cref="Result"/> of the operation.</returns>
 private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, in FspPath path,
Example #6
0
 public ContextUpdater(IFileSystem contextFileSystem)
 {
     _fileSystem = contextFileSystem;
     _context    = default;
 }
Example #7
0
        /// <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);
        }
Example #8
0
        /// <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();
            }
        }