unit dzlib_decompress_utils;

{$MODE FPC}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

uses
  dbitmem_le,
  dadler32,
  dzlib,
  ddeflate,
  dzlib_decompress;

function DeflateDecompressAll(Src: Pointer;
                        SrcEnd: Pointer;
                        Dst: Pointer;
                        DstMax: Pointer;
                        out NextSrc: Pointer;
                        out DstEnd: Pointer): Boolean; overload;

function ZLibDecompressAll(Src: Pointer;
                        SrcLen: SizeUInt;
                        Dst: Pointer;
                        MaxLen: SizeUInt;
                        out DstLen: SizeUInt): Boolean; overload;
function ZLibDecompressAll(Src: Pointer;
                        SrcEnd: Pointer;
                        Dst: Pointer;
                        DstMax: Pointer;
                        out DstEnd: Pointer): Boolean; overload;
      // Deflates compressed data in Src,SrcLen and saves result to
      // Dst,DstLen. Returns False if decompression was failed, DstLen stay
      // unchanged in this case.
      //
      // MaxLen is maximum available memory at Dst. If there is no enough
      // space, the function returns False even if compressed data is correct.

function ZLibDecompressGetMem(Src: Pointer;
                        SrcLen: SizeUInt;
                        PredictedLen: SizeUInt;
                        out Dst: Pointer;
                        out DstLen: SizeUInt): Boolean;
      // Deflates compressed data in Src,SrcLen and saves result to
      // Dst,DstLen. Returns False if decompression was failed, Dst and
      // DstLen stay unchanged in this case.
      //
      // Uses GetMem and ReallocMem to allocate memory for decompressed data,
      // so you should FreeMem the Dst data (if the call was successful).

implementation

function ZLibSkipHeader(var Src: Pointer;
                        SrcEnd: Pointer): Boolean;
var
  Header: TZlibHeader;
begin
  if not ZlibHeader(Src, SrcEnd, Header, Src) then
    Exit(False);
  if Header.FDICT then
    Exit(False);
  if Header.CompressionMethod <> ZLIB_CM_DEFLATE then
    Exit(False);
  Exit(True);
end;

function ZLibCheckHash(Src: Pointer;
                       SrcEnd: Pointer;
                       Dst: Pointer;
                       DstEnd: Pointer): Boolean;
begin
  if SrcEnd - Src < 4 then
    Exit(False);
  Exit(Adler32(Dst, DstEnd) = SwapEndian(Cardinal(Pointer(Src)^)));
end;

procedure MoveSelfRepeated(Src, Dst, DstEnd: Pointer); inline;
      // Repeats data from region Src..Dst-1 to region Dst..DstEnd-1
      // Prerequisite: Src<=Dst<=DstEnd
begin
  while Dst - Src < DstEnd - Dst do begin
    Move(Src^, Dst^, Dst - Src);
    Inc(Dst, Dst - Src);
  end;
  Move(Src^, Dst^, DstEnd - Dst);
end;

function DeflateDecompressAll(Src: Pointer;
                        SrcEnd: Pointer;
                        Dst: Pointer;
                        DstMax: Pointer;
                        out NextSrc: Pointer;
                        out DstEnd: Pointer): Boolean; overload;
var
  BitOffset: PtrInt;
  BFINAL: Boolean;
  BTYPE: PtrUInt;
  Len, NLen: UInt16;
  FixedLiteralTree: THuffmanTree;
  FixedDistanceTree: THuffmanTree;
  DynamicLiteralTree: THuffmanTree;
  DynamicDistanceTree: THuffmanTree;
  LiteralTree: PHuffmanTree;  // points to fixed or dynamic
  DistanceTree: PHuffmanTree; // points to fixed or dynamic
  Sym: LongInt;
  CurLen: LongInt;
  Dist: LongInt;
  Offs: LongInt;
