unit dzip_utils;

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

interface

uses
  dcrc32,
  dzip,
  dzlib_decompress_utils;

type
TZipFile = object
private
  FHeader: PZipLocalFileHeader;
  FCompressed, FCompressedEnd: Pointer;
  FData, FDataEnd: Pointer;
  FName: AnsiString;
  function Size: SizeUInt; inline;
  function Decompress(Data, DataMax: Pointer; out DataEnd: Pointer): Boolean;
public
  procedure Done;
  function UnpackGetMem: Boolean;
  function Unpack(Data, DataMax: Pointer; out DataEnd: Pointer): Boolean;
  property Name: AnsiString read FName;
end;

TZipIndex = record
  Name: AnsiString;
  Header: Pointer;
  Compressed, CompressedEnd: Pointer;
end;

TZipPack = object
private
  FData, FDataEnd: Pointer;
  FIndex: array of TZipIndex;
  procedure BuildIndex;
public
  procedure Init(Data: Pointer; Size: SizeUInt); overload;
  procedure Init(Data, DataEnd: Pointer); overload;
  procedure Done;
  function Find(const FileName: AnsiString;
                out ZipFile: TZipFile): Boolean;
  function Count: SizeUInt;
  function Get(Index: LongInt;
               out ZipFile: TZipFile): Boolean;
end;

implementation

function TZipFile.Size: SizeUInt;
begin
  Exit(0);
end;

function TZipFile.Decompress(Data, DataMax: Pointer; out DataEnd: Pointer): Boolean;
begin
  case PZipLocalFileHeader(FHeader)^.Compression of
    ZIP_COMPRESSION_NO: begin
      if FCompressedEnd - FCompressed > DataMax - Data then
        Exit(False);
      Move(FCompressed^, Data^, FCompressedEnd - FCompressed);
      DataEnd := Data + (FCompressedEnd - FCompressed);
      Exit(True);
    end;
    ZIP_COMPRESSION_DEFLATED:
      Exit(DeflateDecompressAll(FCompressed, FCompressedEnd, Data, DataMax, DataEnd));
    ZIP_COMPRESSION_LZMA: begin
      Writeln(stderr, 'WARNING: LZMA is not implemented yet');
    end;
    ZIP_COMPRESSION_SHRUNK,
    ZIP_COMPRESSION_FACTOR1,
    ZIP_COMPRESSION_FACTOR2,
    ZIP_COMPRESSION_FACTOR3,
    ZIP_COMPRESSION_FACTOR4,
    ZIP_COMPRESSION_IMPLODED,
    ZIP_COMPRESSION_ENHANCED_DEFLATED,
    ZIP_COMPRESSION_DCL_IMPLODED,
    ZIP_COMPRESSION_BZIP2,
    ZIP_COMPRESSION_TERSE,
    ZIP_COMPRESSION_LZ77,
    ZIP_COMPRESSION_PPMD: begin
      Writeln(stderr, 'WARNING: unsupported compression method');
    end;
  else
    Writeln(stderr, 'WARNING: unknown compression method');
  end;
  Exit(False);
end;

procedure TZipFile.Done;
begin
end;

function TZipFile.UnpackGetMem: Boolean;
begin
  Exit(False);
end;

function TZipFile.Unpack(Data, DataMax: Pointer; out DataEnd: Pointer): Boolean;
begin
  if not Decompress(Data, DataMax, DataEnd) then
    Exit(False);
  if GetCrc32Hash(Data, DataEnd) <> FHeader^.CRC32 then
    Exit(False);
  Exit(True);
end;

procedure TZipPack.BuildIndex;
var
  P: Pointer;
begin
  SetLength(FIndex, 0);
  P := FData;
  while P < FDataEnd do begin
    case PZipLocalFileHeader(P)^.Signature of
      ZIP_SIGNATURE: ; // ok
      ZIP_CENRALDIR: Exit;
    else
      Writeln(stderr, 'Wrong signature');
      Exit;
    end;
    SetLength(FIndex, Length(FIndex) + 1);
    SetString(FIndex[High(FIndex)].Name, P + SizeOf(TZipLocalFileHeader), PZipLocalFileHeader(P)^.FileNameLen);
    FIndex[High(FIndex)].Header := P;
    FIndex[High(FIndex)].Compressed := P + SizeOf(TZipLocalFileHeader) + PZipLocalFileHeader(P)^.FileNameLen + PZipLocalFileHeader(P)^.ExtraFieldLen;
    FIndex[High(FIndex)].CompressedEnd := FIndex[High(FIndex)].Compressed + PZipLocalFileHeader(P)^.CompressedSize;
    P := FIndex[High(FIndex)].CompressedEnd;
  end;
end;

procedure TZipPack.Init(Data: Pointer; Size: SizeUInt);
begin
  Init(Data, Data + Size);
end;

procedure TZipPack.Init(Data, DataEnd: Pointer);
begin
  FData := Data;
  FDataEnd := DataEnd;
  BuildIndex;
end;

procedure TZipPack.Done;
begin
end;

function TZipPack.Find(const FileName: AnsiString;
                       out ZipFile: TZipFile): Boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FIndex) do begin
    if FIndex[I].Name = FileName then
      Exit(Get(I, ZipFile));
  end;
  Exit(False);
end;

function TZipPack.Count: SizeUInt;
begin
  Exit(Length(FIndex));
end;

function TZipPack.Get(Index: LongInt;
                      out ZipFile: TZipFile): Boolean;
begin
  if (Index < 0) or (Index > High(FIndex)) then
    Exit(False);
  ZipFile.FHeader := FIndex[Index].Header;
  ZipFile.FCompressed := FIndex[Index].Compressed;
  ZipFile.FCompressedEnd := FIndex[Index].CompressedEnd;
  ZipFile.FName := FIndex[Index].Name;
  Exit(True);
end;

end.
