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

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

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

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

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

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

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

unit Zlib;

{$MODE DELPHI}

interface

uses
    Lang,
    IOStreams;

{%region public }
const
    VERSION = '1.1.0';
    MAX_WBITS = int(15);
    DEF_WBITS = MAX_WBITS;

    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);

const
    CHECKSUM32_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1508}';
    DATA_STREAM_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1509}';
    DEFLATER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C150A}';
    INFLATER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C150B}';

type
    Checksum32 = interface;
    DataStream = interface;
    Deflater = interface;
    Inflater = interface;
    Adler32 = class;
    Crc32 = class;
    Config = class;
    StaticTree = class;
    GZIPHeader = class;
    Tree = class;
    Deflate = class;
    Inflate = class;
    InfTree = class;
    InfCodes = class;
    InfBlocks = class;
    ZStream = class;

    Config_Array1d = array of Config;

    Checksum32 = interface(_Interface) [CHECKSUM32_GUID]
        procedure update(const buf: byte_Array1d; offset, length: int);
        procedure reset(init: int); overload;
        procedure reset(); overload;
        function getValue(): int;
        function copy(): Checksum32;
    end;

    DataStream = interface(_Interface) [DATA_STREAM_GUID]
        procedure setAvailIn(availIn: int);
        procedure setAvailOut(availOut: int);
        procedure setNextIn(const nextIn: byte_Array1d);
        procedure setNextInIndex(nextInIndex: int);
        procedure setNextOut(const nextOut: byte_Array1d);
        procedure setNextOutIndex(nextOutIndex: int);
        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; append: boolean); overload;
        procedure setOutput(const data: byte_Array1d); overload;
        procedure setOutput(const data: byte_Array1d; ofs, len: int); overload;
        function getChecksum(): int;
        function getAvailIn(): int;
        function getAvailOut(): int;
        function getNextInIndex(): int;
        function getNextOutIndex(): int;
        function getTotalIn(): long;
        function getTotalOut(): long;
        function getNextIn(): byte_Array1d;
        function getNextOut(): byte_Array1d;
        function getMessage(): AnsiString;
    end;

    Deflater = interface(DataStream) [DEFLATER_GUID]
        function deflateInit(level: int): int; overload;
        function deflateInit(level: int; nowrap: boolean): int; overload;
        function deflateInit(level, bits: int): int; overload;
        function deflateInit(level, bits, memlevel: int): int; overload;
        function deflateInit(level, bits: int; nowrap: boolean): int; overload;
        function deflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
        function deflateParams(level, strategy: int): int;
        function deflate(flush: int): int;
        function deflateEnd(): int;
    end;

    Inflater = interface(DataStream) [INFLATER_GUID]
        function inflateInit(): int; overload;
        function inflateInit(w: int): int; overload;
        function inflateInit(nowrap: boolean): int; overload;
        function inflateInit(w: int; nowrap: boolean): int; overload;
        function inflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
        function inflateSync(): int;
        function inflateSyncPoint(): int;
        function inflate(flush: int): int;
        function inflateEnd(): int;
        function inflateFinished(): boolean;
    end;

    Adler32 = class(RefCountInterfacedObject, Checksum32)
    strict private
        adler: int;
    public
        constructor create(); overload;
        constructor create(adler: int); overload;
        procedure update(const buf: byte_Array1d; offset, length: int);
        procedure reset(init: int); overload;
        procedure reset(); overload;
        function getValue(): int;
        function copy(): Checksum32;

    strict private const
        BASE = int(65521);
        NMAX = int(5552);
    end;

    Crc32 = class(RefCountInterfacedObject, Checksum32)
    strict private
        crc: int;
    public
        constructor create(); overload;
        constructor create(crc: int); overload;
        procedure update(const buf: byte_Array1d; offset, length: int);
        procedure reset(init: int); overload;
        procedure reset(); overload;
        function getValue(): int;
        function copy(): Checksum32;

    strict private
        TABLE: int_Array1d; static;
    private
        class procedure clinit();
        class procedure cldone();
    end;

    Config = class(_Object)
    private
        goodLength: int;
        maxLazy: int;
        niceLength: int;
        maxChain: int;
        func: int;
    public
        constructor create(goodLength, maxLazy, niceLength, maxChain, func: int);
    end;

    StaticTree = class(_Object)
    private
        staticTree: int_Array1d;
        extraBits: int_Array1d;
        extraBase: int;
        elems: int;
        maxLength: int;
    public
        constructor create(const staticTree, extraBits: int_Array1d; extraBase, elems, maxLength: int);

    strict private const
        MAX_BITS = int(15);
        BL_CODES = int(19);
        D_CODES = int(30);
        LITERALS = int(256);
        LENGTH_CODES = int(29);
        L_CODES = LITERALS + LENGTH_CODES + 1;
    private const
        MAX_BL_BITS = int(7);
    private
        STATIC_LTREE: int_Array1d; static;
        STATIC_DTREE: int_Array1d; static;
        EXTRA_LBITS: int_Array1d; static;
        EXTRA_DBITS: int_Array1d; static;
        EXTRA_BLBITS: int_Array1d; static;
        STATIC_L_DESC: StaticTree; static;
        STATIC_D_DESC: StaticTree; static;
        STATIC_BL_DESC: StaticTree; static;
        class procedure clinit();
        class procedure cldone();
    end;

    GZIPHeader = class(_Object)
    strict private
        fhcrc: boolean;
        text: boolean;
        mtime: int;
    private
        xflags: int;
        os: int;
        hcrc: int;
        time: int;
        crc: int;
        extra: byte_Array1d;
        name: byte_Array1d;
        comment: byte_Array1d;
        procedure put(d: Deflate);
    public
        constructor create();
        procedure setName(const name: AnsiString);
        procedure setComment(const comment: AnsiString);
        procedure setModifiedTime(mtime: int);
        procedure setCRC(crc: int);
        procedure setOS(os: int);
        function getName(): AnsiString;
        function getComment(): AnsiString;
        function getModifiedTime(): int;
        function getCRC(): int;
        function getOS(): int;
    end;

    Tree = class(_Object)
    strict private
        procedure genBitlen(s: Deflate);
    private
        maxCode: int;
        dynTree: int_Array1d;
        statDesc: StaticTree;
    public
        constructor create();
        procedure buildTree(s: Deflate);

    strict private const
        MAX_BITS = int(15);
        LITERALS = int(256);
        LENGTH_CODES = int(29);
        L_CODES = LITERALS + LENGTH_CODES + 1;
        HEAP_SIZE = 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;
    end;

    Deflate = class(_Object)
    strict private
        status: int;
        pendingBufSize: int;
        dataType: int;
        lastFlush: int;
        wSize: int;
        wBits: int;
        wMask: int;
        windowSize: int;
        insh: int;
        hashSize: int;
        hashBits: int;
        hashMask: int;
        hashShift: int;
        blockStart: int;
        matchLength: int;
        prevMatch: int;
        matchAvailable: int;
        strStart: int;
        matchStart: int;
        lookahead: int;
        prevLength: int;
        maxChainLength: int;
        maxLazyMatch: int;
        strategy: int;
        goodMatch: int;
        niceMatch: int;
        litBufSize: int;
        lastLit: int;
        matches: int;
        lastEobLen: int;
        lBuf: int;
        dBuf: int;
        biBuf: int;
        biValid: int;
        window: byte_Array1d;
        prev: int_Array1d;
        head: int_Array1d;
        dynLtree: int_Array1d;
        dynDtree: int_Array1d;
        blTree: int_Array1d;
        lDesc: Tree;
        dDesc: Tree;
        blDesc: Tree;
        gheader: GZIPHeader;
        stream: 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 deflateInit(level, method, windowBits, memLevel, strategy: int): int; overload;
        function longestMatch(curMatch: int): int;
        function getGZIPHeader(): GZIPHeader;
    private
        pendingOut: int;
        pending: int;
        wrap: int;
        level: int;
        heapLen: int;
        heapMax: int;
        optLen: int;
        staticLen: int;
        blCount: int_Array1d;
        heap: int_Array1d;
        pendingBuf: byte_Array1d;
        depth: byte_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 deflateInit(level, bits, memlevel: int): int; overload;
        function deflateInit(level, bits: int): int; overload;
        function deflateInit(level: int): int; overload;
        function deflateEnd(): int;
        function deflateParams(lvl, strtg: int): int;
        function deflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
        function deflate(flush: int): int;
    public
        constructor create(stream: ZStream);
        destructor destroy; override;

    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 = 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 = LITERALS + LENGTH_CODES + 1;
        HEAP_SIZE = 2 * L_CODES + 1;
        END_BLOCK = int(256);
        MAX_MEM_LEVEL = int(9);
        DEF_MEM_LEVEL = int(8);
        STORED = int(0);
        FAST = int(1);
        SLOW = int(2);
    strict private
        DIST_CODE: byte_Array1d; static;
        BL_ORDER: byte_Array1d; static;
        LENGTH_CODE: byte_Array1d; static;
        BASE_LENGTH: int_Array1d; static;
        BASE_DIST: int_Array1d; static;
        CONFIG_TABLE: Config_Array1d; static;
        ERR_MSG: AnsiString_Array1d; static;
    private
        class procedure clinit();
        class procedure cldone();
        class function smaller(const tree: int_Array1d; n, m: int; const depth: byte_Array1d): boolean;
        class function dcode(dist: int): int;
    end;

    Inflate = class(_Object)
    strict private
        readReturn: boolean;
        method: int;
        marker: int;
        wbits: int;
        was: int;
        need: int;
        flags: int;
        needBytes: int;
        crcbuf: byte_Array1d;
        tmpString: ByteArrayOutputStream;
        blocks: InfBlocks;
        gheader: GZIPHeader;
        stream: ZStream;
        procedure checksum(n, v: int);
        function inflateReset(): int;
        function readBytes(z: ZStream; n, r, f: int): int; overload;
        function readBytes(z: ZStream; r, f: int): int; overload;
        function readString(z: ZStream; r, f: int): int;
    private
        mode: int;
        wrap: int;
        function inflateEnd(): int;
        function inflateInit(w: int): int;
        function inflate(f: int): int;
        function inflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
        function inflateSync(): int;
        function inflateSyncPoint(): int;
    public
        constructor create(stream: ZStream);
        destructor destroy; override;

    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
        MARK: byte_Array1d; static;
    private
        class procedure clinit();
        class procedure cldone();
    end;

    InfTree = class(_Object)
    strict private
        hn: int_Array1d;
        v: int_Array1d;
        c: int_Array1d;
        r: int_Array1d;
        u: int_Array1d;
        x: int_Array1d;
        procedure initWorkArea(vsize: int);
        function huftBuild(const b: 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; z: ZStream): int;
        function inflateTreesDynamic(nl, nd: int; const c, bl, bd, tl, td, hp: int_Array1d; z: ZStream): int;
    public
        constructor create();

    strict private const
        MANY = int(1440);
        BMAX = int(15);
        FIXED_BL = int(9);
        FIXED_BD = int(5);
    strict private
        FIXED_TL: int_Array1d; static;
        FIXED_TD: int_Array1d; static;
        CPLENS: int_Array1d; static;
        CPLEXT: int_Array1d; static;
        CPDIST: int_Array1d; static;
        CPDEXT: int_Array1d; static;
    private
        class procedure clinit();
        class procedure cldone();
        class procedure inflateTreesFixed(const bl, bd: int_Array1d; const tl, td: int_Array2d);
    end;

    InfCodes = class(_Object)
    strict private
        lbits: int;
        dbits: int;
        mode: int;
        len: int;
        need: int;
        lit: int;
        get: int;
        dist: int;
        treeIndex: int;
        ltreeIndex: int;
        dtreeIndex: int;
        tree: int_Array1d;
        ltree: int_Array1d;
        dtree: int_Array1d;
        function inflateFast(bl, bd: int; const tl: int_Array1d; tlIndex: int; const td: int_Array1d; tdIndex: int; s: InfBlocks; z: ZStream): int;
    private
        procedure init(bl, bd: int; const tl: int_Array1d; tlIndex: int; const td: int_Array1d; tdIndex: int);
        function proc(s: InfBlocks; z: ZStream; r: int): int;
    public
        constructor create();

    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
        INFLATE_MASK: int_Array1d; static;
    private
        class procedure clinit();
        class procedure cldone();
    end;

    InfBlocks = class(_Object)
    strict private
        check: boolean;
        mode: int;
        left: int;
        table: int;
        index: int;
        last: int;
        blens: int_Array1d;
        bb: int_Array1d;
        tb: int_Array1d;
        hufts: int_Array1d;
        codes: InfCodes;
        itree: InfTree;
        procedure proc1();
    private
        bitk: int;
        bitb: int;
        end_: int;
        read: int;
        write: int;
        window: byte_Array1d;
        procedure reset(z: ZStream);
        procedure free(z: ZStream); overload;
        procedure setDictionary(const d: byte_Array1d; start, n: int);
        procedure update(z: ZStream; b, k, n, p, q: int);
        function proc(z: ZStream; r: int): int;
        function inflateFlush(z: ZStream; r: int): int;
        function syncPoint(): boolean;
    public
        constructor create(z: ZStream; w: int);
        destructor destroy; override;

    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
        INFLATE_MASK: int_Array1d; static;
        BORDER: int_Array1d; static;
    private
        class procedure clinit();
        class procedure cldone();
    end;

    ZStream = class(RefCountInterfacedObject, DataStream, Deflater, Inflater)
    private
        dataType: int;
        nextInIndex: int;
        availIn: int;
        nextOutIndex: int;
        availOut: int;
        totalIn: long;
        totalOut: long;
        nextIn: byte_Array1d;
        nextOut: byte_Array1d;
        dstate: Deflate;
        istate: Inflate;
        adler: Checksum32;
        msg: AnsiString;
        procedure flushPending();
        function readBuf(const dst: byte_Array1d; offset, count: int): int;
    public
        constructor create();
        destructor destroy; override;
        { DataStream }
        procedure setAvailIn(availIn: int);
        procedure setAvailOut(availOut: int);
        procedure setNextIn(const nextIn: byte_Array1d);
        procedure setNextInIndex(nextInIndex: int);
        procedure setNextOut(const nextOut: byte_Array1d);
        procedure setNextOutIndex(nextOutIndex: int);
        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; append: boolean); overload;
        procedure setOutput(const data: byte_Array1d); overload;
        procedure setOutput(const data: byte_Array1d; offset, length: int); overload;
        function getChecksum(): int;
        function getAvailIn(): int;
        function getAvailOut(): int;
        function getNextInIndex(): int;
        function getNextOutIndex(): int;
        function getTotalIn(): long;
        function getTotalOut(): long;
        function getNextIn(): byte_Array1d;
        function getNextOut(): byte_Array1d;
        function getMessage(): AnsiString;
        { Deflater }
        function deflateInit(level: int): int; overload;
        function deflateInit(level: int; nowrap: boolean): int; overload;
        function deflateInit(level, bits: int): int; overload;
        function deflateInit(level, bits, memlevel: int): int; overload;
        function deflateInit(level, bits: int; nowrap: boolean): int; overload;
        function deflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
        function deflateParams(level, strategy: int): int;
        function deflate(flush: int): int;
        function deflateEnd(): int;
        { Inflater }
        function inflateInit(): int; overload;
        function inflateInit(w: int): int; overload;
        function inflateInit(nowrap: boolean): int; overload;
        function inflateInit(w: int; nowrap: boolean): int; overload;
        function inflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
        function inflateSync(): int;
        function inflateSyncPoint(): int;
        function inflate(flush: int): int;
        function inflateEnd(): int;
        function inflateFinished(): boolean;
    end;
{%endregion}