begin
  DeflateBuildFixedTrees(FixedLiteralTree, FixedDistanceTree);
  BitOffset := 0;
  repeat
    if Src >= SrcEnd then
      Exit(False);
    BFINAL := ReadBitLE(Src, BitOffset, Src, BitOffset) <> 0;
    BTYPE := ReadBitsLE(Src, BitOffset, 2, Src, BitOffset);
    if BTYPE = 0 then begin // non-compressed block
      if BitOffset > 0 then // "skip any remaining bits"
        Inc(Src);
      BitOffset := 0;
      Len := SwapEndian(UInt16(Src^));
      NLen := SwapEndian(UInt16(Src^));
      if Len <> ((not NLen) and $ffff) then
        Exit(False);
      if Dst + Len > DstMax then
        Exit(False);
      Move(Src^, Dst^, Len);
      Inc(Dst, Len);
      Inc(Src, SizeOf(Len) + SizeOf(NLen) + Len);

    end else if BTYPE = 3 then begin // reserved value
      Exit(False);

    end else begin // compressed block
      if BTYPE = 2 then begin // dynamic Huffman codes
        DeflateReadTrees(Src, BitOffset, SrcEnd, DynamicLiteralTree, DynamicDistanceTree);
        LiteralTree := @DynamicLiteralTree;
        DistanceTree := @DynamicDistanceTree;
      end else begin // fixed Huffman codes
        LiteralTree := @FixedLiteralTree;
        DistanceTree := @FixedDistanceTree;
      end;

      while True do begin
        Sym := DeflateDecodeSymbol(Src, BitOffset, SrcEnd, LiteralTree);
        if Sym < 256 then begin // literal byte
          if Sym < 0 then
            Exit(False); // bad symbol
          if Dst >= DstMax then
            Exit(False); // no space
          PByte(Dst)^ := Sym;
          Inc(Dst);
          continue;
        end else if Sym = 256 then // end of block
          break;

        Dec(Sym, 257);
        if Sym >= 29 then
          Exit(False); // corrupted data

        CurLen := DeflateReadLength(Src, BitOffset, SrcEnd, Sym);
        Dist := DeflateDecodeSymbol(Src, BitOffset, SrcEnd, DistanceTree);
        if Dist < 0 then begin
          Exit(False); // bad dist
        end else if Dist >= 30 then begin
          Exit(False); // large dist
        end;
        Offs := DeflateReadDistance(Src, BitOffset, SrcEnd, Dist);
        if Dst + CurLen > DstMax then begin
          Exit(False); // no space
        end;
        MoveSelfRepeated(Dst - Offs, Dst, Dst + CurLen);
        Inc(Dst, CurLen);
      end; // of loop of compressed data
    end; // of compressed case
  until BFINAL;
  if BitOffset > 0 then begin
    NextSrc := Src + 1;
  end else
    NextSrc := Src;
  DstEnd := Dst;
  Exit(True);
end;

function ZLibDecompressAll(Src: Pointer;
                        SrcEnd: Pointer;
                        Dst: Pointer;
                        DstMax: Pointer;
                        out DstEnd: Pointer): Boolean;
begin
  if not ZLibSkipHeader(Src, SrcEnd) then
    Exit(False);
  if not DeflateDecompressAll(Src, SrcEnd, Dst, DstMax, Src, DstEnd) then
    Exit(False);
  Exit(ZLibCheckHash(Src, SrcEnd, Dst, DstEnd));
end;

function ZLibDecompressAll(Src: Pointer;
                        SrcLen: SizeUInt;
                        Dst: Pointer;
                        MaxLen: SizeUInt;
                        out DstLen: SizeUInt): Boolean;
var
  DstEnd: Pointer;
begin
  if ZLibDecompressAll(Src, Src + SrcLen, Dst, Dst + MaxLen, DstEnd) then begin
    DstLen := DstEnd - Dst;
    Exit(True);
  end else
    Exit(False);
end;

function ZLibDecompressGetMem(Src: Pointer;
                        SrcLen: SizeUInt;
                        PredictedLen: SizeUInt;
                        out Dst: Pointer;
                        out DstLen: SizeUInt): Boolean;
begin
  Exit(False);
end;

end.
