(* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is TurboPower Abbrevia
 *
 * The Initial Developer of the Original Code is
 * TurboPower Software
 *
 * Portions created by the Initial Developer are Copyright (C) 1997-2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** *)

{*********************************************************}
{* ABBREVIA: AbDfBase.pas                                *}
{*********************************************************}
{* Deflate base unit                                     *}
{*********************************************************}

unit AbDfBase;

{$I AbDefine.inc}

interface

uses
  SysUtils,
  Classes;

type
  PAbDfLongintList = ^TAbDfLongintList;
  TAbDfLongintList =
               array [0..pred(MaxInt div sizeof(longint))] of longint;

const
  dfc_CodeLenCodeLength = 7;
  dfc_LitDistCodeLength = 15;
  dfc_MaxCodeLength     = 15;

const
  dfc_MaxMatchLen = 258;         {lengths are 3..258 for deflate}
  dfc_MaxMatchLen64 = 64 * 1024; {lengths are 3..65536  for deflate64}

const
  dfc_LitExtraOffset = 257;
  dfc_LitExtraBits : array [0..30] of byte =
    (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, 16, 99, 99);
     { note: the last two are required to avoid going beyond the end}
     {       of the array when generating static trees}

  dfc_DistExtraOffset = 0;
  dfc_DistExtraBits : array [0..31] of byte =
    (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, 14, 14);
     { note: the last two are only use for deflate64}

  dfc_LengthBase : array [0..28] of word =
    (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, 3);
     { note: the final 3 is correct for deflate64; for symbol 285,}
     {       lengths are stored as (length - 3)}
     {       for deflate it's very wrong, but there's special code in}
     {       the (de)compression code to cater for this}

  dfc_DistanceBase : array [0..31] of word =
    (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, 32769, 49153);

  dfc_CodeLengthIndex : array [0..18] of byte =
    (16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15);

const
  dfc_CanUseStored  = $01;
  dfc_CanUseStatic  = $02;
  dfc_CanUseDynamic = $04;
  dfc_UseLazyMatch  = $08;
  dfc_UseDeflate64  = $10;
  dfc_UseAdler32    = $20;
  dfc_CanUseHuffman = dfc_CanUseStatic or dfc_CanUseDynamic;
  dfc_TestOnly      = $40000000;

type
  TAbProgressStep = procedure (aPercentDone : integer) of object;
    {-progress metering of deflate/inflate; abort with AbortProgress}

  TAbDeflateHelper = class
    private
      FAmpleLength    : longint;
      FChainLength    : longint;
      FLogFile        : string;
      FMaxLazy        : longint;
      FOnProgressStep : TAbProgressStep;
      FOptions        : longint;
      FPartSize       : Int64;
      FSizeCompressed : Int64;
      FSizeNormal     : Int64;
      FStreamSize     : Int64;
      FWindowSize     : longint;
      FZipOption      : AnsiChar;
    protected
      procedure dhSetAmpleLength(aValue : longint);
      procedure dhSetChainLength(aValue : longint);
      procedure dhSetLogFile(const aValue : string);
      procedure dhSetMaxLazy(aValue : longint);
      procedure dhSetOnProgressStep(aValue : TAbProgressStep);
      procedure dhSetOptions(aValue : longint);
      procedure dhSetWindowSize(aValue : longint);
      procedure dhSetZipOption(aValue : AnsiChar);
    public
      constructor Create;

      procedure Assign(aHelper : TAbDeflateHelper);

      property AmpleLength : longint
                  read FAmpleLength write dhSetAmpleLength;
      property ChainLength : longint
                  read FChainLength write dhSetChainLength;
      property LogFile : string
                  read FLogFile write dhSetLogFile;
      property MaxLazyLength : longint
                  read FMaxLazy write dhSetMaxLazy;
      property Options : longint
                  read FOptions write dhSetOptions;
      property PartialSize : Int64
                  read FPartSize write FPartSize;
      property PKZipOption : AnsiChar
                  read FZipOption write dhSetZipOption;
      property StreamSize : Int64
                  read FStreamSize write FStreamSize;
      property WindowSize : longint
                  read FWindowSize write dhSetWindowSize;

      property CompressedSize : Int64
                  read FSizeCompressed write FSizeCompressed;
      property NormalSize : Int64
                  read FSizeNormal write FSizeNormal;

      property OnProgressStep : TAbProgressStep
                  read FOnProgressStep write dhSetOnProgressStep;
  end;