{%region routine }
    function encodeISO88591(const s: UnicodeString): byte_Array1d;
    function decodeISO88591(const b: byte_Array1d): UnicodeString;
    function compress(const data: byte_Array1d): byte_Array1d; overload;
    function compress(const data: byte_Array1d; offset, length: int): byte_Array1d; overload;
    function compress(const data: byte_Array1d; defaultInit: boolean): byte_Array1d; overload;
    function compress(const data: byte_Array1d; offset, length: int; defaultInit: boolean): byte_Array1d; overload;
    function compress(const data: byte_Array1d; level: int): byte_Array1d; overload;
    function compress(const data: byte_Array1d; offset, length, level: int): byte_Array1d; overload;
    function compress(const data: byte_Array1d; level: int; defaultInit: boolean): byte_Array1d; overload;
    function compress(const data: byte_Array1d; offset, length, level: int; defaultInit: boolean): byte_Array1d; overload;
    function decompress(const data: byte_Array1d): byte_Array1d; overload;
    function decompress(const data: byte_Array1d; offset, length: int): byte_Array1d; overload;
    function decompress(const data: byte_Array1d; defaultInit: boolean): byte_Array1d; overload;
    function decompress(const data: byte_Array1d; offset, length: int; defaultInit: boolean): byte_Array1d; overload;
{%endregion}

implementation

{%region routine }
    function toConfigArray1d(const arr: array of Config): Config_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    function predinc(var target: int): int; inline;
    begin
        inc(target);
        result := target;
    end;

    function encodeISO88591(const s: UnicodeString): byte_Array1d;
    var
        i: int;
        c: int;
    begin
        result := byte_Array1d_create(length(s));
        for i := 0 to length(result) - 1 do begin
            c := int(s[i + 1]);
            if (c >= $0000) and (c <= $00ff) then begin
                result[i] := byte(c);
            end else begin
                result[i] := $3f;
            end;
        end;
    end;

    function decodeISO88591(const b: byte_Array1d): UnicodeString;
    var
        i: int;
    begin
        result := UnicodeString_create(length(b));
        for i := 1 to length(result) do begin
            result[i] := wchar(b[i - 1] and $ff);
        end;
    end;

    function compress(const data: byte_Array1d): byte_Array1d;
    begin
        result := compress(data, 0, length(data), OPTIMAL_SPEED_COMPRESSION, true);
    end;

    function compress(const data: byte_Array1d; offset, length: int): byte_Array1d;
    begin
        result := compress(data, offset, length, OPTIMAL_SPEED_COMPRESSION, true);
    end;

    function compress(const data: byte_Array1d; defaultInit: boolean): byte_Array1d;
    begin
        result := compress(data, 0, length(data), OPTIMAL_SPEED_COMPRESSION, defaultInit);
    end;

    function compress(const data: byte_Array1d; offset, length: int; defaultInit: boolean): byte_Array1d;
    begin
        result := compress(data, offset, length, OPTIMAL_SPEED_COMPRESSION, defaultInit);
    end;

    function compress(const data: byte_Array1d; level: int): byte_Array1d;
    begin
        result := compress(data, 0, length(data), level, true);
    end;

    function compress(const data: byte_Array1d; offset, length, level: int): byte_Array1d;
    begin
        result := compress(data, offset, length, level, true);
    end;

    function compress(const data: byte_Array1d; level: int; defaultInit: boolean): byte_Array1d;
    begin
        result := compress(data, 0, length(data), level, defaultInit);
    end;

    function compress(const data: byte_Array1d; offset, length, level: int; defaultInit: boolean): byte_Array1d;
    var
        compressor: Deflater;
        tempStream: ByteArrayOutputStream;
        zlibResult: int;
        totalOut: long;
    begin
        compressor := ZStream.create();
        tempStream := ByteArrayOutputStream.create();
        try
            result := byte_Array1d_create($10000);
            totalOut := 0;
            if defaultInit then begin
                compressor.deflateInit(level);
            end else begin
                compressor.deflateInit(level, -MAX_WBITS);
            end;
            compressor.setInput(data, offset, length, false);
            repeat
                compressor.setOutput(result);
                zlibResult := compressor.deflate(FINISH);
                tempStream.write(result, 0, int(compressor.getTotalOut() - totalOut));
                if zlibResult = STREAM_END then begin
                    compressor.deflateEnd();
                    break;
                end;
                totalOut := compressor.getTotalOut();
            until false;
            result := tempStream.toByteArray();
        finally
            tempStream.free();
        end;
    end;

    function decompress(const data: byte_Array1d): byte_Array1d;
    begin
        result := decompress(data, 0, length(data), true);
    end;

    function decompress(const data: byte_Array1d; offset, length: int): byte_Array1d;
    begin
        result := decompress(data, offset, length, true);
    end;

    function decompress(const data: byte_Array1d; defaultInit: boolean): byte_Array1d;
    begin
        result := decompress(data, 0, length(data), defaultInit);
    end;

    function decompress(const data: byte_Array1d; offset, length: int; defaultInit: boolean): byte_Array1d;
    var
        decompressor: Inflater;
        tempStream: ByteArrayOutputStream;
        zlibResult: int;
        totalOut: long;
    begin
        decompressor := ZStream.create();
        tempStream := ByteArrayOutputStream.create();
        try
            result := byte_Array1d_create($10000);
            totalOut := 0;
            if defaultInit then begin
                decompressor.inflateInit();
            end else begin
                decompressor.inflateInit(-MAX_WBITS);
            end;
            decompressor.setInput(data, offset, length, false);
            repeat
                decompressor.setOutput(result);
                zlibResult := decompressor.inflate(NO_FLUSH);
                tempStream.write(result, 0, int(decompressor.getTotalOut() - totalOut));
                if (zlibResult = STREAM_END) or (zlibResult < 0) then begin
                    decompressor.inflateEnd();
                    break;
                end;
                totalOut := decompressor.getTotalOut();
            until false;
            result := tempStream.toByteArray();
        finally
            tempStream.free();
        end;
    end;
{%endregion}

{%region Adler32 }
    constructor Adler32.create();
    begin
        inherited create();
        self.adler := 1;
    end;

    constructor Adler32.create(adler: int);
    begin
        inherited create();
        self.adler := adler;
    end;

    procedure Adler32.update(const buf: byte_Array1d; offset, length: int);
    var
        lim: int;
        len: int;
        a: int;
        k: int;
        s1: long;
        s2: long;
    begin
        lim := offset + length;
        len := System.length(buf);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('Adler32.update: индекс элемента массива выходит из диапазона.');
        end;
        a := adler;
        s1 := long(a and $ffff);
        s2 := long((a shr 16) and $ffff);
        while length > 0 do begin
            k := min(length, NMAX);
            dec(length, k);
            while k > 0 do begin
                dec(k);
                inc(s1, buf[offset] and $ff);
                inc(s2, s1);
                inc(offset);
            end;
            divLong(s1, BASE, s1);
            divLong(s2, BASE, s2);
        end;
        adler := int((s2 shl 16) or s1);
    end;

    procedure Adler32.reset(init: int);
    begin
        adler := init;
    end;

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

    function Adler32.getValue(): int;
    begin
        result := adler;
    end;

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

{%region Crc32 }
    class procedure Crc32.clinit();
    var
        i: int;
        j: int;
        c: int;
    begin
        TABLE := int_Array1d_create(256);
        for i := 0 to 255 do begin
            c := i;
            for j := 7 downto 0 do begin
                if (c and 1) <> 0 then begin
                    c := int($edb88320) xor (c shr 1);
                end else begin
                    c := c shr 1;
                end;
            end;
            TABLE[i] := c;
        end;
    end;

    class procedure Crc32.cldone();
    begin
        TABLE := nil;
    end;

    constructor Crc32.create();
    begin
        inherited create();
        self.crc := 0;
    end;

    constructor Crc32.create(crc: int);
    begin
        inherited create();
        self.crc := crc;
    end;

    procedure Crc32.update(const buf: byte_Array1d; offset, length: int);
    var
        lim: int;
        len: int;
        c: int;
    begin
        lim := offset + length;
        len := System.length(buf);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('Crc32.update: индекс элемента массива выходит из диапазона.');
        end;
        c := not crc;
        while length > 0 do begin
            dec(length);
            c := TABLE[int(c xor buf[offset]) and $ff] xor (c shr 8);
            inc(offset);
        end;
        crc := not c;
    end;

    procedure Crc32.reset(init: int);
    begin
        crc := init;
    end;

    procedure Crc32.reset();
    begin
        crc := 0;
    end;

    function Crc32.getValue(): int;
    begin
        result := crc;
    end;

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

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

{%region StaticTree }
    class procedure StaticTree.clinit();
    begin
        STATIC_LTREE := toIntArray1d([
            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
        ]);
        STATIC_DTREE := toIntArray1d([
            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
        ]);
        EXTRA_LBITS := toIntArray1d([
            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
        ]);
        EXTRA_DBITS := toIntArray1d([
            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
        ]);
        EXTRA_BLBITS := toIntArray1d([
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 2, 3, 7
        ]);
        STATIC_L_DESC := Zlib.StaticTree.create(STATIC_LTREE, EXTRA_LBITS, LITERALS + 1, L_CODES, MAX_BITS);
        STATIC_D_DESC := Zlib.StaticTree.create(STATIC_DTREE, EXTRA_DBITS, 0, D_CODES, MAX_BITS);
        STATIC_BL_DESC := Zlib.StaticTree.create(nil, EXTRA_BLBITS, 0, BL_CODES, MAX_BL_BITS);
    end;

    class procedure StaticTree.cldone();
    begin
        STATIC_BL_DESC.free();
        STATIC_D_DESC.free();
        STATIC_L_DESC.free();
        EXTRA_BLBITS := nil;
        EXTRA_DBITS := nil;
        EXTRA_LBITS := nil;
        STATIC_DTREE := nil;
        STATIC_LTREE := nil;
    end;

    constructor StaticTree.create(const staticTree, extraBits: int_Array1d; extraBase, elems, maxLength: int);
    begin
        inherited create();
        self.staticTree := staticTree;
        self.extraBits := extraBits;
        self.extraBase := extraBase;
        self.elems := elems;
        self.maxLength := maxLength;
    end;
{%endregion}

{%region GZIPHeader }
    constructor GZIPHeader.create();
    begin
        inherited create();
        os := 255;
    end;

    procedure GZIPHeader.put(d: Deflate);
    var
        flags: int;
        xfl: int;
    begin
        flags := 0;
        if text then begin
            flags := flags or $01;
        end;
        if fhcrc then begin
            flags := flags or $02;
        end;
        if extra <> nil then begin
            flags := flags or $04;
        end;
        if name <> nil then begin
            flags := flags or $08;
        end;
        if comment <> nil then begin
            flags := flags or $10;
        end;
        xfl := 0;
        if d.level = BEST_SPEED then begin
            xfl := xfl or $04;
        end else
        if d.level = BEST_COMPRESSION then begin
            xfl := xfl or $02;
        end;
        d.putShort($8b1f);
        d.putByte(8);
        d.putByte(flags);
        d.putByte(mtime);
        d.putByte(mtime shr 8);
        d.putByte(mtime shr 16);
        d.putByte(mtime shr 24);
        d.putByte(xfl);
        d.putByte(os);
        if extra <> nil then begin
            flags := length(extra);
            d.putByte(flags);
            d.putByte(flags shr 8);
            d.putBytes(extra, 0, flags);
        end;
        if name <> nil then begin
            d.putBytes(name, 0, length(name));
            d.putByte(0);
        end;
        if comment <> nil then begin
            d.putBytes(comment, 0, length(comment));
            d.putByte(0);
        end;
    end;

    procedure GZIPHeader.setName(const name: AnsiString);
    begin
        self.name := encodeISO88591(toUTF16String(name));
    end;

    procedure GZIPHeader.setComment(const comment: AnsiString);
    begin
        self.comment := encodeISO88591(toUTF16String(comment));
    end;

    procedure GZIPHeader.setModifiedTime(mtime: int);
    begin
        self.mtime := mtime;
    end;

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

    procedure GZIPHeader.setOS(os: int);
    begin
        if ((os < 0) or (os > 13)) and (os <> 255) then begin
            raise IllegalArgumentException.create('os: ' + toDecString(os));
        end;
        self.os := os;
    end;

    function GZIPHeader.getName(): AnsiString;
    begin
        if name <> nil then begin
            result := toUTF8String(decodeISO88591(name));
        end else begin
            result := '';
        end;
    end;

    function GZIPHeader.getComment(): AnsiString;
    begin
        if comment <> nil then begin
            result := toUTF8String(decodeISO88591(comment));
        end else begin
            result := '';
        end;
    end;

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

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

    function GZIPHeader.getOS(): int;
    begin
        result := os;
    end;
{%endregion}

