private void ReadSectionDetails(ArchiveSectionMetadataBuilder[] sections)
        {
            #region Counts

            bool hasStreamCounts = false;

            ArchiveMetadataToken token;
            for (;;)
            {
                token = ReadToken();
                if (token == ArchiveMetadataToken.End || token == ArchiveMetadataToken.CRC || token == ArchiveMetadataToken.Size)
                    break;

                if (token == ArchiveMetadataToken.NumUnpackStream)
                {
                    hasStreamCounts = true;

                    foreach (var section in sections)
                        section.SubStreamCount = ReadNumberAsInt32();
                }
                else
                {
                    SkipDataBlock();
                }
            }

            if (!hasStreamCounts)
                foreach (var section in sections)
                    section.SubStreamCount = 1;

            #endregion

            #region Sizes

            foreach (var section in sections)
            {
                // v3.13 was broken and wrote empty sections
                // v4.07 added compat code to skip empty sections
                if (section.SubStreamCount.Value == 0)
                    continue;

                var remaining = section.OutputLength;
                var subsections = new DecodedStreamMetadata[section.SubStreamCount.Value];

                for (int i = 0; i < subsections.Length - 1; i++)
                {
                    if (token == ArchiveMetadataToken.Size)
                    {
                        var size = ReadNumberAsInt64();
                        if (size == 0 || size >= remaining)
                            throw new InvalidDataException();

                        subsections[i] = new DecodedStreamMetadata(size, null);
                        remaining -= size;
                    }
                }

                if (remaining == 0)
                    throw new InvalidDataException();

                subsections[subsections.Length - 1] = new DecodedStreamMetadata(remaining, null);
                section.Subsections = subsections;
            }

            if (token == ArchiveMetadataToken.Size)
                token = ReadToken();

            #endregion

            #region Checksums

            int requiredChecksumCount = 0;
            int totalChecksumCount = 0;

            ImmutableArray<Checksum?>.Builder checksums = null;

            foreach (var section in sections)
            {
                // If there is only one stream and we have a section checksum 7z doesn't store the checksum again.
                if (!(section.SubStreamCount == 1 && section.OutputChecksum.HasValue))
                    requiredChecksumCount += section.SubStreamCount.Value;

                totalChecksumCount += section.SubStreamCount.Value;
            }

            for (;;)
            {
                if (token == ArchiveMetadataToken.End)
                {
                    if (checksums == null)
                    {
                        checksums = ImmutableArray.CreateBuilder<Checksum?>(totalChecksumCount);
                        for (int i = 0; i < totalChecksumCount; i++)
                            checksums.Add(null);
                    }

                    break;
                }
                else if (token == ArchiveMetadataToken.CRC)
                {
                    checksums = ImmutableArray.CreateBuilder<Checksum?>(totalChecksumCount);

                    var vector = ReadOptionalBitVector(requiredChecksumCount);
                    int requiredChecksumIndex = 0;

                    foreach (var section in sections)
                    {
                        if (section.SubStreamCount == 1 && section.OutputChecksum.HasValue)
                        {
                            checksums.Add(section.OutputChecksum.Value);
                        }
                        else
                        {
                            for (int i = 0; i < section.SubStreamCount; i++)
                            {
                                if (vector[requiredChecksumIndex++])
                                    checksums.Add(new Checksum(ReadInt32()));
                                else
                                    checksums.Add(null);
                            }
                        }
                    }

                    System.Diagnostics.Debug.Assert(requiredChecksumIndex == requiredChecksumCount);
                    System.Diagnostics.Debug.Assert(checksums.Count == totalChecksumCount);
                }
                else
                {
                    SkipDataBlock();
                }

                token = ReadToken();
            }

            #endregion
        }
        private ArchiveSectionMetadataBuilder ReadSection(int rawInputStreamIndex)
        {
            var section = new ArchiveSectionMetadataBuilder();

            int totalInputCount = 0;
            int totalOutputCount = 0;

            var decoderCount = ReadNumberAsInt32();
            if (decoderCount == 0)
                throw new InvalidDataException();

            section.Decoders = new DecoderMetadataBuilder[decoderCount];
            for (int i = 0; i < decoderCount; i++)
            {
                var decoder = ReadDecoder();
                totalInputCount += decoder.InputCount;
                totalOutputCount += decoder.OutputCount;
                section.Decoders[i] = decoder;
            }

            // One output is the final output, the others need to be wired up.
            var usedOutputMask = new bool[totalOutputCount];
            for (int i = 1; i < totalOutputCount; i++)
            {
                int inputIndex = ReadNumberAsInt32();
                int inputDecoderIndex = 0;
                for (;;)
                {
                    if (inputDecoderIndex == decoderCount)
                        throw new InvalidDataException();

                    if (inputIndex < section.Decoders[inputDecoderIndex].InputCount)
                        break;

                    inputIndex -= section.Decoders[inputDecoderIndex].OutputCount;
                    inputDecoderIndex += 1;
                }

                int outputIndex = ReadNumberAsInt32();
                
                // Detect duplicate output connections through the output mask.
                if (outputIndex >= totalOutputCount || usedOutputMask[outputIndex])
                    throw new InvalidDataException();

                usedOutputMask[outputIndex] = true;

                // Separate the output index into decoder index and stream index
                int outputDecoderIndex = 0;
                for (;;)
                {
                    if (outputDecoderIndex == decoderCount)
                        throw new InvalidDataException();

                    var outputCount = section.Decoders[outputDecoderIndex].OutputCount;
                    if (outputIndex < outputCount)
                        break;

                    outputIndex -= outputCount;
                    outputDecoderIndex += 1;
                }

                // Detect duplicate input connections by checking for the placeholder.
                if (section.Decoders[inputDecoderIndex].InputInfo[inputIndex].StreamIndex != Int32.MaxValue)
                    throw new InvalidDataException();

                section.Decoders[inputDecoderIndex].InputInfo[inputIndex] = new DecoderInputMetadata(outputDecoderIndex, outputIndex);
            }

            bool foundFinalOutput = false;

            for (int finalOutputIndex = 0, i = 0; i < decoderCount; i++)
            {
                var decoder = section.Decoders[i];
                for (int j = 0; j < decoder.OutputCount; j++)
                {
                    if (!usedOutputMask[finalOutputIndex++])
                    {
                        if (foundFinalOutput)
                            throw new InvalidDataException();

                        foundFinalOutput = true;
                        section.OutputStream = new DecoderInputMetadata(i, j);
                    }
                }
            }

            if (!foundFinalOutput)
                throw new InvalidDataException();

            // Outputs must be wired up to unique inputs. Inputs which are not wired to outputs must be wired to raw streams.
            // Note that negative overflow is not possible and positive overflow is ok in this calculation,
            // it will fail the range check and trigger the exception, which is the intended behavior.
            var requiredRawStreams = 1 + totalInputCount - totalOutputCount;
            if (requiredRawStreams <= 0)
                throw new InvalidDataException();

            section.RequiredRawInputStreamCount = requiredRawStreams;

            if (requiredRawStreams == 1)
            {
                bool connected = false;

                foreach (var decoder in section.Decoders)
                {
                    for (int i = 0; i < decoder.InputCount; i++)
                    {
                        if (decoder.InputInfo[i].StreamIndex == Int32.MaxValue)
                        {
                            if (connected)
                                throw new InvalidDataException();

                            connected = true;
                            decoder.InputInfo[i] = new DecoderInputMetadata(null, 0);
                        }
                    }
                }

                if (!connected)
                    throw new InvalidDataException();
            }
            else
            {
                for (int i = 0; i < requiredRawStreams; i++)
                {
                    int inputIndex = ReadNumberAsInt32();
                    int inputDecoderIndex = 0;
                    for (;;)
                    {
                        if (inputDecoderIndex == decoderCount)
                            throw new InvalidDataException();

                        var decoder = section.Decoders[inputDecoderIndex];
                        if (inputIndex < decoder.InputCount)
                            break;

                        inputIndex -= decoder.OutputCount;
                        inputDecoderIndex += 1;
                    }

                    var decoderInput = section.Decoders[inputDecoderIndex].InputInfo;
                    if (decoderInput[inputIndex].StreamIndex != Int32.MaxValue)
                        throw new InvalidDataException();

                    decoderInput[inputIndex] = new DecoderInputMetadata(null, rawInputStreamIndex + i);
                }
            }

            return section;
        }
        private ArchiveSectionMetadataBuilder[] ReadSectionHeader(ImmutableArray<Stream> streams)
        {
            SkipToToken(ArchiveMetadataToken.Folder);

            var sectionCount = ReadNumberAsInt32();
            var sections = new ArchiveSectionMetadataBuilder[sectionCount];
            int rawStreamCount = 0;

            using (SelectStream(streams))
            {
                for (int i = 0; i < sectionCount; i++)
                {
                    var section = ReadSection(rawStreamCount);
                    rawStreamCount += section.RequiredRawInputStreamCount;
                    sections[i] = section;
                }
            }

            SkipToToken(ArchiveMetadataToken.CodersUnpackSize);

            foreach (var section in sections)
            {
                for (int decoderIndex = 0; decoderIndex < section.Decoders.Length; decoderIndex++)
                {
                    var decoder = section.Decoders[decoderIndex];
                    var outputCount = decoder.OutputCount;
                    var outputBuilder = ImmutableArray.CreateBuilder<DecoderOutputMetadata>(outputCount);

                    for (int outputIndex = 0; outputIndex < outputCount; outputIndex++)
                    {
                        var length = ReadNumberAsInt64();
                        outputBuilder.Add(new DecoderOutputMetadata(length));

                        var stream = new DecoderInputMetadata(decoderIndex, outputIndex);
                        if (section.OutputStream == stream)
                            section.OutputLength = length;
                    }

                    decoder.OutputInfo = outputBuilder;
                }
            }

            for (;;)
            {
                var token = ReadToken();
                if (token == ArchiveMetadataToken.End)
                    break;

                if (token == ArchiveMetadataToken.CRC)
                {
                    var vector = ReadOptionalBitVector(sectionCount);

                    for (int i = 0; i < sectionCount; i++)
                    {
                        if (vector[i])
                            sections[i].OutputChecksum = new Checksum(ReadInt32());
                        else
                            sections[i].OutputChecksum = null;
                    }
                }
                else
                {
                    SkipDataBlock();
                }
            }

            return sections;
        }