type
  TAbLineDelimiter = (ldCRLF, ldLF);

  TAbLogger = class(TStream)
    private
      FBuffer    : PAnsiChar;
      FCurPos    : PAnsiChar;
      FLineDelim : TAbLineDelimiter;
      FStream    : TFileStream;
    protected
      function logWriteBuffer : boolean;
    public
      constructor Create(const aLogName : string);
      destructor Destroy; override;

      function Read(var Buffer; Count : longint) : longint; override;
      function Seek(const Offset : Int64; Origin : TSeekOrigin) : Int64; override;
      function Write(const Buffer; Count : longint) : longint; override;
      procedure WriteLine(const S : string);
      procedure WriteStr(const S : string);

      property LineDelimiter : TAbLineDelimiter
                  read FLineDelim write FLineDelim;
  end;

type
  TAbNodeManager = class
    private
      FFreeList     : pointer;
      FNodeSize     : cardinal;
      FNodesPerPage : cardinal;
      FPageHead     : pointer;
      FPageSize     : cardinal;
    protected
      function nmAllocNewPage : pointer;
    public
      constructor Create(aNodeSize : cardinal);
      destructor Destroy; override;

      function AllocNode : pointer;
      function AllocNodeClear : pointer;
      procedure FreeNode(aNode : pointer);
  end;

{---exception classes---}
type
  EAbAbortProgress = class(Exception);
  EAbPartSizedInflate = class(Exception);
  EAbInflatePasswordError = class(Exception);
  EAbInternalInflateError = class(Exception);
  EAbInflateError = class(Exception)
    public
      constructor Create(const aMsg : string);
      constructor CreateUnknown(const aMsg : string;
                                const aErrorMsg : string);
  end;
  EAbInternalDeflateError = class(Exception);
  EAbDeflateError = class(Exception)
    public
      constructor Create(const aMsg : string);
      constructor CreateUnknown(const aMsg : string;
                                const aErrorMsg : string);
  end;

{---aborting a process---}
procedure AbortProgress;

{---calculation of checksums---}
procedure AbUpdateAdlerBuffer(var aAdler : longint;
                              var aBuffer; aCount : integer);
procedure AbUpdateCRCBuffer(var aCRC : longint;
                            var aBuffer; aCount : integer);


implementation

uses
  AbUtils;

{===TAbDeflateHelper=================================================}
constructor TAbDeflateHelper.Create;
begin
  inherited Create;
  FAmpleLength := 8;
  FChainLength := 32;
  {FLogFile := '';}
  FMaxLazy := 16;
  {FOnProgressStep := nil;}
  FOptions := $F;
  {FStreamSize := 0;}
  FWindowSize := 32 * 1024;
  FZipOption := 'n';
end;
{--------}
procedure TAbDeflateHelper.Assign(aHelper : TAbDeflateHelper);
begin
  FAmpleLength := aHelper.FAmpleLength;
  FChainLength := aHelper.FChainLength;
  FLogFile := aHelper.FLogFile;
  FMaxLazy := aHelper.FMaxLazy;
  FOnProgressStep := aHelper.FOnProgressStep;
  FOptions := aHelper.FOptions;
  FPartSize := aHelper.FPartSize;
  FStreamSize := aHelper.FStreamSize;
  FWindowSize := aHelper.FWindowSize;
  FZipOption := aHelper.FZipOption;
end;
{--------}
procedure TAbDeflateHelper.dhSetAmpleLength(aValue : longint);
begin
  if (aValue <> AmpleLength) then begin
    if (aValue <> -1) and (aValue < 4) then
      aValue := 4;
    FAmpleLength := aValue;
    FZipOption := '?';
  end;