{%region Tree }
    class procedure Tree.genCodes(const tree: int_Array1d; maxCode: int; const blCount: int_Array1d);
    var
        nextCode: int_Array1d;
        code: int;
        bits: int;
        len: int;
        buf: int;
        n: int;
    begin
        nextCode := int_Array1d_create(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 n := 0 to maxCode do begin
            len := tree[n * 2 + 1];
            if len = 0 then begin
                continue;
            end;
            buf := nextCode[len];
            tree[n * 2] := short(biReverse(buf, len));
            nextCode[len] := buf + 1;
        end;
    end;

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

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

    procedure Tree.genBitlen(s: Deflate);
    var
        tree: int_Array1d;
        stree: int_Array1d;
        extra: int_Array1d;
        f: int;
        base: int;
        maxLength: int;
        h: int;
        n: int;
        m: int;
        bits: int;
        xbits: int;
        overflow: int;
    begin
        tree := dynTree;
        stree := statDesc.staticTree;
        extra := statDesc.extraBits;
        base := statDesc.extraBase;
        maxLength := statDesc.maxLength;
        overflow := 0;
        for bits := 0 to MAX_BITS do begin
            s.blCount[bits] := 0;
        end;
        tree[s.heap[s.heapMax] * 2 + 1] := 0;
        h := s.heapMax + 1;
        while h < HEAP_SIZE do begin
            n := s.heap[h];
            bits := tree[tree[n * 2 + 1] * 2 + 1] + 1;
            if bits > maxLength then begin
                bits := maxLength;
                inc(overflow);
            end;
            tree[n * 2 + 1] := short(bits);
            if n > maxCode then begin
                inc(h);
                continue;
            end;
            inc(s.blCount[bits]);
            xbits := 0;
            if n >= base then begin
                xbits := extra[n - base];
            end;
            f := tree[n * 2];
            inc(s.optLen, f * (bits + xbits));
            if stree <> nil then begin
                inc(s.staticLen, f * (stree[n * 2 + 1] + xbits));
            end;
            inc(h);
        end;
        if overflow = 0 then begin
            exit;
        end;
        repeat
            bits := maxLength - 1;
            while s.blCount[bits] = 0 do begin
                dec(bits);
            end;
            dec(s.blCount[bits]);
            inc(s.blCount[bits + 1], 2);
            dec(s.blCount[maxLength]);
            dec(overflow, 2);
        until overflow <= 0;
        for bits := maxLength downto 1 do begin
            n := s.blCount[bits];
            while n <> 0 do begin
                dec(h);
                m := s.heap[h];
                if m > maxCode then begin
                    continue;
                end;
                if tree[m * 2 + 1] <> bits then begin
                    inc(s.optLen, int((long(bits) - long(tree[m * 2 + 1])) * long(tree[m * 2])));
                    tree[m * 2 + 1] := short(bits);
                end;
                dec(n);
            end;
        end;
    end;

    procedure Tree.buildTree(s: Deflate);
    var
        tree: int_Array1d;
        stree: int_Array1d;
        elems: int;
        n: int;
        m: int;
        maxCodeLocal: int;
        node: int;
    begin
        tree := dynTree;
        stree := statDesc.staticTree;
        elems := statDesc.elems;
        maxCodeLocal := -1;
        s.heapLen := 0;
        s.heapMax := HEAP_SIZE;
        for n := 0 to elems - 1 do begin
            if tree[n * 2] <> 0 then begin
                inc(s.heapLen);
                maxCodeLocal := n;
                s.heap[s.heapLen] := n;
                s.depth[n] := 0;
            end else begin
                tree[n * 2 + 1] := 0;
            end;
        end;
        while s.heapLen < 2 do begin
            inc(s.heapLen);
            if maxCodeLocal < 2 then begin
                inc(maxCodeLocal);
                node := maxCodeLocal;
            end else begin
                node := 0;
            end;
            s.heap[s.heapLen] := node;
            tree[node * 2] := 1;
            s.depth[node] := 0;
            dec(s.optLen);
            if stree <> nil then begin
                dec(s.staticLen, stree[node * 2 + 1]);
            end;
        end;
        maxCode := maxCodeLocal;
        for n := s.heapLen div 2 downto 1 do begin
            s.pqDownHeap(tree, n);
        end;
        node := elems;
        repeat
            n := s.heap[1];
            s.heap[1] := s.heap[s.heapLen];
            dec(s.heapLen);
            s.pqDownHeap(tree, 1);
            m := s.heap[1];
            dec(s.heapMax);
            s.heap[s.heapMax] := n;
            dec(s.heapMax);
            s.heap[s.heapMax] := m;
            tree[node * 2] := short(tree[n * 2] + tree[m * 2]);
            s.depth[node] := byte(max(s.depth[n], s.depth[m]) + 1);
            tree[n * 2 + 1] := short(node);
            tree[m * 2 + 1] := short(node);
            s.heap[1] := node;
            inc(node);
            s.pqDownHeap(tree, 1);
        until s.heapLen < 2;
        dec(s.heapMax);
        s.heap[s.heapMax] := s.heap[1];
        genBitlen(s);
        genCodes(tree, maxCodeLocal, s.blCount);
    end;
{%endregion}

{%region Deflate }
    class procedure Deflate.clinit();
    begin
        DIST_CODE := toByteArray1d([
            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
        ]);
        BL_ORDER := toByteArray1d([
            16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
            11, 4, 12, 3, 13, 2, 14, 1, 15
        ]);
        LENGTH_CODE := toByteArray1d([
            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
        ]);
        BASE_LENGTH := toIntArray1d([
            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
        ]);
        BASE_DIST := toIntArray1d([
            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
        ]);
        CONFIG_TABLE := toConfigArray1d([
            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)
        ]);
        ERR_MSG := toStringArray1d([
            'need dictionary',
            'stream end',
            '',
            'file error',
            'stream error',
            'data error',
            'insufficient memory',
            'buffer error',
            'incompatible version',
            ''
        ]);
    end;

    class procedure Deflate.cldone();
    var
        c: Config_Array1d;
        i: int;
    begin
        ERR_MSG := nil;
        c := CONFIG_TABLE;
        for i := length(c) - 1 downto 0 do begin
            c[i].free();
            c[i] := nil;
        end;
        c := nil;
        CONFIG_TABLE := nil;
        BASE_DIST := nil;
        BASE_LENGTH := nil;
        LENGTH_CODE := nil;
        BL_ORDER := nil;
        DIST_CODE := nil;
    end;

    class function Deflate.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 Deflate.dcode(dist: int): int;
    begin
        if dist < 256 then begin
            result := DIST_CODE[dist];
        end else begin
            result := DIST_CODE[256 + (dist shr 7)];
        end;
    end;

    constructor Deflate.create(stream: ZStream);
    begin
        inherited create();
        wrap := 1;
        dynLtree := int_Array1d_create(HEAP_SIZE * 2);
        dynDtree := int_Array1d_create((2 * D_CODES + 1) * 2);
        blTree := int_Array1d_create((2 * BL_CODES + 1) * 2);
        blCount := int_Array1d_create(MAX_BITS + 1);
        heap := int_Array1d_create(2 * L_CODES + 1);
        depth := byte_Array1d_create(2 * L_CODES + 1);
        lDesc := Tree.create();
        dDesc := Tree.create();
        blDesc := Tree.create();
        self.stream := stream;
    end;

    destructor Deflate.destroy;
    begin
        gheader.free();
        blDesc.free();
        dDesc.free();
        lDesc.free();
        inherited destroy;
    end;

    procedure Deflate.lmInit();
    var
        i: int;
        c: Config;
    begin
        windowSize := 2 * wSize;
        for i := 0 to hashSize - 1 do begin
            head[i] := 0;
        end;
        c := CONFIG_TABLE[level];
        maxLazyMatch := c.maxLazy;
        goodMatch := c.goodLength;
        niceMatch := c.niceLength;
        maxChainLength := c.maxChain;
        strStart := 0;
        blockStart := 0;
        lookahead := 0;
        matchLength := MIN_MATCH - 1;
        prevLength := MIN_MATCH - 1;
        matchAvailable := 0;
        insh := 0;
    end;

    procedure Deflate.trInit();
    begin
        lDesc.dynTree := dynLtree;
        lDesc.statDesc := StaticTree.STATIC_L_DESC;
        dDesc.dynTree := dynDtree;
        dDesc.statDesc := StaticTree.STATIC_D_DESC;
        blDesc.dynTree := blTree;
        blDesc.statDesc := StaticTree.STATIC_BL_DESC;
        biBuf := 0;
        biValid := 0;
        lastEobLen := 8;
        initBlock();
    end;

    procedure Deflate.initBlock();
    var
        i: int;
    begin
        for i := 0 to L_CODES - 1 do begin
            dynLtree[i * 2] := 0;
        end;
        for i := 0 to D_CODES - 1 do begin
            dynDtree[i * 2] := 0;
        end;
        for i := 0 to BL_CODES - 1 do begin
            blTree[i * 2] := 0;
        end;
        dynLtree[END_BLOCK * 2] := 1;
        optLen := 0;
        staticLen := 0;
        lastLit := 0;
        matches := 0;
    end;

    procedure Deflate.scanTree(const tree: int_Array1d; maxCode: int);
    var
        n: int;
        prevlen: int;
        currlen: int;
        nextlen: int;
        count: 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;
        tree[(maxCode + 1) * 2 + 1] := -1;
        for n := 0 to maxCode do begin
            currlen := nextlen;
            nextlen := tree[(n + 1) * 2 + 1];
            inc(count);
            if (count < maxCount) and (currlen = nextlen) then begin
                continue;
            end else
            if count < minCount then begin
                inc(blTree[currlen * 2], count);
            end else
            if currlen <> 0 then begin
                if currlen <> prevlen then begin
                    inc(blTree[currlen * 2]);
                end;
                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 Deflate.sendAllTrees(lcodes, dcodes, blcodes: int);
    var
        rank: int;
    begin
        sendBits(lcodes - 257, 5);
        sendBits(dcodes - 1, 5);
        sendBits(blcodes - 4, 4);
        for rank := 0 to blcodes - 1 do begin
            sendBits(blTree[BL_ORDER[rank] * 2 + 1], 3);
        end;
        sendTree(dynLtree, lcodes - 1);
        sendTree(dynDtree, dcodes - 1);
    end;

    procedure Deflate.sendTree(const tree: int_Array1d; maxCode: int);
    var
        n: int;
        prevlen: int;
        currlen: int;
        nextlen: int;
        count: 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 n := 0 to maxCode do begin
            currlen := nextlen;
            nextlen := tree[(n + 1) * 2 + 1];
            inc(count);
            if (count < maxCount) and (currlen = nextlen) then begin
                continue;
            end else
            if count < minCount then begin
                repeat
                    sendCode(currlen, blTree);
                    dec(count);
                until count = 0;
            end else
            if currlen <> 0 then begin
                if currlen <> prevlen then begin
                    sendCode(currlen, blTree);
                    dec(count);
                end;
                sendCode(REP_3_6, blTree);
                sendBits(count - 3, 2);
            end else
            if count <= 10 then begin
                sendCode(REPZ_3_10, blTree);
                sendBits(count - 3, 3);
            end else begin
                sendCode(REPZ_11_138, blTree);
                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 Deflate.putShortMSB(b: int);
    begin
        putByte(b shr 8);
        putByte(b);
    end;

    procedure Deflate.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 Deflate.sendBits(val, len: int);
    begin
        biBuf := biBuf or ((val shl biValid) and $ffff);
        if biValid > BUF_SIZE - len then begin
            putShort(biBuf);
            biBuf := short(val shr (BUF_SIZE - biValid));
            inc(biValid, len - BUF_SIZE);
        end else begin
            inc(biValid, len);
        end;
    end;

    procedure Deflate.trAlign();
    begin
        sendBits(STATIC_TREES shl 1, 3);
        sendCode(END_BLOCK, StaticTree.STATIC_LTREE);
        biFlush();
        if lastEobLen - biValid + 11 < 9 then begin
            sendBits(STATIC_TREES shl 1, 3);
            sendCode(END_BLOCK, StaticTree.STATIC_LTREE);
            biFlush();
        end;
        lastEobLen := 7;
    end;

    procedure Deflate.compressBlock(const ltree, dtree: int_Array1d);
    var
        dist: int;
        lc: int;
        lx: int;
        code: int;
        extra: int;
    begin
        lx := 0;
        if lastLit <> 0 then begin
            repeat
                dist := ((int(pendingBuf[dBuf + lx * 2]) shl 8) and $ff00) or (int(pendingBuf[dBuf + lx * 2 + 1]) and $ff);
                lc := int(pendingBuf[lBuf + lx]) and $ff;
                inc(lx);
                if dist = 0 then begin
                    sendCode(lc, ltree);
                end else begin
                    code := LENGTH_CODE[lc];
                    sendCode(code + (LITERALS + 1), ltree);
                    extra := StaticTree.EXTRA_LBITS[code];
                    if extra <> 0 then begin
                        dec(lc, BASE_LENGTH[code]);
                        sendBits(lc, extra);
                    end;
                    dec(dist);
                    code := dcode(dist);
                    sendCode(code, dtree);
                    extra := StaticTree.EXTRA_DBITS[code];
                    if extra <> 0 then begin
                        dec(dist, BASE_DIST[code]);
                        sendBits(dist, extra);
                    end;
                end;
            until lx >= lastLit;
        end;
        sendCode(END_BLOCK, ltree);
        lastEobLen := ltree[END_BLOCK * 2 + 1];
    end;

    procedure Deflate.setDataType();
    var
        n: int;
        asciiFreq: int;
        binFreq: int;
    begin
        n := 0;
        asciiFreq := 0;
        binFreq := 0;
        while n < 7 do begin
            inc(binFreq, dynLtree[n * 2]);
            inc(n);
        end;
        while n < 128 do begin
            inc(asciiFreq, dynLtree[n * 2]);
            inc(n);
        end;
        while n < LITERALS do begin
            inc(binFreq, dynLtree[n * 2]);
            inc(n);
        end;
        if binFreq > (asciiFreq shr 2) then begin
            dataType := Z_BINARY;
        end else begin
            dataType := Z_ASCII;
        end;
    end;

    procedure Deflate.biFlush();
    begin
        if biValid = 16 then begin
            putShort(biBuf);
            biBuf := 0;
            biValid := 0;
        end else
        if biValid >= 8 then begin
            putByte(biBuf);
            biBuf := biBuf shr 8;
            dec(biValid, 8);
        end;
    end;

    procedure Deflate.biWindup();
    begin
        if biValid > 8 then begin
            putShort(biBuf);
        end else
        if biValid > 0 then begin
            putByte(biBuf);
        end;
        biBuf := 0;
        biValid := 0;
    end;

    procedure Deflate.copyBlock(buf, len: int; header: boolean);
    begin
        biWindup();
        lastEobLen := 8;
        if header then begin
            putShort(len);
            putShort(not len);
        end;
        putBytes(window, buf, len);
    end;

    procedure Deflate.flushBlockOnly(eof: boolean);
    begin
        if blockStart >= 0 then begin
            trFlushBlock(blockStart, strStart - blockStart, eof);
        end else begin
            trFlushBlock(-1, strStart - blockStart, eof);
        end;
        blockStart := strStart;
        stream.flushPending();
    end;

    procedure Deflate.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 Deflate.trFlushBlock(buf, storedLen: int; eof: boolean);
    var
        optLenb: int;
        staticLenb: int;
        maxBlindex: int;
    begin
        maxBlindex := 0;
        if level > 0 then begin
            if dataType = Z_UNKNOWN then begin
                setDataType();
            end;
            lDesc.buildTree(self);
            dDesc.buildTree(self);
            maxBlindex := buildBlTree();
            optLenb := (optLen + 10) shr 3;
            staticLenb := (staticLen + 10) shr 3;
            if staticLenb <= optLenb then begin
                optLenb := staticLenb;
            end;
        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.STATIC_LTREE, StaticTree.STATIC_DTREE);
        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(lDesc.maxCode + 1, dDesc.maxCode + 1, maxBlindex + 1);
            compressBlock(dynLtree, dynDtree);
        end;
        initBlock();
        if eof then begin
            biWindup();
        end;
    end;

    procedure Deflate.fillWindow();
    var
        n: int;
        m: int;
        p: int;
        more: int;
    begin
        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
                arraycopy(window, wSize, window, 0, wSize);
                dec(matchStart, wSize);
                dec(strStart, wSize);
                dec(blockStart, wSize);
                n := hashSize;
                p := n;
                repeat
                    dec(p);
                    m := head[p] and $ffff;
                    if m >= wSize then begin
                        head[p] := short(m - wSize);
                    end else begin
                        head[p] := 0;
                    end;
                    dec(n);
                until n = 0;
                n := wSize;
                p := n;
                repeat
                    dec(p);
                    m := prev[p] and $ffff;
                    if m >= wSize then begin
                        prev[p] := short(m - wSize);
                    end else begin
                        prev[p] := 0;
                    end;
                    dec(n);
                until n = 0;
                inc(more, wSize);
            end;
            if stream.availIn = 0 then begin
                exit;
            end;
            n := stream.readBuf(window, strStart + lookahead, more);
            inc(lookahead, n);
            if lookahead >= MIN_MATCH then begin
                insh := int(window[strStart]) and $ff;
                insh := ((insh shl hashShift) xor (int(window[strStart + 1]) and $ff)) and hashMask;
            end;
        until (lookahead >= MIN_LOOKAHEAD) or (stream.availIn = 0);
    end;

    function Deflate.trTally(dist, lc: int): boolean;
    var
        outLength: int;
        inLength: int;
        dcode: int;
    begin
        pendingBuf[dBuf + lastLit * 2] := byte(dist shr 8);
        pendingBuf[dBuf + lastLit * 2 + 1] := byte(dist);
        pendingBuf[lBuf + lastLit] := byte(lc);
        inc(lastLit);
        if dist = 0 then begin
            inc(dynLtree[lc * 2]);
        end else begin
            inc(matches);
            dec(dist);
            inc(dynLtree[(LENGTH_CODE[lc] + (LITERALS + 1)) * 2]);
            inc(dynDtree[self.dcode(dist) * 2]);
        end;
        if ((lastLit and $1fff) = 0) and (level > 2) then begin
            outLength := lastLit * 8;
            inLength := strStart - blockStart;
            for dcode := 0 to D_CODES - 1 do begin
                inc(outLength, int(long(dynDtree[dcode * 2]) * (long(5) + long(StaticTree.EXTRA_DBITS[dcode]))));
            end;
            outLength := outLength shr 3;
            if (matches < lastLit div 2) and (outLength < inLength div 2) then begin
                result := true;
                exit;
            end;
        end;
        result := lastLit = litBufSize - 1;
    end;

    function Deflate.buildBlTree(): int;
    begin
        scanTree(dynLtree, lDesc.maxCode);
        scanTree(dynDtree, dDesc.maxCode);
        blDesc.buildTree(self);
        result := BL_CODES - 1;
        while result >= 3 do begin
            if blTree[BL_ORDER[result] * 2 + 1] <> 0 then begin
                break;
            end;
            dec(result);
        end;
        inc(optLen, 3 * (result + 1) + 14);
    end;

    function Deflate.deflateStored(flush: int): int;
    var
        maxBlockSize: int;
        maxStart: int;
    begin
        maxBlockSize := $ffff;
        if maxBlockSize > pendingBufSize - 5 then begin
            maxBlockSize := pendingBufSize - 5;
        end;
        repeat
            if lookahead <= 1 then begin
                fillWindow();
                if (lookahead = 0) and (flush = NO_FLUSH) then begin
                    result := NEED_MORE;
                    exit;
                end;
                if lookahead = 0 then begin
                    break;
                end;
            end;
            inc(strStart, lookahead);
            lookahead := 0;
            maxStart := blockStart + maxBlockSize;
            if (strStart = 0) or (strStart >= maxStart) then begin
                lookahead := strStart - maxStart;
                strStart := maxStart;
                flushBlockOnly(false);
                if stream.availOut = 0 then begin
                    result := NEED_MORE;
                    exit;
                end;
            end;
            if strStart - blockStart >= wSize - MIN_LOOKAHEAD then begin
                flushBlockOnly(false);
                if stream.availOut = 0 then begin
                    result := NEED_MORE;
                    exit;
                end;
            end;
        until false;
        flushBlockOnly(flush = FINISH);
        if stream.availOut = 0 then begin
            if flush = FINISH then begin
                result := FINISH_STARTED;
            end else begin
                result := NEED_MORE;
            end;
        end else begin
            if flush = FINISH then begin
                result := FINISH_DONE;
            end else begin
                result := BLOCK_DONE;
            end;
        end;
    end;

    function Deflate.deflateFast(flush: int): int;
    var
        hashHead: int;
        bflush: boolean;
    begin
        hashHead := 0;
        repeat
            if lookahead < MIN_LOOKAHEAD then begin
                fillWindow();
                if (lookahead < MIN_LOOKAHEAD) and (flush = NO_FLUSH) then begin
                    result := NEED_MORE;
                    exit;
                end;
                if lookahead = 0 then begin
                    break;
                end;
            end;
            if lookahead >= MIN_MATCH then begin
                insh := ((insh shl hashShift) xor (int(window[strStart + (MIN_MATCH - 1)]) and $ff)) and hashMask;
                hashHead := head[insh] and $ffff;
                prev[strStart and wMask] := head[insh];
                head[insh] := short(strStart);
            end;
            if (hashHead <> 0) and (((strStart - hashHead) and $ffff) <= wSize - MIN_LOOKAHEAD) and (strategy <> HUFFMAN_ONLY) then begin
                matchLength := longestMatch(hashHead);
            end;
            if matchLength >= MIN_MATCH then begin
                bflush := trTally(strStart - matchStart, matchLength - MIN_MATCH);
                dec(lookahead, matchLength);
                if (matchLength <= maxLazyMatch) and (lookahead >= MIN_MATCH) then begin
                    dec(matchLength);
                    repeat
                        inc(strStart);
                        insh := ((insh shl hashShift) xor (int(window[strStart + (MIN_MATCH - 1)]) and $ff)) and hashMask;
                        hashHead := head[insh] and $ffff;
                        prev[strStart and wMask] := head[insh];
                        head[insh] := short(strStart);
                        dec(matchLength);
                    until matchLength = 0;
                    inc(strStart);
                end else begin
                    inc(strStart, matchLength);
                    matchLength := 0;
                    insh := int(window[strStart]) and $ff;
                    insh := ((insh shl hashShift) xor (int(window[strStart + 1]) and $ff)) and hashMask;
                end;
            end else begin
                bflush := trTally(0, int(window[strStart]) and $ff);
                dec(lookahead);
                inc(strStart);
            end;
            if bflush then begin
                flushBlockOnly(false);
                if stream.availOut = 0 then begin
                    result := NEED_MORE;
                    exit;
                end;
            end;
        until false;
        flushBlockOnly(flush = FINISH);
        if stream.availOut = 0 then begin
            if flush = FINISH then begin
                result := FINISH_STARTED;
            end else begin
                result := NEED_MORE;
            end;
        end else begin
            if flush = FINISH then begin
                result := FINISH_DONE;
            end else begin
                result := BLOCK_DONE;
            end;
        end;
    end;

    function Deflate.deflateSlow(flush: int): int;
    var
        maxInsert: int;
        hashHead: int;
        bflush: boolean;
    begin
        hashHead := 0;
        repeat
            if lookahead < MIN_LOOKAHEAD then begin
                fillWindow();
                if (lookahead < MIN_LOOKAHEAD) and (flush = NO_FLUSH) then begin
                    result := NEED_MORE;
                    exit;
                end;
                if lookahead = 0 then begin
                    break;
                end;
            end;
            if lookahead >= MIN_MATCH then begin
                insh := ((insh shl hashShift) xor (int(window[strStart + (MIN_MATCH - 1)]) and $ff)) and hashMask;
                hashHead := head[insh] and $ffff;
                prev[strStart and wMask] := head[insh];
                head[insh] := short(strStart);
            end;
            prevLength := matchLength;
            prevMatch := matchStart;
            matchLength := MIN_MATCH - 1;
            if (hashHead <> 0) and (prevLength < maxLazyMatch) and (((strStart - hashHead) and $ffff) <= wSize - MIN_LOOKAHEAD) then begin
                if strategy <> HUFFMAN_ONLY then begin
                    matchLength := longestMatch(hashHead);
                end;
                if (matchLength <= 5) and ((strategy = FILTERED) or ((matchLength = MIN_MATCH) and (strStart - matchStart > $1000))) then begin
                    matchLength := MIN_MATCH - 1;
                end;
            end;
            if (prevLength >= MIN_MATCH) and (matchLength <= prevLength) then begin
                maxInsert := strStart + lookahead - MIN_MATCH;
                bflush := trTally(strStart - prevMatch - 1, prevLength - MIN_MATCH);
                dec(lookahead, prevLength - 1);
                dec(prevLength, 2);
                repeat
                    inc(strStart);
                    if strStart <= maxInsert then begin
                        insh := ((insh shl hashShift) xor (int(window[strStart + (MIN_MATCH - 1)]) and $ff)) and hashMask;
                        hashHead := head[insh] and $ffff;
                        prev[strStart and wMask] := head[insh];
                        head[insh] := short(strStart);
                    end;
                    dec(prevLength);
                until prevLength = 0;
                matchAvailable := 0;
                matchLength := MIN_MATCH - 1;
                inc(strStart);
                if bflush then begin
                    flushBlockOnly(false);
                    if stream.availOut = 0 then begin
                        result := NEED_MORE;
                        exit;
                    end;
                end;
            end else
            if matchAvailable <> 0 then begin
                bflush := trTally(0, int(window[strStart - 1]) and $ff);
                if bflush then begin
                    flushBlockOnly(false);
                end;
                inc(strStart);
                dec(lookahead);
                if stream.availOut = 0 then begin
                    result := NEED_MORE;
                    exit;
                end;
            end else begin
                matchAvailable := 1;
                inc(strStart);
                dec(lookahead);
            end;
        until false;
        if matchAvailable <> 0 then begin
            trTally(0, int(window[strStart - 1]) and $ff);
            matchAvailable := 0;
        end;
        flushBlockOnly(flush = FINISH);
        if stream.availOut = 0 then begin
            if flush = FINISH then begin
                result := FINISH_STARTED;
            end else begin
                result := NEED_MORE;
            end;
        end else begin
            if flush = FINISH then begin
                result := FINISH_DONE;
            end else begin
                result := BLOCK_DONE;
            end;
        end;
    end;

    function Deflate.deflateReset(): int;
    begin
        stream.totalIn := 0;
        stream.totalOut := 0;
        stream.msg := '';
        stream.dataType := Z_UNKNOWN;
        pending := 0;
        pendingOut := 0;
        if wrap < 0 then begin
            wrap := -wrap;
        end;
        if wrap = 0 then begin
            status := BUSY_STATE;
        end else begin
            status := INIT_STATE;
        end;
        stream.adler.reset();
        lastFlush := NO_FLUSH;
        trInit();
        lmInit();
        result := OK;
    end;

    function Deflate.deflateInit(level, method, windowBits, memLevel, strategy: int): int;
    var
        wraplocal: int;
    begin
        wraplocal := 1;
        stream.msg := '';
        if level = DEFAULT_COMPRESSION then begin
            level := 6;
        end;
        if windowBits < 0 then begin
            wraplocal := 0;
            windowBits := -windowBits;
        end else
        if windowBits > 15 then begin
            wraplocal := 2;
            dec(windowBits, 16);
            stream.adler := Crc32.create();
        end;
        if (memLevel < 1) or (memLevel > MAX_MEM_LEVEL) or (method <> Z_DEFLATED) or (windowBits < 9) or (windowBits > 15) or (level < 0) or (level > 9) or (strategy < 0) or (strategy > HUFFMAN_ONLY) then begin
            result := STREAM_ERROR;
            exit;
        end;
        wrap := wraplocal;
        wBits := windowBits;
        wSize := 1 shl wBits;
        wMask := wSize - 1;
        hashBits := memLevel + 7;
        hashSize := 1 shl hashBits;
        hashMask := hashSize - 1;
        hashShift := (hashBits + (MIN_MATCH - 1)) div MIN_MATCH;
        window := nil;
        prev := nil;
        head := nil;
        window := byte_Array1d_create(wSize * 2);
        prev := int_Array1d_create(wSize);
        head := int_Array1d_create(hashSize);
        litBufSize := 1 shl (memLevel + 6);
        pendingBuf := nil;
        pendingBuf := byte_Array1d_create(litBufSize * 4);
        pendingBufSize := litBufSize * 4;
        dBuf := litBufSize div 2;
        lBuf := litBufSize * 3;
        self.level := level;
        self.strategy := strategy;
        result := deflateReset();
    end;

    function Deflate.longestMatch(curMatch: int): int;
    var
        chainLength: int;
        scan: int;
        match: int;
        len: int;
        bestLen: int;
        limit: int;
        niceMatchLocal: int;
        wmask: int;
        strend: int;
        scanEnd: byte;
        scanEnd1: byte;
    begin
        chainLength := maxChainLength;
        scan := strStart;
        bestLen := prevLength;
        if strStart > wSize - MIN_LOOKAHEAD then begin
            limit := strStart - (wSize - MIN_LOOKAHEAD);
        end else begin
            limit := 0;
        end;
        niceMatchLocal := niceMatch;
        wmask := self.wMask;
        strend := strStart + MAX_MATCH;
        scanEnd1 := window[scan + bestLen - 1];
        scanEnd := window[scan + bestLen];
        if prevLength >= goodMatch then begin
            chainLength := sar(chainLength, 2);
        end;
        if niceMatchLocal > lookahead then begin
            niceMatchLocal := lookahead;
        end;
        repeat
            match := curMatch;
            if (window[match + bestLen] = scanEnd) and (window[match + bestLen - 1] = scanEnd1) and (window[match] = window[scan]) and (window[predinc(match)] = window[scan + 1]) then begin
                inc(scan, 2);
                inc(match);
                repeat
                until
                    (window[predinc(scan)] <> window[predinc(match)]) or (window[predinc(scan)] <> window[predinc(match)]) or (window[predinc(scan)] <> window[predinc(match)]) or
                    (window[predinc(scan)] <> window[predinc(match)]) or (window[predinc(scan)] <> window[predinc(match)]) or (window[predinc(scan)] <> window[predinc(match)]) or
                    (window[predinc(scan)] <> window[predinc(match)]) or (window[predinc(scan)] <> window[predinc(match)]) or (scan >= strend)
                ;
                len := MAX_MATCH - (strend - scan);
                scan := strend - MAX_MATCH;
                if len > bestLen then begin
                    matchStart := curMatch;
                    bestLen := len;
                    if len >= niceMatchLocal then begin
                        break;
                    end;
                    scanEnd1 := window[scan + bestLen - 1];
                    scanEnd := window[scan + bestLen];
                end;
            end;
            curMatch := prev[curMatch and wmask] and $ffff;
            if curMatch > limit then begin
                dec(chainLength);
            end else begin
                break;
            end;
        until chainLength = 0;
        result := min(bestLen, lookahead);
    end;

    function Deflate.getGZIPHeader(): GZIPHeader;
    begin
        result := gheader;
        if result = nil then begin
            result := GZIPHeader.create();
            gheader := result;
        end;
    end;

    procedure Deflate.pqDownHeap(const tree: int_Array1d; k: int);
    var
        v: int;
        j: int;
    begin
        v := heap[k];
        j := k shl 1;
        while j <= heapLen do begin
            if (j < heapLen) and smaller(tree, heap[j + 1], heap[j], depth) then begin
                inc(j);
            end;
            if smaller(tree, v, heap[j], depth) then begin
                break;
            end;
            heap[k] := heap[j];
            k := j;
            j := j shl 1;
        end;
        heap[k] := v;
    end;

    procedure Deflate.putBytes(const data: byte_Array1d; offset, length: int);
    var
        lim: int;
        len: int;
    begin
        lim := offset + length;
        len := System.length(data);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('Deflate.putBytes: индекс элемента массива выходит из диапазона.');
        end;
        arraycopy(data, offset, pendingBuf, pending, length);
        inc(pending, length);
    end;

    procedure Deflate.putByte(c: int);
    begin
        pendingBuf[pending] := byte(c);
        inc(pending);
    end;

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

    function Deflate.deflateInit(level, bits, memlevel: int): int;
    begin
        result := deflateInit(level, Z_DEFLATED, bits, memlevel, DEFAULT_STRATEGY);
    end;

    function Deflate.deflateInit(level, bits: int): int;
    begin
        result := deflateInit(level, Z_DEFLATED, bits, DEF_MEM_LEVEL, DEFAULT_STRATEGY);
    end;

    function Deflate.deflateInit(level: int): int;
    begin
        result := deflateInit(level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, DEFAULT_STRATEGY);
    end;

    function Deflate.deflateEnd(): int;
    begin
        if (status <> INIT_STATE) and (status <> BUSY_STATE) and (status <> FINISH_STATE) then begin
            result := STREAM_ERROR;
            exit;
        end;
        pendingBuf := nil;
        head := nil;
        prev := nil;
        window := nil;
        if status = BUSY_STATE then begin
            result := DATA_ERROR;
        end else begin
            result := OK;
        end;
    end;

    function Deflate.deflateParams(lvl, strtg: int): int;
    var
        c: Config;
    begin
        result := OK;
        if lvl = DEFAULT_COMPRESSION then begin
            lvl := 6;
        end;
        if (lvl < 0) or (lvl > 9) or (strtg < 0) or (strtg > HUFFMAN_ONLY) then begin
            result := STREAM_ERROR;
            exit;
        end;
        if (CONFIG_TABLE[level].func <> CONFIG_TABLE[lvl].func) and (stream.totalIn <> 0) then begin
            result := stream.deflate(PARTIAL_FLUSH);
        end;
        if level <> lvl then begin
            c := CONFIG_TABLE[level];
            level := lvl;
            maxLazyMatch := c.maxLazy;
            goodMatch := c.goodLength;
            niceMatch := c.niceLength;
            maxChainLength := c.maxChain;
        end;
        strategy := strtg;
    end;

    function Deflate.deflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
    var
        len: int;
        index: int;
        n: int;
    begin
        if (dictionary = nil) or (status <> INIT_STATE) then begin
            result := STREAM_ERROR;
            exit;
        end;
        len := dictLength;
        index := 0;
        stream.adler.update(dictionary, 0, dictLength);
        if len < MIN_MATCH then begin
            result := OK;
            exit;
        end;
        if len > wSize - MIN_LOOKAHEAD then begin
            len := wSize - MIN_LOOKAHEAD;
            index := dictLength - len;
        end;
        arraycopy(dictionary, index, window, 0, len);
        strStart := len;
        blockStart := len;
        insh := int(window[0]) and $ff;
        insh := ((insh shl hashShift) xor (int(window[1]) and $ff)) and hashMask;
        for n := 0 to len - MIN_MATCH do begin
            insh := ((insh shl hashShift) xor (int(window[n + (MIN_MATCH - 1)]) and $ff)) and hashMask;
            prev[n and wMask] := head[insh];
            head[insh] := short(n);
        end;
        result := OK;
    end;

    function Deflate.deflate(flush: int): int;
    var
        oldFlush: int;
        header: int;
        levelFlags: int;
        bstate: int;
        adler: int;
        i: int;
    begin
        if (flush > FINISH) or (flush < 0) then begin
            result := STREAM_ERROR;
            exit;
        end;
        if (stream.nextOut = nil) or ((stream.nextIn = nil) and (stream.availIn <> 0)) or ((status = FINISH_STATE) and (flush <> FINISH)) then begin
            stream.msg := ERR_MSG[NEED_DICT - STREAM_ERROR];
            result := STREAM_ERROR;
            exit;
        end;
        if stream.availOut = 0 then begin
            stream.msg := ERR_MSG[NEED_DICT - BUF_ERROR];
            result := BUF_ERROR;
            exit;
        end;
        oldFlush := lastFlush;
        lastFlush := flush;
        if status = INIT_STATE then begin
            if wrap = 2 then begin
                getGZIPHeader().put(self);
                status := BUSY_STATE;
                stream.adler.reset();
            end else begin
                header := (Z_DEFLATED + ((wBits - 8) shl 4)) shl 8;
                levelFlags := ((level - 1) and $ff) shr 1;
                if levelFlags > 3 then begin
                    levelFlags := 3;
                end;
                header := header or (levelFlags shl 6);
                if strStart <> 0 then begin
                    header := header or PRESET_DICT;
                end;
                inc(header, 31 - (header mod 31));
                status := BUSY_STATE;
                putShortMSB(header);
                if strStart <> 0 then begin
                    adler := stream.adler.getValue();
                    putShortMSB(adler shr 16);
                    putShortMSB(adler);
                end;
                stream.adler.reset();
            end;
        end;
        if pending <> 0 then begin
            stream.flushPending();
            if stream.availOut = 0 then begin
                lastFlush := -1;
                result := OK;
                exit;
            end;
        end else
        if (stream.availIn = 0) and (flush <= oldFlush) and (flush <> FINISH) then begin
            stream.msg := ERR_MSG[NEED_DICT - BUF_ERROR];
            result := BUF_ERROR;
            exit;
        end;
        if (status = FINISH_STATE) and (stream.availIn <> 0) then begin
            stream.msg := ERR_MSG[NEED_DICT - BUF_ERROR];
            result := BUF_ERROR;
            exit;
        end;
        if (stream.availIn <> 0) or (lookahead <> 0) or ((flush <> NO_FLUSH) and (status <> FINISH_STATE)) then begin
            bstate := -1;
            case CONFIG_TABLE[level].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
                status := FINISH_STATE;
            end;
            if (bstate = NEED_MORE) or (bstate = FINISH_STARTED) then begin
                if stream.availOut = 0 then begin
                    lastFlush := -1;
                end;
                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
                        for i := 0 to hashSize - 1 do begin
                            head[i] := 0;
                        end;
                    end;
                end;
                stream.flushPending();
                if stream.availOut = 0 then begin
                    lastFlush := -1;
                    result := OK;
                    exit;
                end;
            end;
        end;
        if flush <> FINISH then begin
            result := OK;
            exit;
        end;
        if wrap <= 0 then begin
            result := STREAM_END;
            exit;
        end;
        adler := stream.adler.getValue();
        if wrap = 2 then begin
            putByte(adler);
            putByte(adler shr 8);
            putByte(adler shr 16);
            putByte(adler shr 24);
            putByte(int(stream.totalIn));
            putByte(int(stream.totalIn) shr 8);
            putByte(int(stream.totalIn) shr 16);
            putByte(int(stream.totalIn) shr 24);
            getGZIPHeader().setCRC(adler);
        end else begin
            putShortMSB(adler shr 16);
            putShortMSB(adler);
        end;
        stream.flushPending();
        if wrap > 0 then begin
            wrap := -wrap;
        end;
        if pending <> 0 then begin
            result := OK;
        end else begin
            result := STREAM_END;
        end;
    end;
{%endregion}

