platform.independent.streamformat.compression.zlib.pas

Переключить прокрутку окна
Загрузить этот исходный код

{
    zlib – библиотека сжатия данных общего назначения. Версия 1.1.0
    Это изменённая объектно-ориентированная версия библиотеки, полностью
    совместимая с оригинальной библиотекой.

    Copyright © 1995–2005 Jean-loup Gailly и Mark Adler
    Copyright © 2000–2011 ymnk, JCraft, Inc.
    Copyright © 2016, 2019, 2022–2023, 2026 Малик Разработчик

    Эта библиотека поставляется «как есть», без каких-либо явных или
    подразумеваемых гарантий. Ни при каких обстоятельствах авторы не
    несут какой-либо ответственности в случае потери данных вследствие
    использования данной библиотеки.

    Разрешается всем использовать эту библиотеку для любых целей, в том
    числе и для коммерческих приложений, а также изменять её и
    распространять свободно при соблюдении следующих условий:

        1. Оригинал библиотеки не должен быть искажён; вы не должны
    заявлять, что именно вы написали оригинальную библиотеку. Если вы
    используете эту библиотеку в своём программном продукте, то ссылка
    на авторов библиотеки была бы желательна, но это не является
    обязательным требованием.

        2. Изменённые версии исходных текстов должны быть отчётливо
    маркированы и не должны выдаваться за оригинал библиотеки.

        3. Эти замечания не могут быть удалены либо изменены при
    каком-либо варианте распространения исходных текстов.
}

unit platform.independent.streamformat.compression.zlib;

    {$MODE DELPHI}

interface

    {%region} uses
        pascalx.lang,
        pascalx.io,
        pascalx.io.bytearray,
        pascalx.io.extension;
    {%enregion}

    {$WARN 3018 OFF} { позволить конструкторы с любой видимостью }

    {$TYPEINFO ON}
    {$CALLING REGISTER}

    const UNIT_NAME = 'platform.independent.streamformat.compression.zlib';

    {%region} type
        Checksum32    = class;
        Adler32       = class;
        CRC32         = class;
        ZLib          = class;
        ZStream       = class;
        GZIPHeader    = class;
        GZIPException = class;

        Checksum32 = class abstract(&Object)
        protected
            fldValue: int;
            constructor create(); overload;
            constructor create(value: int); overload;
        public
            procedure update(const src: byte_Array1d; offset, length: int); virtual; abstract;
            procedure reset(); virtual; overload;
            procedure reset(value: int); virtual; overload;
            function copy(): Checksum32; virtual; abstract;
            function getValue(): int; virtual;
        end;

        Adler32 = class(Checksum32)
        private const
            NMAX = int(5552);
            BASE = long(65521);
        public
            constructor create(); overload;
            constructor create(value: int); overload;
            procedure reset(); override; overload;
            procedure update(const src: byte_Array1d; offset, length: int); override;
            function copy(): Checksum32; override;
        end;

        CRC32 = class(Checksum32)
        private
            class var table: int_Array1d;
            class procedure initialize(); static;
            class procedure finalize(); static;
        public
            constructor create(); overload;
            constructor create(value: int); overload;
            procedure update(const src: byte_Array1d; offset, length: int); override;
            function copy(): Checksum32; override;
        end;

        ZLib = class(&Object)
        public const
            { wbits }
            MIN_WBITS     = int(9);
            MAX_WBITS     = int(15);
            DEFAULT_WBITS = MAX_WBITS;
            USE_CRC       = int(16);
            { уровни сжатия }
            DEFAULT_COMPRESSION       = int(-1);
            NO_COMPRESSION            = int(0);
            BEST_SPEED                = int(1);
            OPTIMAL_SPEED_COMPRESSION = int(7);
            BEST_COMPRESSION          = int(9);
            { стратегии сжатия }
            FILTERED         = int(1);
            HUFFMAN_ONLY     = int(2);
            DEFAULT_STRATEGY = int(0);
            { режимы ввода-вывода }
            NO_FLUSH      = int(0);
            PARTIAL_FLUSH = int(1);
            SYNC_FLUSH    = int(2);
            FULL_FLUSH    = int(3);
            FINISH        = int(4);
            { возвращаемые значения }
            OK           = int(0);
            STREAM_END   = int(1);
            NEED_DICT    = int(2);
            ERRNO        = int(-1);
            STREAM_ERROR = int(-2);
            DATA_ERROR   = int(-3);
            MEM_ERROR    = int(-4);
            BUF_ERROR    = int(-5);
        public
            class function getVersion(): AnsiString; static;
            class function compress(const src: byte_Array1d; offset, length: int; wbits: int = DEFAULT_WBITS; level: int = DEFAULT_COMPRESSION; strategy: int = DEFAULT_STRATEGY): byte_Array1d; static;
            class function decompress(const src: byte_Array1d; offset, length: int; wbits: int = DEFAULT_WBITS): byte_Array1d; static;
        end;

        ZStream = class sealed(ZLib)
        private
            fldDataType: int;
            fldNextInIndex: int;
            fldAvailableIn: int;
            fldNextOutIndex: int;
            fldAvailableOut: int;
            fldTotalIn: long;
            fldTotalOut: long;
            fldMessage: AnsiString;
            fldNextInArray: byte_Array1d;
            fldNextOutArray: byte_Array1d;
            fldDeflateState: ZLib; { DefState }
            fldInflateState: ZLib; { InfState }
            fldChecksum: Checksum32;
            procedure flushPending();
            function readBuf(const dst: byte_Array1d; offset, length: int): int;
        public
            constructor create();
            destructor destroy; override;
            procedure setAvailableIn(availableIn: int);
            procedure setAvailableOut(availableOut: int);
            procedure setNextInIndex(nextInIndex: int);
            procedure setNextInArray(const nextInArray: byte_Array1d);
            procedure setNextOutIndex(nextOutIndex: int);
            procedure setNextOutArray(const nextOutArray: byte_Array1d);
            procedure setInput(const data: byte_Array1d); overload;
            procedure setInput(const data: byte_Array1d; append: boolean); overload;
            procedure setInput(const data: byte_Array1d; offset, length: int); overload;
            procedure setInput(const data: byte_Array1d; offset, length: int; append: boolean); overload;
            procedure setOutput(const data: byte_Array1d); overload;
            procedure setOutput(const data: byte_Array1d; offset, length: int); overload;
            function inflateFinished(): boolean;
            function inflateBegin(): int; overload;
            function inflateBegin(wbits: int): int; overload;
            function inflateBegin(nowrap: boolean): int; overload;
            function inflateBegin(wbits: int; nowrap: boolean): int; overload;
            function inflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
            function inflateSync(): int;
            function inflateSyncPoint(): int;
            function inflate(flush: int): int;
            function inflateEnd(): int;
            function deflateBegin(level: int): int; overload;
            function deflateBegin(level: int; nowrap: boolean): int; overload;
            function deflateBegin(level, wbits: int): int; overload;
            function deflateBegin(level, wbits, memLevel: int): int; overload;
            function deflateBegin(level, wbits: int; nowrap: boolean): int; overload;
            function deflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
            function deflateParameters(level, strategy: int): int;
            function deflate(flush: int): int;
            function deflateEnd(): int;
            function getChecksum(): int;
            function getAvailableIn(): int;
            function getAvailableOut(): int;
            function getNextInIndex(): int;
            function getNextOutIndex(): int;
            function getTotalIn(): long;
            function getTotalOut(): long;
            function getMessage(): AnsiString;
            function getNextInArray(): byte_Array1d;
            function getNextOutArray(): byte_Array1d;
        end;

        GZIPHeader = class sealed(ZLib)
        strict private
            fldModifiedTime: int;
        private
            { fldXFlags: int; }
            fldOS: int;
            { fldHCRC: int; }
            { fldTime: int; }
            fldCRC: int;
            fldExtra: byte_Array1d;
            fldName: byte_Array1d;
            fldComment: byte_Array1d;
            procedure put(deflateState: ZLib { DefState });
        public
            constructor create();
            procedure setOS(os: int);
            procedure setCRC(crc: int);
            procedure setModifiedTime(modifiedTime: int);
            procedure setName(const name: AnsiString);
            procedure setComment(const comment: AnsiString);
            function getOS(): int;
            function getCRC(): int;
            function getModifiedTime(): int;
            function getName(): AnsiString;
            function getComment(): AnsiString;
        end;

        GZIPException = class(IOException);
    {%endregion}