end;
{--------}
procedure TAbDeflateHelper.dhSetChainLength(aValue : longint);
begin
  if (aValue <> ChainLength) then begin
    if (aValue <> -1) and (aValue < 4) then
      aValue := 4;
    FChainLength := aValue;
    FZipOption := '?';
  end;
end;
{--------}
procedure TAbDeflateHelper.dhSetLogFile(const aValue : string);
begin
  FLogFile := aValue;
end;
{--------}
procedure TAbDeflateHelper.dhSetMaxLazy(aValue : longint);
begin
  if (aValue <> MaxLazyLength) then begin
    if (aValue <> -1) and (aValue < 4) then
      aValue := 4;
    FMaxLazy := aValue;
    FZipOption := '?';
  end;
end;
{--------}
procedure TAbDeflateHelper.dhSetOnProgressStep(aValue : TAbProgressStep);
begin
  FOnProgressStep := aValue;
end;
{--------}
procedure TAbDeflateHelper.dhSetOptions(aValue : longint);
begin
  if (aValue <> Options) then begin
    FOptions := aValue;
    FZipOption := '?';
  end;
end;
{--------}
procedure TAbDeflateHelper.dhSetWindowSize(aValue : longint);
var
  NewValue : longint;
begin
  if (aValue <> WindowSize) then begin
    {calculate the window size rounded to nearest 1024 bytes}
    NewValue := ((aValue + 1023) div 1024) * 1024;
    {if the new window size is greater than 32KB...}
    if (NewValue > 32 * 1024) then
      {if the Deflate64 option is set, force to 64KB}
      if ((Options and dfc_UseDeflate64) <> 0) then
        NewValue := 64 * 1024
      {otherwise, force to 32KB}
      else
        NewValue := 32 * 1024;
    {set the new window size}
    FWindowSize := NewValue;
  end;
end;
{--------}
procedure TAbDeflateHelper.dhSetZipOption(aValue : AnsiChar);
begin
  {notes:
     The original Abbrevia code used the following table for
     setting the equivalent values:
            Good  Lazy  Chain  UseLazy  Option
               4     4      4     N       s        ^
               4     5      8     N                |
               4     6     32     N       f      faster
               4     4     16     Y              slower
               8    16     32     Y       n        |
               8    16    128     Y                |
               8    32    256     Y                |
              32   128   1024     Y                |
              32   258   4096     Y       x        V
     The new Abbrevia 3 code follows these values to a certain extent.
  }

  {force to lower case}
  if ('A' <= aValue) and (aValue <= 'Z') then
    aValue := AnsiChar(ord(aValue) + ord('a') - ord('A'));

  {if the value has changed...}
  if (aValue <> PKZipOption) then begin

    {switch on the new value...}
    case aValue of
      '0' : {no compression}
        begin
          FZipOption := aValue;
          FOptions := (FOptions and (not $0F)) or dfc_CanUseStored;
          FAmpleLength := 8;  { not actually needed}
          FChainLength := 32; { not actually needed}
          FMaxLazy := 16;     { not actually needed}
        end;
      '2' : {hidden option: Abbrevia 2 compatibility}
        begin
          FZipOption := aValue;
          FOptions := FOptions or $0F;
          FAmpleLength := 8;
          FChainLength := 32;
          FMaxLazy := 16;
        end;
      'f' : {fast compression}
        begin
          FZipOption := aValue;
          FOptions := FOptions or $07; { no lazy matching}
          FAmpleLength := 4;
          FChainLength := 32;
          FMaxLazy := 6;
        end;
      'n' : {normal compression}
        begin
          FZipOption := aValue;
          FOptions := FOptions or $0F;
          FAmpleLength := 16;
          FChainLength := 32;
          FMaxLazy := 24;
        end;
      's' : {super fast compression}
        begin
          FZipOption := aValue;
          FOptions := FOptions or $07; { no lazy matching}
          FAmpleLength := 4;
          FChainLength := 4;
          FMaxLazy := 4;
        end;
      'x' : {maximum compression}
        begin
          FZipOption := aValue;
          FOptions := FOptions or $0F;
          FAmpleLength := 64;{32;}
          FChainLength := 4096;
          FMaxLazy := 258;
        end;
    end;
  end;