{%region Inflate }
    class procedure Inflate.clinit();
    begin
        MARK := toByteArray1d([0, 0, -1, -1]);
    end;

    class procedure Inflate.cldone();
    begin
        MARK := nil;
    end;

    constructor Inflate.create(stream: ZStream);
    begin
        inherited create();
        needBytes := -1;
        was := -1;
        crcbuf := byte_Array1d_create(4);
        self.stream := stream;
    end;

    destructor Inflate.destroy;
    begin
        tmpString.free();
        blocks.free();
        gheader.free();
        inherited destroy;
    end;

    procedure Inflate.checksum(n, v: int);
    var
        i: int;
    begin
        for i := 0 to n - 1 do begin
            crcbuf[i] := byte(v);
            v := v shr 8;
        end;
        stream.adler.update(crcbuf, 0, n);
    end;

    function Inflate.inflateReset(): int;
    begin
        if stream = nil then begin
            result := STREAM_ERROR;
            exit;
        end;
        stream.totalIn := 0;
        stream.totalOut := 0;
        stream.msg := '';
        mode := HEAD;
        needBytes := -1;
        blocks.reset(stream);
        result := OK;
    end;

    function Inflate.readBytes(z: ZStream; n, r, f: int): int;
    begin
        readReturn := false;
        if needBytes = -1 then begin
            needBytes := n;
            need := 0;
        end;
        while needBytes > 0 do begin
            if z.availIn = 0 then begin
                readReturn := true;
                result := r;
                exit;
            end;
            r := f;
            dec(z.availIn);
            inc(z.totalIn);
            need := need or ((int(z.nextIn[z.nextInIndex]) and $ff) shl int((n - needBytes) * 8));
            inc(z.nextInIndex);
            dec(needBytes);
        end;
        if n = 2 then begin
            need := need and $ffff;
        end;
        needBytes := -1;
        result := r;
    end;

    function Inflate.readBytes(z: ZStream; r, f: int): int;
    begin
        readReturn := false;
        if tmpString = nil then begin
            tmpString := ByteArrayOutputStream.create();
        end;
        while need <> 0 do begin
            if z.availIn = 0 then begin
                readReturn := true;
                result := r;
                exit;
            end;
            r := f;
            dec(z.availIn);
            inc(z.totalIn);
            tmpString.write(z.nextIn, z.nextInIndex, 1);
            z.adler.update(z.nextIn, z.nextInIndex, 1);
            inc(z.nextInIndex);
            dec(need);
        end;
        result := r;
    end;

    function Inflate.readString(z: ZStream; r, f: int): int;
    var
        b: int;
    begin
        readReturn := false;
        if tmpString = nil then begin
            tmpString := ByteArrayOutputStream.create();
        end;
        b := 0;
        repeat
            if z.availIn = 0 then begin
                readReturn := true;
                result := r;
                exit;
            end;
            r := f;
            dec(z.availIn);
            inc(z.totalIn);
            b := z.nextIn[z.nextInIndex];
            if b <> 0 then begin
                tmpString.write(b);
            end;
            z.adler.update(z.nextIn, z.nextInIndex, 1);
            inc(z.nextInIndex);
        until b = 0;
        result := r;
    end;

    function Inflate.inflateEnd(): int;
    begin
        if blocks <> nil then begin
            blocks.free(stream);
        end;
        result := OK;
    end;

    function Inflate.inflateInit(w: int): int;
    begin
        stream.msg := '';
        blocks.free();
        blocks := nil;
        wrap := 0;
        if w < 0 then begin
            w := -w;
        end else begin
            wrap := sar(w, 4) + 1;
            if w < 48 then begin
                w := w and $f;
            end;
        end;
        if (w < 8) or (w > 15) then begin
            inflateEnd();
            result := STREAM_ERROR;
            exit;
        end;
        if (blocks <> nil) and (wbits <> w) then begin
            blocks.free(stream);
            blocks.free();
            blocks := nil;
        end;
        wbits := w;
        blocks := InfBlocks.create(stream, 1 shl w);
        inflateReset();
        result := OK;
    end;

    function Inflate.inflate(f: int): int;
    var
        r: int;
        b: int;
        abyte: byte_Array1d;
    begin
        if (stream = nil) or (stream.nextIn = nil) then begin
            if (f = FINISH) and (mode = HEAD) then begin
                result := OK;
            end else begin
                result := STREAM_ERROR;
            end;
            exit;
        end;
        if f = FINISH then begin
            f := BUF_ERROR;
        end else begin
            f := OK;
        end;
        r := BUF_ERROR;
        repeat
            case mode of
            HEAD: begin
                if wrap = 0 then begin
                    mode := BLOCKS_;
                    continue;
                end;
                r := readBytes(stream, 2, r, f);
                if readReturn then begin
                    result := r;
                    exit;
                end;
                if ((wrap and $02) <> 0) and (need = $8b1f) then begin
                    stream.adler := Crc32.create();
                    checksum(2, need);
                    if gheader = nil then begin
                        gheader := GZIPHeader.create();
                    end;
                    mode := FLAGS_;
                    continue;
                end;
                flags := 0;
                method := need and $ff;
                b := (need shr 8) and $ff;
                if ((wrap and $01) = 0) or ((((method shl 8) + b) mod 31) <> 0) then begin
                    mode := BAD;
                    stream.msg := 'incorrect header check';
                    continue;
                end;
                if (method and $0f) <> Z_DEFLATED then begin
                    mode := BAD;
                    stream.msg := 'unknown compression method';
                    continue;
                end;
                if sar(method, 4) + 8 > wbits then begin
                    mode := BAD;
                    stream.msg := 'invalid window size';
                    continue;
                end;
                stream.adler := Adler32.create();
                if (b and PRESET_DICT) = 0 then begin
                    mode := BLOCKS_;
                    continue;
                end;
                mode := DICT4;
            end;
            DICT4: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := (int(stream.nextIn[stream.nextInIndex]) and $ff) shl 24;
                inc(stream.nextInIndex);
                mode := DICT3;
            end;
            DICT3: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := need or ((int(stream.nextIn[stream.nextInIndex]) and $ff) shl 16);
                inc(stream.nextInIndex);
                mode := DICT2;
            end;
            DICT2: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := need or ((int(stream.nextIn[stream.nextInIndex]) and $ff) shl 8);
                inc(stream.nextInIndex);
                mode := DICT1;
            end;
            DICT1: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := need or (int(stream.nextIn[stream.nextInIndex]) and $ff);
                inc(stream.nextInIndex);
                stream.adler.reset(need);
                mode := DICT0;
                result := NEED_DICT;
                exit;
            end;
            DICT0: begin
                mode := BAD;
                stream.msg := 'need dictionary';
                marker := 0;
                result := STREAM_ERROR;
                exit;
            end;
            BLOCKS_: begin
                r := blocks.proc(stream, r);
                if r = DATA_ERROR then begin
                    mode := BAD;
                    marker := 0;
                    continue;
                end;
                if r = OK then begin
                    r := f;
                end;
                if r <> STREAM_END then begin
                    result := r;
                    exit;
                end;
                r := f;
                was := stream.adler.getValue();
                blocks.reset(stream);
                if wrap = 0 then begin
                    mode := DONE;
                    continue;
                end;
                mode := CHECK4;
            end;
            CHECK4: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := (int(stream.nextIn[stream.nextInIndex]) and $ff) shl 24;
                inc(stream.nextInIndex);
                mode := CHECK3;
            end;
            CHECK3: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := need or ((int(stream.nextIn[stream.nextInIndex]) and $ff) shl 16);
                inc(stream.nextInIndex);
                mode := CHECK2;
            end;
            CHECK2: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := need or ((int(stream.nextIn[stream.nextInIndex]) and $ff) shl 8);
                inc(stream.nextInIndex);
                mode := CHECK1;
            end;
            CHECK1: begin
                if stream.availIn = 0 then begin
                    result := r;
                    exit;
                end;
                r := f;
                dec(stream.availIn);
                inc(stream.totalIn);
                need := need or (int(stream.nextIn[stream.nextInIndex]) and $ff);
                inc(stream.nextInIndex);
                if flags <> 0 then begin
                    need := byteSwap(need);
                end;
                if was <> need then begin
                    stream.msg := 'incorrect data check';
                end else
                if (flags <> 0) and (gheader <> nil) then begin
                    gheader.crc := need;
                end;
                mode := LENGTH_;
            end;
            LENGTH_: begin
                if (wrap <> 0) and (flags <> 0) then begin
                    r := readBytes(stream, 4, r, f);
                    if readReturn then begin
                        result := r;
                        exit;
                    end;
                    if stream.msg = 'incorrect data check' then begin
                        mode := BAD;
                        marker := 5;
                        continue;
                    end;
                    if need <> int(stream.totalOut) then begin
                        stream.msg := 'incorrect length check';
                        mode := BAD;
                        continue;
                    end;
                    stream.msg := '';
                end else begin
                    if stream.msg = 'incorrect data check' then begin
                        mode := BAD;
                        marker := 5;
                        continue;
                    end;
                end;
                mode := DONE;
            end;
            DONE: begin
                result := STREAM_END;
                exit;
            end;
            BAD: begin
                result := DATA_ERROR;
                exit;
            end;
            FLAGS_: begin
                r := readBytes(stream, 2, r, f);
                if readReturn then begin
                    result := r;
                    exit;
                end;
                flags := need and $ffff;
                if (flags and $ff) <> Z_DEFLATED then begin
                    stream.msg := 'unknown compression method';
                    mode := BAD;
                    continue;
                end;
                if (flags and $e000) <> 0 then begin
                    stream.msg := 'unknown header flags set';
                    mode := BAD;
                    continue;
                end;
                if (flags and $0200) <> 0 then begin
                    checksum(2, need);
                end;
                mode := TIME;
            end;
            TIME: begin
                r := readBytes(stream, 4, r, f);
                if readReturn then begin
                    result := r;
                    exit;
                end;
                if gheader <> nil then begin
                    gheader.time := need;
                end;
                if (flags and $0200) <> 0 then begin
                    checksum(4, need);
                end;
                mode := OS;
            end;
            OS: begin
                r := readBytes(stream, 2, r, f);
                if readReturn then begin
                    result := r;
                    exit;
                end;
                if gheader <> nil then begin
                    gheader.xflags := need and $ff;
                    gheader.os := (need shr 8) and $ff;
                end;
                if (flags and $0200) <> 0 then begin
                    checksum(2, need);
                end;
                mode := EXLEN;
            end;
            EXLEN: begin
                if (flags and $0400) <> 0 then begin
                    r := readBytes(stream, 2, r, f);
                    if readReturn then begin
                        result := r;
                        exit;
                    end;
                    if gheader <> nil then begin
                        gheader.extra := byte_Array1d_create(need and $ffff);
                    end;
                    if (flags and $0200) <> 0 then begin
                        checksum(2, need);
                    end;
                end else
                if gheader <> nil then begin
                    gheader.extra := nil;
                end;
                mode := EXTRA;
            end;
            EXTRA: begin
                if (flags and $0400) <> 0 then begin
                    r := readBytes(stream, r, f);
                    if readReturn then begin
                        result := r;
                        exit;
                    end;
                    if gheader <> nil then begin
                        abyte := tmpString.toByteArray();
                        tmpString.free();
                        tmpString := nil;
                        if length(abyte) = length(gheader.extra) then begin
                            arraycopy(abyte, 0, gheader.extra, 0, length(abyte));
                        end else begin
                            stream.msg := 'bad extra field length';
                            mode := BAD;
                            continue;
                        end;
                    end;
                end else
                if gheader <> nil then begin
                    gheader.extra := nil;
                end;
                mode := NAME;
            end;
            NAME: begin
                if (flags and $0800) <> 0 then begin
                    r := readString(stream, r, f);
                    if readReturn then begin
                        result := r;
                        exit;
                    end;
                    if gheader <> nil then begin
                        gheader.name := tmpString.toByteArray();
                    end;
                    tmpString.free();
                    tmpString := nil;
                end else
                if gheader <> nil then begin
                    gheader.name := nil;
                end;
                mode := COMMENT;
            end;
            COMMENT: begin
                if (flags and $1000) <> 0 then begin
                    r := readString(stream, r, f);
                    if readReturn then begin
                        result := r;
                        exit;
                    end;
                    if gheader <> nil then begin
                        gheader.comment := tmpString.toByteArray();
                    end;
                    tmpString.free();
                    tmpString := nil;
                end else
                if gheader <> nil then begin
                    gheader.comment := nil;
                end;
                mode := HCRC;
            end;
            HCRC: begin
                if (flags and $0200) <> 0 then begin
                    r := readBytes(stream, 2, r, f);
                    if readReturn then begin
                        result := r;
                        exit;
                    end;
                    if gheader <> nil then begin
                        gheader.hcrc := need and $ffff;
                    end;
                    if need <> (stream.adler.getValue() and $ffff) then begin
                        mode := BAD;
                        stream.msg := 'header crc mismatch';
                        marker := 5;
                        continue;
                    end;
                end;
                stream.adler := Crc32.create();
                mode := BLOCKS_;
            end;
            else
                result := STREAM_ERROR;
                exit;
            end;
        until false;
    end;

    function Inflate.inflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
    var
        index: int;
        len: int;
        adlerNeed: int;
        adler: Checksum32;
    begin
        if (stream = nil) or ((mode <> DICT0) and (wrap <> 0)) then begin
            result := STREAM_ERROR;
            exit;
        end;
        index := 0;
        len := dictLength;
        adler := stream.adler;
        if mode = DICT0 then begin
            adlerNeed := adler.getValue();
            adler.reset();
            adler.update(dictionary, 0, dictLength);
            if adler.getValue() <> adlerNeed then begin
                result := DATA_ERROR;
                exit;
            end;
        end;
        adler.reset();
        if len >= 1 shl wbits then begin
            len := (1 shl wbits) - 1;
            index := dictLength - len;
        end;
        blocks.setDictionary(dictionary, index, len);
        mode := BLOCKS_;
        result := OK;
    end;

    function Inflate.inflateSync(): int;
    var
        n: int;
        p: int;
        m: int;
        r: long;
        w: long;
    begin
        if stream = nil then begin
            result := STREAM_ERROR;
            exit;
        end;
        if mode <> BAD then begin
            mode := BAD;
            marker := 0;
        end;
        n := stream.availIn;
        if n = 0 then begin
            result := BUF_ERROR;
            exit;
        end;
        p := stream.nextInIndex;
        m := marker;
        while (n <> 0) and (m < 4) do begin
            if stream.nextIn[p] = MARK[m] then begin
                inc(m);
            end else
            if stream.nextIn[p] <> 0 then begin
                m := 0;
            end else begin
                m := 4 - m;
            end;
            inc(p);
            dec(n);
        end;
        inc(stream.totalIn, long(p) - long(stream.nextInIndex));
        stream.nextInIndex := p;
        stream.availIn := n;
        marker := m;
        if m <> 4 then begin
            result := DATA_ERROR;
            exit;
        end;
        r := stream.totalIn;
        w := stream.totalOut;
        inflateReset();
        stream.totalIn := r;
        stream.totalOut := w;
        mode := BLOCKS_;
        result := OK;
    end;

    function Inflate.inflateSyncPoint(): int;
    begin
        if (stream = nil) or (blocks = nil) then begin
            result := STREAM_ERROR;
        end else
        if blocks.syncPoint() then begin
            result := 1;
        end else begin
            result := 0;
        end;
    end;
{%endregion}