implementation

    {$TYPEINFO OFF}
    {$CALLING REGISTER}

    {%region} type
        Config     = class;
        DefState   = class;
        InfState   = class;
        InfBlocks  = class;
        InfCodes   = class;
        InfTree    = class;
        StaticTree = class;
        Tree       = class;

        Config_Array1d = packed array of Config;

        Config = class(ZLib)
        private
            fldGoodLength: int;
            fldMaxLazy: int;
            fldNiceLength: int;
            fldMaxChain: int;
            fldFunc: int;
        public
            constructor create(goodLength, maxLazy, niceLength, maxChain, func: int);
            property goodLength: int read fldGoodLength;
            property niceLength: int read fldNiceLength;
            property maxChain: int read fldMaxChain;
            property maxLazy: int read fldMaxLazy;
            property func: int read fldFunc;
        end;

        DefState = class(ZLib)
        strict private const
            NEED_MORE      = int(0);
            BLOCK_DONE     = int(1);
            FINISH_STARTED = int(2);
            FINISH_DONE    = int(3);
            PRESET_DICT    = int($20);
            INIT_STATE     = int(42);
            BUSY_STATE     = int(113);
            FINISH_STATE   = int(666);
            Z_DEFLATED     = int(8);
            STORED_BLOCK   = int(0);
            STATIC_TREES   = int(1);
            DYN_TREES      = int(2);
            Z_BINARY       = int(0);
            Z_ASCII        = int(1);
            Z_UNKNOWN      = int(2);
            BUF_SIZE       = int(8 * 2);
            REP_3_6        = int(16);
            REPZ_3_10      = int(17);
            REPZ_11_138    = int(18);
            MIN_MATCH      = int(3);
            MAX_MATCH      = int(258);
            MIN_LOOKAHEAD  = int(MAX_MATCH + MIN_MATCH + 1);
            MAX_BITS       = int(15);
            D_CODES        = int(30);
            BL_CODES       = int(19);
            LENGTH_CODES   = int(29);
            LITERALS       = int(256);
            L_CODES        = int(LITERALS + LENGTH_CODES + 1);
            HEAP_SIZE      = int(2 * L_CODES + 1);
            END_BLOCK      = int(256);
            MIN_MEM_LEVEL  = int(1);
            MAX_MEM_LEVEL  = int(9);
            DEF_MEM_LEVEL  = int(8);
            STORED         = int(0);
            FAST           = int(1);
            SLOW           = int(2);
        strict private
            class var distCode: byte_Array1d;
            class var blOrder: byte_Array1d;
            class var lengthCode: byte_Array1d;
            class var baseLength: int_Array1d;
            class var baseDist: int_Array1d;
            class var errorMessages: AnsiString_Array1d;
            class var configTable: Config_Array1d;
        private
            class procedure initialize(); static;
            class procedure finalize(); static;
            class function smaller(const tree: int_Array1d; n, m: int; const depth: byte_Array1d): boolean; static;
            class function dcode(dist: int): int; static;
        strict private
            fldStatus: int;
            fldPendingSize: int;
            fldDataType: int;
            fldLastFlush: int;
            fldWSize: int;
            fldWBits: int;
            fldWMask: int;
            fldWindowSize: int;
            fldInsh: int;
            fldHashSize: int;
            fldHashBits: int;
            fldHashMask: int;
            fldHashShift: int;
            fldBlockStart: int;
            fldMatchLength: int;
            fldPrevMatch: int;
            fldMatchAvailable: int;
            fldStrStart: int;
            fldMatchStart: int;
            fldLookahead: int;
            fldPrevLength: int;
            fldMaxChainLength: int;
            fldMaxLazyMatch: int;
            fldStrategy: int;
            fldGoodMatch: int;
            fldNiceMatch: int;
            fldLitBufSize: int;
            fldLastLit: int;
            fldMatches: int;
            fldLastEobLen: int;
            fldLBuf: int;
            fldDBuf: int;
            fldBiBuf: int;
            fldBiValid: int;
            fldWindowBuf: byte_Array1d;
            fldPrevBuf: int_Array1d;
            fldHeadBuf: int_Array1d;
            fldDynamicLTree: int_Array1d;
            fldDynamicDTree: int_Array1d;
            fldBlTree: int_Array1d;
            fldLDesc: Tree;
            fldDDesc: Tree;
            fldBlDesc: Tree;
            fldGHeader: GZIPHeader;
            fldStream: ZStream;
            procedure lmInit();
            procedure trInit();
            procedure initBlock();
            procedure scanTree(const tree: int_Array1d; maxCode: int);
            procedure sendAllTrees(lCodes, dCodes, blCodes: int);
            procedure sendTree(const tree: int_Array1d; maxCode: int);
            procedure putShortMSB(b: int);
            procedure sendCode(c: int; const tree: int_Array1d);
            procedure sendBits(val, len: int);
            procedure trAlign();
            procedure compressBlock(const lTree, dTree: int_Array1d);
            procedure setDataType();
            procedure biFlush();
            procedure biWindup();
            procedure copyBlock(buf, len: int; header: boolean);
            procedure flushBlockOnly(eof: boolean);
            procedure trStoredBlock(buf, storedLen: int; eof: boolean);
            procedure trFlushBlock(buf, storedLen: int; eof: boolean);
            procedure fillWindow();
            function trTally(dist, lc: int): boolean;
            function buildBlTree(): int;
            function deflateStored(flush: int): int;
            function deflateFast(flush: int): int;
            function deflateSlow(flush: int): int;
            function deflateReset(): int;
            function deflateBegin(comLevel, method, windowBits, memLevel, strategy: int): int; overload;
            function longestMatch(curMatch: int): int;
            function getGZIPHeader(): GZIPHeader;
        private
            fldPendingOut: int;
            fldPendingPos: int;
            fldWrap: int;
            fldLevel: int;
            fldHeapLen: int;
            fldHeapMax: int;
            fldOptLen: int;
            fldStaticLen: int;
            fldPendingBuf: byte_Array1d;
            fldDepth: byte_Array1d;
            fldBlCount: int_Array1d;
            fldHeapBuf: int_Array1d;
            procedure pqDownHeap(const tree: int_Array1d; k: int);
            procedure putBytes(const data: byte_Array1d; offset, length: int);
            procedure putByte(c: int);
            procedure putShort(w: int);
            function deflateBegin(level, wbits, memLevel: int): int; overload;
            function deflateBegin(level, wbits: int): int; overload;
            function deflateBegin(level: int): int; overload;
            function deflateEnd(): int;
            function deflateParameters(level, strategy: int): int;
            function deflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
            function deflate(flush: int): int;
        public
            constructor create(stream: ZStream);
            destructor destroy; override;
        end;

        InfState = class(ZLib)
        strict private const
            PRESET_DICT = int($20);
            Z_DEFLATED  = int(8);
            DICT4       = int(2);
            DICT3       = int(3);
            DICT2       = int(4);
            DICT1       = int(5);
            DICT0       = int(6);
            BLOCKS      = int(7);
            CHECK4      = int(8);
            CHECK3      = int(9);
            CHECK2      = int(10);
            CHECK1      = int(11);
            DONE        = int(12);
            BAD         = int(13);
            HEAD        = int(14);
            LENGTH      = int(15);
            TIME        = int(16);
            OS          = int(17);
            EXLEN       = int(18);
            EXTRA       = int(19);
            NAME        = int(20);
            COMMENT     = int(21);
            HCRC        = int(22);
            FLAGS       = int(23);
        strict private
            class var mark: byte_Array1d;
        private
            class procedure initialize(); static;
            class procedure finalize(); static;
        strict private
            fldReadReturn: boolean;
            fldMethod: int;
            fldMarker: int;
            fldFlags: int;
            fldWBits: int;
            fldWas: int;
            fldNeedCheck: int;
            fldNeedBytes: int;
            fldCRCBuf: byte_Array1d;
            fldTmpString: ByteArrayOutputStream;
            fldBlocks: InfBlocks;
            fldGHeader: GZIPHeader;
            fldStream: ZStream;
            procedure checksum(n, v: int);
            function inflateReset(): int;
            function readBytes(n, r, f: int): int; overload;
            function readBytes(r, f: int): int; overload;
            function readString(r, f: int): int;
        private
            fldMode: int;
            fldWrap: int;
            function inflateEnd(): int;
            function inflateBegin(wbits: int): int;
            function inflate(f: int): int;
            function inflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
            function inflateSync(): int;
            function inflateSyncPoint(): int;
        public
            constructor create(stream: ZStream);
            destructor destroy; override;
        end;

        InfBlocks = class(ZLib)
        strict private const
            MANY   = int(1440);
            &TYPE  = int(0);
            LENS   = int(1);
            STORED = int(2);
            TABLE  = int(3);
            BTREE  = int(4);
            DTREE  = int(5);
            CODES  = int(6);
            DRY    = int(7);
            DONE   = int(8);
            BAD    = int(9);
        strict private
            class var inflateMask: int_Array1d;
            class var border: int_Array1d;
        private
            class procedure initialize(); static;
            class procedure finalize(); static;
        strict private
            fldCheck: boolean;
            fldMode: int;
            fldLeft: int;
            fldTable: int;
            fldIndex: int;
            fldLast: int;
            fldBlens: int_Array1d;
            fldBB: int_Array1d;
            fldTB: int_Array1d;
            fldHufts: int_Array1d;
            fldCodes: InfCodes;
            fldTree: InfTree;
            procedure proc1();
        private
            fldBitK: int;
            fldBitB: int;
            fldEnd: int;
            fldRead: int;
            fldWrite: int;
            fldWindow: byte_Array1d;
            procedure reset(stream: ZStream);
            procedure setDictionary(const d: byte_Array1d; start, n: int);
            procedure update(stream: ZStream; b, k, n, p, q: int);
            function syncPoint(): boolean;
            function proc(stream: ZStream; r: int): int;
            function inflateFlush(stream: ZStream; r: int): int;
        public
            constructor create(stream: ZStream; w: int);
            destructor destroy; override;
        end;

        InfCodes = class(ZLib)
        strict private const
            START   = int(0);
            LEN     = int(1);
            LENEXT  = int(2);
            DIST    = int(3);
            DISTEXT = int(4);
            COPY    = int(5);
            LIT     = int(6);
            WASH    = int(7);
            &END    = int(8);
            BADCODE = int(9);
        strict private
            class var inflateMask: int_Array1d;
        private
            class procedure initialize(); static;
            class procedure finalize(); static;
        strict private
            fldLBits: int;
            fldDBits: int;
            fldMode: int;
            fldLen: int;
            fldNeed: int;
            fldLit: int;
            fldGet: int;
            fldDist: int;
            fldTreeIndex: int;
            fldLTreeIndex: int;
            fldDTreeIndex: int;
            fldTreeArray: int_Array1d;
            fldLTreeArray: int_Array1d;
            fldDTreeArray: int_Array1d;
            function inflateFast(bl, bd: int; const tlArray: int_Array1d; tlIndex: int; const tdArray: int_Array1d; tdIndex: int; blocks: InfBlocks; stream: ZStream): int;
        private
            procedure init(bl, bd: int; const tlArray: int_Array1d; tlIndex: int; const tdArray: int_Array1d; tdIndex: int);
            function proc(blocks: InfBlocks; stream: ZStream; r: int): int;
        end;

        InfTree = class(ZLib)
        strict private const
            MANY     = int(1440);
            BMAX     = int(15);
            FIXED_BL = int(9);
            FIXED_BD = int(5);
        strict private
            class var fixedTL: int_Array1d;
            class var fixedTD: int_Array1d;
            class var cpLens: int_Array1d;
            class var cpLext: int_Array1d;
            class var cpDist: int_Array1d;
            class var cpDext: int_Array1d;
        private
            class procedure initialize(); static;
            class procedure finalize(); static;
            class procedure inflateTreesFixed(const bl, bd: int_Array1d; const tl, td: int_Array2d); static;
        strict private
            fldHN: int_Array1d;
            fldV: int_Array1d;
            fldC: int_Array1d;
            fldR: int_Array1d;
            fldU: int_Array1d;
            fldX: int_Array1d;
            procedure initWorkArea(vsize: int);
            function huftBuild(const bArray: int_Array1d; bIndex, n, s: int; const d, e, t, m, hp, hn, v: int_Array1d): int;
        private
            function inflateTreesBits(const c, bb, tb, hp: int_Array1d; stream: ZStream): int;
            function inflateTreesDynamic(nl, nd: int; const c, bl, bd, tl, td, hp: int_Array1d; stream: ZStream): int;
        end;

        StaticTree = class(ZLib)
        strict private const
            MAX_BITS     = int(15);
            BL_CODES     = int(19);
            D_CODES      = int(30);
            LITERALS     = int(256);
            LENGTH_CODES = int(29);
            L_CODES      = int(LITERALS + LENGTH_CODES + 1);
        private const
            MAX_BL_BITS = int(7);
        private
            class var staticLTree: int_Array1d;
            class var staticDTree: int_Array1d;
            class var extraLBits: int_Array1d;
            class var extraDBits: int_Array1d;
            class var extraBLBits: int_Array1d;
            class var staticLDesc: StaticTree;
            class var staticDDesc: StaticTree;
            class var staticBLDesc: StaticTree;
            class procedure initialize(); static;
            class procedure finalize(); static;
        private
            fldExtraBase: int;
            fldElements: int;
            fldMaxLength: int;
            fldStaticTree: int_Array1d;
            fldExtraBits: int_Array1d;
        public
            constructor create(const staticTree, extraBits: int_Array1d; extraBase, elements, maxLength: int);
        end;

        Tree = class(ZLib)
        strict private const
            MAX_BITS     = int(15);
            LITERALS     = int(256);
            LENGTH_CODES = int(29);
            L_CODES      = int(LITERALS + LENGTH_CODES + 1);
            HEAP_SIZE    = int(2 * L_CODES + 1);
        strict private
            class procedure genCodes(const tree: int_Array1d; maxCode: int; const blCount: int_Array1d);
            class function biReverse(code, len: int): int;
        strict private
            procedure genBitlen(dState: DefState);
        private
            fldMaxCode: int;
            fldDynamicTree: int_Array1d;
            fldStaticDesc: StaticTree;
        public
            procedure buildTree(dState: DefState);
        end;
    {%endregion}

    {%region  Checksum32 }
        constructor Checksum32.create();
        begin
            inherited create();
        end;

        constructor Checksum32.create(value: int);
        begin
            inherited create();
            fldValue := value;
        end;

        procedure Checksum32.reset();
        begin
            fldValue := 0;
        end;

        procedure Checksum32.reset(value: int);
        begin
            fldValue := value;
        end;

        function Checksum32.getValue(): int;
        begin
            result := fldValue;
        end;
    {%endregion}

    {%region  Adler32 }
        constructor Adler32.create();
        begin
            inherited create(1);
        end;

        constructor Adler32.create(value: int);
        begin
            inherited create(value);
        end;

        procedure Adler32.reset();
        begin
            fldValue := 1;
        end;

        procedure Adler32.update(const src: byte_Array1d; offset, length: int);
        var
            len: int;
            adler: int;
            word0: long;
            word1: long;
        begin
            &Array.checkBounds(src, offset, length);
            adler := fldValue;
            word0 := long(adler and $ffff);
            word1 := long((adler shr 16) and $ffff);
            while length > 0 do begin
                len := CoInt.min(length, NMAX);
                dec(length, len);
                while len > 0 do begin
                    dec(len);
                    inc(word0, src[offset] and $ff);
                    inc(word1, word0);
                    inc(offset);
                end;
                word0 := word0 mod BASE;
                word1 := word1 mod BASE;
            end;
            fldValue := int((word1 shl 16) or word0);
        end;

        function Adler32.copy(): Checksum32;
        begin
            result := Adler32.create(fldValue);
        end;
    {%endregion}

    {%region  CRC32 }
        class procedure CRC32.initialize();
        var
            idx: int;
            bit: int;
            crc: int;
        begin
            table := &Array.newInt1d(256);
            for idx := 0 to 255 do begin
                crc := idx;
                for bit := 7 downto 0 do begin
                    if (crc and 1) <> 0 then begin
                        crc := int($edb88320) xor (crc shr 1);
                    end else begin
                        crc := crc shr 1;
                    end;
                end;
                table[idx] := crc;
            end;
        end;

        class procedure CRC32.finalize();
        begin
            table := nil;
        end;

        constructor CRC32.create();
        begin
            inherited create();
        end;

        constructor CRC32.create(value: int);
        begin
            inherited create(value);
        end;

        procedure CRC32.update(const src: byte_Array1d; offset, length: int);
        var
            crc: int;
        begin
            &Array.checkBounds(src, offset, length);
            crc := not fldValue;
            while length > 0 do begin
                dec(length);
                crc := table[int(crc xor src[offset]) and $ff] xor (crc shr 8);
                inc(offset);
            end;
            fldValue := not crc;
        end;

        function CRC32.copy(): Checksum32;
        begin
            result := CRC32.create(fldValue);
        end;
    {%endregion}

    {%region  ZLib }
        class function ZLib.getVersion(): AnsiString;
        begin
            result := '1.1.0';
        end;

        class function ZLib.compress(const src: byte_Array1d; offset, length: int; wbits, level, strategy: int): byte_Array1d;
        var
            zlibResult: int;
            currTotalOut: long;
            prevTotalOut: long;
            buffer: byte_Array1d;
            compressor: ZStream;
            tempStream: ByteArrayOutputStream;
        begin
            &Array.checkBounds(src, offset, length);
            if (wbits < -MAX_WBITS) or (wbits > -MIN_WBITS) and (wbits < MIN_WBITS) or (wbits > MAX_WBITS) and (wbits < MIN_WBITS + USE_CRC) or (wbits > MAX_WBITS + USE_CRC) then begin
                raise IllegalArgumentException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'illegal-argument'), [ CoAnsiString.create('wbits') ]));
            end;
            if (level < DEFAULT_COMPRESSION) or (level > BEST_COMPRESSION) then begin
                raise IllegalArgumentException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'illegal-argument'), [ CoAnsiString.create('level') ]));
            end;
            if (strategy < DEFAULT_STRATEGY) or (strategy > HUFFMAN_ONLY) then begin
                raise IllegalArgumentException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'illegal-argument'), [ CoAnsiString.create('strategy') ]));
            end;
            compressor := nil;
            tempStream := nil;
            try
                prevTotalOut := 0;
                buffer := &Array.newByte1d($10000);
                compressor := ZStream.create();
                tempStream := ByteArrayOutputStream.create();
                compressor.deflateBegin(level, wbits);
                if strategy > DEFAULT_STRATEGY then compressor.deflateParameters(level, strategy);
                compressor.setInput(src, offset, length, false);
                repeat
                    compressor.setOutput(buffer);
                    zlibResult := compressor.deflate(FINISH);
                    currTotalOut := compressor.getTotalOut();
                    tempStream.write(buffer, 0, int(currTotalOut - prevTotalOut));
                    if zlibResult = STREAM_END then begin
                        compressor.deflateEnd();
                        break;
                    end;
                    prevTotalOut := currTotalOut;
                until false;
                result := tempStream.toByteArray();
            finally
                compressor.free();
                tempStream.free();
            end;
        end;

        class function ZLib.decompress(const src: byte_Array1d; offset, length: int; wbits: int): byte_Array1d;
        var
            zlibResult: int;
            currTotalOut: long;
            prevTotalOut: long;
            buffer: byte_Array1d;
            compressor: ZStream;
            tempStream: ByteArrayOutputStream;
        begin
            &Array.checkBounds(src, offset, length);
            if (wbits < -MAX_WBITS) or (wbits > -MIN_WBITS) and (wbits < MIN_WBITS) or (wbits > MAX_WBITS) and (wbits < MIN_WBITS + USE_CRC) or (wbits > MAX_WBITS + USE_CRC) then begin
                raise IllegalArgumentException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'illegal-argument'), [ CoAnsiString.create('wbits') ]));
            end;
            compressor := nil;
            tempStream := nil;
            try
                prevTotalOut := 0;
                buffer := &Array.newByte1d($10000);
                compressor := ZStream.create();
                tempStream := ByteArrayOutputStream.create();
                compressor.inflateBegin(wbits);
                compressor.setInput(src, offset, length, false);
                repeat
                    compressor.setOutput(buffer);
                    zlibResult := compressor.inflate(NO_FLUSH);
                    currTotalOut := compressor.getTotalOut();
                    tempStream.write(buffer, 0, int(currTotalOut - prevTotalOut));
                    if (zlibResult = STREAM_END) or (zlibResult < 0) then begin
                        compressor.inflateEnd();
                        break;
                    end;
                    prevTotalOut := currTotalOut;
                until false;
                result := tempStream.toByteArray();
            finally
                compressor.free();
                tempStream.free();
            end;
        end;
    {%endregion}

    {%region  ZStream }
        procedure ZStream.flushPending();
        var
            length: int;
            dState: DefState;
        begin
            dState := DefState(fldDeflateState);
            length := dState.fldPendingPos;
            if length > fldAvailableOut then length := fldAvailableOut;
            if length <> 0 then begin
                &Array.copyPrimitives(dState.fldPendingBuf, dState.fldPendingOut, fldNextOutArray, fldNextOutIndex, length);
                inc(fldNextOutIndex, length);
                inc(dState.fldPendingOut, length);
                inc(fldTotalOut, long(length));
                dec(fldAvailableOut, length);
                dec(dState.fldPendingPos, length);
                if dState.fldPendingPos = 0 then dState.fldPendingOut := 0;
            end;
        end;

        function ZStream.readBuf(const dst: byte_Array1d; offset, length: int): int;
        var
            count: int;
        begin
            count := fldAvailableIn;
            if count > length then count := length;
            if count = 0 then begin
                result := 0;
                exit;
            end;
            dec(fldAvailableIn, count);
            if DefState(fldDeflateState).fldWrap <> 0 then fldChecksum.update(fldNextInArray, fldNextInIndex, count);
            &Array.copyPrimitives(fldNextInArray, fldNextInIndex, dst, offset, count);
            inc(fldNextInIndex, count);
            inc(fldTotalIn, long(count));
            result := count;
        end;

        constructor ZStream.create();
        begin
            inherited create();
            fldChecksum := Adler32.create();
        end;

        destructor ZStream.destroy;
        begin
            fldDeflateState.free();
            fldInflateState.free();
            fldChecksum.free();
            inherited destroy;
        end;

        procedure ZStream.setAvailableIn(availableIn: int);
        begin
            fldAvailableIn := availableIn;
        end;

        procedure ZStream.setAvailableOut(availableOut: int);
        begin
            fldAvailableOut := availableOut;
        end;

        procedure ZStream.setNextInIndex(nextInIndex: int);
        begin
            fldNextInIndex := nextInIndex;
        end;

        procedure ZStream.setNextInArray(const nextInArray: byte_Array1d);
        begin
            fldNextInArray := nextInArray;
        end;

        procedure ZStream.setNextOutIndex(nextOutIndex: int);
        begin
            fldNextOutIndex := nextOutIndex;
        end;

        procedure ZStream.setNextOutArray(const nextOutArray: byte_Array1d);
        begin
            fldNextOutArray := nextOutArray;
        end;

        procedure ZStream.setInput(const data: byte_Array1d);
        begin
            setInput(data, 0, system.length(data), false);
        end;

        procedure ZStream.setInput(const data: byte_Array1d; append: boolean);
        begin
            setInput(data, 0, system.length(data), append);
        end;

        procedure ZStream.setInput(const data: byte_Array1d; offset, length: int);
        begin
            setInput(data, offset, length, false);
        end;

        procedure ZStream.setInput(const data: byte_Array1d; offset, length: int; append: boolean);
        var
            tmp: byte_Array1d;
        begin
            &Array.checkBounds(data, offset, length);
            if (length > 0) or not append or (fldNextInArray = nil) then begin
                if (fldAvailableIn > 0) and append then begin
                    tmp := &Array.newByte1d(fldAvailableIn + length);
                    &Array.copyPrimitives(fldNextInArray, fldNextInIndex, tmp, 0, fldAvailableIn);
                    &Array.copyPrimitives(data, offset, tmp, fldAvailableIn, length);
                    fldNextInArray := tmp;
                    fldNextInIndex := 0;
                    inc(fldAvailableIn, length);
                end else begin
                    fldNextInArray := data;
                    fldNextInIndex := offset;
                    fldAvailableIn := length;
                end;
            end;
        end;

        procedure ZStream.setOutput(const data: byte_Array1d);
        begin
            setOutput(data, 0, system.length(data));
        end;

        procedure ZStream.setOutput(const data: byte_Array1d; offset, length: int);
        begin
            &Array.checkBounds(data, offset, length);
            fldNextOutArray := data;
            fldNextOutIndex := offset;
            fldAvailableOut := length;
        end;

        function ZStream.inflateFinished(): boolean;
        var
            iState: InfState;
        begin
            iState := InfState(fldInflateState);
            result := (iState <> nil) and (iState.fldMode = 12);
        end;

        function ZStream.inflateBegin(): int;
        begin
            result := inflateBegin(DEFAULT_WBITS, false);
        end;

        function ZStream.inflateBegin(wbits: int): int;
        begin
            result := inflateBegin(wbits, false);
        end;

        function ZStream.inflateBegin(nowrap: boolean): int;
        begin
            result := inflateBegin(DEFAULT_WBITS, nowrap);
        end;

        function ZStream.inflateBegin(wbits: int; nowrap: boolean): int;
        begin
            fldInflateState.free();
            fldInflateState := InfState.create(self);
            if nowrap then begin
                result := InfState(fldInflateState).inflateBegin(-wbits);
                exit;
            end;
            result := InfState(fldInflateState).inflateBegin(wbits);
        end;

        function ZStream.inflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
        var
            iState: InfState;
        begin
            iState := InfState(fldInflateState);
            if iState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := iState.inflateSetDictionary(dictionary, length);
        end;

        function ZStream.inflateSync(): int;
        var
            iState: InfState;
        begin
            iState := InfState(fldInflateState);
            if iState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := iState.inflateSync();
        end;

        function ZStream.inflateSyncPoint(): int;
        var
            iState: InfState;
        begin
            iState := InfState(fldInflateState);
            if iState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := iState.inflateSyncPoint();
        end;

        function ZStream.inflate(flush: int): int;
        var
            iState: InfState;
        begin
            iState := InfState(fldInflateState);
            if iState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := iState.inflate(flush);
        end;

        function ZStream.inflateEnd(): int;
        var
            iState: InfState;
        begin
            iState := InfState(fldInflateState);
            if iState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := iState.inflateEnd();
            fldInflateState := nil;
            iState.free();
        end;

        function ZStream.deflateBegin(level: int): int;
        begin
            result := deflateBegin(level, DEFAULT_WBITS, false);
        end;

        function ZStream.deflateBegin(level: int; nowrap: boolean): int;
        begin
            result := deflateBegin(level, DEFAULT_WBITS, nowrap);
        end;

        function ZStream.deflateBegin(level, wbits: int): int;
        begin
            result := deflateBegin(level, wbits, false);
        end;

        function ZStream.deflateBegin(level, wbits, memLevel: int): int;
        begin
            fldDeflateState.free();
            fldDeflateState := DefState.create(self);
            result := DefState(fldDeflateState).deflateBegin(level, wbits, memLevel);
        end;

        function ZStream.deflateBegin(level, wbits: int; nowrap: boolean): int;
        begin
            fldDeflateState.free();
            fldDeflateState := DefState.create(self);
            if nowrap then begin
                result := DefState(fldDeflateState).deflateBegin(level, -wbits);
                exit;
            end;
            result := DefState(fldDeflateState).deflateBegin(level, wbits);
        end;

        function ZStream.deflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
        var
            dState: DefState;
        begin
            dState := DefState(fldDeflateState);
            if dState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := dState.deflateSetDictionary(dictionary, length);
        end;

        function ZStream.deflateParameters(level, strategy: int): int;
        var
            dState: DefState;
        begin
            dState := DefState(fldDeflateState);
            if dState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := dState.deflateParameters(level, strategy);
        end;

        function ZStream.deflate(flush: int): int;
        var
            dState: DefState;
        begin
            dState := DefState(fldDeflateState);
            if dState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := dState.deflate(flush);
        end;

        function ZStream.deflateEnd(): int;
        var
            dState: DefState;
        begin
            dState := DefState(fldDeflateState);
            if dState = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            result := dState.deflateEnd();
            fldDeflateState := nil;
            dState.free();
        end;

        function ZStream.getChecksum(): int;
        begin
            result := fldChecksum.getValue();
        end;

        function ZStream.getAvailableIn(): int;
        begin
            result := fldAvailableIn;
        end;

        function ZStream.getAvailableOut(): int;
        begin
            result := fldAvailableOut;
        end;

        function ZStream.getNextInIndex(): int;
        begin
            result := fldNextInIndex;
        end;

        function ZStream.getNextOutIndex(): int;
        begin
            result := fldNextOutIndex;
        end;

        function ZStream.getTotalIn(): long;
        begin
            result := fldTotalIn;
        end;

        function ZStream.getTotalOut(): long;
        begin
            result := fldTotalOut;
        end;

        function ZStream.getMessage(): AnsiString;
        begin
            result := fldMessage;
        end;

        function ZStream.getNextInArray(): byte_Array1d;
        begin
            result := fldNextInArray;
        end;

        function ZStream.getNextOutArray(): byte_Array1d;
        begin
            result := fldNextOutArray;
        end;
    {%endregion}

    {%region  GZIPHeader }
        procedure GZIPHeader.put(deflateState: ZLib);
        var
            xfl: int;
            flags: int;
            extra: byte_Array1d;
            name: byte_Array1d;
            comment: byte_Array1d;
            dState: DefState absolute deflateState;
        begin
            flags := 0;
            extra := fldExtra;
            name := fldName;
            comment := fldComment;
            if extra <> nil then flags := flags or $04;
            if name <> nil then flags := flags or $08;
            if comment <> nil then flags := flags or $10;
            case dState.fldLevel of
            BEST_SPEED:
                xfl := 4;
            BEST_COMPRESSION:
                xfl := 2;
            else
                xfl := 0;
            end;
            dState.putShort($8b1f);
            dState.putByte(8);
            dState.putByte(flags);
            dState.putByte(fldModifiedTime);
            dState.putByte(fldModifiedTime shr 8);
            dState.putByte(fldModifiedTime shr 16);
            dState.putByte(fldModifiedTime shr 24);
            dState.putByte(xfl);
            dState.putByte(fldOS);
            if extra <> nil then begin
                flags := system.length(extra);
                dState.putByte(flags);
                dState.putByte(flags shr 8);
                dState.putBytes(extra, 0, flags);
            end;
            if name <> nil then begin
                dState.putBytes(name, 0, system.length(name));
                dState.putByte(0);
            end;
            if comment <> nil then begin
                dState.putBytes(comment, 0, system.length(comment));
                dState.putByte(0);
            end;
        end;

        constructor GZIPHeader.create();
        begin
            inherited create();
            fldOS := 255;
        end;

        procedure GZIPHeader.setOS(os: int);
        begin
            if ((os < 0) or (os > 13)) and (os <> 255) then begin
                raise IllegalArgumentException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'illegal-argument'), [ CoAnsiString.create('os') ]));
            end;
            fldOS := os;
        end;

        procedure GZIPHeader.setCRC(crc: int);
        begin
            fldCRC := crc;
        end;

        procedure GZIPHeader.setModifiedTime(modifiedTime: int);
        begin
            fldModifiedTime := modifiedTime;
        end;

        procedure GZIPHeader.setName(const name: AnsiString);
        begin
            fldName := name.toByteArray();
        end;

        procedure GZIPHeader.setComment(const comment: AnsiString);
        begin
            fldComment := comment.toByteArray();
        end;

        function GZIPHeader.getOS(): int;
        begin
            result := fldOS;
        end;

        function GZIPHeader.getCRC(): int;
        begin
            result := fldCRC;
        end;

        function GZIPHeader.getModifiedTime(): int;
        begin
            result := fldModifiedTime;
        end;

        function GZIPHeader.getName(): AnsiString;
        begin
            result := AnsiString.create(fldName);
        end;

        function GZIPHeader.getComment(): AnsiString;
        begin
            result := AnsiString.create(fldComment);
        end;
    {%endregion}

    {%region  Config }
        constructor Config.create(goodLength, maxLazy, niceLength, maxChain, func: int);
        begin
            inherited create();
            fldGoodLength := goodLength;
            fldMaxLazy := maxLazy;
            fldNiceLength := niceLength;
            fldMaxChain := maxChain;
            fldFunc := func;
        end;
    {%endregion}

    {%region  DefState }
        class procedure DefState.initialize();
        begin
            distCode := [
                0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
                8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10,
                10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
                11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
                13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
                13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
                14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
                14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
                14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
                15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
                15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
                15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17,
                18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
                23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
                24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
                26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
                26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
                27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
                27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
                28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
                28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
                28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
                29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
                29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
                29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
            ];
            blOrder := [
                16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
                11, 4, 12, 3, 13, 2, 14, 1, 15
            ];
            lengthCode := [
                0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12,
                13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
                17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
                19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
                21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
                22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
                23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
                24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
                25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
                25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
                26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
                26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
                27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
            ];
            baseLength := [
                0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
                64, 80, 96, 112, 128, 160, 192, 224, 0
            ];
            baseDist := [
                0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
                1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
            ];
            errorMessages := [
                'need dictionary',
                'stream end',
                '',
                'file error',
                'stream error',
                'data error',
                'insufficient memory',
                'buffer error',
                'incompatible version',
                ''
            ];
            configTable := [
                Config.create(0, 0, 0, 0, STORED),
                Config.create(4, 4, 8, 4, FAST),
                Config.create(4, 5, 16, 8, FAST),
                Config.create(4, 6, 32, 32, FAST),
                Config.create(4, 4, 16, 16, SLOW),
                Config.create(8, 16, 32, 32, SLOW),
                Config.create(8, 16, 128, 128, SLOW),
                Config.create(8, 32, 128, 256, SLOW),
                Config.create(32, 128, 258, 1024, SLOW),
                Config.create(32, 258, 258, 4096, SLOW)
            ];
        end;

        class procedure DefState.finalize();
        var
            index: int;
        begin
            for index := system.length(configTable) - 1 downto 0 do begin
                configTable[index].free();
            end;
            configTable := nil;
            errorMessages := nil;
            baseDist := nil;
            baseLength := nil;
            lengthCode := nil;
            blOrder := nil;
            distCode := nil;
        end;

        class function DefState.smaller(const tree: int_Array1d; n, m: int; const depth: byte_Array1d): boolean;
        var
            tn2: int;
            tm2: int;
        begin
            tn2 := tree[n * 2];
            tm2 := tree[m * 2];
            result := (tn2 < tm2) or ((tn2 = tm2) and (depth[n] <= depth[m]));
        end;

        class function DefState.dcode(dist: int): int;
        begin
            if dist < 256 then begin
                result := distCode[dist];
                exit;
            end;
            result := distCode[256 + (dist shr 7)];
        end;

        procedure DefState.lmInit();
        var
            cfg: Config;
        begin
            fldWindowSize := 2 * fldWSize;
            &Array.fillPrimitives(fldHeadBuf, 0, fldHashSize, 0);
            cfg := configTable[fldLevel];
            fldMaxLazyMatch := cfg.maxLazy;
            fldGoodMatch := cfg.goodLength;
            fldNiceMatch := cfg.niceLength;
            fldMaxChainLength := cfg.maxChain;
            fldStrStart := 0;
            fldBlockStart := 0;
            fldLookahead := 0;
            fldMatchLength := MIN_MATCH - 1;
            fldPrevLength := MIN_MATCH - 1;
            fldMatchAvailable := 0;
            fldInsh := 0;
        end;

        procedure DefState.trInit();
        var
            desc: Tree;
        begin
            desc := fldLDesc;
            desc.fldDynamicTree := fldDynamicLTree;
            desc.fldStaticDesc := StaticTree.staticLDesc;
            desc := fldDDesc;
            desc.fldDynamicTree := fldDynamicDTree;
            desc.fldStaticDesc := StaticTree.staticDDesc;
            desc := fldBlDesc;
            desc.fldDynamicTree := fldBlTree;
            desc.fldStaticDesc := StaticTree.staticBLDesc;
            fldBiBuf := 0;
            fldBiValid := 0;
            fldLastEobLen := 8;
            initBlock();
        end;

        procedure DefState.initBlock();
        var
            index: int;
            tree: int_Array1d;
        begin
            tree := fldBlTree;
            for index := 0 to BL_CODES - 1 do begin
                tree[index * 2] := 0;
            end;
            tree := fldDynamicDTree;
            for index := 0 to D_CODES - 1 do begin
                tree[index * 2] := 0;
            end;
            tree := fldDynamicLTree;
            for index := 0 to L_CODES - 1 do begin
                tree[index * 2] := 0;
            end;
            tree[END_BLOCK * 2] := 1;
            fldOptLen := 0;
            fldStaticLen := 0;
            fldLastLit := 0;
            fldMatches := 0;
        end;

        procedure DefState.scanTree(const tree: int_Array1d; maxCode: int);
        var
            index: int;
            count: int;
            prevLen: int;
            currLen: int;
            nextLen: int;
            maxCount: int;
            minCount: int;
            blTree: int_Array1d;
        begin
            prevLen := -1;
            nextLen := tree[1];
            count := 0;
            maxCount := 7;
            minCount := 4;
            if nextLen = 0 then begin
                maxCount := 138;
                minCount := 3;
            end;
            tree[(maxCode + 1) * 2 + 1] := -1;
            blTree := fldBlTree;
            for index := 0 to maxCode do begin
                currLen := nextLen;
                nextLen := tree[index * 2 + 3];
                inc(count);
                if (count < maxCount) and (currLen = nextLen) then continue;
                if count < minCount then begin
                    inc(blTree[currLen * 2], count);
                end else
                if currLen <> 0 then begin
                    if currLen <> prevLen then inc(blTree[currLen * 2]);
                    inc(blTree[REP_3_6 * 2]);
                end else
                if count <= 10 then begin
                    inc(blTree[REPZ_3_10 * 2]);
                end else begin
                    inc(blTree[REPZ_11_138 * 2]);
                end;
                count := 0;
                prevLen := currLen;
                if nextLen = 0 then begin
                    maxCount := 138;
                    minCount := 3;
                end else
                if currLen = nextLen then begin
                    maxCount := 6;
                    minCount := 3;
                end else begin
                    maxCount := 7;
                    minCount := 4;
                end;
            end;
        end;

        procedure DefState.sendAllTrees(lCodes, dCodes, blCodes: int);
        var
            rank: int;
            blTree: int_Array1d;
        begin
            sendBits(lCodes - 257, 5);
            sendBits(dCodes - 1, 5);
            sendBits(blCodes - 4, 4);
            blTree := fldBlTree;
            for rank := 0 to blCodes - 1 do begin
                sendBits(blTree[blOrder[rank] * 2 + 1], 3);
            end;
            sendTree(fldDynamicLTree, lCodes - 1);
            sendTree(fldDynamicDTree, dCodes - 1);
        end;

        procedure DefState.sendTree(const tree: int_Array1d; maxCode: int);
        var
            index: int;
            count: int;
            prevLen: int;
            currLen: int;
            nextLen: int;
            maxCount: int;
            minCount: int;
        begin
            prevLen := -1;
            nextLen := tree[1];
            count := 0;
            maxCount := 7;
            minCount := 4;
            if nextLen = 0 then begin
                maxCount := 138;
                minCount := 3;
            end;
            for index := 0 to maxCode do begin
                currLen := nextLen;
                nextLen := tree[index * 2 + 3];
                inc(count);
                if (count < maxCount) and (currLen = nextLen) then continue;
                if count < minCount then begin
                    repeat
                        sendCode(currLen, fldBlTree);
                        dec(count);
                    until count = 0;
                end else
                if currLen <> 0 then begin
                    if currLen <> prevLen then begin
                        sendCode(currLen, fldBlTree);
                        dec(count);
                    end;
                    sendCode(REP_3_6, fldBlTree);
                    sendBits(count - 3, 2);
                end else
                if count <= 10 then begin
                    sendCode(REPZ_3_10, fldBlTree);
                    sendBits(count - 3, 3);
                end else begin
                    sendCode(REPZ_11_138, fldBlTree);
                    sendBits(count - 11, 7);
                end;
                count := 0;
                prevLen := currLen;
                if nextLen = 0 then begin
                    maxCount := 138;
                    minCount := 3;
                end else
                if currLen = nextLen then begin
                    maxCount := 6;
                    minCount := 3;
                end else begin
                    maxCount := 7;
                    minCount := 4;
                end;
            end;
        end;

        procedure DefState.putShortMSB(b: int);
        begin
            putByte(b shr 8);
            putByte(b);
        end;

        procedure DefState.sendCode(c: int; const tree: int_Array1d);
        var
            c2: int;
        begin
            c2 := c * 2;
            sendBits(tree[c2] and $ffff, tree[c2 + 1] and $ffff);
        end;

        procedure DefState.sendBits(val, len: int);
        var
            buf: int;
            valid: int;
        begin
            valid := fldBiValid;
            buf := fldBiBuf or ((val shl valid) and $ffff);
            if valid > BUF_SIZE - len then begin
                putShort(buf);
                buf := short(val shr (BUF_SIZE - valid));
                fldBiValid := valid + len - BUF_SIZE;
            end else begin
                fldBiValid := valid + len;
            end;
            fldBiBuf := buf;
        end;

        procedure DefState.trAlign();
        begin
            sendBits(STATIC_TREES shl 1, 3);
            sendCode(END_BLOCK, StaticTree.staticLTree);
            biFlush();
            if fldLastEobLen - fldBiValid + 11 < 9 then begin
                sendBits(STATIC_TREES shl 1, 3);
                sendCode(END_BLOCK, StaticTree.staticLTree);
                biFlush();
            end;
            fldLastEobLen := 7;
        end;

        procedure DefState.compressBlock(const lTree, dTree: int_Array1d);
        var
            lc: int;
            lx: int;
            dist: int;
            code: int;
            extra: int;
            buf: byte_Array1d;
        begin
            lx := 0;
            if fldLastLit <> 0 then begin
                buf := fldPendingBuf;
                repeat
                    dist := ((int(buf[fldDBuf + lx * 2]) shl 8) and $ff00) or (int(buf[fldDBuf + lx * 2 + 1]) and $ff);
                    lc := int(buf[fldLBuf + lx]) and $ff;
                    inc(lx);
                    if dist = 0 then begin
                        sendCode(lc, lTree);
                        continue;
                    end;
                    code := lengthCode[lc];
                    sendCode(code + (LITERALS + 1), lTree);
                    extra := StaticTree.extraLBits[code];
                    if extra <> 0 then begin
                        dec(lc, baseLength[code]);
                        sendBits(lc, extra);
                    end;
                    dec(dist);
                    code := dcode(dist);
                    sendCode(code, dTree);
                    extra := StaticTree.extraDBits[code];
                    if extra <> 0 then begin
                        dec(dist, baseDist[code]);
                        sendBits(dist, extra);
                    end;
                until lx >= fldLastLit;
            end;
            sendCode(END_BLOCK, lTree);
            fldLastEobLen := lTree[END_BLOCK * 2 + 1];
        end;

        procedure DefState.setDataType();
        var
            index: int;
            binFreq: int;
            asciiFreq: int;
            tree: int_Array1d;
        begin
            index := 0;
            binFreq := 0;
            asciiFreq := 0;
            tree := fldDynamicLTree;
            while index < 7 do begin
                inc(binFreq, tree[index * 2]);
                inc(index);
            end;
            while index < 128 do begin
                inc(asciiFreq, tree[index * 2]);
                inc(index);
            end;
            while index < LITERALS do begin
                inc(binFreq, tree[index * 2]);
                inc(index);
            end;
            if binFreq > (asciiFreq shr 2) then begin
                fldDataType := Z_BINARY;
                exit;
            end;
            fldDataType := Z_ASCII;
        end;

        procedure DefState.biFlush();
        var
            buf: int;
            valid: int;
        begin
            buf := fldBiBuf;
            valid := fldBiValid;
            if valid = 16 then begin
                putShort(buf);
                fldBiBuf := 0;
                fldBiValid := 0;
                exit;
            end;
            if valid >= 8 then begin
                putByte(buf);
                fldBiBuf := buf shr 8;
                fldBiValid := valid - 8;
            end;
        end;

        procedure DefState.biWindup();
        var
            valid: int;
        begin
            valid := fldBiValid;
            if valid > 8 then begin
                putShort(fldBiBuf);
            end else
            if valid > 0 then begin
                putByte(fldBiBuf);
            end;
            fldBiBuf := 0;
            fldBiValid := 0;
        end;

        procedure DefState.copyBlock(buf, len: int; header: boolean);
        begin
            biWindup();
            fldLastEobLen := 8;
            if header then begin
                putShort(len);
                putShort(not len);
            end;
            putBytes(fldWindowBuf, buf, len);
        end;

        procedure DefState.flushBlockOnly(eof: boolean);
        var
            blockStart: int;
        begin
            blockStart := fldBlockStart;
            if blockStart >= 0 then begin
                trFlushBlock(blockStart, fldStrStart - blockStart, eof);
            end else begin
                trFlushBlock(-1, fldStrStart - blockStart, eof);
            end;
            fldBlockStart := fldStrStart;
            fldStream.flushPending();
        end;

        procedure DefState.trStoredBlock(buf, storedLen: int; eof: boolean);
        begin
            if eof then begin
                sendBits((STORED_BLOCK shl 1) + 1, 3);
            end else begin
                sendBits(STORED_BLOCK shl 1, 3);
            end;
            copyBlock(buf, storedLen, true);
        end;

        procedure DefState.trFlushBlock(buf, storedLen: int; eof: boolean);
        var
            optLenb: int;
            staticLenb: int;
            maxBlIndex: int;
        begin
            maxBlIndex := 0;
            if fldLevel > 0 then begin
                if fldDataType = Z_UNKNOWN then setDataType();
                fldLDesc.buildTree(self);
                fldDDesc.buildTree(self);
                maxBlIndex := buildBlTree();
                optLenb := (fldOptLen + 10) shr 3;
                staticLenb := (fldStaticLen + 10) shr 3;
                if staticLenb <= optLenb then optLenb := staticLenb;
            end else begin
                optLenb := storedLen + 5;
                staticLenb := optLenb;
            end;
            if (storedLen + 4 <= optLenb) and (buf <> -1) then begin
                trStoredBlock(buf, storedLen, eof);
            end else
            if staticLenb = optLenb then begin
                if eof then begin
                    sendBits((STATIC_TREES shl 1) + 1, 3);
                end else begin
                    sendBits(STATIC_TREES shl 1, 3);
                end;
                compressBlock(StaticTree.staticLTree, StaticTree.staticDTree);
            end else begin
                if eof then begin
                    sendBits((DYN_TREES shl 1) + 1, 3);
                end else begin
                    sendBits(DYN_TREES shl 1, 3);
                end;
                sendAllTrees(fldLDesc.fldMaxCode + 1, fldDDesc.fldMaxCode + 1, maxBlIndex + 1);
                compressBlock(fldDynamicLTree, fldDynamicDTree);
            end;
            initBlock();
            if eof then biWindup();
        end;

        procedure DefState.fillWindow();
        var
            n: int;
            m: int;
            p: int;
            more: int;
            insh: int;
            wSize: int;
            strStart: int;
            hashSize: int;
            hashMask: int;
            hashShift: int;
            lookahead: int;
            blockStart: int;
            matchStart: int;
            windowSize: int;
            windowBuf: byte_Array1d;
            headBuf: int_Array1d;
            prevBuf: int_Array1d;
            stream: ZStream;
        begin
            insh := fldInsh;
            wSize := fldWSize;
            strStart := fldStrStart;
            hashSize := fldHashSize;
            hashMask := fldHashMask;
            hashShift := fldHashShift;
            lookahead := fldLookahead;
            blockStart := fldBlockStart;
            matchStart := fldMatchStart;
            windowSize := fldWindowSize;
            windowBuf := fldWindowBuf;
            headBuf := fldHeadBuf;
            prevBuf := fldPrevBuf;
            stream := fldStream;
            repeat
                more := windowSize - lookahead - strStart;
                if (more = 0) and (strStart = 0) and (lookahead = 0) then begin
                    more := wSize;
                end else
                if more = -1 then begin
                    dec(more);
                end else
                if strStart >= wSize * 2 - MIN_LOOKAHEAD then begin
                    &Array.copyPrimitives(windowBuf, wSize, windowBuf, 0, wSize);
                    dec(matchStart, wSize);
                    dec(strStart, wSize);
                    dec(blockStart, wSize);
                    n := hashSize;
                    p := n;
                    repeat
                        dec(p);
                        m := headBuf[p] and $ffff;
                        if m >= wSize then begin
                            headBuf[p] := short(m - wSize);
                        end else begin
                            headBuf[p] := 0;
                        end;
                        dec(n);
                    until n = 0;
                    n := wSize;
                    p := n;
                    repeat
                        dec(p);
                        m := prevBuf[p] and $ffff;
                        if m >= wSize then begin
                            prevBuf[p] := short(m - wSize);
                        end else begin
                            prevBuf[p] := 0;
                        end;
                        dec(n);
                    until n = 0;
                    inc(more, wSize);
                end;
                if stream.fldAvailableIn = 0 then exit;
                n := stream.readBuf(windowBuf, strStart + lookahead, more);
                inc(lookahead, n);
                if lookahead >= MIN_MATCH then insh := (((int(windowBuf[strStart]) and $ff) shl hashShift) xor (int(windowBuf[strStart + 1]) and $ff)) and hashMask;
            until (lookahead >= MIN_LOOKAHEAD) or (stream.fldAvailableIn = 0);
            fldMatchStart := matchStart;
            fldBlockStart := blockStart;
            fldLookahead := lookahead;
            fldStrStart := strStart;
            fldInsh := insh;
        end;

        function DefState.trTally(dist, lc: int): boolean;
        var
            dBuf: int;
            dCode: int;
            lastLit: int;
            inLength: int;
            outLength: int;
            pendingBuf: byte_Array1d;
            tree: int_Array1d;
        begin
            dBuf := fldDBuf;
            lastLit := fldLastLit;
            pendingBuf := fldPendingBuf;
            pendingBuf[dBuf + lastLit * 2] := byte(dist shr 8);
            pendingBuf[dBuf + lastLit * 2 + 1] := byte(dist);
            pendingBuf[fldLBuf + lastLit] := byte(lc);
            inc(lastLit);
            fldLastLit := lastLit;
            if dist = 0 then begin
                inc(fldDynamicLTree[lc * 2]);
            end else begin
                dec(dist);
                inc(fldMatches);
                inc(fldDynamicLTree[(lengthCode[lc] + (LITERALS + 1)) * 2]);
                inc(fldDynamicDTree[self.dcode(dist) * 2]);
            end;
            if ((lastLit and $1fff) = 0) and (fldLevel > 2) then begin
                tree := fldDynamicDTree;
                outLength := lastLit * 8;
                inLength := fldStrStart - fldBlockStart;
                for dCode := 0 to D_CODES - 1 do begin
                    inc(outLength, int(long(tree[dCode * 2]) * (long(5) + long(StaticTree.extraDBits[dCode]))));
                end;
                outLength := outLength shr 3;
                if (fldMatches < lastLit div 2) and (outLength < inLength div 2) then begin
                    result := true;
                    exit;
                end;
            end;
            result := lastLit = fldLitBufSize - 1;
        end;

        function DefState.buildBlTree(): int;
        var
            codes: int;
            tree: int_Array1d;
        begin
            scanTree(fldDynamicLTree, fldLDesc.fldMaxCode);
            scanTree(fldDynamicDTree, fldDDesc.fldMaxCode);
            fldBlDesc.buildTree(self);
            codes := BL_CODES - 1;
            tree := fldBlTree;
            while (codes >= 3) and (tree[blOrder[codes] * 2 + 1] = 0) do begin
                dec(codes);
            end;
            inc(fldOptLen, 3 * (codes + 1) + 14);
            result := codes;
        end;

        function DefState.deflateStored(flush: int): int;
        var
            maxStart: int;
            maxBlockSize: int;
            stream:ZStream;
        begin
            stream := fldStream;
            maxBlockSize := $ffff;
            if maxBlockSize > fldPendingSize - 5 then maxBlockSize := fldPendingSize - 5;
            repeat
                if fldLookahead <= 1 then begin
                    fillWindow();
                    if (fldLookahead = 0) and (flush = NO_FLUSH) then begin
                        result := NEED_MORE;
                        exit;
                    end;
                    if fldLookahead = 0 then break;
                end;
                inc(fldStrStart, fldLookahead);
                fldLookahead := 0;
                maxStart := fldBlockStart + maxBlockSize;
                if (fldStrStart = 0) or (fldStrStart >= maxStart) then begin
                    fldLookahead := fldStrStart - maxStart;
                    fldStrStart := maxStart;
                    flushBlockOnly(false);
                    if stream.fldAvailableOut = 0 then begin
                        result := NEED_MORE;
                        exit;
                    end;
                end;
                if fldStrStart - fldBlockStart >= fldWSize - MIN_LOOKAHEAD then begin
                    flushBlockOnly(false);
                    if stream.fldAvailableOut = 0 then begin
                        result := NEED_MORE;
                        exit;
                    end;
                end;
            until false;
            flushBlockOnly(flush = FINISH);
            if stream.fldAvailableOut = 0 then begin
                if flush = FINISH then begin
                    result := FINISH_STARTED;
                    exit;
                end;
                result := NEED_MORE;
                exit;
            end;
            if flush = FINISH then begin
                result := FINISH_DONE;
                exit;
            end;
            result := BLOCK_DONE;
        end;

        function DefState.deflateFast(flush: int): int;
        var
            bFlush: boolean;
            hashHead: int;
            stream: ZStream;
        begin
            stream := fldStream;
            hashHead := 0;
            repeat
                if fldLookahead < MIN_LOOKAHEAD then begin
                    fillWindow();
                    if (fldLookahead < MIN_LOOKAHEAD) and (flush = NO_FLUSH) then begin
                        result := NEED_MORE;
                        exit;
                    end;
                    if fldLookahead = 0 then break;
                end;
                if fldLookahead >= MIN_MATCH then begin
                    fldInsh := ((fldInsh shl fldHashShift) xor (int(fldWindowBuf[fldStrStart + (MIN_MATCH - 1)]) and $ff)) and fldHashMask;
                    hashHead := fldHeadBuf[fldInsh] and $ffff;
                    fldPrevBuf[fldStrStart and fldWMask] := fldHeadBuf[fldInsh];
                    fldHeadBuf[fldInsh] := short(fldStrStart);
                end;
                if (hashHead <> 0) and (((fldStrStart - hashHead) and $ffff) <= fldWSize - MIN_LOOKAHEAD) and (fldStrategy <> HUFFMAN_ONLY) then fldMatchLength := longestMatch(hashHead);
                if fldMatchLength >= MIN_MATCH then begin
                    bFlush := trTally(fldStrStart - fldMatchStart, fldMatchLength - MIN_MATCH);
                    dec(fldLookahead, fldMatchLength);
                    if (fldMatchLength <= fldMaxLazyMatch) and (fldLookahead >= MIN_MATCH) then begin
                        dec(fldMatchLength);
                        repeat
                            inc(fldStrStart);
                            fldInsh := ((fldInsh shl fldHashShift) xor (int(fldWindowBuf[fldStrStart + (MIN_MATCH - 1)]) and $ff)) and fldHashMask;
                            hashHead := fldHeadBuf[fldInsh] and $ffff;
                            fldPrevBuf[fldStrStart and fldWMask] := fldHeadBuf[fldInsh];
                            fldHeadBuf[fldInsh] := short(fldStrStart);
                            dec(fldMatchLength);
                        until fldMatchLength = 0;
                        inc(fldStrStart);
                    end else begin
                        inc(fldStrStart, fldMatchLength);
                        fldMatchLength := 0;
                        fldInsh := int(fldWindowBuf[fldStrStart]) and $ff;
                        fldInsh := ((fldInsh shl fldHashShift) xor (int(fldWindowBuf[fldStrStart + 1]) and $ff)) and fldHashMask;
                    end;
                end else begin
                    bFlush := trTally(0, int(fldWindowBuf[fldStrStart]) and $ff);
                    dec(fldLookahead);
                    inc(fldStrStart);
                end;
                if bFlush then begin
                    flushBlockOnly(false);
                    if stream.fldAvailableOut = 0 then begin
                        result := NEED_MORE;
                        exit;
                    end;
                end;
            until false;
            flushBlockOnly(flush = FINISH);
            if stream.fldAvailableOut = 0 then begin
                if flush = FINISH then begin
                    result := FINISH_STARTED;
                    exit;
                end;
                result := NEED_MORE;
                exit;
            end;
            if flush = FINISH then begin
                result := FINISH_DONE;
                exit;
            end;
            result := BLOCK_DONE;
        end;

        function DefState.deflateSlow(flush: int): int;
        var
            bFlush: boolean;
            maxInsert: int;
            hashHead: int;
            stream: ZStream;
        begin
            stream := fldStream;
            hashHead := 0;
            repeat
                if fldLookahead < MIN_LOOKAHEAD then begin
                    fillWindow();
                    if (fldLookahead < MIN_LOOKAHEAD) and (flush = NO_FLUSH) then begin
                        result := NEED_MORE;
                        exit;
                    end;
                    if fldLookahead = 0 then break;
                end;
                if fldLookahead >= MIN_MATCH then begin
                    fldInsh := ((fldInsh shl fldHashShift) xor (int(fldWindowBuf[fldStrStart + (MIN_MATCH - 1)]) and $ff)) and fldHashMask;
                    hashHead := fldHeadBuf[fldInsh] and $ffff;
                    fldPrevBuf[fldStrStart and fldWMask] := fldHeadBuf[fldInsh];
                    fldHeadBuf[fldInsh] := short(fldStrStart);
                end;
                fldPrevLength := fldMatchLength;
                fldPrevMatch := fldMatchStart;
                fldMatchLength := MIN_MATCH - 1;
                if (hashHead <> 0) and (fldPrevLength < fldMaxLazyMatch) and (((fldStrStart - hashHead) and $ffff) <= fldWSize - MIN_LOOKAHEAD) then begin
                    if fldStrategy <> HUFFMAN_ONLY then begin
                        fldMatchLength := longestMatch(hashHead);
                    end;
                    if (fldMatchLength <= 5) and ((fldStrategy = FILTERED) or ((fldMatchLength = MIN_MATCH) and (fldStrStart - fldMatchStart > $1000))) then begin
                        fldMatchLength := MIN_MATCH - 1;
                    end;
                end;
                if (fldPrevLength >= MIN_MATCH) and (fldMatchLength <= fldPrevLength) then begin
                    maxInsert := fldStrStart + fldLookahead - MIN_MATCH;
                    bFlush := trTally(fldStrStart - fldPrevMatch - 1, fldPrevLength - MIN_MATCH);
                    dec(fldLookahead, fldPrevLength - 1);
                    dec(fldPrevLength, 2);
                    repeat
                        inc(fldStrStart);
                        if fldStrStart <= maxInsert then begin
                            fldInsh := ((fldInsh shl fldHashShift) xor (int(fldWindowBuf[fldStrStart + (MIN_MATCH - 1)]) and $ff)) and fldHashMask;
                            hashHead := fldHeadBuf[fldInsh] and $ffff;
                            fldPrevBuf[fldStrStart and fldWMask] := fldHeadBuf[fldInsh];
                            fldHeadBuf[fldInsh] := short(fldStrStart);
                        end;
                        dec(fldPrevLength);
                    until fldPrevLength = 0;
                    fldMatchAvailable := 0;
                    fldMatchLength := MIN_MATCH - 1;
                    inc(fldStrStart);
                    if bFlush then begin
                        flushBlockOnly(false);
                        if stream.fldAvailableOut = 0 then begin
                            result := NEED_MORE;
                            exit;
                        end;
                    end;
                end else
                if fldMatchAvailable <> 0 then begin
                    bFlush := trTally(0, int(fldWindowBuf[fldStrStart - 1]) and $ff);
                    if bFlush then flushBlockOnly(false);
                    inc(fldStrStart);
                    dec(fldLookahead);
                    if stream.fldAvailableOut = 0 then begin
                        result := NEED_MORE;
                        exit;
                    end;
                end else begin
                    fldMatchAvailable := 1;
                    inc(fldStrStart);
                    dec(fldLookahead);
                end;
            until false;
            if fldMatchAvailable <> 0 then begin
                trTally(0, int(fldWindowBuf[fldStrStart - 1]) and $ff);
                fldMatchAvailable := 0;
            end;
            flushBlockOnly(flush = FINISH);
            if stream.fldAvailableOut = 0 then begin
                if flush = FINISH then begin
                    result := FINISH_STARTED;
                    exit;
                end;
                result := NEED_MORE;
                exit;
            end;
            if flush = FINISH then begin
                result := FINISH_DONE;
                exit;
            end;
            result := BLOCK_DONE;
        end;

        function DefState.deflateReset(): int;
        var
            wrap: int;
            stream: ZStream;
        begin
            stream := fldStream;
            stream.fldTotalIn := 0;
            stream.fldTotalOut := 0;
            stream.fldMessage := '';
            stream.fldDataType := Z_UNKNOWN;
            fldPendingPos := 0;
            fldPendingOut := 0;
            wrap := fldWrap;
            if wrap < 0 then fldWrap := -wrap;
            if wrap = 0 then begin
                fldStatus := BUSY_STATE;
            end else begin
                fldStatus := INIT_STATE;
            end;
            stream.fldChecksum.reset();
            fldLastFlush := NO_FLUSH;
            trInit();
            lmInit();
            result := OK;
        end;

        function DefState.deflateBegin(comLevel, method, windowBits, memLevel, strategy: int): int;
        var
            wrapLocal: int;
            stream: ZStream;
        begin
            wrapLocal := 1;
            stream := fldStream;
            stream.fldMessage := '';
            if comLevel = DEFAULT_COMPRESSION then comLevel := 6;
            if windowBits < 0 then begin
                wrapLocal := 0;
                windowBits := -windowBits;
            end else
            if windowBits >= USE_CRC then begin
                wrapLocal := 2;
                dec(windowBits, USE_CRC);
                stream.fldChecksum.free();
                stream.fldChecksum := CRC32.create();
            end;
            if
                (method <> Z_DEFLATED) or
                (windowBits < MIN_WBITS) or (windowBits > MAX_WBITS) or
                (memLevel < MIN_MEM_LEVEL) or (memLevel > MAX_MEM_LEVEL) or
                (strategy < DEFAULT_STRATEGY) or (strategy > HUFFMAN_ONLY) or
                (comLevel < NO_COMPRESSION) or (comLevel > BEST_COMPRESSION)
            then begin
                result := STREAM_ERROR;
                exit;
            end;
            fldWrap := wrapLocal;
            fldWBits := windowBits;
            fldWSize := 1 shl windowBits;
            fldWMask := fldWSize - 1;
            fldHashBits := memLevel + 7;
            fldHashSize := 1 shl fldHashBits;
            fldHashMask := fldHashSize - 1;
            fldHashShift := (fldHashBits + (MIN_MATCH - 1)) div MIN_MATCH;
            fldWindowBuf := &Array.newByte1d(fldWSize * 2);
            fldPrevBuf := &Array.newInt1d(fldWSize);
            fldHeadBuf := &Array.newInt1d(fldHashSize);
            fldLitBufSize := 1 shl (memLevel + 6);
            fldPendingBuf := &Array.newByte1d(fldLitBufSize * 4);
            fldPendingSize := fldLitBufSize * 4;
            fldDBuf := fldLitBufSize div 2;
            fldLBuf := fldLitBufSize * 3;
            fldLevel := comLevel;
            fldStrategy := strategy;
            result := deflateReset();
        end;

        function DefState.longestMatch(curMatch: int): int;
            function predinc(var variable: int): int; inline;
            begin
                inc(variable);
                result := variable;
            end;
        var
            scanEndCurr: byte;
            scanEndPrev: byte;
            scan: int;
            match: int;
            limit: int;
            wMask: int;
            strEnd: int;
            length: int;
            lookahead: int;
            bestLength: int;
            chainLength: int;
            niceMatchLocal: int;
            windowBuf: byte_Array1d;
        begin
            chainLength := fldMaxChainLength;
            scan := fldStrStart;
            bestLength := fldPrevLength;
            if scan > fldWSize - MIN_LOOKAHEAD then begin
                limit := scan - (fldWSize - MIN_LOOKAHEAD);
            end else begin
                limit := 0;
            end;
            niceMatchLocal := fldNiceMatch;
            wMask := fldWMask;
            strEnd := scan + MAX_MATCH;
            windowBuf := fldWindowBuf;
            scanEndPrev := windowBuf[scan + bestLength - 1];
            scanEndCurr := windowBuf[scan + bestLength];
            if fldPrevLength >= fldGoodMatch then chainLength := CoInt.sar(chainLength, 2);
            lookahead := fldLookahead;
            if niceMatchLocal > lookahead then niceMatchLocal := lookahead;
            repeat
                match := curMatch;
                if
                    (windowBuf[match + bestLength] = scanEndCurr) and
                    (windowBuf[match + bestLength - 1] = scanEndPrev) and
                    (windowBuf[match] = windowBuf[scan]) and
                    (windowBuf[predinc(match)] = windowBuf[scan + 1])
                then begin
                    inc(scan, 2);
                    inc(match);
                    repeat
                    until
                        (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or
                        (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or
                        (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or
                        (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or (windowBuf[predinc(scan)] <> windowBuf[predinc(match)]) or
                        (scan >= strEnd)
                    ;
                    length := MAX_MATCH - (strEnd - scan);
                    scan := strEnd - MAX_MATCH;
                    if length > bestLength then begin
                        fldMatchStart := curMatch;
                        bestLength := length;
                        if length >= niceMatchLocal then break;
                        scanEndPrev := windowBuf[scan + bestLength - 1];
                        scanEndCurr := windowBuf[scan + bestLength];
                    end;
                end;
                curMatch := fldPrevBuf[curMatch and wMask] and $ffff;
                if curMatch <= limit then break;
                dec(chainLength);
            until chainLength = 0;
            result := CoInt.min(bestLength, lookahead);
        end;

        function DefState.getGZIPHeader(): GZIPHeader;
        var
            gHeader: GZIPHeader;
        begin
            gHeader := fldGHeader;
            if gHeader = nil then begin
                gHeader := GZIPHeader.create();
                fldGHeader := gHeader;
            end;
            result := gHeader;
        end;

        procedure DefState.pqDownHeap(const tree: int_Array1d; k: int);
        var
            v: int;
            j: int;
            len: int;
            depth: byte_Array1d;
            buf: int_Array1d;
        begin
            depth := fldDepth;
            len := fldHeapLen;
            buf := fldHeapBuf;
            v := buf[k];
            j := k shl 1;
            while j <= len do begin
                if (j < len) and smaller(tree, buf[j + 1], buf[j], depth) then inc(j);
                if smaller(tree, v, buf[j], depth) then break;
                buf[k] := buf[j];
                k := j;
                j := j shl 1;
            end;
            buf[k] := v;
        end;

        procedure DefState.putBytes(const data: byte_Array1d; offset, length: int);
        begin
            &Array.checkBounds(data, offset, length);
            &Array.copyPrimitives(data, offset, fldPendingBuf, fldPendingPos, length);
            inc(fldPendingPos, length);
        end;

        procedure DefState.putByte(c: int);
        begin
            fldPendingBuf[fldPendingPos] := byte(c);
            inc(fldPendingPos);
        end;

        procedure DefState.putShort(w: int);
        begin
            putByte(w);
            putByte(w shr 8);
        end;

        function DefState.deflateBegin(level, wbits, memLevel: int): int;
        begin
            result := deflateBegin(level, Z_DEFLATED, wbits, memLevel, DEFAULT_STRATEGY);
        end;

        function DefState.deflateBegin(level, wbits: int): int;
        begin
            result := deflateBegin(level, Z_DEFLATED, wbits, DEF_MEM_LEVEL, DEFAULT_STRATEGY);
        end;

        function DefState.deflateBegin(level: int): int;
        begin
            result := deflateBegin(level, Z_DEFLATED, DEFAULT_WBITS, DEF_MEM_LEVEL, DEFAULT_STRATEGY);
        end;

        function DefState.deflateEnd(): int;
        var
            status: int;
        begin
            status := fldStatus;
            if (status <> INIT_STATE) and (status <> BUSY_STATE) and (status <> FINISH_STATE) then begin
                result := STREAM_ERROR;
                exit;
            end;
            fldPendingBuf := nil;
            fldHeadBuf := nil;
            fldPrevBuf := nil;
            fldWindowBuf := nil;
            if status = BUSY_STATE then begin
                result := DATA_ERROR;
                exit;
            end;
            result := OK;
        end;

        function DefState.deflateParameters(level, strategy: int): int;
        var
            cfg: Config;
            stream: ZStream;
        begin
            result := OK;
            if level = DEFAULT_COMPRESSION then level := 6;
            if (level < 0) or (level > 9) or (strategy < 0) or (strategy > HUFFMAN_ONLY) then begin
                result := STREAM_ERROR;
                exit;
            end;
            stream := fldStream;
            if (configTable[fldLevel].func <> configTable[level].func) and (stream.fldTotalIn <> 0) then begin
                result := stream.deflate(PARTIAL_FLUSH);
            end;
            if fldLevel <> level then begin
                cfg := configTable[fldLevel];
                fldLevel := level;
                fldMaxLazyMatch := cfg.maxLazy;
                fldGoodMatch := cfg.goodLength;
                fldNiceMatch := cfg.niceLength;
                fldMaxChainLength := cfg.maxChain;
            end;
            fldStrategy := strategy;
        end;

        function DefState.deflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
        var
            idx: int;
            len: int;
            dif: int;
            insh: int;
            wMask: int;
            hashMask: int;
            hashShift: int;
            windowBuf: byte_Array1d;
            prevBuf: int_Array1d;
            headBuf: int_Array1d;
        begin
            if (dictionary = nil) or (fldStatus <> INIT_STATE) then begin
                result := STREAM_ERROR;
                exit;
            end;
            len := length;
            idx := 0;
            fldStream.fldChecksum.update(dictionary, 0, length);
            if len < MIN_MATCH then begin
                result := OK;
                exit;
            end;
            dif := fldWSize - MIN_LOOKAHEAD;
            if len > dif then begin
                len := dif;
                idx := length - len;
            end;
            wMask := fldWMask;
            hashMask := fldHashMask;
            hashShift := fldHashShift;
            windowBuf := fldWindowBuf;
            prevBuf := fldPrevBuf;
            headBuf := fldHeadBuf;
            &Array.copyPrimitives(dictionary, idx, windowBuf, 0, len);
            fldStrStart := len;
            fldBlockStart := len;
            insh := (((int(windowBuf[0]) and $ff) shl hashShift) xor (int(windowBuf[1]) and $ff)) and hashMask;
            for idx := 0 to len - MIN_MATCH do begin
                insh := ((insh shl hashShift) xor (int(windowBuf[idx + (MIN_MATCH - 1)]) and $ff)) and hashMask;
                prevBuf[idx and wMask] := headBuf[insh];
                headBuf[insh] := short(idx);
            end;
            fldInsh := insh;
            result := OK;
        end;

        function DefState.deflate(flush: int): int;
        var
            adler: int;
            header: int;
            bState: int;
            oldFlush: int;
            levelFlags: int;
            stream: ZStream;
        begin
            if (flush > FINISH) or (flush < 0) then begin
                result := STREAM_ERROR;
                exit;
            end;
            stream := fldStream;
            if (stream.fldNextOutArray = nil) or ((stream.fldNextInArray = nil) and (stream.fldAvailableIn <> 0)) or ((fldStatus = FINISH_STATE) and (flush <> FINISH)) then begin
                stream.fldMessage := errorMessages[NEED_DICT - STREAM_ERROR];
                result := STREAM_ERROR;
                exit;
            end;
            if stream.fldAvailableOut = 0 then begin
                stream.fldMessage := errorMessages[NEED_DICT - BUF_ERROR];
                result := BUF_ERROR;
                exit;
            end;
            oldFlush := fldLastFlush;
            fldLastFlush := flush;
            if fldStatus = INIT_STATE then begin
                if fldWrap = 2 then begin
                    getGZIPHeader().put(self);
                    fldStatus := BUSY_STATE;
                    stream.fldChecksum.reset();
                end else begin
                    header := (Z_DEFLATED + ((fldWBits - 8) shl 4)) shl 8;
                    levelFlags := ((fldLevel - 1) and $ff) shr 1;
                    if levelFlags > 3 then levelFlags := 3;
                    header := header or (levelFlags shl 6);
                    if fldStrStart <> 0 then header := header or PRESET_DICT;
                    inc(header, 31 - (header mod 31));
                    fldStatus := BUSY_STATE;
                    putShortMSB(header);
                    if fldStrStart <> 0 then begin
                        adler := stream.fldChecksum.getValue();
                        putShortMSB(adler shr 16);
                        putShortMSB(adler);
                    end;
                    stream.fldChecksum.reset();
                end;
            end;
            if fldPendingPos <> 0 then begin
                stream.flushPending();
                if stream.fldAvailableOut = 0 then begin
                    fldLastFlush := -1;
                    result := OK;
                    exit;
                end;
            end else
            if (stream.fldAvailableIn = 0) and (flush <= oldFlush) and (flush <> FINISH) then begin
                stream.fldMessage := errorMessages[NEED_DICT - BUF_ERROR];
                result := BUF_ERROR;
                exit;
            end;
            if (fldStatus = FINISH_STATE) and (stream.fldAvailableIn <> 0) then begin
                stream.fldMessage := errorMessages[NEED_DICT - BUF_ERROR];
                result := BUF_ERROR;
                exit;
            end;
            if (stream.fldAvailableIn <> 0) or (fldLookahead <> 0) or ((flush <> NO_FLUSH) and (fldStatus <> FINISH_STATE)) then begin
                bState := -1;
                case configTable[fldLevel].func of
                STORED:
                    bState := deflateStored(flush);
                FAST:
                    bState := deflateFast(flush);
                SLOW:
                    bState := deflateSlow(flush);
                end;
                if (bState = FINISH_STARTED) or (bState = FINISH_DONE) then begin
                    fldStatus := FINISH_STATE;
                end;
                if (bState = NEED_MORE) or (bState = FINISH_STARTED) then begin
                    if stream.fldAvailableOut = 0 then fldLastFlush := -1;
                    result := OK;
                    exit;
                end;
                if bState = BLOCK_DONE then begin
                    if flush = PARTIAL_FLUSH then begin
                        trAlign();
                    end else begin
                        trStoredBlock(0, 0, false);
                        if flush = FULL_FLUSH then begin
                            &Array.fillPrimitives(fldHeadBuf, 0, fldHashSize, 0);
                        end;
                    end;
                    stream.flushPending();
                    if stream.fldAvailableOut = 0 then begin
                        fldLastFlush := -1;
                        result := OK;
                        exit;
                    end;
                end;
            end;
            if flush <> FINISH then begin
                result := OK;
                exit;
            end;
            if fldWrap <= 0 then begin
                result := STREAM_END;
                exit;
            end;
            adler := stream.fldChecksum.getValue();
            if fldWrap = 2 then begin
                putByte(adler);
                putByte(adler shr 8);
                putByte(adler shr 16);
                putByte(adler shr 24);
                putByte(int(stream.fldTotalIn));
                putByte(int(stream.fldTotalIn) shr 8);
                putByte(int(stream.fldTotalIn) shr 16);
                putByte(int(stream.fldTotalIn) shr 24);
                getGZIPHeader().setCRC(adler);
            end else begin
                putShortMSB(adler shr 16);
                putShortMSB(adler);
            end;
            stream.flushPending();
            if fldWrap > 0 then fldWrap := -fldWrap;
            if fldPendingPos = 0 then begin
                result := STREAM_END;
                exit;
            end;
            result := OK;
        end;

        constructor DefState.create(stream: ZStream);
        begin
            inherited create();
            fldWrap := 1;
            fldDynamicLTree := &Array.newInt1d(HEAP_SIZE * 2);
            fldDynamicDTree := &Array.newInt1d((2 * D_CODES + 1) * 2);
            fldBlTree := &Array.newInt1d((2 * BL_CODES + 1) * 2);
            fldBlCount := &Array.newInt1d(MAX_BITS + 1);
            fldHeapBuf := &Array.newInt1d(2 * L_CODES + 1);
            fldDepth := &Array.newByte1d(2 * L_CODES + 1);
            fldLDesc := Tree.create();
            fldDDesc := Tree.create();
            fldBlDesc := Tree.create();
            fldStream := stream;
        end;

        destructor DefState.destroy;
        begin
            fldLDesc.free();
            fldDDesc.free();
            fldBlDesc.free();
            fldGHeader.free();
            inherited destroy;
        end;
    {%endregion}

    {%region  InfState }
        class procedure InfState.initialize();
        begin
            mark := [ 0, 0, -1, -1 ];
        end;

        class procedure InfState.finalize();
        begin
            mark := nil;
        end;

        procedure InfState.checksum(n, v: int);
        var
            idx: int;
            buf: byte_Array1d;
        begin
            buf := fldCRCBuf;
            for idx := 0 to n - 1 do begin
                buf[idx] := byte(v);
                v := v shr 8;
            end;
            fldStream.fldChecksum.update(buf, 0, n);
        end;

        function InfState.inflateReset(): int;
        var
            stream: ZStream;
            iBlocks: InfBlocks;
        begin
            stream := fldStream;
            iBlocks := fldBlocks;
            if (stream = nil) or (iBlocks = nil) then begin
                result := STREAM_ERROR;
                exit;
            end;
            with stream do begin
                fldTotalIn := 0;
                fldTotalOut := 0;
                fldMessage := '';
            end;
            fldMode := HEAD;
            fldNeedBytes := -1;
            iBlocks.reset(stream);
            result := OK;
        end;

        function InfState.readBytes(n, r, f: int): int;
        var
            needBytes: int;
            needCheck: int;
            stream: ZStream;
        begin
            fldReadReturn := false;
            stream := fldStream;
            if fldNeedBytes = -1 then begin
                fldNeedBytes := n;
                fldNeedCheck := 0;
            end;
            needBytes := fldNeedBytes;
            needCheck := fldNeedCheck;
            while needBytes > 0 do begin
                if stream.fldAvailableIn = 0 then begin
                    fldNeedCheck := needCheck;
                    fldReadReturn := true;
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.fldAvailableIn);
                inc(stream.fldTotalIn);
                needCheck := needCheck or ((int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl int((n - needBytes) * 8));
                inc(stream.fldNextInIndex);
                dec(needBytes);
            end;
            if n = 2 then needCheck := needCheck and $ffff;
            fldNeedCheck := needCheck;
            fldNeedBytes := -1;
            result := r;
        end;

        function InfState.readBytes(r, f: int): int;
        var
            needCheck: int;
            tmpString: ByteArrayOutputStream;
            stream: ZStream;
        begin
            fldReadReturn := false;
            needCheck := fldNeedCheck;
            tmpString := fldTmpString;
            stream := fldStream;
            while needCheck <> 0 do begin
                if stream.fldAvailableIn = 0 then begin
                    fldReadReturn := true;
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.fldAvailableIn);
                inc(stream.fldTotalIn);
                tmpString.write(stream.fldNextInArray, stream.fldNextInIndex, 1);
                stream.fldChecksum.update(stream.fldNextInArray, stream.fldNextInIndex, 1);
                inc(stream.fldNextInIndex);
                dec(needCheck);
            end;
            fldNeedCheck := needCheck;
            result := r;
        end;

        function InfState.readString(r, f: int): int;
        var
            b: int;
            tmpString: ByteArrayOutputStream;
            stream: ZStream;
        begin
            fldReadReturn := false;
            tmpString := fldTmpString;
            stream := fldStream;
            b := 0;
            repeat
                if stream.fldAvailableIn = 0 then begin
                    fldReadReturn := true;
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.fldAvailableIn);
                inc(stream.fldTotalIn);
                b := stream.fldNextInArray[stream.fldNextInIndex];
                if b <> 0 then tmpString.write(b);
                stream.fldChecksum.update(stream.fldNextInArray, stream.fldNextInIndex, 1);
                inc(stream.fldNextInIndex);
            until b = 0;
            result := r;
        end;

        function InfState.inflateEnd(): int;
        var
            iBlocks: InfBlocks;
        begin
            iBlocks := fldBlocks;
            if iBlocks <> nil then iBlocks.reset(fldStream);
            result := OK;
        end;

        function InfState.inflateBegin(wbits: int): int;
        var
            stream: ZStream;
        begin
            stream := fldStream;
            stream.fldMessage := '';
            fldBlocks.free();
            fldBlocks := nil;
            fldWrap := 0;
            if wbits < 0 then begin
                wbits := -wbits;
            end else begin
                fldWrap := (wbits shr 4) + 1;
                if wbits < 48 then wbits := wbits and $0f;
            end;
            if (wbits < 8) or (wbits > 15) then begin
                inflateEnd();
                result := STREAM_ERROR;
                exit;
            end;
            fldWBits := wbits;
            fldBlocks := InfBlocks.create(stream, 1 shl wbits);
            inflateReset();
            result := OK;
        end;

        function InfState.inflate(f: int): int;
        var
            r: int;
            b: int;
            needCheck: int;
            abyte: byte_Array1d;
            stream: ZStream;
            iBlocks: InfBlocks;
            gHeader: GZIPHeader;
            tmpString: ByteArrayOutputStream;
        begin
            stream := fldStream;
            if (stream = nil) or (stream.fldNextInArray = nil) then begin
                if (f = FINISH) and (fldMode = HEAD) then begin
                    result := OK;
                    exit;
                end;
                result := STREAM_ERROR;
                exit;
            end;
            gHeader := fldGHeader;
            tmpString := fldTmpString;
            needCheck := fldNeedCheck;
            if f = FINISH then begin
                f := BUF_ERROR;
            end else begin
                f := OK;
            end;
            r := BUF_ERROR;
            repeat
                case fldMode of
                HEAD: begin
                    if fldWrap = 0 then begin
                        fldMode := BLOCKS;
                        continue;
                    end;
                    r := readBytes(2, r, f);
                    if fldReadReturn then begin
                        result := r;
                        exit;
                    end;
                    needCheck := fldNeedCheck;
                    if ((fldWrap and $02) <> 0) and (needCheck = $8b1f) then begin
                        stream.fldChecksum.free();
                        stream.fldChecksum := CRC32.create();
                        checksum(2, needCheck);
                        if gHeader = nil then begin
                            gHeader := GZIPHeader.create();
                            fldGHeader := gHeader;
                        end;
                        fldMode := FLAGS;
                        continue;
                    end;
                    fldFlags := 0;
                    fldMethod := needCheck and $ff;
                    b := (needCheck shr 8) and $ff;
                    if ((fldWrap and $01) = 0) or ((((fldMethod shl 8) + b) mod 31) <> 0) then begin
                        fldMode := BAD;
                        stream.fldMessage := 'incorrect header check';
                        continue;
                    end;
                    if (fldMethod and $0f) <> Z_DEFLATED then begin
                        fldMode := BAD;
                        stream.fldMessage := 'unknown compression method';
                        continue;
                    end;
                    if CoInt.sar(fldMethod, 4) + 8 > fldWBits then begin
                        fldMode := BAD;
                        stream.fldMessage := 'invalid window size';
                        continue;
                    end;
                    stream.fldChecksum.free();
                    stream.fldChecksum := Adler32.create();
                    if (b and PRESET_DICT) = 0 then begin
                        fldMode := BLOCKS;
                        continue;
                    end;
                    fldMode := DICT4;
                end;
                DICT4: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := (int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl 24;
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    fldMode := DICT3;
                end;
                DICT3: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := needCheck or ((int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl 16);
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    fldMode := DICT2;
                end;
                DICT2: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := needCheck or ((int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl 8);
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    fldMode := DICT1;
                end;
                DICT1: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := needCheck or (int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff);
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    stream.fldChecksum.reset(needCheck);
                    fldMode := DICT0;
                    result := NEED_DICT;
                    exit;
                end;
                DICT0: begin
                    fldMode := BAD;
                    stream.fldMessage := 'need dictionary';
                    fldMarker := 0;
                    result := STREAM_ERROR;
                    exit;
                end;
                BLOCKS: begin
                    iBlocks := fldBlocks;
                    if iBlocks = nil then begin
                        result := STREAM_ERROR;
                        exit;
                    end;
                    r := iBlocks.proc(stream, r);
                    if r = DATA_ERROR then begin
                        fldMode := BAD;
                        fldMarker := 0;
                        continue;
                    end;
                    if r = OK then r := f;
                    if r <> STREAM_END then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    fldWas := stream.fldChecksum.getValue();
                    iBlocks.reset(stream);
                    if fldWrap = 0 then begin
                        fldMode := DONE;
                        continue;
                    end;
                    fldMode := CHECK4;
                end;
                CHECK4: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := (int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl 24;
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    fldMode := CHECK3;
                end;
                CHECK3: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := needCheck or ((int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl 16);
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    fldMode := CHECK2;
                end;
                CHECK2: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := needCheck or ((int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff) shl 8);
                    fldNeedCheck := needCheck;
                    inc(stream.fldNextInIndex);
                    fldMode := CHECK1;
                end;
                CHECK1: begin
                    if stream.fldAvailableIn = 0 then begin
                        result := r;
                        exit;
                    end;
                    r := f;
                    dec(stream.fldAvailableIn);
                    inc(stream.fldTotalIn);
                    needCheck := needCheck or (int(stream.fldNextInArray[stream.fldNextInIndex]) and $ff);
                    inc(stream.fldNextInIndex);
                    if fldFlags <> 0 then needCheck := CoInt.byteSwap(needCheck);
                    if fldWas <> needCheck then begin
                        stream.fldMessage := 'incorrect data check';
                    end else
                    if (fldFlags <> 0) and (gHeader <> nil) then begin
                        gHeader.fldCRC := needCheck;
                    end;
                    fldNeedCheck := needCheck;
                    fldMode := LENGTH;
                end;
                LENGTH: begin
                    if (fldWrap <> 0) and (fldFlags <> 0) then begin
                        r := readBytes(4, r, f);
                        if fldReadReturn then begin
                            result := r;
                            exit;
                        end;
                        if stream.fldMessage = 'incorrect data check' then begin
                            fldMode := BAD;
                            fldMarker := 5;
                            continue;
                        end;
                        if fldNeedCheck <> int(stream.fldTotalOut) then begin
                            stream.fldMessage := 'incorrect length check';
                            fldMode := BAD;
                            continue;
                        end;
                        stream.fldMessage := '';
                    end else begin
                        if stream.fldMessage = 'incorrect data check' then begin
                            fldMode := BAD;
                            fldMarker := 5;
                            continue;
                        end;
                    end;
                    fldMode := DONE;
                end;
                DONE: begin
                    result := STREAM_END;
                    exit;
                end;
                BAD: begin
                    result := DATA_ERROR;
                    exit;
                end;
                FLAGS: begin
                    r := readBytes(2, r, f);
                    if fldReadReturn then begin
                        result := r;
                        exit;
                    end;
                    needCheck := fldNeedCheck;
                    fldFlags := needCheck and $ffff;
                    if (fldFlags and $ff) <> Z_DEFLATED then begin
                        stream.fldMessage := 'unknown compression method';
                        fldMode := BAD;
                        continue;
                    end;
                    if (fldFlags and $e000) <> 0 then begin
                        stream.fldMessage := 'unknown header flags set';
                        fldMode := BAD;
                        continue;
                    end;
                    if (fldFlags and $0200) <> 0 then checksum(2, needCheck);
                    fldMode := TIME;
                end;
                TIME: begin
                    r := readBytes(4, r, f);
                    if fldReadReturn then begin
                        result := r;
                        exit;
                    end;
                    needCheck := fldNeedCheck;
                    { if gHeader <> nil then begin
                        gHeader.fldTime := needCheck;
                    end; }
                    if (fldFlags and $0200) <> 0 then checksum(4, needCheck);
                    fldMode := OS;
                end;
                OS: begin
                    r := readBytes(2, r, f);
                    if fldReadReturn then begin
                        result := r;
                        exit;
                    end;
                    needCheck := fldNeedCheck;
                    if gHeader <> nil then begin
                        { gHeader.fldXFlags := needCheck and $ff; }
                        gHeader.fldOS := (needCheck shr 8) and $ff;
                    end;
                    if (fldFlags and $0200) <> 0 then checksum(2, needCheck);
                    fldMode := EXLEN;
                end;
                EXLEN: begin
                    if (fldFlags and $0400) <> 0 then begin
                        r := readBytes(2, r, f);
                        if fldReadReturn then begin
                            result := r;
                            exit;
                        end;
                        needCheck := fldNeedCheck;
                        if gHeader <> nil then gHeader.fldExtra := &Array.newByte1d(needCheck and $ffff);
                        if (fldFlags and $0200) <> 0 then checksum(2, needCheck);
                    end else
                    if gHeader <> nil then gHeader.fldExtra := nil;
                    fldMode := EXTRA;
                end;
                EXTRA: begin
                    if (fldFlags and $0400) <> 0 then begin
                        r := readBytes(r, f);
                        if fldReadReturn then begin
                            result := r;
                            exit;
                        end;
                        if gHeader <> nil then begin
                            abyte := tmpString.toByteArray();
                            tmpString.seek(0, SeekFrom.sfBegin);
                            tmpString.truncate();
                            if system.length(abyte) = system.length(gHeader.fldExtra) then begin
                                &Array.copyPrimitives(abyte, 0, gHeader.fldExtra, 0, system.length(abyte));
                            end else begin
                                stream.fldMessage := 'bad extra field length';
                                fldMode := BAD;
                                continue;
                            end;
                        end;
                    end else
                    if gHeader <> nil then gHeader.fldExtra := nil;
                    fldMode := NAME;
                end;
                NAME: begin
                    if (fldFlags and $0800) <> 0 then begin
                        r := readString(r, f);
                        if fldReadReturn then begin
                            result := r;
                            exit;
                        end;
                        if gHeader <> nil then gHeader.fldName := tmpString.toByteArray();
                        tmpString.seek(0, SeekFrom.sfBegin);
                        tmpString.truncate();
                    end else
                    if gHeader <> nil then gHeader.fldName := nil;
                    fldMode := COMMENT;
                end;
                COMMENT: begin
                    if (fldFlags and $1000) <> 0 then begin
                        r := readString(r, f);
                        if fldReadReturn then begin
                            result := r;
                            exit;
                        end;
                        if gHeader <> nil then gHeader.fldComment := tmpString.toByteArray();
                        tmpString.seek(0, SeekFrom.sfBegin);
                        tmpString.truncate();
                    end else
                    if gHeader <> nil then gHeader.fldComment := nil;
                    fldMode := HCRC;
                end;
                HCRC: begin
                    if (fldFlags and $0200) <> 0 then begin
                        r := readBytes(2, r, f);
                        if fldReadReturn then begin
                            result := r;
                            exit;
                        end;
                        needCheck := fldNeedCheck;
                        { if gHeader <> nil then begin
                            gHeader.fldHCRC := needCheck and $ffff;
                        end; }
                        if needCheck <> (stream.fldChecksum.getValue() and $ffff) then begin
                            fldMode := BAD;
                            stream.fldMessage := 'header crc mismatch';
                            fldMarker := 5;
                            continue;
                        end;
                    end;
                    stream.fldChecksum.free();
                    stream.fldChecksum := CRC32.create();
                    fldMode := BLOCKS;
                end;
                else
                    result := STREAM_ERROR;
                    exit;
                end;
            until false;
        end;

        function InfState.inflateSetDictionary(const dictionary: byte_Array1d; length: int): int;
        var
            idx: int;
            len: int;
            need: int;
            wbits: int;
            stream: ZStream;
            adler: Checksum32;
            iBlocks: InfBlocks;
        begin
            stream := fldStream;
            iBlocks := fldBlocks;
            if (stream = nil) or (iBlocks = nil) or ((fldMode <> DICT0) and (fldWrap <> 0)) then begin
                result := STREAM_ERROR;
                exit;
            end;
            idx := 0;
            len := length;
            adler := stream.fldChecksum;
            if fldMode = DICT0 then begin
                need := adler.getValue();
                adler.reset();
                adler.update(dictionary, 0, length);
                if adler.getValue() <> need then begin
                    result := DATA_ERROR;
                    exit;
                end;
            end;
            adler.reset();
            wbits := 1 shl fldWBits;
            if len >= wbits then begin
                len := wbits - 1;
                idx := length - len;
            end;
            iBlocks.setDictionary(dictionary, idx, len);
            fldMode := BLOCKS;
            result := OK;
        end;

        function InfState.inflateSync(): int;
        var
            b: byte;
            n: int;
            p: int;
            m: int;
            r: long;
            w: long;
            nextInArray: byte_Array1d;
            stream: ZStream;
        begin
            stream := fldStream;
            if stream = nil then begin
                result := STREAM_ERROR;
                exit;
            end;
            if fldMode <> BAD then begin
                fldMode := BAD;
                fldMarker := 0;
            end;
            n := stream.fldAvailableIn;
            if n = 0 then begin
                result := BUF_ERROR;
                exit;
            end;
            p := stream.fldNextInIndex;
            m := fldMarker;
            nextInArray := stream.fldNextInArray;
            while (n <> 0) and (m < 4) do begin
                b := nextInArray[p];
                if b = mark[m] then begin
                    inc(m);
                end else
                if b <> 0 then begin
                    m := 0;
                end else begin
                    m := 4 - m;
                end;
                inc(p);
                dec(n);
            end;
            inc(stream.fldTotalIn, long(p) - long(stream.fldNextInIndex));
            stream.fldNextInIndex := p;
            stream.fldAvailableIn := n;
            fldMarker := m;
            if m <> 4 then begin
                result := DATA_ERROR;
                exit;
            end;
            r := stream.fldTotalIn;
            w := stream.fldTotalOut;
            inflateReset();
            stream.fldTotalIn := r;
            stream.fldTotalOut := w;
            fldMode := BLOCKS;
            result := OK;
        end;

        function InfState.inflateSyncPoint(): int;
        var
            iBlocks: InfBlocks;
        begin
            iBlocks := fldBlocks;
            if (fldStream = nil) or (iBlocks = nil) then begin
                result := STREAM_ERROR;
                exit;
            end;
            if iBlocks.syncPoint() then begin
                result := 1;
                exit;
            end;
            result := 0;
        end;

        constructor InfState.create(stream: ZStream);
        begin
            inherited create();
            fldNeedBytes := -1;
            fldWas := -1;
            fldCRCBuf := &Array.newByte1d(4);
            fldTmpString := ByteArrayOutputStream.create();
            fldStream := stream;
        end;

        destructor InfState.destroy;
        begin
            fldTmpString.free();
            fldBlocks.free();
            fldGHeader.free();
            inherited destroy;
        end;
    {%endregion}

    {%region  InfBlocks }
        class procedure InfBlocks.initialize();
        begin
            inflateMask := [
                $0000, $0001, $0003, $0007, $000f, $001f, $003f, $007f, $00ff, $01ff,
                $03ff, $07ff, $0fff, $1fff, $3fff, $7fff, $ffff
            ];
            border := [
                16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
                11, 4, 12, 3, 13, 2, 14, 1, 15
            ];
        end;

        class procedure InfBlocks.finalize();
        begin
            border := nil;
            inflateMask := nil;
        end;

        procedure InfBlocks.proc1();
        var
            bl: int_Array1d;
            bd: int_Array1d;
            tl: int_Array2d;
            td: int_Array2d;
        begin
            bl := &Array.newInt1d(1);
            bd := &Array.newInt1d(1);
            tl := &Array.newInt2d(1);
            td := &Array.newInt2d(1);
            InfTree.inflateTreesFixed(bl, bd, tl, td);
            fldCodes.init(bl[0], bd[0], tl[0], 0, td[0], 0);
        end;

        procedure InfBlocks.reset(stream: ZStream);
        begin
            fldMode := &TYPE;
            fldBitK := 0;
            fldBitB := 0;
            fldRead := 0;
            fldWrite := 0;
            if fldCheck then stream.fldChecksum.reset();
        end;

        procedure InfBlocks.setDictionary(const d: byte_Array1d; start, n: int);
        begin
            &Array.copyPrimitives(d, start, fldWindow, 0, n);
            fldRead := n;
            fldWrite := n;
        end;

        procedure InfBlocks.update(stream: ZStream; b, k, n, p, q: int);
        begin
            fldBitB := b;
            fldBitK := k;
            stream.fldAvailableIn := n;
            inc(stream.fldTotalIn, long(p) - long(stream.fldNextInIndex));
            stream.fldNextInIndex := p;
            fldWrite := q;
        end;

        function InfBlocks.syncPoint(): boolean;
        begin
            result := fldMode = LENS;
        end;

        function InfBlocks.proc(stream: ZStream; r: int): int;
        var
            t: int;
            b: int;
            k: int;
            p: int;
            n: int;
            q: int;
            m: int;
            i: int;
            j: int;
            c: int;
            index: int;
            bb: int_Array1d;
            bl: int_Array1d;
            bd: int_Array1d;
            tb: int_Array1d;
            tl: int_Array1d;
            td: int_Array1d;
            blens: int_Array1d;
            hufts: int_Array1d;
            iCodes: InfCodes;
            iTree: InfTree;
        begin
            p := stream.fldNextInIndex;
            n := stream.fldAvailableIn;
            b := fldBitB;
            k := fldBitK;
            q := fldWrite;
            bb := fldBB;
            tb := fldTB;
            hufts := fldHufts;
            iCodes := fldCodes;
            iTree := fldTree;
            if q < fldRead then begin
                m := fldRead - q - 1;
            end else begin
                m := fldEnd - q;
            end;
            repeat
                case fldMode of
                &TYPE: begin
                    while k < 3 do begin
                        if n = 0 then begin
                            update(stream, b, k, n, p, q);
                            result := inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    t := b and $07;
                    fldLast := t and $01;
                    case t shr 1 of
                     0: begin
                        b := b shr 3;
                        dec(k, 3);
                        t := k and $07;
                        b := b shr t;
                        dec(k, t);
                        fldMode := LENS;
                    end;
                     1: begin
                        proc1();
                        b := b shr 3;
                        dec(k, 3);
                        fldMode := CODES;
                    end;
                     2: begin
                        b := b shr 3;
                        dec(k, 3);
                        fldMode := TABLE;
                    end;
                     3: begin
                        b := b shr 3;
                        dec(k, 3);
                        fldMode := BAD;
                        stream.fldMessage := 'invalid block type';
                        r := DATA_ERROR;
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    end;
                end;
                LENS: begin
                    while k < 32 do begin
                        if n = 0 then begin
                            update(stream, b, k, n, p, q);
                            result := inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    if (((not b) shr 16) and $ffff) <> (b and $ffff) then begin
                        fldMode := BAD;
                        stream.fldMessage := 'invalid stored block lengths';
                        r := DATA_ERROR;
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    fldLeft := b and $ffff;
                    b := 0;
                    k := 0;
                    if fldLeft <> 0 then begin
                        fldMode := STORED;
                    end else
                    if fldLast <> 0 then begin
                        fldMode := DRY;
                    end else begin
                        fldMode := &TYPE;
                    end;
                end;
                STORED: begin
                    if n = 0 then begin
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    if m = 0 then begin
                        if (q = fldEnd) and (fldRead <> 0) then begin
                            q := 0;
                            if q < fldRead then begin
                                m := fldRead - q - 1;
                            end else begin
                                m := fldEnd - q;
                            end;
                        end;
                        if m = 0 then begin
                            fldWrite := q;
                            r := inflateFlush(stream, r);
                            q := fldWrite;
                            if q < fldRead then begin
                                m := fldRead - q - 1;
                            end else begin
                                m := fldEnd - q;
                            end;
                            if (q = fldEnd) and (fldRead <> 0) then begin
                                q := 0;
                                if q < fldRead then begin
                                    m := fldRead - q - 1;
                                end else begin
                                    m := fldEnd - q;
                                end;
                            end;
                            if m = 0 then begin
                                update(stream, b, k, n, p, q);
                                result := inflateFlush(stream, r);
                                exit;
                            end;
                        end;
                    end;
                    r := OK;
                    t := fldLeft;
                    if t > n then t := n;
                    if t > m then t := m;
                    &Array.copyPrimitives(stream.fldNextInArray, p, fldWindow, q, t);
                    inc(p, t);
                    dec(n, t);
                    inc(q, t);
                    dec(m, t);
                    dec(fldLeft, t);
                    if fldLeft <> 0 then continue;
                    if fldLast <> 0 then begin
                        fldMode := DRY;
                    end else begin
                        fldMode := &TYPE;
                    end;
                end;
                TABLE: begin
                    while k < 14 do begin
                        if n = 0 then begin
                            update(stream, b, k, n, p, q);
                            result := inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    t := b and $3fff;
                    fldTable := t;
                    if ((t and $1f) > 29) or (((t shr 5) and $1f) > 29) then begin
                        fldMode := BAD;
                        stream.fldMessage := 'too many length or distance symbols';
                        r := DATA_ERROR;
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    t := (t and $1f) + ((t shr 5) and $1f) + 258;
                    blens := fldBlens;
                    if (blens = nil) or (system.length(blens) < t) then begin
                        fldBlens := &Array.newInt1d(t);
                    end else begin
                        &Array.fillPrimitives(blens, 0, t, 0);
                    end;
                    b := b shr 14;
                    dec(k, 14);
                    fldIndex := 0;
                    fldMode := BTREE;
                end;
                BTREE: begin
                    blens := fldBlens;
                    while fldIndex < (fldTable shr 10) + 4 do begin
                        while k < 3 do begin
                            if n = 0 then begin
                                update(stream, b, k, n, p, q);
                                result := inflateFlush(stream, r);
                                exit;
                            end;
                            r := OK;
                            dec(n);
                            b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                            inc(p);
                            inc(k, 8);
                        end;
                        blens[border[fldIndex]] := b and $07;
                        inc(fldIndex);
                        b := b shr 3;
                        dec(k, 3);
                    end;
                    index := fldIndex;
                    while index < 19 do begin
                        blens[border[index]] := 0;
                        inc(index);
                    end;
                    fldIndex := index;
                    bb[0] := 7;
                    t := iTree.inflateTreesBits(blens, bb, tb, hufts, stream);
                    if t <> OK then begin
                        r := t;
                        if r = DATA_ERROR then begin
                            blens := nil;
                            fldMode := BAD;
                        end;
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    fldIndex := 0;
                    fldMode := DTREE;
                end;
                DTREE: begin
                    blens := fldBlens;
                    repeat
                        t := fldTable;
                        if fldIndex >= (t and $1f) + ((t shr 5) and $1f) + 258 then break;
                        t := bb[0];
                        while k < t do begin
                            if n = 0 then begin
                                update(stream, b, k, n, p, q);
                                result := inflateFlush(stream, r);
                                exit;
                            end;
                            r := OK;
                            dec(n);
                            b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                            inc(p);
                            inc(k, 8);
                        end;
                        t := hufts[(tb[0] + (b and inflateMask[t])) * 3 + 1];
                        c := hufts[(tb[0] + (b and inflateMask[t])) * 3 + 2];
                        if c < 16 then begin
                            b := b shr t;
                            dec(k, t);
                            blens[fldIndex] := c;
                            inc(fldIndex);
                        end else begin
                            if c = 18 then begin
                                i := 7;
                                j := 11;
                            end else begin
                                i := c - 14;
                                j := 3;
                            end;
                            while k < t + i do begin
                                if n = 0 then begin
                                    update(stream, b, k, n, p, q);
                                    result := inflateFlush(stream, r);
                                    exit;
                                end;
                                r := OK;
                                dec(n);
                                b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                                inc(p);
                                inc(k, 8);
                            end;
                            b := b shr t;
                            dec(k, t);
                            inc(j, b and inflateMask[i]);
                            b := b shr i;
                            dec(k, i);
                            i := fldIndex;
                            t := fldTable;
                            if (i + j > (t and $1f) + ((t shr 5) and $1f) + 258) or ((c = 16) and (i < 1)) then begin
                                fldBlens := nil;
                                fldMode := BAD;
                                stream.fldMessage := 'invalid bit length repeat';
                                r := DATA_ERROR;
                                update(stream, b, k, n, p, q);
                                result := inflateFlush(stream, r);
                                exit;
                            end;
                            if c = 16 then begin
                                c := blens[i - 1];
                            end else begin
                                c := 0;
                            end;
                            &Array.fillPrimitives(blens, i, j, c);
                            fldIndex := i + j;
                        end;
                    until false;
                    tb[0] := -1;
                    bl := &Array.newInt1d(1);
                    bd := &Array.newInt1d(1);
                    tl := &Array.newInt1d(1);
                    td := &Array.newInt1d(1);
                    bl[0] := 9;
                    bd[0] := 6;
                    t := fldTable;
                    t := iTree.inflateTreesDynamic((t and $1f) + 257, ((t shr 5) and $1f) + 1, blens, bl, bd, tl, td, hufts, stream);
                    if t <> OK then begin
                        if t = DATA_ERROR then begin
                            fldBlens := nil;
                            fldMode := BAD;
                        end;
                        r := t;
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    iCodes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0]);
                    fldMode := CODES;
                end;
                CODES: begin
                    update(stream, b, k, n, p, q);
                    r := iCodes.proc(self, stream, r);
                    if r <> STREAM_END then begin
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    r := OK;
                    p := stream.fldNextInIndex;
                    n := stream.fldAvailableIn;
                    b := fldBitB;
                    k := fldBitK;
                    q := fldWrite;
                    if q < fldRead then begin
                        m := fldRead - q - 1;
                    end else begin
                        m := fldEnd - q;
                    end;
                    if fldLast = 0 then begin
                        fldMode := &TYPE;
                    end else begin
                        fldMode := DRY;
                    end;
                end;
                DRY: begin
                    fldWrite := q;
                    r := inflateFlush(stream, r);
                    q := fldWrite;
                    if q < fldRead then begin
                        m := fldRead - q - 1;
                    end else begin
                        m := fldEnd - q;
                    end;
                    if fldRead <> fldWrite then begin
                        update(stream, b, k, n, p, q);
                        result := inflateFlush(stream, r);
                        exit;
                    end;
                    fldMode := DONE;
                end;
                DONE: begin
                    r := STREAM_END;
                    update(stream, b, k, n, p, q);
                    result := inflateFlush(stream, r);
                    exit;
                end;
                BAD: begin
                    r := DATA_ERROR;
                    update(stream, b, k, n, p, q);
                    result := inflateFlush(stream, r);
                    exit;
                end;
                else
                    r := STREAM_ERROR;
                    update(stream, b, k, n, p, q);
                    result := inflateFlush(stream, r);
                    exit;
                end;
            until false;
        end;

        function InfBlocks.inflateFlush(stream: ZStream; r: int): int;
        var
            n: int;
            p: int;
            q: int;
            window: byte_Array1d;
        begin
            p := stream.fldNextOutIndex;
            q := fldRead;
            if q <= fldWrite then begin
                n := fldWrite - q;
            end else begin
                n := fldEnd - q;
            end;
            if n > stream.fldAvailableOut then n := stream.fldAvailableOut;
            if (n <> 0) and (r = BUF_ERROR) then r := OK;
            dec(stream.fldAvailableOut, n);
            inc(stream.fldTotalOut, long(n));
            window := fldWindow;
            if fldCheck then stream.fldChecksum.update(window, q, n);
            &Array.copyPrimitives(window, q, stream.fldNextOutArray, p, n);
            inc(p, n);
            inc(q, n);
            if q = fldEnd then begin
                q := 0;
                if fldWrite = fldEnd then fldWrite := 0;
                n := fldWrite - q;
                if n > stream.fldAvailableOut then n := stream.fldAvailableOut;
                if (n <> 0) and (r = BUF_ERROR) then r := OK;
                dec(stream.fldAvailableOut, n);
                inc(stream.fldTotalOut, long(n));
                if fldCheck then stream.fldChecksum.update(window, q, n);
                &Array.copyPrimitives(window, q, stream.fldNextOutArray, p, n);
                inc(p, n);
                inc(q, n);
            end;
            stream.fldNextOutIndex := p;
            fldRead := q;
            result := r;
        end;

        constructor InfBlocks.create(stream: ZStream; w: int);
        begin
            inherited create();
            fldEnd := w;
            fldWindow := &Array.newByte1d(w);
            fldCheck := InfState(stream.fldInflateState).fldWrap <> 0;
            fldMode := &TYPE;
            fldBB := &Array.newInt1d(1);
            fldTB := &Array.newInt1d(1);
            fldHufts := &Array.newInt1d(MANY * 3);
            fldCodes := InfCodes.create();
            fldTree := InfTree.create();
            reset(stream);
        end;

        destructor InfBlocks.destroy;
        begin
            fldTree.free();
            fldCodes.free();
            inherited destroy;
        end;
    {%endregion}

    {%region  InfCodes }
        class procedure InfCodes.initialize();
        begin
            inflateMask := [
                $00000000, $00000001, $00000003, $00000007, $0000000f,
                $0000001f, $0000003f, $0000007f, $000000ff, $000001ff,
                $000003ff, $000007ff, $00000fff, $00001fff, $00003fff,
                $00007fff, $0000ffff
            ];
        end;

        class procedure InfCodes.finalize();
        begin
            inflateMask := nil;
        end;

        function InfCodes.inflateFast(bl, bd: int; const tlArray: int_Array1d; tlIndex: int; const tdArray: int_Array1d; tdIndex: int; blocks: InfBlocks; stream: ZStream): int;
        var
            t: int;
            e: int;
            b: int;
            k: int;
            p: int;
            n: int;
            q: int;
            m: int;
            c: int;
            d: int;
            r: int;
            ml: int;
            md: int;
            &end: int;
            tpIndexBase: int;
            tpIndexPTM3: int;
            window: byte_Array1d;
            tpArray: int_Array1d;
        begin
            p := stream.fldNextInIndex;
            n := stream.fldAvailableIn;
            b := blocks.fldBitB;
            k := blocks.fldBitK;
            q := blocks.fldWrite;
            if q < blocks.fldRead then begin
                m := blocks.fldRead - q - 1;
            end else begin
                m := blocks.fldEnd - q;
            end;
            ml := inflateMask[bl];
            md := inflateMask[bd];
            window := blocks.fldWindow;
            repeat
                while k < 20 do begin
                    dec(n);
                    b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                t := b and ml;
                tpArray := tlArray;
                tpIndexBase := tlIndex;
                tpIndexPTM3 := (tpIndexBase + t) * 3;
                e := tpArray[tpIndexPTM3];
                if e = 0 then begin
                    b := CoInt.sar(b, tpArray[tpIndexPTM3 + 1]);
                    dec(k, tpArray[tpIndexPTM3 + 1]);
                    window[q] := byte(tpArray[tpIndexPTM3 + 2]);
                    inc(q);
                    dec(m);
                    continue;
                end;
                repeat
                    b := CoInt.sar(b, tpArray[tpIndexPTM3 + 1]);
                    dec(k, tpArray[tpIndexPTM3 + 1]);
                    if (e and $10) <> 0 then begin
                        e := e and $f;
                        c := tpArray[tpIndexPTM3 + 2] + (b and inflateMask[e]);
                        b := CoInt.sar(b, e);
                        dec(k, e);
                        while k < 15 do begin
                            dec(n);
                            b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                            inc(p);
                            inc(k, 8);
                        end;
                        t := b and md;
                        tpArray := tdArray;
                        tpIndexBase := tdIndex;
                        tpIndexPTM3 := (tpIndexBase + t) * 3;
                        e := tpArray[tpIndexPTM3];
                        repeat
                            b := CoInt.sar(b, tpArray[tpIndexPTM3 + 1]);
                            dec(k, tpArray[tpIndexPTM3 + 1]);
                            if (e and $10) <> 0 then begin
                                e := e and $f;
                                while k < e do begin
                                    dec(n);
                                    b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                                    inc(p);
                                    inc(k, 8);
                                end;
                                d := tpArray[tpIndexPTM3 + 2] + (b and inflateMask[e]);
                                b := CoInt.sar(b, e);
                                dec(k, e);
                                dec(m, c);
                                if q >= d then begin
                                    r := q - d;
                                    if d = 1 then begin
                                        window[q] := window[r];
                                        inc(q);
                                        inc(r);
                                        window[q] := window[r];
                                        inc(q);
                                        inc(r);
                                        dec(c, 2);
                                    end else begin
                                        &Array.copyPrimitives(window, r, window, q, 2);
                                        inc(q, 2);
                                        inc(r, 2);
                                        dec(c, 2);
                                    end;
                                end else begin
                                    r := q - d;
                                    &end := blocks.fldEnd;
                                    repeat
                                        inc(r, &end);
                                    until r >= 0;
                                    e := &end - r;
                                    if c > e then begin
                                        dec(c, e);
                                        if (q - r <= 0) or (q - r >= e) then begin
                                            &Array.copyPrimitives(window, r, window, q, e);
                                            inc(q, e);
                                            inc(r, e);
                                            e := 0;
                                        end else repeat
                                            window[q] := window[r];
                                            inc(q);
                                            inc(r);
                                            dec(e);
                                        until e = 0;
                                        r := 0;
                                    end;
                                end;
                                if (q - r <= 0) and (q - r >= c) then begin
                                    &Array.copyPrimitives(window, r, window, q, c);
                                    inc(q, c);
                                    inc(r, c);
                                    c := 0;
                                    break;
                                end;
                                repeat
                                    window[q] := window[r];
                                    inc(q);
                                    inc(r);
                                    dec(c);
                                until c = 0;
                                break;
                            end;
                            if (e and $40) <> 0 then begin
                                stream.fldMessage := 'invalid distance code';
                                c := stream.fldAvailableIn - n;
                                if CoInt.sar(k, 3) < c then c := CoInt.sar(k, 3);
                                inc(n, c);
                                dec(p, c);
                                dec(k, c shl 3);
                                blocks.update(stream, b, k, n, p, q);
                                result := DATA_ERROR;
                                exit;
                            end;
                            inc(t, tpArray[tpIndexPTM3 + 2] + (b and inflateMask[e]));
                            tpIndexPTM3 := (tpIndexBase + t) * 3;
                            e := tpArray[tpIndexPTM3];
                        until false;
                        break;
                    end;
                    if (e and $40) <> 0 then begin
                        if (e and $20) <> 0 then begin
                            c := stream.fldAvailableIn - n;
                            if CoInt.sar(k, 3) < c then c := CoInt.sar(k, 3);
                            inc(n, c);
                            dec(p, c);
                            dec(k, c shl 3);
                            blocks.update(stream, b, k, n, p, q);
                            result := STREAM_END;
                            exit;
                        end;
                        stream.fldMessage := 'invalid literal/length code';
                        c := stream.fldAvailableIn - n;
                        if CoInt.sar(k, 3) < c then c := CoInt.sar(k, 3);
                        inc(n, c);
                        dec(p, c);
                        dec(k, c shl 3);
                        blocks.update(stream, b, k, n, p, q);
                        result := DATA_ERROR;
                        exit;
                    end;
                    inc(t, tpArray[tpIndexPTM3 + 2] + (b and inflateMask[e]));
                    tpIndexPTM3 := (tpIndexBase + t) * 3;
                    e := tpArray[tpIndexPTM3];
                    if e = 0 then begin
                        b := CoInt.sar(b, tpArray[tpIndexPTM3 + 1]);
                        dec(k, tpArray[tpIndexPTM3 + 1]);
                        window[q] := byte(tpArray[tpIndexPTM3 + 2]);
                        inc(q);
                        dec(m);
                        break;
                    end;
                until false;
            until (m < 258) or (n < 10);
            c := stream.fldAvailableIn - n;
            if CoInt.sar(k, 3) < c then c := COInt.sar(k, 3);
            inc(n, c);
            dec(p, c);
            dec(k, c shl 3);
            blocks.update(stream, b, k, n, p, q);
            result := OK;
        end;

        procedure InfCodes.init(bl, bd: int; const tlArray: int_Array1d; tlIndex: int; const tdArray: int_Array1d; tdIndex: int);
        begin
            fldMode := START;
            fldLBits := byte(bl);
            fldDBits := byte(bd);
            fldLTreeArray := tlArray;
            fldLTreeIndex := tlIndex;
            fldDTreeArray := tdArray;
            fldDTreeIndex := tdIndex;
            fldTreeArray := nil;
        end;

        function InfCodes.proc(blocks: InfBlocks; stream: ZStream; r: int): int;
        var
            j: int;
            e: int;
            b: int;
            k: int;
            p: int;
            n: int;
            q: int;
            m: int;
            f: int;
            bEnd: int;
            tIndex: int;
            window: byte_Array1d;
            tArray: int_Array1d;
        begin
            p := stream.fldNextInIndex;
            n := stream.fldAvailableIn;
            b := blocks.fldBitB;
            k := blocks.fldBitK;
            q := blocks.fldWrite;
            if q < blocks.fldRead then begin
                m := blocks.fldRead - q - 1;
            end else begin
                m := blocks.fldEnd - q;
            end;
            window := blocks.fldWindow;
            repeat
                case fldMode of
                START: begin
                    if (m >= 258) and (n >= 10) then begin
                        blocks.update(stream, b, k, n, p, q);
                        r := inflateFast(fldLBits, fldDBits, fldLTreeArray, fldLTreeIndex, fldDTreeArray, fldDTreeIndex, blocks, stream);
                        p := stream.fldNextInIndex;
                        n := stream.fldAvailableIn;
                        b := blocks.fldBitB;
                        k := blocks.fldBitK;
                        q := blocks.fldWrite;
                        if q < blocks.fldRead then begin
                            m := blocks.fldRead - q - 1;
                        end else begin
                            m := blocks.fldEnd - q;
                        end;
                        if r <> OK then begin
                            if r = STREAM_END then begin
                                fldMode := WASH;
                                continue;
                            end;
                            fldMode := BADCODE;
                            continue;
                        end;
                    end;
                    fldNeed := fldLBits;
                    fldTreeArray := fldLTreeArray;
                    fldTreeIndex := fldLTreeIndex;
                    fldMode := LEN;
                end;
                LEN: begin
                    j := fldNeed;
                    while k < j do begin
                        if n = 0 then begin
                            blocks.update(stream, b, k, n, p, q);
                            result := blocks.inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    tArray := fldTreeArray;
                    tIndex := (fldTreeIndex + (b and inflateMask[j])) * 3;
                    b := b shr tArray[tIndex + 1];
                    dec(k, tArray[tIndex + 1]);
                    e := tArray[tIndex];
                    if e = 0 then begin
                        fldLit := tArray[tIndex + 2];
                        fldMode := LIT;
                        continue;
                    end;
                    if (e and $10) <> 0 then begin
                        fldGet := e and $0f;
                        fldLen := tArray[tIndex + 2];
                        fldMode := LENEXT;
                        continue;
                    end;
                    if (e and $40) = 0 then begin
                        fldNeed := e;
                        fldTreeIndex := (tIndex div 3) + tArray[tIndex + 2];
                        continue;
                    end;
                    if (e and $20) <> 0 then begin
                        fldMode := WASH;
                        continue;
                    end;
                    fldMode := BADCODE;
                    stream.fldMessage := 'invalid literal/length code';
                    r := DATA_ERROR;
                    blocks.update(stream, b, k, n, p, q);
                    result := blocks.inflateFlush(stream, r);
                    exit;
                end;
                LENEXT: begin
                    j := fldGet;
                    while k < j do begin
                        if n = 0 then begin
                            blocks.update(stream, b, k, n, p, q);
                            result := blocks.inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    inc(fldLen, b and inflateMask[j]);
                    b := CoInt.sar(b, j);
                    dec(k, j);
                    fldNeed := fldDBits;
                    fldTreeArray := fldDTreeArray;
                    fldTreeIndex := fldDTreeIndex;
                    fldMode := DIST;
                end;
                DIST: begin
                    j := fldNeed;
                    while k < j do begin
                        if n = 0 then begin
                            blocks.update(stream, b, k, n, p, q);
                            result := blocks.inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    tArray := fldTreeArray;
                    tIndex := (fldTreeIndex + (b and inflateMask[j])) * 3;
                    b := CoInt.sar(b, tArray[tIndex + 1]);
                    dec(k, tArray[tIndex + 1]);
                    e := tArray[tIndex];
                    if (e and $10) <> 0 then begin
                        fldGet := e and $f;
                        fldDist := tArray[tIndex + 2];
                        fldMode := DISTEXT;
                        continue;
                    end;
                    if (e and $40) = 0 then begin
                        fldNeed := e;
                        fldTreeIndex := (tIndex div 3) + tArray[tIndex + 2];
                        continue;
                    end;
                    fldMode := BADCODE;
                    stream.fldMessage := 'invalid distance code';
                    r := DATA_ERROR;
                    blocks.update(stream, b, k, n, p, q);
                    result := blocks.inflateFlush(stream, r);
                    exit;
                end;
                DISTEXT: begin
                    j := fldGet;
                    while k < j do begin
                        if n = 0 then begin
                            blocks.update(stream, b, k, n, p, q);
                            result := blocks.inflateFlush(stream, r);
                            exit;
                        end;
                        r := OK;
                        dec(n);
                        b := b or ((int(stream.fldNextInArray[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    inc(fldDist, b and inflateMask[j]);
                    b := CoInt.sar(b, j);
                    dec(k, j);
                    fldMode := COPY;
                end;
                COPY: begin
                    f := q - fldDist;
                    bEnd := blocks.fldEnd;
                    while f < 0 do begin
                        inc(f, bEnd);
                    end;
                    while fldLen <> 0 do begin
                        if m = 0 then begin
                            if (q = blocks.fldEnd) and (blocks.fldRead <> 0) then begin
                                q := 0;
                                if q < blocks.fldRead then begin
                                    m := blocks.fldRead - q - 1;
                                end else begin
                                    m := blocks.fldEnd - q;
                                end;
                            end;
                            if m = 0 then begin
                                blocks.fldWrite := q;
                                r := blocks.inflateFlush(stream, r);
                                q := blocks.fldWrite;
                                if q < blocks.fldRead then begin
                                    m := blocks.fldRead - q - 1;
                                end else begin
                                    m := blocks.fldEnd - q;
                                end;
                                if (q = blocks.fldEnd) and (blocks.fldRead <> 0) then begin
                                    q := 0;
                                    if q < blocks.fldRead then begin
                                        m := blocks.fldRead - q - 1;
                                    end else begin
                                        m := blocks.fldEnd - q;
                                    end;
                                end;
                                if m = 0 then begin
                                    blocks.update(stream, b, k, n, p, q);
                                    result := blocks.inflateFlush(stream, r);
                                    exit;
                                end;
                            end;
                        end;
                        window[q] := window[f];
                        inc(q);
                        inc(f);
                        dec(m);
                        if f = blocks.fldEnd then f := 0;
                        dec(fldLen);
                    end;
                    fldMode := START;
                end;
                LIT: begin
                    if m = 0 then begin
                        if (q = blocks.fldEnd) and (blocks.fldRead <> 0) then begin
                            q := 0;
                            if q < blocks.fldRead then begin
                                m := blocks.fldRead - q - 1;
                            end else begin
                                m := blocks.fldEnd - q;
                            end;
                        end;
                        if m = 0 then begin
                            blocks.fldWrite := q;
                            r := blocks.inflateFlush(stream, r);
                            q := blocks.fldWrite;
                            if q < blocks.fldRead then begin
                                m := blocks.fldRead - q - 1;
                            end else begin
                                m := blocks.fldEnd - q;
                            end;
                            if (q = blocks.fldEnd) and (blocks.fldRead <> 0) then begin
                                q := 0;
                                if q < blocks.fldRead then begin
                                    m := blocks.fldRead - q - 1;
                                end else begin
                                    m := blocks.fldEnd - q;
                                end;
                            end;
                            if m = 0 then begin
                                blocks.update(stream, b, k, n, p, q);
                                result := blocks.inflateFlush(stream, r);
                                exit;
                            end;
                        end;
                    end;
                    r := OK;
                    window[q] := byte(fldLit);
                    inc(q);
                    dec(m);
                    fldMode := START;
                end;
                WASH: begin
                    if k > 7 then begin
                        dec(k, 8);
                        inc(n);
                        dec(p);
                    end;
                    blocks.fldWrite := q;
                    r := blocks.inflateFlush(stream, r);
                    q := blocks.fldWrite;
                    if q < blocks.fldRead then begin
                        m := blocks.fldRead - q - 1;
                    end else begin
                        m := blocks.fldEnd - q;
                    end;
                    if blocks.fldRead <> blocks.fldWrite then begin
                        blocks.update(stream, b, k, n, p, q);
                        result := blocks.inflateFlush(stream, r);
                        exit;
                    end;
                    fldMode := &END;
                end;
                &END: begin
                    r := STREAM_END;
                    blocks.update(stream, b, k, n, p, q);
                    result := blocks.inflateFlush(stream, r);
                    exit;
                end;
                BADCODE: begin
                    r := DATA_ERROR;
                    blocks.update(stream, b, k, n, p, q);
                    result := blocks.inflateFlush(stream, r);
                    exit;
                end;
                else
                    r := STREAM_ERROR;
                    blocks.update(stream, b, k, n, p, q);
                    result := blocks.inflateFlush(stream, r);
                    exit;
                end;
            until false;
        end;
    {%endregion}

    {%region  InfTree }
        class procedure InfTree.initialize();
        begin
            fixedTL := [
                96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115,
                82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192,
                80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160,
                0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 224,
                80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144,
                83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208,
                81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 176,
                0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240,
                80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227,
                83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200,
                81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 168,
                0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232,
                80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152,
                84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 216,
                82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184,
                0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248,
                80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163,
                83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 196,
                81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164,
                0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228,
                80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 148,
                84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212,
                82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180,
                0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244,
                80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0,
                83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204,
                81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172,
                0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 236,
                80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156,
                84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220,
                82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188,
                0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252,
                96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131,
                82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194,
                80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 162,
                0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226,
                80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146,
                83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210,
                81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178,
                0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242,
                80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258,
                83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 202,
                81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170,
                0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234,
                80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154,
                84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218,
                82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186,
                0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250,
                80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195,
                83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198,
                81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166,
                0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230,
                80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150,
                84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214,
                82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182,
                0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 246,
                80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0,
                83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206,
                81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174,
                0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238,
                80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158,
                84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222,
                82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 190,
                0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254,
                96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115,
                82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 193,
                80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161,
                0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225,
                80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145,
                83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 209,
                81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177,
                0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241,
                80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227,
                83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201,
                81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169,
                0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233,
                80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 153,
                84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217,
                82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185,
                0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 249,
                80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163,
                83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197,
                81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165,
                0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 229,
                80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149,
                84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213,
                82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 181,
                0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245,
                80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0,
                83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205,
                81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 173,
                0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237,
                80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157,
                84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 221,
                82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189,
                0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 253,
                96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131,
                82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 195,
                80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163,
                0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227,
                80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 147,
                83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211,
                81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179,
                0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243,
                80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258,
                83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203,
                81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171,
                0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 235,
                80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155,
                84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219,
                82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187,
                0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 251,
                80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195,
                83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199,
                81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 167,
                0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231,
                80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151,
                84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215,
                82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 183,
                0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247,
                80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0,
                83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 207,
                81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175,
                0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239,
                80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159,
                84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 223,
                82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191,
                0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255
            ];
            fixedTD := [
                80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097,
                81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385,
                80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5, 8193,
                82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577,
                80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145,
                81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5, 24577,
                80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289,
                82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577
            ];
            cpLens := [
                3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
                15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
                67, 83, 99, 115, 131, 163, 195, 227, 258, 0,
                0
            ];
            cpLext := [
                0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
                1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
                4, 4, 4, 4, 5, 5, 5, 5, 0, 112,
                112
            ];
            cpDist := [
                1, 2, 3, 4, 5, 7, 9, 13, 17,
                25, 33, 49, 65, 97, 129, 193, 257, 385, 513,
                769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385,
                24577
            ];
            cpDext := [
                0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
                4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
                9, 9, 10, 10, 11, 11, 12, 12, 13, 13
            ];
        end;

        class procedure InfTree.finalize();
        begin
            cpDext := nil;
            cpDist := nil;
            cpLext := nil;
            cpLens := nil;
            fixedTD := nil;
            fixedTL := nil;
        end;

        class procedure InfTree.inflateTreesFixed(const bl, bd: int_Array1d; const tl, td: int_Array2d);
        begin
            bl[0] := FIXED_BL;
            bd[0] := FIXED_BD;
            tl[0] := fixedTL;
            td[0] := fixedTD;
        end;

        procedure InfTree.initWorkArea(vsize: int);
        var
            index: int;
            component: int_Array1d;
            components: int_Array2d;
        begin
            if fldHN = nil then begin
                fldHN := &Array.newInt1d(1);
                fldV := &Array.newInt1d(vsize);
                fldC := &Array.newInt1d(BMAX + 1);
                fldR := &Array.newInt1d(3);
                fldU := &Array.newInt1d(BMAX);
                fldX := &Array.newInt1d(BMAX + 1);
            end;
            if system.length(fldV) < vsize then fldV := &Array.newInt1d(vsize);
            components := [ fldV, fldC, fldR, fldU, fldX ];
            for index := system.length(components) - 1 downto 0 do begin
                component := components[index];
                &Array.fillPrimitives(component, 0, system.length(component), 0);
            end;
        end;

        function InfTree.huftBuild(const bArray: int_Array1d; bIndex, n, s: int; const d, e, t, m, hp, hn, v: int_Array1d): int;
        var
            a: int;
            f: int;
            g: int;
            h: int;
            i: int;
            j: int;
            k: int;
            l: int;
            p: int;
            q: int;
            w: int;
            y: int;
            z: int;
            xp: int;
            mask: int;
            c: int_Array1d;
            r: int_Array1d;
            u: int_Array1d;
            x: int_Array1d;
        begin
            p := 0;
            i := n;
            c := fldC;
            repeat
                inc(c[bArray[bIndex + p]]);
                inc(p);
                dec(i);
            until i = 0;
            if c[0] = n then begin
                t[0] := -1;
                m[0] := 0;
                result := OK;
                exit;
            end;
            l := m[0];
            j := 1;
            while (j <= BMAX) and (c[j] = 0) do begin
                inc(j);
            end;
            k := j;
            if l < j then l := j;
            i := BMAX;
            while (i > 0) and (c[i] = 0) do begin
                dec(i);
            end;
            g := i;
            if l > i then l := i;
            m[0] := l;
            y := 1 shl j;
            while j < i do begin
                dec(y, c[j]);
                if y < 0 then begin
                    result := DATA_ERROR;
                    exit;
                end;
                inc(j);
                y := y shl 1;
            end;
            dec(y, c[i]);
            if y < 0 then begin
                result := DATA_ERROR;
                exit;
            end;
            inc(c[i], y);
            x := fldX;
            x[1] := 0;
            j := 0;
            p := 1;
            xp := 2;
            dec(i);
            while i <> 0 do begin
                inc(j, c[p]);
                x[xp] := j;
                inc(xp);
                inc(p);
                dec(i);
            end;
            i := 0;
            p := 0;
            repeat
                j := bArray[bIndex + p];
                if j <> 0 then begin
                    v[x[j]] := i;
                    inc(x[j]);
                end;
                inc(p);
                inc(i);
            until i >= n;
            n := x[g];
            x[0] := 0;
            i := 0;
            p := 0;
            h := -1;
            w := -l;
            u := fldU;
            u[0] := 0;
            q := 0;
            z := 0;
            r := fldR;
            while k <= g do begin
                a := c[k];
                while a <> 0 do begin
                    dec(a);
                    while k > w + l do begin
                        inc(h);
                        inc(w, l);
                        z := g - w;
                        if z > l then z := l;
                        j := k - w;
                        f := 1 shl j;
                        if f > a + 1 then begin
                            dec(f, a + 1);
                            xp := k;
                            if j < z then begin
                                inc(j);
                                while j < z do begin
                                    f := f shl 1;
                                    inc(xp);
                                    if f <= c[xp] then break;
                                    dec(f, c[xp]);
                                    inc(j);
                                end;
                            end;
                        end;
                        z := 1 shl j;
                        if hn[0] + z > MANY then begin
                            result := DATA_ERROR;
                            exit;
                        end;
                        q := hn[0];
                        u[h] := q;
                        inc(hn[0], z);
                        if h <> 0 then begin
                            x[h] := i;
                            r[0] := byte(j);
                            r[1] := byte(l);
                            j := i shr (w - l);
                            r[2] := q - u[h - 1] - j;
                            &Array.copyPrimitives(r, 0, hp, (u[h - 1] + j) * 3, 3);
                        end else begin
                            t[0] := q;
                        end;
                    end;
                    r[1] := byte(k - w);
                    if p >= n then begin
                        r[0] := 128 + 64;
                    end else begin
                        if v[p] < s then begin
                            if v[p] < 256 then begin
                                r[0] := 0;
                            end else begin
                                r[0] := 32 + 64;
                            end;
                            r[2] := v[p];
                        end else begin
                            r[0] := byte(e[v[p] - s] + (16 + 64));
                            r[2] := d[v[p] - s];
                        end;
                        inc(p);
                    end;
                    f := 1 shl (k - w);
                    j := i shr w;
                    while j < z do begin
                        &Array.copyPrimitives(r, 0, hp, (q + j) * 3, 3);
                        inc(j, f);
                    end;
                    j := 1 shl (k - 1);
                    while (i and j) <> 0 do begin
                        i := i xor j;
                        j := j shr 1;
                    end;
                    i := i xor j;
                    mask := (1 shl w) - 1;
                    while (i and mask) <> x[h] do begin
                        dec(h);
                        dec(w, l);
                        mask := (1 shl w) - 1;
                    end;
                end;
                inc(k);
            end;
            if (y <> 0) and (g <> 1) then begin
                result := BUF_ERROR;
                exit;
            end;
            result := OK;
        end;

        function InfTree.inflateTreesBits(const c, bb, tb, hp: int_Array1d; stream: ZStream): int;
        var
            zlibResult: int;
            hn: int_Array1d;
        begin
            initWorkArea(19);
            hn := fldHN;
            hn[0] := 0;
            zlibResult := huftBuild(c, 0, 19, 19, nil, nil, tb, bb, hp, hn, fldV);
            if zlibResult = DATA_ERROR then begin
                stream.fldMessage := 'oversubscribed dynamic bit lengths tree';
            end else
            if (zlibResult = BUF_ERROR) or (bb[0] = 0) then begin
                stream.fldMessage := 'incomplete dynamic bit lengths tree';
                result := DATA_ERROR;
                exit;
            end;
            result := zlibResult;
        end;

        function InfTree.inflateTreesDynamic(nl, nd: int; const c, bl, bd, tl, td, hp: int_Array1d; stream: ZStream): int;
        var
            zlibResult: int;
            hn: int_Array1d;
        begin
            initWorkArea(288);
            hn := fldHN;
            hn[0] := 0;
            zlibResult := huftBuild(c, 0, nl, 257, cpLens, cpLext, tl, bl, hp, hn, fldV);
            if (zlibResult <> OK) or (bl[0] = 0) then begin
                if zlibResult = DATA_ERROR then begin
                    stream.fldMessage := 'oversubscribed literal/length tree';
                end else
                if zlibResult <> MEM_ERROR then begin
                    stream.fldMessage := 'incomplete literal/length tree';
                    result := DATA_ERROR;
                    exit;
                end;
                result := zlibResult;
                exit;
            end;
            initWorkArea(288);
            zlibResult := huftBuild(c, nl, nd, 0, cpDist, cpDext, td, bd, hp, hn, fldV);
            if (zlibResult <> OK) or ((bd[0] = 0) and (nl > 257)) then begin
                case zlibResult of
                DATA_ERROR: begin
                    stream.fldMessage := 'oversubscribed distance tree';
                end;
                BUF_ERROR: begin
                    stream.fldMessage := 'incomplete distance tree';
                    result := DATA_ERROR;
                    exit;
                end;
                else
                    if zlibResult <> MEM_ERROR then begin
                        stream.fldMessage := 'empty distance tree with lengths';
                        result := DATA_ERROR;
                        exit;
                    end;
                end;
                result := zlibResult;
                exit;
            end;
            result := OK;
        end;
    {%endregion}

    {%region  StaticTree }
        class procedure StaticTree.initialize();
        begin
            staticLTree := [
                12, 8, 140, 8, 76, 8, 204, 8, 44, 8,
                172, 8, 108, 8, 236, 8, 28, 8, 156, 8,
                92, 8, 220, 8, 60, 8, 188, 8, 124, 8,
                252, 8, 2, 8, 130, 8, 66, 8, 194, 8,
                34, 8, 162, 8, 98, 8, 226, 8, 18, 8,
                146, 8, 82, 8, 210, 8, 50, 8, 178, 8,
                114, 8, 242, 8, 10, 8, 138, 8, 74, 8,
                202, 8, 42, 8, 170, 8, 106, 8, 234, 8,
                26, 8, 154, 8, 90, 8, 218, 8, 58, 8,
                186, 8, 122, 8, 250, 8, 6, 8, 134, 8,
                70, 8, 198, 8, 38, 8, 166, 8, 102, 8,
                230, 8, 22, 8, 150, 8, 86, 8, 214, 8,
                54, 8, 182, 8, 118, 8, 246, 8, 14, 8,
                142, 8, 78, 8, 206, 8, 46, 8, 174, 8,
                110, 8, 238, 8, 30, 8, 158, 8, 94, 8,
                222, 8, 62, 8, 190, 8, 126, 8, 254, 8,
                1, 8, 129, 8, 65, 8, 193, 8, 33, 8,
                161, 8, 97, 8, 225, 8, 17, 8, 145, 8,
                81, 8, 209, 8, 49, 8, 177, 8, 113, 8,
                241, 8, 9, 8, 137, 8, 73, 8, 201, 8,
                41, 8, 169, 8, 105, 8, 233, 8, 25, 8,
                153, 8, 89, 8, 217, 8, 57, 8, 185, 8,
                121, 8, 249, 8, 5, 8, 133, 8, 69, 8,
                197, 8, 37, 8, 165, 8, 101, 8, 229, 8,
                21, 8, 149, 8, 85, 8, 213, 8, 53, 8,
                181, 8, 117, 8, 245, 8, 13, 8, 141, 8,
                77, 8, 205, 8, 45, 8, 173, 8, 109, 8,
                237, 8, 29, 8, 157, 8, 93, 8, 221, 8,
                61, 8, 189, 8, 125, 8, 253, 8, 19, 9,
                275, 9, 147, 9, 403, 9, 83, 9, 339, 9,
                211, 9, 467, 9, 51, 9, 307, 9, 179, 9,
                435, 9, 115, 9, 371, 9, 243, 9, 499, 9,
                11, 9, 267, 9, 139, 9, 395, 9, 75, 9,
                331, 9, 203, 9, 459, 9, 43, 9, 299, 9,
                171, 9, 427, 9, 107, 9, 363, 9, 235, 9,
                491, 9, 27, 9, 283, 9, 155, 9, 411, 9,
                91, 9, 347, 9, 219, 9, 475, 9, 59, 9,
                315, 9, 187, 9, 443, 9, 123, 9, 379, 9,
                251, 9, 507, 9, 7, 9, 263, 9, 135, 9,
                391, 9, 71, 9, 327, 9, 199, 9, 455, 9,
                39, 9, 295, 9, 167, 9, 423, 9, 103, 9,
                359, 9, 231, 9, 487, 9, 23, 9, 279, 9,
                151, 9, 407, 9, 87, 9, 343, 9, 215, 9,
                471, 9, 55, 9, 311, 9, 183, 9, 439, 9,
                119, 9, 375, 9, 247, 9, 503, 9, 15, 9,
                271, 9, 143, 9, 399, 9, 79, 9, 335, 9,
                207, 9, 463, 9, 47, 9, 303, 9, 175, 9,
                431, 9, 111, 9, 367, 9, 239, 9, 495, 9,
                31, 9, 287, 9, 159, 9, 415, 9, 95, 9,
                351, 9, 223, 9, 479, 9, 63, 9, 319, 9,
                191, 9, 447, 9, 127, 9, 383, 9, 255, 9,
                511, 9, 0, 7, 64, 7, 32, 7, 96, 7,
                16, 7, 80, 7, 48, 7, 112, 7, 8, 7,
                72, 7, 40, 7, 104, 7, 24, 7, 88, 7,
                56, 7, 120, 7, 4, 7, 68, 7, 36, 7,
                100, 7, 20, 7, 84, 7, 52, 7, 116, 7,
                3, 8, 131, 8, 67, 8, 195, 8, 35, 8,
                163, 8, 99, 8, 227, 8
            ];
            staticDTree := [
                0, 5, 16, 5, 8, 5, 24, 5, 4, 5,
                20, 5, 12, 5, 28, 5, 2, 5, 18, 5,
                10, 5, 26, 5, 6, 5, 22, 5, 14, 5,
                30, 5, 1, 5, 17, 5, 9, 5, 25, 5,
                5, 5, 21, 5, 13, 5, 29, 5, 3, 5,
                19, 5, 11, 5, 27, 5, 7, 5, 23, 5
            ];
            extraLBits := [
                0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
                1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
                4, 4, 4, 4, 5, 5, 5, 5, 0
            ];
            extraDBits := [
                0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
                4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
                9, 9, 10, 10, 11, 11, 12, 12, 13, 13
            ];
            extraBLBits := [
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 2, 3, 7
            ];
            staticLDesc := StaticTree.create(staticLTree, extraLBits, LITERALS + 1, L_CODES, MAX_BITS);
            staticDDesc := StaticTree.create(staticDTree, extraDBits, 0, D_CODES, MAX_BITS);
            staticBLDesc := StaticTree.create(nil, extraBLBits, 0, BL_CODES, MAX_BL_BITS);
        end;

        class procedure StaticTree.finalize();
        begin
            staticBLDesc.free();
            staticDDesc.free();
            staticLDesc.free();
            extraBLBits := nil;
            extraDBits := nil;
            extraLBits := nil;
            staticDTree := nil;
            staticLTree := nil;
        end;

        constructor StaticTree.create(const staticTree, extraBits: int_Array1d; extraBase, elements, maxLength: int);
        begin
            inherited create();
            fldStaticTree := staticTree;
            fldExtraBits := extraBits;
            fldExtraBase := extraBase;
            fldElements := elements;
            fldMaxLength := maxLength;
        end;
    {%endregion}

    {%region  Tree }
        class procedure Tree.genCodes(const tree: int_Array1d; maxCode: int; const blCount: int_Array1d);
        var
            idx: int;
            len: int;
            buf: int;
            code: int;
            bits: int;
            nextCode: int_Array1d;
        begin
            nextCode := &Array.newInt1d(MAX_BITS + 1);
            code := 0;
            for bits := 1 to MAX_BITS do begin
                code := short((code + blCount[bits - 1]) shl 1);
                nextCode[bits] := code;
            end;
            for idx := 0 to maxCode do begin
                len := tree[idx * 2 + 1];
                if len = 0 then continue;
                buf := nextCode[len];
                tree[idx * 2] := short(biReverse(buf, len));
                nextCode[len] := buf + 1;
            end;
        end;

        class function Tree.biReverse(code, len: int): int;
        var
            bits: int;
        begin
            bits := 0;
            repeat
                bits := bits or (code and $01);
                code := code shr 1;
                bits := bits shl 1;
                dec(len);
            until len <= 0;
            result := bits shr 1;
        end;

        procedure Tree.genBitlen(dState: DefState);
        var
            f: int;
            h: int;
            n: int;
            m: int;
            base: int;
            bits: int;
            xbits: int;
            heapMax: int;
            maxCode: int;
            overflow: int;
            maxLength: int;
            heapBuf: int_Array1d;
            blCount: int_Array1d;
            dTree: int_Array1d;
            sTree: int_Array1d;
            extra: int_Array1d;
            sDesc: StaticTree;
        begin
            sDesc := fldStaticDesc;
            dTree := fldDynamicTree;
            sTree := sDesc.fldStaticTree;
            extra := sDesc.fldExtraBits;
            base := sDesc.fldExtraBase;
            maxLength := sDesc.fldMaxLength;
            overflow := 0;
            blCount := dState.fldBlCount;
            &Array.fillPrimitives(blCount, 0, MAX_BITS, 0);
            maxCode := fldMaxCode;
            heapMax := dState.fldHeapMax;
            heapBuf := dState.fldHeapBuf;
            dTree[heapBuf[heapMax] * 2 + 1] := 0;
            h := heapMax + 1;
            while h < HEAP_SIZE do begin
                n := heapBuf[h];
                bits := dTree[dTree[n * 2 + 1] * 2 + 1] + 1;
                if bits > maxLength then begin
                    bits := maxLength;
                    inc(overflow);
                end;
                dTree[n * 2 + 1] := short(bits);
                if n > maxCode then begin
                    inc(h);
                    continue;
                end;
                inc(blCount[bits]);
                xbits := 0;
                if n >= base then xbits := extra[n - base];
                f := dTree[n * 2];
                inc(dState.fldOptLen, f * (bits + xbits));
                if sTree <> nil then inc(dState.fldStaticLen, f * (sTree[n * 2 + 1] + xbits));
                inc(h);
            end;
            if overflow = 0 then exit;
            repeat
                bits := &Array.lastIndexOfNon(0, blCount, maxLength - 1, 0);
                dec(blCount[bits]);
                inc(blCount[bits + 1], 2);
                dec(blCount[maxLength]);
                dec(overflow, 2);
            until overflow <= 0;
            for bits := maxLength downto 1 do begin
                n := blCount[bits];
                while n <> 0 do begin
                    dec(h);
                    m := heapBuf[h];
                    if m > maxCode then continue;
                    if dTree[m * 2 + 1] <> bits then begin
                        inc(dState.fldOptLen, int((long(bits) - long(dTree[m * 2 + 1])) * long(dTree[m * 2])));
                        dTree[m * 2 + 1] := short(bits);
                    end;
                    dec(n);
                end;
            end;
        end;

        procedure Tree.buildTree(dState: DefState);
        var
            n: int;
            m: int;
            node: int;
            heapLen: int;
            heapMax: int;
            elements: int;
            maxCodeLocal: int;
            depth: byte_Array1d;
            heapBuf: int_Array1d;
            dTree: int_Array1d;
            sTree: int_Array1d;
            sDesc: StaticTree;
        begin
            sDesc := fldStaticDesc;
            dTree := fldDynamicTree;
            sTree := sDesc.fldStaticTree;
            elements := sDesc.fldElements;
            maxCodeLocal := -1;
            heapLen := 0;
            heapMax := HEAP_SIZE;
            heapBuf := dState.fldHeapBuf;
            depth := dState.fldDepth;
            for n := 0 to elements - 1 do begin
                if dTree[n * 2] <> 0 then begin
                    inc(heapLen);
                    maxCodeLocal := n;
                    heapBuf[heapLen] := n;
                    depth[n] := 0;
                end else begin
                    dTree[n * 2 + 1] := 0;
                end;
            end;
            while heapLen < 2 do begin
                inc(heapLen);
                if maxCodeLocal < 2 then begin
                    inc(maxCodeLocal);
                    node := maxCodeLocal;
                end else begin
                    node := 0;
                end;
                heapBuf[heapLen] := node;
                dTree[node * 2] := 1;
                depth[node] := 0;
                dec(dState.fldOptLen);
                if sTree <> nil then dec(dState.fldStaticLen, sTree[node * 2 + 1]);
            end;
            fldMaxCode := maxCodeLocal;
            dState.fldHeapLen := heapLen;
            for n := heapLen div 2 downto 1 do begin
                dState.pqDownHeap(dTree, n);
            end;
            node := elements;
            repeat
                n := heapBuf[1];
                heapBuf[1] := heapBuf[heapLen];
                dec(heapLen);
                dState.fldHeapLen := heapLen;
                dState.pqDownHeap(dTree, 1);
                m := heapBuf[1];
                dec(heapMax);
                heapBuf[heapMax] := n;
                dec(heapMax);
                heapBuf[heapMax] := m;
                dTree[node * 2] := short(dTree[n * 2] + dTree[m * 2]);
                depth[node] := byte(CoInt.max(depth[n], depth[m]) + 1);
                dTree[n * 2 + 1] := short(node);
                dTree[m * 2 + 1] := short(node);
                heapBuf[1] := node;
                inc(node);
                dState.fldHeapLen := heapLen;
                dState.pqDownHeap(dTree, 1);
            until heapLen < 2;
            dec(heapMax);
            heapBuf[heapMax] := heapBuf[1];
            dState.fldHeapLen := heapLen;
            dState.fldHeapMax := heapMax;
            genBitlen(dState);
            genCodes(dTree, maxCodeLocal, dState.fldBlCount);
        end;
    {%endregion}

    {%region} initialization
        CRC32.initialize();
        DefState.initialize();
        InfState.initialize();
        InfBlocks.initialize();
        InfCodes.initialize();
        InfTree.initialize();
        StaticTree.initialize();
    {%endregion}

    {%region} finalization
        StaticTree.finalize();
        InfTree.finalize();
        InfCodes.finalize();
        InfBlocks.finalize();
        InfState.finalize();
        DefState.finalize();
        CRC32.finalize();
    {%endregion}

end.