public Memory <byte> DecompressGcz(Stream source, int length, int decompLength)
        {
            var props = new BemaniLzss2Properties
            {
                RingBufferOffset = 0xFFE,
                RingBufferSize   = 0x1000,
                Type             = BemaniLzss2Type.GCZ
            };

            return(Decompress(source, length, decompLength, props));
        }
        private Memory <byte> Decompress(Stream source, int length, int decompLength, BemaniLzss2Properties props)
        {
            var       ring            = new byte[props.RingBufferSize];
            var       ringPos         = props.RingBufferOffset;
            var       controlWord     = 1;
            var       controlBitsLeft = 0;
            const int controlBitMask  = 0x1;

            if (decompLength <= 0)
            {
                decompLength = int.MaxValue;
            }

            var sourceReader = new BinaryReader(source);
            var target       = new MemoryStream();
            var writer       = new BinaryWriter(target);

            using (var mem = new MemoryStream(sourceReader.ReadBytes(length)))
            {
                var reader = new BinaryReader(mem);

                while (decompLength > 0 && length > 0)
                {
                    if (controlBitsLeft == 0)
                    {
                        /* Read a control byte */
                        controlWord = reader.ReadByte();
                        length--;
                        controlBitsLeft = 8;
                    }

                    /* Decode a byte according to the current control byte bit */
                    if ((controlWord & controlBitMask) != 0)
                    {
                        /* Straight copy, store into history ring */
                        var data = reader.ReadByte();
                        length--;

                        writer.Write(data);
                        ring[ringPos] = data;

                        ringPos = (ringPos + 1) % props.RingBufferSize;
                        decompLength--;
                    }
                    else
                    {
                        /* Reference to data in ring buffer */

                        byte cmd2;
                        byte cmd1;
                        int  chunkLength;
                        int  chunkOffset;
                        switch (props.Type)
                        {
                        case BemaniLzss2Type.Firebeat:
                            cmd1        = reader.ReadByte();
                            cmd2        = reader.ReadByte();
                            length     -= 2;
                            chunkLength = (cmd1 & 0x0F) + 3;
                            chunkOffset = ((cmd1 & 0xF0) << 4) + cmd2;
                            chunkOffset = ringPos - chunkOffset;
                            while (chunkOffset < 0)
                            {
                                chunkOffset += props.RingBufferSize;
                            }
                            break;

                        case BemaniLzss2Type.GCZ:
                            cmd1        = reader.ReadByte();
                            cmd2        = reader.ReadByte();
                            length     -= 2;
                            chunkLength = (cmd2 & 0x0F) + 3;
                            chunkOffset = ((cmd2 & 0xF0) << 4) | cmd1;
                            break;

                        default:
                            return(target.AsMemory());
                        }

                        for (; chunkLength > 0 && length > 0; chunkLength--)
                        {
                            /* Copy historical data to output AND current ring pos */
                            writer.Write(ring[chunkOffset]);
                            ring[ringPos] = ring[chunkOffset];

                            /* Update counters */
                            chunkOffset = (chunkOffset + 1) % props.RingBufferSize;
                            ringPos     = (ringPos + 1) % props.RingBufferSize;
                            decompLength--;
                        }
                    }

                    /* Get next control bit */
                    controlWord >>= 1;
                    controlBitsLeft--;
                }
            }

            return(target.AsMemory());
        }