end;
{====================================================================}


{===TAbLogger========================================================}
const
  LogBufferSize = 4096;
{--------}
constructor TAbLogger.Create(const aLogName : string);
begin
  Assert(aLogName <> '',
         'TAbLogger.Create: a filename must be provided for the logger');

  {create the ancestor}
  inherited Create;

  {set the default line terminator}
  {$IFDEF MSWINDOWS}
  FLineDelim := ldCRLF;
  {$ENDIF}
  {$IFDEF UNIX}
  FLineDelim := ldLF;
  {$ENDIF}

  {create and initialize the buffer}
  GetMem(FBuffer, LogBufferSize);
  FCurPos := FBuffer;

  {create the log file}
  FStream := TFileStream.Create(aLogName, fmCreate);
end;
{--------}
destructor TAbLogger.Destroy;
begin
  {if there is a buffer ensure that it is flushed before freeing it}
  if (FBuffer <> nil) then begin
    if (FCurPos <> FBuffer) then
      logWriteBuffer;
    FreeMem(FBuffer, LogBufferSize);
  end;

  {free the stream}
  FStream.Free;

  {destroy the ancestor}
  inherited Destroy;
end;
{--------}
function TAbLogger.logWriteBuffer : boolean;
var
  BytesToWrite : longint;
  BytesWritten : longint;
begin
  BytesToWrite := FCurPos - FBuffer;
  BytesWritten := FStream.Write(FBuffer^, BytesToWrite);
  if (BytesWritten = BytesToWrite) then begin
    Result := true;
    FCurPos := FBuffer;
  end
  else begin
    Result := false;
    if (BytesWritten <> 0) then begin
      Move(FBuffer[BytesWritten], FBuffer^, BytesToWrite - BytesWritten);
      FCurPos := FBuffer + (BytesToWrite - BytesWritten);
    end;
  end;
end;
{--------}
function TAbLogger.Read(var Buffer; Count : longint) : longint;
begin
  Assert(false, 'TAbLogger.Read: loggers are write-only, no reading allowed');
  Result := 0;
end;
{--------}
function TAbLogger.Seek(const Offset : Int64; Origin : TSeekOrigin) : Int64;
begin
  case Origin of
    soBeginning :
      begin
      end;
    soCurrent :
      if (Offset = 0) then begin
        Result := FStream.Position + (FCurPos - FBuffer);
        Exit;
      end;
    soEnd :
      if (Offset = 0) then begin
        Result := FStream.Position + (FCurPos - FBuffer);
        Exit;
      end;
  end;

  Assert(false, 'TAbLogger.Seek: loggers are write-only, no seeking allowed');
  Result := 0;
end;
{--------}
function TAbLogger.Write(const Buffer; Count : longint) : longint;
var
  UserBuf      : PAnsiChar;
  BytesToGo    : longint;
  BytesToWrite : longint;