{%region InfTree }
    class procedure InfTree.clinit();
    begin
        FIXED_TL := toIntArray1d([
            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
        ]);
        FIXED_TD := toIntArray1d([
            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 := toIntArray1d([
            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 := toIntArray1d([
            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 := toIntArray1d([
            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 := toIntArray1d([
            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.cldone();
    begin
        CPDEXT := nil;
        CPDIST := nil;
        CPLEXT := nil;
        CPLENS := nil;
        FIXED_TD := nil;
        FIXED_TL := 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] := FIXED_TL;
        td[0] := FIXED_TD;
    end;

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

    procedure InfTree.initWorkArea(vsize: int);
    var
        i: int;
    begin
        if hn = nil then begin
            hn := int_Array1d_create(1);
            v := int_Array1d_create(vsize);
            c := int_Array1d_create(BMAX + 1);
            r := int_Array1d_create(3);
            u := int_Array1d_create(BMAX);
            x := int_Array1d_create(BMAX + 1);
        end;
        if length(v) < vsize then begin
            v := int_Array1d_create(vsize);
        end;
        for i := 0 to length(v) - 1 do begin
            v[i] := 0;
        end;
        for i := 0 to length(c) - 1 do begin
            c[i] := 0;
        end;
        for i := 0 to length(r) - 1 do begin
            r[i] := 0;
        end;
        for i := 0 to length(u) - 1 do begin
            u[i] := 0;
        end;
        for i := 0 to length(x) - 1 do begin
            x[i] := 0;
        end;
    end;

    function InfTree.huftBuild(const b: 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;
        xp: int;
        y: int;
        z: int;
        mask: int;
    begin
        p := 0;
        i := n;
        repeat
            inc(c[b[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 do begin
            if c[j] <> 0 then begin
                break;
            end;
            inc(j);
        end;
        k := j;
        if l < j then begin
            l := j;
        end;
        i := BMAX;
        while i > 0 do begin
            if c[i] <> 0 then begin
                break;
            end;
            dec(i);
        end;
        g := i;
        if l > i then begin
            l := i;
        end;
        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[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 := b[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[0] := 0;
        q := 0;
        z := 0;
        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 begin
                        z := l;
                    end;
                    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 begin
                                    break;
                                end;
                                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;
                        arraycopy(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
                    arraycopy(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;
        end else begin
            result := OK;
        end;
    end;

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

    function InfTree.inflateTreesDynamic(nl, nd: int; const c, bl, bd, tl, td, hp: int_Array1d; z: ZStream): int;
    begin
        initWorkArea(288);
        hn[0] := 0;
        result := huftBuild(c, 0, nl, 257, CPLENS, CPLEXT, tl, bl, hp, hn, v);
        if (result <> OK) or (bl[0] = 0) then begin
            if result = DATA_ERROR then begin
                z.msg := 'oversubscribed literal/length tree';
            end else
            if result <> MEM_ERROR then begin
                z.msg := 'incomplete literal/length tree';
                result := DATA_ERROR;
            end;
            exit;
        end;
        initWorkArea(288);
        result := huftBuild(c, nl, nd, 0, CPDIST, CPDEXT, td, bd, hp, hn, v);
        if (result <> OK) or ((bd[0] = 0) and (nl > 257)) then begin
            case result of
            DATA_ERROR: begin
                z.msg := 'oversubscribed distance tree';
            end;
            BUF_ERROR: begin
                z.msg := 'incomplete distance tree';
                result := DATA_ERROR;
            end;
            else
                if result <> MEM_ERROR then begin
                    z.msg := 'empty distance tree with lengths';
                    result := DATA_ERROR;
                end;
            end;
            exit;
        end;
        result := OK;
    end;
{%endregion}

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

    class procedure InfCodes.cldone();
    begin
        INFLATE_MASK := nil;
    end;

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

    function InfCodes.inflateFast(bl, bd: int; const tl: int_Array1d; tlIndex: int; const td: int_Array1d; tdIndex: int; s: InfBlocks; z: ZStream): int;
    var
        tp: int_Array1d;
        t: int;
        e: int;
        b: int;
        k: int;
        p: int;
        n: int;
        q: int;
        m: int;
        ml: int;
        md: int;
        c: int;
        d: int;
        r: int;
        tpIndex: int;
        tpIndexT3: int;
    begin
        p := z.nextInIndex;
        n := z.availIn;
        b := s.bitb;
        k := s.bitk;
        q := s.write;
        if q < s.read then begin
            m := s.read - q - 1;
        end else begin
            m := s.end_ - q;
        end;
        ml := INFLATE_MASK[bl];
        md := INFLATE_MASK[bd];
        repeat
            while k < 20 do begin
                dec(n);
                b := b or ((int(z.nextIn[p]) and $ff) shl k);
                inc(p);
                inc(k, 8);
            end;
            t := b and ml;
            tp := tl;
            tpIndex := tlIndex;
            tpIndexT3 := (tpIndex + t) * 3;
            e := tp[tpIndexT3];
            if e = 0 then begin
                b := sar(b, tp[tpIndexT3 + 1]);
                dec(k, tp[tpIndexT3 + 1]);
                s.window[q] := byte(tp[tpIndexT3 + 2]);
                inc(q);
                dec(m);
                continue;
            end;
            repeat
                b := sar(b, tp[tpIndexT3 + 1]);
                dec(k, tp[tpIndexT3 + 1]);
                if (e and $10) <> 0 then begin
                    e := e and $f;
                    c := tp[tpIndexT3 + 2] + (b and INFLATE_MASK[e]);
                    b := sar(b, e);
                    dec(k, e);
                    while k < 15 do begin
                        dec(n);
                        b := b or ((int(z.nextIn[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    t := b and md;
                    tp := td;
                    tpIndex := tdIndex;
                    tpIndexT3 := (tpIndex + t) * 3;
                    e := tp[tpIndexT3];
                    repeat
                        b := sar(b, tp[tpIndexT3 + 1]);
                        dec(k, tp[tpIndexT3 + 1]);
                        if (e and $10) <> 0 then begin
                            e := e and $f;
                            while k < e do begin
                                dec(n);
                                b := b or ((int(z.nextIn[p]) and $ff) shl k);
                                inc(p);
                                inc(k, 8);
                            end;
                            d := tp[tpIndexT3 + 2] + (b and INFLATE_MASK[e]);
                            b := sar(b, e);
                            dec(k, e);
                            dec(m, c);
                            if q >= d then begin
                                r := q - d;
                                if d = 1 then begin
                                    s.window[q] := s.window[r];
                                    inc(q);
                                    inc(r);
                                    s.window[q] := s.window[r];
                                    inc(q);
                                    inc(r);
                                    dec(c, 2);
                                end else begin
                                    arraycopy(s.window, r, s.window, q, 2);
                                    inc(q, 2);
                                    inc(r, 2);
                                    dec(c, 2);
                                end;
                            end else begin
                                r := q - d;
                                repeat
                                    inc(r, s.end_);
                                until r >= 0;
                                e := s.end_ - r;
                                if c > e then begin
                                    dec(c, e);
                                    if (q - r > 0) and (q - r < e) then begin
                                        repeat
                                            s.window[q] := s.window[r];
                                            inc(q);
                                            inc(r);
                                            dec(e);
                                        until e = 0;
                                    end else begin
                                        arraycopy(s.window, r, s.window, q, e);
                                        inc(q, e);
                                        inc(r, e);
                                        e := 0;
                                    end;
                                    r := 0;
                                end;
                            end;
                            if (q - r > 0) and (q - r < c) then begin
                                repeat
                                    s.window[q] := s.window[r];
                                    inc(q);
                                    inc(r);
                                    dec(c);
                                until c = 0;
                            end else begin
                                arraycopy(s.window, r, s.window, q, c);
                                inc(q, c);
                                inc(r, c);
                                c := 0;
                            end;
                            break;
                        end else
                        if (e and $40) = 0 then begin
                            inc(t, tp[tpIndexT3 + 2] + (b and INFLATE_MASK[e]));
                            tpIndexT3 := (tpIndex + t) * 3;
                            e := tp[tpIndexT3];
                        end else begin
                            z.msg := 'invalid distance code';
                            c := z.availIn - n;
                            if sar(k, 3) < c then begin
                                c := sar(k, 3);
                            end;
                            inc(n, c);
                            dec(p, c);
                            dec(k, c shl 3);
                            s.update(z, b, k, n, p, q);
                            result := DATA_ERROR;
                            exit;
                        end;
                    until false;
                    break;
                end;
                if (e and $40) = 0 then begin
                    inc(t, tp[tpIndexT3 + 2] + (b and INFLATE_MASK[e]));
                    tpIndexT3 := (tpIndex + t) * 3;
                    e := tp[tpIndexT3];
                    if e = 0 then begin
                        b := sar(b, tp[tpIndexT3 + 1]);
                        dec(k, tp[tpIndexT3 + 1]);
                        s.window[q] := byte(tp[tpIndexT3 + 2]);
                        inc(q);
                        dec(m);
                        break;
                    end;
                end else
                if (e and $20) <> 0 then begin
                    c := z.availIn - n;
                    if sar(k, 3) < c then begin
                        c := sar(k, 3);
                    end;
                    inc(n, c);
                    dec(p, c);
                    dec(k, c shl 3);
                    s.update(z, b, k, n, p, q);
                    result := STREAM_END;
                    exit;
                end else begin
                    z.msg := 'invalid literal/length code';
                    c := z.availIn - n;
                    if sar(k, 3) < c then begin
                        c := sar(k, 3);
                    end;
                    inc(n, c);
                    dec(p, c);
                    dec(k, c shl 3);
                    s.update(z, b, k, n, p, q);
                    result := DATA_ERROR;
                    exit;
                end;
            until false;
        until (m < 258) or (n < 10);
        c := z.availIn - n;
        if sar(k, 3) < c then begin
            c := sar(k, 3);
        end;
        inc(n, c);
        dec(p, c);
        dec(k, c shl 3);
        s.update(z, b, k, n, p, q);
        result := OK;
    end;

    procedure InfCodes.init(bl, bd: int; const tl: int_Array1d; tlIndex: int; const td: int_Array1d; tdIndex: int);
    begin
        mode := START;
        lbits := byte(bl);
        dbits := byte(bd);
        ltree := tl;
        ltreeIndex := tlIndex;
        dtree := td;
        dtreeIndex := tdIndex;
        tree := nil;
    end;

    function InfCodes.proc(s: InfBlocks; z: ZStream; r: int): int;
    var
        tindex: int;
        j: int;
        e: int;
        b: int;
        k: int;
        p: int;
        n: int;
        q: int;
        m: int;
        f: int;
    begin
        p := z.nextInIndex;
        n := z.availIn;
        b := s.bitb;
        k := s.bitk;
        q := s.write;
        if q < s.read then begin
            m := s.read - q - 1;
        end else begin
            m := s.end_ - q;
        end;
        repeat
            case mode of
            START: begin
                if (m >= 258) and (n >= 10) then begin
                    s.update(z, b, k, n, p, q);
                    r := inflateFast(lbits, dbits, ltree, ltreeIndex, dtree, dtreeIndex, s, z);
                    p := z.nextInIndex;
                    n := z.availIn;
                    b := s.bitb;
                    k := s.bitk;
                    q := s.write;
                    if q < s.read then begin
                        m := s.read - q - 1;
                    end else begin
                        m := s.end_ - q;
                    end;
                    if r <> OK then begin
                        if r = STREAM_END then begin
                            mode := WASH;
                        end else begin
                            mode := BADCODE;
                        end;
                        continue;
                    end;
                end;
                need := lbits;
                tree := ltree;
                treeIndex := ltreeIndex;
                mode := LEN_;
            end;
            LEN_: begin
                j := need;
                while k < j do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        s.update(z, b, k, n, p, q);
                        result := s.inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                tindex := (treeIndex + (b and INFLATE_MASK[j])) * 3;
                b := b shr tree[tindex + 1];
                dec(k, tree[tindex + 1]);
                e := tree[tindex];
                if e = 0 then begin
                    lit := tree[tindex + 2];
                    mode := LIT_;
                    continue;
                end;
                if (e and $10) <> 0 then begin
                    get := e and $0f;
                    len := tree[tindex + 2];
                    mode := LENEXT;
                    continue;
                end;
                if (e and $40) = 0 then begin
                    need := e;
                    treeIndex := (tindex div 3) + tree[tindex + 2];
                    continue;
                end;
                if (e and $20) <> 0 then begin
                    mode := WASH;
                    continue;
                end;
                mode := BADCODE;
                z.msg := 'invalid literal/length code';
                r := DATA_ERROR;
                s.update(z, b, k, n, p, q);
                result := s.inflateFlush(z, r);
                exit;
            end;
            LENEXT: begin
                j := get;
                while k < j do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        s.update(z, b, k, n, p, q);
                        result := s.inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                inc(len, b and INFLATE_MASK[j]);
                b := sar(b, j);
                dec(k, j);
                need := dbits;
                tree := dtree;
                treeIndex := dtreeIndex;
                mode := DIST_;
            end;
            DIST_: begin
                j := need;
                while k < j do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        s.update(z, b, k, n, p, q);
                        result := s.inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                tindex := (treeIndex + (b and INFLATE_MASK[j])) * 3;
                b := sar(b, tree[tindex + 1]);
                dec(k, tree[tindex + 1]);
                e := tree[tindex];
                if (e and $10) <> 0 then begin
                    get := e and $f;
                    dist := tree[tindex + 2];
                    mode := DISTEXT;
                    continue;
                end;
                if (e and $40) = 0 then begin
                    need := e;
                    treeIndex := (tindex div 3) + tree[tindex + 2];
                    continue;
                end;
                mode := BADCODE;
                z.msg := 'invalid distance code';
                r := DATA_ERROR;
                s.update(z, b, k, n, p, q);
                result := s.inflateFlush(z, r);
                exit;
            end;
            DISTEXT: begin
                j := get;
                while k < j do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        s.update(z, b, k, n, p, q);
                        result := s.inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                inc(dist, b and INFLATE_MASK[j]);
                b := sar(b, j);
                dec(k, j);
                mode := COPY;
            end;
            COPY: begin
                f := q - dist;
                while f < 0 do begin
                    inc(f, s.end_);
                end;
                while len <> 0 do begin
                    if m = 0 then begin
                        if (q = s.end_) and (s.read <> 0) then begin
                            q := 0;
                            if q < s.read then begin
                                m := s.read - q - 1;
                            end else begin
                                m := s.end_ - q;
                            end;
                        end;
                        if m = 0 then begin
                            s.write := q;
                            r := s.inflateFlush(z, r);
                            q := s.write;
                            if q < s.read then begin
                                m := s.read - q - 1;
                            end else begin
                                m := s.end_ - q;
                            end;
                            if (q = s.end_) and (s.read <> 0) then begin
                                q := 0;
                                if q < s.read then begin
                                    m := s.read - q - 1;
                                end else begin
                                    m := s.end_ - q;
                                end;
                            end;
                            if m = 0 then begin
                                s.update(z, b, k, n, p, q);
                                result := s.inflateFlush(z, r);
                                exit;
                            end;
                        end;
                    end;
                    s.window[q] := s.window[f];
                    inc(q);
                    inc(f);
                    dec(m);
                    if f = s.end_ then begin
                        f := 0;
                    end;
                    dec(len);
                end;
                mode := START;
            end;
            LIT_: begin
                if m = 0 then begin
                    if (q = s.end_) and (s.read <> 0) then begin
                        q := 0;
                        if q < s.read then begin
                            m := s.read - q - 1;
                        end else begin
                            m := s.end_ - q;
                        end;
                    end;
                    if m = 0 then begin
                        s.write := q;
                        r := s.inflateFlush(z, r);
                        q := s.write;
                        if q < s.read then begin
                            m := s.read - q - 1;
                        end else begin
                            m := s.end_ - q;
                        end;
                        if (q = s.end_) and (s.read <> 0) then begin
                            q := 0;
                            if q < s.read then begin
                                m := s.read - q - 1;
                            end else begin
                                m := s.end_ - q;
                            end;
                        end;
                        if m = 0 then begin
                            s.update(z, b, k, n, p, q);
                            result := s.inflateFlush(z, r);
                            exit;
                        end;
                    end;
                end;
                r := OK;
                s.window[q] := byte(lit);
                inc(q);
                dec(m);
                mode := START;
            end;
            WASH: begin
                if k > 7 then begin
                    dec(k, 8);
                    inc(n);
                    dec(p);
                end;
                s.write := q;
                r := s.inflateFlush(z, r);
                q := s.write;
                if q < s.read then begin
                    m := s.read - q - 1;
                end else begin
                    m := s.end_ - q;
                end;
                if s.read <> s.write then begin
                    s.update(z, b, k, n, p, q);
                    result := s.inflateFlush(z, r);
                    exit;
                end;
                mode := END_;
            end;
            END_: begin
                r := STREAM_END;
                s.update(z, b, k, n, p, q);
                result := s.inflateFlush(z, r);
                exit;
            end;
            BADCODE: begin
                r := DATA_ERROR;
                s.update(z, b, k, n, p, q);
                result := s.inflateFlush(z, r);
                exit;
            end;
            else
                r := STREAM_ERROR;
                s.update(z, b, k, n, p, q);
                result := s.inflateFlush(z, r);
                exit;
            end;
        until false;
    end;
{%endregion}

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

    class procedure InfBlocks.cldone();
    begin
        BORDER := nil;
        INFLATE_MASK := nil;
    end;

    constructor InfBlocks.create(z: ZStream; w: int);
    begin
        inherited create();
        end_ := w;
        window := byte_Array1d_create(w);
        check := z.istate.wrap <> 0;
        mode := TYPE_;
        bb := int_Array1d_create(1);
        tb := int_Array1d_create(1);
        hufts := int_Array1d_create(MANY * 3);
        codes := InfCodes.create();
        itree := InfTree.create();
        reset(z);
    end;

    destructor InfBlocks.destroy;
    begin
        itree.free();
        codes.free();
        inherited destroy;
    end;

    procedure InfBlocks.proc1();
    var
        bl: int_Array1d;
        bd: int_Array1d;
        tl: int_Array2d;
        td: int_Array2d;
    begin
        bl := int_Array1d_create(1);
        bd := int_Array1d_create(1);
        tl := int_Array2d_create(1);
        td := int_Array2d_create(1);
        InfTree.inflateTreesFixed(bl, bd, tl, td);
        codes.init(bl[0], bd[0], tl[0], 0, td[0], 0);
    end;

    procedure InfBlocks.reset(z: ZStream);
    begin
        mode := TYPE_;
        bitk := 0;
        bitb := 0;
        read := 0;
        write := 0;
        if check then begin
            z.adler.reset();
        end;
    end;

    procedure InfBlocks.free(z: ZStream);
    begin
        reset(z);
        window := nil;
        hufts := nil;
    end;

    procedure InfBlocks.setDictionary(const d: byte_Array1d; start, n: int);
    begin
        arraycopy(d, start, window, 0, n);
        read := n;
        write := n;
    end;

    procedure InfBlocks.update(z: ZStream; b, k, n, p, q: int);
    begin
        bitb := b;
        bitk := k;
        z.availIn := n;
        inc(z.totalIn, long(p) - long(z.nextInIndex));
        z.nextInIndex := p;
        write := q;
    end;

    function InfBlocks.proc(z: 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;
        bl: int_Array1d;
        bd: int_Array1d;
        tl: int_Array1d;
        td: int_Array1d;
    begin
        p := z.nextInIndex;
        n := z.availIn;
        b := bitb;
        k := bitk;
        q := write;
        if q < read then begin
            m := read - q - 1;
        end else begin
            m := end_ - q;
        end;
        repeat
            case mode of
            TYPE_: begin
                while k < 3 do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        update(z, b, k, n, p, q);
                        result := inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                t := b and $07;
                last := 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);
                    mode := LENS;
                end;
                 1: begin
                    proc1();
                    b := b shr 3;
                    dec(k, 3);
                    mode := CODES_;
                end;
                 2: begin
                    b := b shr 3;
                    dec(k, 3);
                    mode := TABLE_;
                end;
                 3: begin
                    b := b shr 3;
                    dec(k, 3);
                    mode := BAD;
                    z.msg := 'invalid block type';
                    r := DATA_ERROR;
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                end;
            end;
            LENS: begin
                while k < 32 do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        update(z, b, k, n, p, q);
                        result := inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                if (((not b) shr 16) and $ffff) <> (b and $ffff) then begin
                    mode := BAD;
                    z.msg := 'invalid stored block lengths';
                    r := DATA_ERROR;
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                left := b and $ffff;
                b := 0;
                k := 0;
                if left <> 0 then begin
                    mode := STORED;
                end else begin
                    if last <> 0 then begin
                        mode := DRY;
                    end else begin
                        mode := TYPE_;
                    end;
                end;
            end;
            STORED: begin
                if n = 0 then begin
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                if m = 0 then begin
                    if (q = end_) and (read <> 0) then begin
                        q := 0;
                        if q < read then begin
                            m := read - q - 1;
                        end else begin
                            m := end_ - q;
                        end;
                    end;
                    if m = 0 then begin
                        write := q;
                        r := inflateFlush(z, r);
                        q := write;
                        if q < read then begin
                            m := read - q - 1;
                        end else begin
                            m := end_ - q;
                        end;
                        if (q = end_) and (read <> 0) then begin
                            q := 0;
                            if q < read then begin
                                m := read - q - 1;
                            end else begin
                                m := end_ - q;
                            end;
                        end;
                        if m = 0 then begin
                            update(z, b, k, n, p, q);
                            result := inflateFlush(z, r);
                            exit;
                        end;
                    end;
                end;
                r := OK;
                t := left;
                if t > n then begin
                    t := n;
                end;
                if t > m then begin
                    t := m;
                end;
                arraycopy(z.nextIn, p, window, q, t);
                inc(p, t);
                dec(n, t);
                inc(q, t);
                dec(m, t);
                dec(left, t);
                if left <> 0 then begin
                    continue;
                end;
                if last <> 0 then begin
                    mode := DRY;
                end else begin
                    mode := TYPE_;
                end;
            end;
            TABLE_: begin
                while k < 14 do begin
                    if n <> 0 then begin
                        r := OK;
                    end else begin
                        update(z, b, k, n, p, q);
                        result := inflateFlush(z, r);
                        exit;
                    end;
                    dec(n);
                    b := b or ((int(z.nextIn[p]) and $ff) shl k);
                    inc(p);
                    inc(k, 8);
                end;
                t := b and $3fff;
                table := t;
                if ((t and $1f) > 29) or (((t shr 5) and $1f) > 29) then begin
                    mode := BAD;
                    z.msg := 'too many length or distance symbols';
                    r := DATA_ERROR;
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                t := (t and $1f) + ((t shr 5) and $1f) + 258;
                if (blens = nil) or (length(blens) < t) then begin
                    blens := int_Array1d_create(t);
                end else begin
                    for i := 0 to t - 1 do begin
                        blens[i] := 0;
                    end;
                end;
                b := b shr 14;
                dec(k, 14);
                index := 0;
                mode := BTREE;
            end;
            BTREE: begin
                while index < (table shr 10) + 4 do begin
                    while k < 3 do begin
                        if n <> 0 then begin
                            r := OK;
                        end else begin
                            update(z, b, k, n, p, q);
                            result := inflateFlush(z, r);
                            exit;
                        end;
                        dec(n);
                        b := b or ((int(z.nextIn[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    blens[BORDER[index]] := b and $07;
                    inc(index);
                    b := b shr 3;
                    dec(k, 3);
                end;
                while index < 19 do begin
                    blens[BORDER[index]] := 0;
                    inc(index);
                end;
                bb[0] := 7;
                t := itree.inflateTreesBits(blens, bb, tb, hufts, z);
                if t <> OK then begin
                    r := t;
                    if r = DATA_ERROR then begin
                        blens := nil;
                        mode := BAD;
                    end;
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                index := 0;
                mode := DTREE;
            end;
            DTREE: begin
                repeat
                    t := table;
                    if index >= (t and $1f) + ((t shr 5) and $1f) + 258 then begin
                        break;
                    end;
                    t := bb[0];
                    while k < t do begin
                        if n <> 0 then begin
                            r := OK;
                        end else begin
                            update(z, b, k, n, p, q);
                            result := inflateFlush(z, r);
                            exit;
                        end;
                        dec(n);
                        b := b or ((int(z.nextIn[p]) and $ff) shl k);
                        inc(p);
                        inc(k, 8);
                    end;
                    t := hufts[(tb[0] + (b and INFLATE_MASK[t])) * 3 + 1];
                    c := hufts[(tb[0] + (b and INFLATE_MASK[t])) * 3 + 2];
                    if c < 16 then begin
                        b := b shr t;
                        dec(k, t);
                        blens[index] := c;
                        inc(index);
                    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
                                r := OK;
                            end else begin
                                update(z, b, k, n, p, q);
                                result := inflateFlush(z, r);
                                exit;
                            end;
                            dec(n);
                            b := b or ((int(z.nextIn[p]) and $ff) shl k);
                            inc(p);
                            inc(k, 8);
                        end;
                        b := b shr t;
                        dec(k, t);
                        inc(j, b and INFLATE_MASK[i]);
                        b := b shr i;
                        dec(k, i);
                        i := index;
                        t := table;
                        if (i + j > (t and $1f) + ((t shr 5) and $1f) + 258) or ((c = 16) and (i < 1)) then begin
                            blens := nil;
                            mode := BAD;
                            z.msg := 'invalid bit length repeat';
                            r := DATA_ERROR;
                            update(z, b, k, n, p, q);
                            result := inflateFlush(z, r);
                            exit;
                        end;
                        if c = 16 then begin
                            c := blens[i - 1];
                        end else begin
                            c := 0;
                        end;
                        repeat
                            blens[i] := c;
                            inc(i);
                            dec(j);
                        until j = 0;
                        index := i;
                    end;
                until false;
                tb[0] := -1;
                bl := int_Array1d_create(1);
                bd := int_Array1d_create(1);
                tl := int_Array1d_create(1);
                td := int_Array1d_create(1);
                bl[0] := 9;
                bd[0] := 6;
                t := table;
                t := itree.inflateTreesDynamic((t and $1f) + 257, ((t shr 5) and $1f) + 1, blens, bl, bd, tl, td, hufts, z);
                if t <> OK then begin
                    if t = DATA_ERROR then begin
                        blens := nil;
                        mode := BAD;
                    end;
                    r := t;
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0]);
                mode := CODES_;
            end;
            CODES_: begin
                update(z, b, k, n, p, q);
                r := codes.proc(self, z, r);
                if r <> STREAM_END then begin
                    result := inflateFlush(z, r);
                    exit;
                end;
                r := OK;
                p := z.nextInIndex;
                n := z.availIn;
                b := bitb;
                k := bitk;
                q := write;
                if q < read then begin
                    m := read - q - 1;
                end else begin
                    m := end_ - q;
                end;
                if last = 0 then begin
                    mode := TYPE_;
                end else begin
                    mode := DRY;
                end;
            end;
            DRY: begin
                write := q;
                r := inflateFlush(z, r);
                q := write;
                if q < read then begin
                    m := read - q - 1;
                end else begin
                    m := end_ - q;
                end;
                if read <> write then begin
                    update(z, b, k, n, p, q);
                    result := inflateFlush(z, r);
                    exit;
                end;
                mode := DONE;
            end;
            DONE: begin
                r := STREAM_END;
                update(z, b, k, n, p, q);
                result := inflateFlush(z, r);
                exit;
            end;
            BAD: begin
                r := DATA_ERROR;
                update(z, b, k, n, p, q);
                result := inflateFlush(z, r);
                exit;
            end;
            else
                r := STREAM_ERROR;
                update(z, b, k, n, p, q);
                result := inflateFlush(z, r);
                exit;
            end;
        until false;
    end;

    function InfBlocks.inflateFlush(z: ZStream; r: int): int;
    var
        n: int;
        p: int;
        q: int;
    begin
        p := z.nextOutIndex;
        q := read;
        if q <= write then begin
            n := write - q;
        end else begin
            n := end_ - q;
        end;
        if n > z.availOut then begin
            n := z.availOut;
        end;
        if (n <> 0) and (r = BUF_ERROR) then begin
            r := OK;
        end;
        dec(z.availOut, n);
        inc(z.totalOut, long(n));
        if check then begin
            z.adler.update(window, q, n);
        end;
        arraycopy(window, q, z.nextOut, p, n);
        inc(p, n);
        inc(q, n);
        if q = end_ then begin
            q := 0;
            if write = end_ then begin
                write := 0;
            end;
            n := write - q;
            if n > z.availOut then begin
                n := z.availOut;
            end;
            if (n <> 0) and (r = BUF_ERROR) then begin
                r := OK;
            end;
            dec(z.availOut, n);
            inc(z.totalOut, long(n));
            if check then begin
                z.adler.update(window, q, n);
            end;
            arraycopy(window, q, z.nextOut, p, n);
            inc(p, n);
            inc(q, n);
        end;
        z.nextOutIndex := p;
        read := q;
        result := r;
    end;

    function InfBlocks.syncPoint(): boolean;
    begin
        result := mode = LENS;
    end;
{%endregion}

{%region ZStream }
    constructor ZStream.create();
    begin
        inherited create();
        adler := Adler32.create();
    end;

    destructor ZStream.destroy;
    begin
        adler := nil;
        dstate.free();
        istate.free();
        inherited destroy;
    end;

    procedure ZStream.flushPending();
    var
        len: int;
    begin
        len := dstate.pending;
        if len > availOut then begin
            len := availOut;
        end;
        if len <> 0 then begin
            arraycopy(dstate.pendingBuf, dstate.pendingOut, nextOut, nextOutIndex, len);
            inc(nextOutIndex, len);
            inc(dstate.pendingOut, len);
            inc(totalOut, long(len));
            dec(availOut, len);
            dec(dstate.pending, len);
            if dstate.pending = 0 then begin
                dstate.pendingOut := 0;
            end;
        end;
    end;

    function ZStream.readBuf(const dst: byte_Array1d; offset, count: int): int;
    begin
        result := availIn;
        if result > count then begin
            result := count;
        end;
        if result = 0 then begin
            exit;
        end;
        dec(availIn, result);
        if dstate.wrap <> 0 then begin
            adler.update(nextIn, nextInIndex, result);
        end;
        arraycopy(nextIn, nextInIndex, dst, offset, result);
        inc(nextInIndex, result);
        inc(totalIn, long(result));
    end;

    procedure ZStream.setAvailIn(availIn: int);
    begin
        self.availIn := availIn;
    end;

    procedure ZStream.setAvailOut(availOut: int);
    begin
        self.availOut := availOut;
    end;

    procedure ZStream.setNextIn(const nextIn: byte_Array1d);
    begin
        self.nextIn := nextIn;
    end;

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

    procedure ZStream.setNextOut(const nextOut: byte_Array1d);
    begin
        self.nextOut := nextOut;
    end;

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

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

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

    procedure ZStream.setInput(const data: byte_Array1d; offset, length: int; append: boolean);
    var
        lim: int;
        len: int;
        tmp: byte_Array1d;
    begin
        lim := offset + length;
        len := System.length(data);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('ZStream.setInput: индекс элемента массива выходит из диапазона.');
        end;
        if (length > 0) or (not append) or (nextIn = nil) then begin
            if (availIn > 0) and append then begin
                tmp := byte_Array1d_create(availIn + length);
                arraycopy(nextIn, nextInIndex, tmp, 0, availIn);
                arraycopy(data, offset, tmp, availIn, length);
                nextIn := tmp;
                nextInIndex := 0;
                inc(availIn, length);
            end else begin
                nextIn := data;
                nextInIndex := offset;
                availIn := length;
            end;
        end;
    end;

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

    procedure ZStream.setOutput(const data: byte_Array1d; offset, length: int);
    var
        lim: int;
        len: int;
    begin
        lim := offset + length;
        len := System.length(data);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('ZStream.setOutput: индекс элемента массива выходит из диапазона.');
        end;
        nextOut := data;
        nextOutIndex := offset;
        availOut := length;
    end;

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

    function ZStream.getAvailIn(): int;
    begin
        result := availIn;
    end;

    function ZStream.getAvailOut(): int;
    begin
        result := availOut;
    end;

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

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

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

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

    function ZStream.getNextIn(): byte_Array1d;
    begin
        result := nextIn;
    end;

    function ZStream.getNextOut(): byte_Array1d;
    begin
        result := nextOut;
    end;

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

    function ZStream.deflateInit(level: int): int;
    begin
        result := deflateInit(level, MAX_WBITS, false);
    end;

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

    function ZStream.deflateInit(level, bits: int): int;
    begin
        result := deflateInit(level, bits, false);
    end;

    function ZStream.deflateInit(level, bits, memlevel: int): int;
    begin
        dstate.free();
        dstate := Zlib.Deflate.create(self);
        result := dstate.deflateInit(level, bits, memlevel);
    end;

    function ZStream.deflateInit(level, bits: int; nowrap: boolean): int;
    begin
        dstate.free();
        dstate := Zlib.Deflate.create(self);
        if nowrap then begin
            result := dstate.deflateInit(level, -bits);
        end else begin
            result := dstate.deflateInit(level, bits);
        end;
    end;

    function ZStream.deflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
    begin
        if dstate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := dstate.deflateSetDictionary(dictionary, dictLength);
        end;
    end;

    function ZStream.deflateParams(level, strategy: int): int;
    begin
        if dstate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := dstate.deflateParams(level, strategy);
        end;
    end;

    function ZStream.deflate(flush: int): int;
    begin
        if dstate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := dstate.deflate(flush);
        end;
    end;

    function ZStream.deflateEnd(): int;
    begin
        if dstate = nil then begin
            result := STREAM_ERROR;
            exit;
        end;
        result := dstate.deflateEnd();
        dstate.free();
        dstate := nil;
    end;

    function ZStream.inflateInit(): int;
    begin
        result := inflateInit(DEF_WBITS, false);
    end;

    function ZStream.inflateInit(w: int): int;
    begin
        result := inflateInit(w, false);
    end;

    function ZStream.inflateInit(nowrap: boolean): int;
    begin
        result := inflateInit(DEF_WBITS, nowrap);
    end;

    function ZStream.inflateInit(w: int; nowrap: boolean): int;
    begin
        istate.free();
        istate := Zlib.Inflate.create(self);
        if nowrap then begin
            result := istate.inflateInit(-w);
        end else begin
            result := istate.inflateInit(w);
        end;
    end;

    function ZStream.inflateSetDictionary(const dictionary: byte_Array1d; dictLength: int): int;
    begin
        if istate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := istate.inflateSetDictionary(dictionary, dictLength);
        end;
    end;

    function ZStream.inflateSync(): int;
    begin
        if istate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := istate.inflateSync();
        end;
    end;

    function ZStream.inflateSyncPoint(): int;
    begin
        if istate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := istate.inflateSyncPoint();
        end;
    end;

    function ZStream.inflate(flush: int): int;
    begin
        if istate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := istate.inflate(flush);
        end;
    end;

    function ZStream.inflateEnd(): int;
    begin
        if istate = nil then begin
            result := STREAM_ERROR;
        end else begin
            result := istate.inflateEnd();
        end;
    end;

    function ZStream.inflateFinished(): boolean;
    begin
        result := (istate <> nil) and (istate.mode = 12);
    end;
{%endregion}

initialization {%region}
    Crc32.clinit();
    StaticTree.clinit();
    Deflate.clinit();
    Inflate.clinit();
    InfTree.clinit();
    InfCodes.clinit();
    InfBlocks.clinit();
{%endregion}

finalization {%region}
    InfBlocks.cldone();
    InfCodes.cldone();
    InfTree.cldone();
    Inflate.cldone();
    Deflate.cldone();
    StaticTree.cldone();
    Crc32.cldone();
{%endregion}

end.

