public void FsTrim()
        {
            if (Type != IntegrityStorageType.Save)
            {
                return;
            }

            Span <byte> digest = stackalloc byte[DigestSize];

            for (int i = 0; i < SectorCount; i++)
            {
                long hashPos = i * DigestSize;
                HashStorage.Read(digest, hashPos);

                if (!Util.IsEmpty(digest))
                {
                    continue;
                }

                int dataOffset = i * SectorSize;
                BaseStorage.Fill(SaveDataFileSystem.TrimFillValue, dataOffset, SectorSize);
            }
        }
        private void ReadImpl(Span <byte> destination, long offset, IntegrityCheckLevel integrityCheckLevel)
        {
            int count = destination.Length;

            if (count < 0 || count > SectorSize)
            {
                throw new ArgumentOutOfRangeException(nameof(destination), "Length is invalid.");
            }

            long blockIndex = offset / SectorSize;

            if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
            {
                // Todo: Differentiate between the top and lower layers
                ThrowHelper.ThrowResult(ResultFs.InvalidHashInIvfc, "Hash error!");
            }

            bool needsHashCheck = integrityCheckLevel != IntegrityCheckLevel.None &&
                                  BlockValidities[blockIndex] == Validity.Unchecked;

            if (Type != IntegrityStorageType.Save && !needsHashCheck)
            {
                BaseStorage.Read(destination, offset);
                return;
            }

            Span <byte> hashBuffer = stackalloc byte[DigestSize];
            long        hashPos    = blockIndex * DigestSize;

            HashStorage.Read(hashBuffer, hashPos);

            if (Type == IntegrityStorageType.Save)
            {
                if (Util.IsEmpty(hashBuffer))
                {
                    destination.Clear();
                    BlockValidities[blockIndex] = Validity.Valid;
                    return;
                }

                if (!needsHashCheck)
                {
                    BaseStorage.Read(destination, offset);
                    return;
                }
            }

            byte[] dataBuffer = ArrayPool <byte> .Shared.Rent(SectorSize);

            try
            {
                BaseStorage.Read(destination, offset);
                destination.CopyTo(dataBuffer);

                if (BlockValidities[blockIndex] != Validity.Unchecked)
                {
                    return;
                }

                int bytesToHash = SectorSize;

                if (count < SectorSize)
                {
                    // Pad out unused portion of block
                    Array.Clear(dataBuffer, count, SectorSize - count);

                    // Partition FS hashes don't pad out an incomplete block
                    if (Type == IntegrityStorageType.PartitionFs)
                    {
                        bytesToHash = count;
                    }
                }

                byte[] hash = DoHash(dataBuffer, 0, bytesToHash);

                Validity validity = Util.SpansEqual(hashBuffer, hash) ? Validity.Valid : Validity.Invalid;
                BlockValidities[blockIndex] = validity;

                if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
                {
                    ThrowHelper.ThrowResult(ResultFs.InvalidHashInIvfc, "Hash error!");
                }
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(dataBuffer);
            }
        }
        private void ReadImpl(Span <byte> destination, long offset, IntegrityCheckLevel integrityCheckLevel)
        {
            int count = destination.Length;

            if (count < 0 || count > SectorSize)
            {
                throw new ArgumentOutOfRangeException(nameof(destination), "Length is invalid.");
            }

            Span <byte> hashBuffer = stackalloc byte[DigestSize];
            long        blockIndex = offset / SectorSize;
            long        hashPos    = blockIndex * DigestSize;

            if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
            {
                throw new InvalidDataException("Hash error!");
            }

            HashStorage.Read(hashBuffer, hashPos);

            if (Type == IntegrityStorageType.Save && Util.IsEmpty(hashBuffer))
            {
                destination.Clear();
                BlockValidities[blockIndex] = Validity.Valid;
                return;
            }

            byte[] dataBuffer = ArrayPool <byte> .Shared.Rent(SectorSize);

            try
            {
                BaseStorage.Read(dataBuffer, offset, count, 0);
                dataBuffer.AsSpan(0, count).CopyTo(destination);

                if (integrityCheckLevel == IntegrityCheckLevel.None)
                {
                    return;
                }
                if (BlockValidities[blockIndex] != Validity.Unchecked)
                {
                    return;
                }

                int bytesToHash = SectorSize;

                if (count < SectorSize)
                {
                    // Pad out unused portion of block
                    Array.Clear(dataBuffer, count, SectorSize - count);

                    // Partition FS hashes don't pad out an incomplete block
                    if (Type == IntegrityStorageType.PartitionFs)
                    {
                        bytesToHash = count;
                    }
                }

                byte[] hash = DoHash(dataBuffer, 0, bytesToHash);

                Validity validity = Util.SpansEqual(hashBuffer, hash) ? Validity.Valid : Validity.Invalid;
                BlockValidities[blockIndex] = validity;

                if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
                {
                    throw new InvalidDataException("Hash error!");
                }
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(dataBuffer);
            }
        }