begin
  {reference the user's buffer as a PChar}
  UserBuf := @Buffer;

  {start the counter for the number of bytes written}
  Result := 0;

  {if needed, empty the internal buffer into the underlying stream}
  if (LogBufferSize = FCurPos - FBuffer) then
    if not logWriteBuffer then
      Exit;

  {calculate the number of bytes to copy this time from the user's
   buffer to the internal buffer}
  BytesToGo := Count;
  BytesToWrite := LogBufferSize - (FCurPos - FBuffer);
  if (BytesToWrite > BytesToGo) then
    BytesToWrite := BytesToGo;

  {copy the bytes}
  Move(UserBuf^, FCurPos^, BytesToWrite);

  {adjust the counters}
  inc(FCurPos, BytesToWrite);
  dec(BytesToGo, BytesToWrite);
  inc(Result, BytesToWrite);

  {while there are still more bytes to copy, do so}
  while (BytesToGo <> 0) do begin
    {advance the user's buffer}
    inc(UserBuf, BytesToWrite);

    {empty the internal buffer into the underlying stream}
    if not logWriteBuffer then
      Exit;

    {calculate the number of bytes to copy this time from the user's
     buffer to the internal buffer}
    BytesToWrite := LogBufferSize;
    if (BytesToWrite > BytesToGo) then
      BytesToWrite := BytesToGo;

    {copy the bytes}
    Move(UserBuf^, FCurPos^, BytesToWrite);

    {adjust the counters}
    inc(FCurPos, BytesToWrite);
    dec(BytesToGo, BytesToWrite);
    inc(Result, BytesToWrite);
  end;
end;
{--------}
procedure TAbLogger.WriteLine(const S : string);
const
  cLF : AnsiChar = ^J;
  cCRLF : array [0..1] of AnsiChar = ^M^J;
begin
  if (length(S) > 0) then
    Write(S[1], length(S));
  case FLineDelim of
    ldLF   : Write(cLF, sizeof(cLF));
    ldCRLF : Write(cCRLF, sizeof(cCRLF));
  end;
end;
{--------}
procedure TAbLogger.WriteStr(const S : string);
begin
  if (length(S) > 0) then
    Write(S[1], length(S));
end;
{====================================================================}


{===Calculate checksums==============================================}
procedure AbUpdateAdlerBuffer(var aAdler : longint;
                              var aBuffer; aCount : integer);
var
  S1 : LongWord;
  S2 : LongWord;
  i  : integer;
  Buffer     : PAnsiChar;
  BytesToUse : integer;
begin
  {Note: this algorithm will *only* work if the buffer is 4KB or less,
         which is why we go to such lengths to chop up the user buffer
         into usable chunks of 4KB.

         However, for Delphi 3 there is no proper 32-bit longword.
         Although the additions pose no problems in this situation,
         the mod operations below (especially for S2) will be signed
         integer divisions, producing an (invalid) signed result. In
         this case, the buffer is chopped up into 2KB chunks to avoid
         any signed problems.}

  {split the current Adler checksum into its halves}
  S1 := LongWord(aAdler) and $FFFF;
  S2 := LongWord(aAdler) shr 16;

  {reference the user buffer as a PChar: it makes it easier}
  Buffer := @aBuffer;

  {while there's still data to checksum...}
  while (aCount <> 0) do begin

    {calculate the number of bytes to checksum this time}
    {$IFDEF HasLongWord}
    BytesToUse := 4096;
    {$ELSE}
    BytesToUse := 2048;
    {$ENDIF}
    if (BytesToUse > aCount) then
      BytesToUse := aCount;

    {checksum the bytes}
    for i := 0 to pred(BytesToUse) do begin
      inc(S1, ord(Buffer^));
      inc(S2, S1);
      inc(Buffer);
    end;

    {recalibrate the Adler checksum halves}
    S1 := S1 mod 65521;
    S2 := S2 mod 65521;

    {calculate the number of bytes still to go}
    dec(aCount, BytesToUse);
  end;

  {join the halves to produce the complete Adler checksum}
  aAdler := longint((S2 shl 16) or S1);
end;
{--------}
procedure AbUpdateCRCBuffer(var aCRC : longint;
                            var aBuffer; aCount : integer);
var
  i      : integer;
  CRC    : LongWord;
  Buffer : PAnsiChar;
begin
{$R-}{$Q-}
  {reference the user buffer as a PChar: it makes it easier}
  Buffer := @aBuffer;

  {get the current CRC as a local variable, it's faster}
  CRC := aCRC;

  {checksum the bytes in the buffer}
  for i := 0 to pred(aCount) do begin
    CRC := AbCrc32Table[byte(CRC) xor byte(Buffer^)] xor (CRC shr 8);
    inc(Buffer);
  end;

  {return the new CRC}
  aCRC := CRC;
{$R+}{$Q+}
end;
{====================================================================}


{===EAbInflateError==================================================}
constructor EAbInflateError.Create(const aMsg : string);
begin
  inherited Create(
     'Abbrevia inflate error, possibly a corrupted compressed stream. ' +
     '(Internal cause: ' + aMsg + ')');
end;
{--------}
constructor EAbInflateError.CreateUnknown(const aMsg : string;
                                          const aErrorMsg : string);
begin
  inherited Create(aMsg + ': ' + aErrorMsg);
end;
{====================================================================}


{===EAbDeflateError==================================================}
constructor EAbDeflateError.Create(const aMsg : string);
begin
  inherited Create(
     'Abbrevia deflate error. ' +
     '(Internal cause: ' + aMsg + ')');
end;
{--------}
constructor EAbDeflateError.CreateUnknown(const aMsg : string;
                                          const aErrorMsg : string);
begin
  inherited Create(aMsg + ': ' + aErrorMsg);
end;
{====================================================================}


{===Node manager=====================================================}
const
  PageSize = 8 * 1024;
type
  PGenericNode = ^TGenericNode;
  TGenericNode = packed record
    gnNext : PGenericNode;
    gnData : record end;
  end;
{--------}
constructor TAbNodeManager.Create(aNodeSize : cardinal);
const
  Gran = sizeof(pointer);
  Mask = not (Gran - 1);
begin
  {create the ancestor}
  inherited Create;

  {save the node size rounded to nearest 4 bytes}
  if (aNodeSize <= sizeof(pointer)) then
    aNodeSize := sizeof(pointer)
  else
    aNodeSize := (aNodeSize + Gran - 1) and Mask;
  FNodeSize := aNodeSize;

  {calculate the page size (default 1024 bytes) and the number of
   nodes per page; if the default page size is not large enough for
   two or more nodes, force a single node per page}
  FNodesPerPage := (PageSize - sizeof(pointer)) div aNodeSize;
  if (FNodesPerPage > 1) then
    FPageSize := PageSize
  else begin
    FNodesPerPage := 1;
    FPagesize := aNodeSize + sizeof(pointer);
  end;
end;
{--------}
destructor TAbNodeManager.Destroy;
var
  Temp : pointer;
begin
  {dispose of all the pages, if there are any}
  while (FPageHead <> nil) do begin
    Temp := PGenericNode(FPageHead)^.gnNext;
    FreeMem(FPageHead, FPageSize);
    FPageHead := Temp;
  end;

  {destroy the ancestor}
  inherited Destroy;
end;
{--------}
function TAbNodeManager.AllocNode : pointer;
begin
  Result := FFreeList;
  if (Result = nil) then
    Result := nmAllocNewPage
  else
    FFreeList := PGenericNode(Result)^.gnNext;
end;
{--------}
function TAbNodeManager.AllocNodeClear : pointer;
begin
  Result := FFreeList;
  if (Result = nil) then
    Result := nmAllocNewPage
  else
    FFreeList := PGenericNode(Result)^.gnNext;
  FillChar(Result^, FNodeSize, 0);
end;
{--------}
procedure TAbNodeManager.FreeNode(aNode : pointer);
begin
  {add the node (if non-nil) to the top of the free list}
  if (aNode <> nil) then begin
    PGenericNode(aNode)^.gnNext := FFreeList;
    FFreeList := aNode;
  end;
end;
{--------}
function TAbNodeManager.nmAllocNewPage : pointer;
var
  NewPage  : PAnsiChar;
  i        : integer;
  FreeList : pointer;
  NodeSize : integer;
begin
  {allocate a new page and add it to the front of the page list}
  GetMem(NewPage, FPageSize);
  PGenericNode(NewPage)^.gnNext := FPageHead;
  FPageHead := NewPage;

  {now split up the new page into nodes and push them all onto the
   free list; note that the first 4 bytes of the page is a pointer to
   the next page, so remember to skip over it}
  inc(NewPage, sizeof(pointer));
  FreeList := FFreeList;
  NodeSize := FNodeSize;
  for i := 0 to pred(FNodesPerPage) do begin
    PGenericNode(NewPage)^.gnNext := FreeList;
    FreeList := NewPage;
    inc(NewPage, NodeSize);
  end;

  {return the top of the list}
  Result := FreeList;
  FFreeList := PGenericNode(Result)^.gnNext;
end;
{====================================================================}


{====================================================================}
procedure AbortProgress;
begin
  raise EAbAbortProgress.Create('Abort');
end;
{====================================================================}

end.
