unit PrecompOodle;

interface

uses
  OodleDLL, XDeltaDLL,
  Utils,
  PrecompUtils,
  System.SysUtils, System.Classes, System.Types, System.Math;

{ 8C 07 -  0:LZH
  8C 00 -  1:LZHLW
  8C 01 -  2:LZNIB
  CC 07 -  3:None
  8C 02 -  4:LZB16
  8C 03 -  5:LZBLW
  8C 04 -  6:LZA
  8C 05 -  7:LZNA
  8C 06 -  8:Kraken
  8C 0A -  9:Mermaid
  8C 0B - 10:BitKnit
  8C 0A - 11:Selkie
  8C 0A - 12:Hydra
  8C 0C - 13:Leviathan }

var
  Codec: TPrecompressor;

implementation

const
  OodleCodecs: array of PChar = ['lzna', 'kraken', 'mermaid', 'selkie', 'hydra',
    'leviathan'];
  CODEC_COUNT = 6;
  LZNA_CODEC = 0;
  KRAKEN_CODEC = 1;
  MERMAID_CODEC = 2;
  SELKIE_CODEC = 3;
  HYDRA_CODEC = 4;
  LEVIATHAN_CODEC = 5;

const
  O_COUNT = 0;
  O_TRADEOFF = 256;
  O_MAXSIZE = 16 * 1024 * 1024;

var
  SOList: array of array [0 .. CODEC_COUNT - 1] of TSOList;
  OCount: Integer = O_COUNT;
  OTradeOff: Integer = O_TRADEOFF;
  CodecAvailable, CodecEnabled: TArray<Boolean>;

type
  POodleSI = ^TOodleSI;

  TOodleSI = record
    CSize, DSize: Integer;
    Codec: Integer;
    HasCRC: Boolean;
  end;

procedure GetOodleSI(Buff: PByte; Size: Integer; StreamInfo: POodleSI;
  MaxBlocks: Integer = Integer.MaxValue; First: Boolean = True);
const
  MinSize = 64;
  BlkSize = 262144;
var
  I, J, K: Integer;
  Compressed: Boolean;
begin
  if MaxBlocks <= 0 then
    exit;
  I := 0;
  if First then
  begin
    StreamInfo^.CSize := 0;
    StreamInfo^.DSize := 0;
    StreamInfo^.Codec := 0;
    StreamInfo^.HasCRC := False;
    if Size < 8 then
      exit;
    if ((Buff^ in [$8C, $CC]) = False) then
      exit;
    Compressed := Buff^ = $8C;
    if Compressed then
    begin
      case (Buff + 1)^ of
        { $02:
          if not(((Buff + 2)^ shr 4 = 0) and (((Buff + 4)^ shr 4 = $F) or
          ((Buff + 4)^ and $F = $F))) then
          exit; }
        $06, $0A, $0C:
          begin
            I := EndianSwap(PInteger(Buff + 2)^) shr 8 + 6;
            J := ((EndianSwap(PInteger(Buff + 5)^) shr 8) and $7FFFF) + 8;
            if I > J then
            begin
              K := ((EndianSwap(PInteger(Buff + J)^) shr 8) and $7FFFF) + 3;
              if I <> (J + K) then
                exit;
            end
            else if I <> J then
              exit;
          end;
        $86, $8A, $8C:
          begin
            StreamInfo^.HasCRC := True;
            I := EndianSwap(PInteger(Buff + 2)^) shr 8 + 9;
            J := ((EndianSwap(PInteger(Buff + 8)^) shr 8) and $7FFFF) + 11;
            if I > J then
            begin
              K := ((EndianSwap(PInteger(Buff + J)^) shr 8) and $7FFFF) + 3;
              if I <> (J + K) then
                exit;
            end
            else if I <> J then
              exit;
          end;
      else
        exit;
      end;
    end
    else
    begin
      if not(Buff + 1)^ in [ { $02, } $06, $0A, $0C] then
        exit;
    end;
    case (Buff + 1)^ of
      { $02:
        StreamInfo^.Codec := 0; // Old oodle }
      $06, $86:
        StreamInfo^.Codec := 1; // Kraken
      $0A, $8A:
        StreamInfo^.Codec := 2; // Mermaid/Selkie
      $0C, $8C:
        StreamInfo^.Codec := 3; // Leviathan
    end;
  end
  else
  begin
    if not(Buff^ in [$0C, $4C]) then
      exit;
    Compressed := Buff^ = $0C;
    if Compressed then
    begin
      case (Buff + 1)^ of
        { $02:
          if not(((Buff + 2)^ shr 4 = 0) and (((Buff + 4)^ shr 4 = $F) or
          ((Buff + 4)^ and $F = $F))) then
          exit; }
        $06, $0A, $0C:
          if not(Buff + 5)^ shr 4 in [3, 8] then
            exit;
        $86, $8A, $8C:
          if not(Buff + 8)^ shr 4 in [3, 8] then
            exit;
      end;
    end
    else
    begin
      if not(Buff + 1)^ in [$06, $0A, $0C] then
        exit;
    end;
  end;
  if Compressed then
  begin
    case (Buff + 1)^ of
      { $02:
        I := EndianSwap(PWord(Buff + 2)^) + 5; }
      $06, $0A, $0C:
        I := EndianSwap(PInteger(Buff + 2)^) shr 8 + 6;
      $86, $8A, $8C:
        I := EndianSwap(PInteger(Buff + 2)^) shr 8 + 9;
    else
      exit;
    end;
    if First and (I < MinSize) then
      exit;
    if StreamInfo^.CSize + I > Size then
    begin
      StreamInfo^.CSize := 0;
      StreamInfo^.DSize := 0;
      exit;
    end;
    if I = $00080005 then
      I := 6;
    Inc(StreamInfo^.CSize, I);
    Inc(StreamInfo^.DSize, BlkSize);
    Dec(MaxBlocks);
    GetOodleSI(Buff + I, Size, StreamInfo, MaxBlocks, False);
  end
  else
  begin
    case (Buff + 1)^ of
      $06, $0A, $0C:
        begin
          if (StreamInfo^.CSize + BlkSize + 3 <= Size) and
            ((Buff + BlkSize + 2)^ in [$0C, $4C]) and
            ((Buff + BlkSize + 3)^ = (Buff + 1)^) then
          begin
            Inc(StreamInfo^.CSize, BlkSize + 2);
            Inc(StreamInfo^.DSize, BlkSize);
          end
          else if (First = False) and (StreamInfo^.CSize + 8 <= Size) then
          begin
            Inc(StreamInfo^.CSize, 8 + 2);
            Inc(StreamInfo^.DSize, 8);
            exit;
          end
          else
            exit;
          Dec(MaxBlocks);
          GetOodleSI(Buff + BlkSize + 2, Size, StreamInfo, MaxBlocks, False);
        end;
    else
      exit;
    end;
  end;
end;

procedure OodleDecompressCB(userdata: Pointer; rawBuf: PByte;
  rawLen: NativeUInt; compBuf: PByte; compBufferSize, rawDone,
  compUsed: NativeUInt);
begin
  WriteLn(ErrOutput, rawDone);
end;

function LocalLZ_Decompress(asrc, adst: PByte; asrcSize, adstCapacity: Integer;
  aIdent: Byte; out Res: Integer): Integer;
const
  BlkSize = 262144;
var
  A, B: Integer;
begin
  B := IfThen(adstCapacity mod BlkSize = 0, Pred(adstCapacity div BlkSize),
    adstCapacity div BlkSize) * BlkSize;
  FillChar((adst + B)^, adstCapacity - B, aIdent);
  Res := OodleLZ_Decompress(asrc, asrcSize, adst, adstCapacity);
  A := Pred(adstCapacity);
  while A > B do
  begin
    if (adst + A)^ <> aIdent then
      break;
    Dec(A);
  end;
  Inc(A);
  Result := A;
end;

function CustomLZ_Decompress0(src, dst: PByte; srcSize, dstCapacity: Integer;
  var Res: Integer): Boolean;

type
  T3Res = array [0 .. 2] of Integer;

  procedure AddRes(const I: Integer; var Res: T3Res);
  begin
    Res[0] := Res[1];
    Res[1] := Res[2];
    Res[2] := I;
  end;

const
  MinSize = 64;
  BlkSize = 262144;
  Range = 262144;

  function ValidSize(Res: T3Res): Boolean;
  const
    ThresSize = 32;
  begin
    Result := (Res[0] > 0) and (Res[0] < Res[1]) and
      InRange(Res[0], Res[0], Res[2] + 32);
  end;

var
  LBuffer: array [0 .. BlkSize - 1] of Byte;
  I, J, W, X, Y, Z: Integer;
  LR1, LR2: T3Res;
begin
  Result := False;
  Y := Max(LocalLZ_Decompress(src, dst, srcSize, dstCapacity, 0, Z),
    LocalLZ_Decompress(src, dst, srcSize, dstCapacity, 1, Z));
  if Y > MinSize then
  begin
    W := IfThen(Y mod BlkSize = 0, Pred(Y div BlkSize), Y div BlkSize)
      * BlkSize;
    Move((dst + W)^, LBuffer[0], Y - W);
  end;
  if (Y = Z) and (Y = dstCapacity) then
  begin
    Res := Y;
    I := Max(LocalLZ_Decompress(src, dst, srcSize, dstCapacity - 1, 0, Z),
      LocalLZ_Decompress(src, dst, srcSize, dstCapacity - 1, 1, Z));
    if (Res <> I) and (Res <> Pred(I)) then
    begin
      Move(LBuffer[0], (dst + W)^, Res - W);
      Result := True;
      exit;
    end;
  end;
  FillChar(LR1, SizeOf(T3Res), 0);
  FillChar(LR2, SizeOf(T3Res), 0);
  I := Y;
  J := Min(dstCapacity, Y + Range);
  while I < J do
  begin
    Y := Max(LocalLZ_Decompress(src, dst, srcSize, I, 0, Z),
      LocalLZ_Decompress(src, dst, srcSize, I, 1, Z));
    AddRes(Y, LR1);
    AddRes(Z, LR2);
    if (LR1[1] = LR2[1]) and ValidSize(LR1) then
    begin
      Res := LR1[1];
      Move(LBuffer[0], (dst + W)^, Res - W);
      Result := True;
      break;
    end;
    if Y > MinSize then
    begin
      W := IfThen(Y mod BlkSize = 0, Pred(Y div BlkSize), Y div BlkSize)
        * BlkSize;
      Move((dst + W)^, LBuffer[0], Y - W);
    end;
    Inc(I);
  end;
end;

function CustomLZ_DecompressN(src, dst: PByte; srcSize, dstCapacity: Integer;
  var Res: TIntegerDynArray): Boolean;

const
  BlkSize = 262144;
  UpLen = 128;
  DownLen = 16;
var
  I, J, X, Y, Z: Integer;
  Sizes: array [0 .. UpLen + DownLen - 1] of Integer;
begin
  SetLength(Res, 0);
  Y := Max(LocalLZ_Decompress(src, dst, srcSize, dstCapacity, 0, Z),
    LocalLZ_Decompress(src, dst, srcSize, dstCapacity, 1, Z));
  for I := Low(Sizes) to High(Sizes) do
    Sizes[I] := -1;
  J := Min(dstCapacity, Y + UpLen);
  I := Max(IfThen(dstCapacity mod BlkSize = 0, Pred(dstCapacity div BlkSize),
    dstCapacity div BlkSize) * BlkSize, Y - DownLen);
  X := J - I;
  while (J > I) do
  begin
    Y := Max(LocalLZ_Decompress(src, dst, srcSize, J, 0, Z),
      LocalLZ_Decompress(src, dst, srcSize, J, 1, Z));
    Sizes[Length(Sizes) - (J - I)] := Z;
    Dec(J);
  end;
  for I := Low(Sizes) to High(Sizes) do
  begin
    X := Sizes[I];
    for J := Low(Sizes) to High(Sizes) do
    begin
      Y := Sizes[J];
      if I <> J then
        if X = Y then
        begin
          Sizes[I] := -1;
          Sizes[J] := -1;
        end;
    end;
  end;
  for I := Low(Sizes) to High(Sizes) do
    if Sizes[I] > srcSize then
      if OodleLZ_Decompress(src, srcSize, dst, Sizes[I]) = Sizes[I] then
      begin
        Insert(Sizes[I], Res, Length(Res));
        if Length(Res) >= OCount then
          break;
      end;
  Result := Length(Res) > 0;
end;

function GetOodleUS(Instance: Integer; Input: PByte; Pos: NativeInt;
  StreamInfo: POodleSI; Output: _PrecompOutput; Add: _PrecompAdd;
  Funcs: PPrecompFuncs): Integer;
const
  MinSize = 64;
var
  Buffer: PByte;
  B: Boolean;
  I: Integer;
  ResultN: TIntegerDynArray;
  SI: _StrInfo1;
begin
  Result := 0;
  { if StreamInfo^.Codec = 3 then
    exit; }
  // StreamInfo^.DSize:=$8001;
  Buffer := Funcs^.Allocator(Instance, StreamInfo^.DSize);
  if OCount <= 0 then
    B := CustomLZ_Decompress0(Input + Pos, Buffer, StreamInfo^.CSize,
      StreamInfo^.DSize, Result)
  else
  begin
    B := CustomLZ_DecompressN(Input + Pos, Buffer, StreamInfo^.CSize,
      StreamInfo^.DSize, ResultN);
    if B then
      Result := ResultN[0];
  end;
  If B then
    if (Result > MinSize) and (Result > StreamInfo^.CSize) then
    begin
      Output(Instance, Buffer, Result);
      SI.Position := Pos;
      SI.OldSize := StreamInfo^.CSize;
      SI.Option := 0;
      SetBits(SI.Option, OTradeOff, 13, 11);
      case StreamInfo^.Codec of
        1:
          SetBits(SI.Option, KRAKEN_CODEC, 0, 5);
        2:
          if CodecEnabled[MERMAID_CODEC] then
            SetBits(SI.Option, MERMAID_CODEC, 0, 5)
          else
            SetBits(SI.Option, SELKIE_CODEC, 0, 5);
        3:
          SetBits(SI.Option, LEVIATHAN_CODEC, 0, 5);
      end;
      if CodecEnabled[HYDRA_CODEC] then
        SetBits(SI.Option, HYDRA_CODEC, 0, 5);
      SetBits(SI.Option, Integer(StreamInfo^.HasCRC), 12, 1);
      SI.Status := TStreamStatus.None;
      if OCount <= 0 then
      begin
        SI.NewSize := Result;
        Funcs^.LogScan1(OodleCodecs[GetBits(SI.Option, 0, 5)], SI.Position,
          SI.OldSize, SI.NewSize);
        Add(Instance, @SI, nil, nil);
      end
      else
      begin
        if Length(ResultN) > 0 then
          for I := Low(ResultN) to High(ResultN) do
          begin
            SI.NewSize := ResultN[I];
            Funcs^.LogScan1(OodleCodecs[GetBits(SI.Option, 0, 5)], SI.Position,
              SI.OldSize, SI.NewSize);
            Add(Instance, @SI, nil, nil);
          end;
      end;
    end;
end;

function GetOodleCodec(Index: Integer): Integer;
begin
  case Index of
    LZNA_CODEC:
      Result := 7;
    KRAKEN_CODEC:
      Result := 8;
    MERMAID_CODEC:
      Result := 9;
    SELKIE_CODEC:
      Result := 11;
    HYDRA_CODEC:
      Result := 12;
    LEVIATHAN_CODEC:
      Result := 13;
  else
    Result := 8;
  end;
end;

function OodleInit(Command: PChar; Count: Integer;
  Funcs: PPrecompFuncs): Boolean;
var
  I: Integer;
  Options: TArray<Integer>;
  S: String;
  X, Y: Integer;
begin
  Result := True;
  SetLength(SOList, Count);
  for X := Low(SOList) to High(SOList) do
    for Y := Low(SOList[X]) to High(SOList[X]) do
      SOList[X, Y] := TSOList.Create([], TSOMethod.MTF);
  for X := Low(CodecAvailable) to High(CodecAvailable) do
  begin
    CodecAvailable[X] := False;
    CodecEnabled[X] := False;
  end;
  for X := Low(CodecAvailable) to High(CodecAvailable) do
    CodecAvailable[X] := OodleDLL.DLLLoaded;
  X := 0;
  while Funcs^.GetCodec(Command, X, False) <> '' do
  begin
    S := Funcs^.GetCodec(Command, X, False);
    for Y := Low(OodleCodecs) to High(OodleCodecs) do
      if (CompareText(S, OodleCodecs[Y]) = 0) and OodleDLL.DLLLoaded then
      begin
        CodecEnabled[Y] := True;
        if Funcs^.GetParam(Command, X, 'l') <> '' then
          for I := Low(SOList) to High(SOList) do
            SOList[I][Y].Update
              ([StrToInt(Funcs^.GetParam(Command, X, 'l'))], True);
        if Funcs^.GetParam(Command, X, 'n') <> '' then
          OCount := StrToInt(Funcs^.GetParam(Command, X, 'n'));
        if Funcs^.GetParam(Command, X, 't') <> '' then
          OTradeOff := StrToInt(Funcs^.GetParam(Command, X, 't'));
      end;
    Inc(X);
  end;
  SetLength(Options, 0);
  for I := 1 to 9 do
    Insert(I, Options, Length(Options));
  for X := Low(SOList) to High(SOList) do
    for Y := Low(SOList[X]) to High(SOList[X]) do
      if SOList[X, Y].Count = 0 then
        SOList[X, Y].Update(Options);
end;

procedure OodleFree(Funcs: PPrecompFuncs);
var
  X, Y: Integer;
begin
  for X := Low(SOList) to High(SOList) do
    for Y := Low(SOList[X]) to High(SOList[X]) do
      SOList[X, Y].Free;
end;

function OodleParse(Command: PChar; Option: PInteger;
  Funcs: PPrecompFuncs): Boolean;
var
  S: String;
  I, J: Integer;
begin
  Result := False;
  Option^ := 0;
  SetBits(Option^, OTradeOff, 13, 11);
  I := 0;
  while Funcs^.GetCodec(Command, I, False) <> '' do
  begin
    S := Funcs^.GetCodec(Command, I, False);
    for J := Low(OodleCodecs) to High(OodleCodecs) do
      if (CompareText(S, OodleCodecs[J]) = 0) and OodleDLL.DLLLoaded then
      begin
        SetBits(Option^, J, 0, 5);
        if Funcs^.GetParam(Command, I, 'l') <> '' then
          SetBits(Option^, StrToInt(Funcs^.GetParam(Command, I, 'l')), 5, 7);
        if String(Funcs^.GetParam(Command, I, 'c')) = '1' then
          SetBits(Option^, 1, 12, 1);
        if Funcs^.GetParam(Command, I, 't') <> '' then
          SetBits(Option^, StrToInt(Funcs^.GetParam(Command, I, 't')), 13, 11);
        Result := True;
      end;
    Inc(I);
  end;
end;

procedure OodleScan1(Instance, Depth: Integer; Input: PByte;
  Size, SizeEx: NativeInt; Output: _PrecompOutput; Add: _PrecompAdd;
  Funcs: PPrecompFuncs);
var
  Buffer: PByte;
  Pos: NativeInt;
  X: Integer;
  Res: Integer;
  SI: _StrInfo1;
  OodleSI: TOodleSI;
  DI1, DI2: TDepthInfo;
  DS: TPrecompStr;
begin
  DI1 := Funcs^.GetDepthInfo(Instance);
  DS := Funcs^.GetCodec(DI1.Codec, 0, False);
  if DS <> '' then
  begin
    X := IndexTextW(@DS[0], OodleCodecs);
    if (X < 0) or (DI1.OldSize <> SizeEx) then
      exit;
    if not CodecAvailable[X] then
      exit;
    if (X in [LZNA_CODEC, LEVIATHAN_CODEC]) and (DI1.NewSize <= 0) then
      exit;
    if DI1.NewSize <= 0 then
      Res := O_MAXSIZE
    else
      Res := DI1.NewSize;
    Buffer := Funcs^.Allocator(Instance, Res);
    case X of
      KRAKEN_CODEC, MERMAID_CODEC, SELKIE_CODEC, HYDRA_CODEC:
        begin
          if DI1.NewSize <= 0 then
          begin
            if not CustomLZ_Decompress0(Input, Buffer, DI1.OldSize, Res, Res)
            then
              Res := 0;
          end
          else
            Res := OodleLZ_Decompress(Input, DI1.OldSize, Buffer, Res);
        end;
    else
      begin
        if DI1.NewSize > 0 then
          Res := OodleLZ_Decompress(Input, DI1.OldSize, Buffer, Res)
        else
          Res := 0;
      end;
    end;
    if (Res > DI1.OldSize) then
    begin
      Output(Instance, Buffer, Res);
      SI.Position := 0;
      SI.OldSize := DI1.OldSize;
      SI.NewSize := Res;
      SI.Option := 0;
      SetBits(SI.Option, X, 0, 5);
      if System.Pos(SPrecompSep2, DI1.Codec) > 0 then
        SI.Status := TStreamStatus.Predicted
      else
        SI.Status := TStreamStatus.None;
      DS := Funcs^.GetDepthCodec(DI1.Codec);
      Move(DS[0], DI2.Codec, SizeOf(DI2.Codec));
      DI2.OldSize := SI.NewSize;
      DI2.NewSize := SI.NewSize;
      Funcs^.LogScan1(OodleCodecs[GetBits(SI.Option, 0, 5)], SI.Position,
        SI.OldSize, SI.NewSize);
      Add(Instance, @SI, DI1.Codec, @DI2);
    end;
    exit;
  end;
  if BoolArray(CodecEnabled, False) then
    exit;
  Pos := 0;
  while Pos < Size do
  begin
    GetOodleSI(Input + Pos, SizeEx - Pos, @OodleSI);
    if (OodleSI.CSize > 0) then
    begin
      if GetOodleUS(Instance, Input, Pos, @OodleSI, Output, Add, Funcs) > 0 then
      begin
        Inc(Pos, OodleSI.CSize);
        continue;
      end;
    end;
    Inc(Pos);
  end;
end;

function OodleScan2(Instance, Depth: Integer; Input: Pointer; Size: cardinal;
  StreamInfo: PStrInfo2; Offset: PInteger; Output: _PrecompOutput;
  Funcs: PPrecompFuncs): Boolean;
var
  Buffer: PByte;
  B: Boolean;
  I: Integer;
  ResultN: TIntegerDynArray;
  X: Integer;
  Res: Integer;
  OodleSI: TOodleSI;
begin
  Result := False;
  X := GetBits(StreamInfo^.Option, 0, 5);
  if (X <> LZNA_CODEC) and (StreamInfo^.OldSize <= 0) then
  begin
    GetOodleSI(Input, Size, @OodleSI);
    StreamInfo^.OldSize := OodleSI.CSize;
  end;
  if StreamInfo^.NewSize > 0 then
  begin
    Buffer := Funcs^.Allocator(Instance, StreamInfo^.NewSize);
    Res := OodleLZ_Decompress(Input, StreamInfo^.OldSize, Buffer,
      StreamInfo^.NewSize);
    if Res = StreamInfo^.NewSize then
    begin
      Output(Instance, Buffer, Res);
      Result := True;
    end;
  end
  else if (not X in [LZNA_CODEC, LEVIATHAN_CODEC]) and (StreamInfo^.NewSize <= 0)
  then
  begin
    Buffer := Funcs^.Allocator(Instance, OodleSI.DSize);
    if CustomLZ_Decompress0(Input, Buffer, StreamInfo^.OldSize,
      OodleSI.DSize, Res) then
    begin
      Output(Instance, Buffer, Res);
      StreamInfo^.NewSize := Res;
      Funcs^.LogScan2(OodleCodecs[GetBits(StreamInfo^.Option, 0, 5)],
        StreamInfo^.OldSize, StreamInfo^.NewSize);
      Result := True;
    end;
  end;
end;

function OodleProcess(Instance, Depth: Integer; OldInput, NewInput: Pointer;
  StreamInfo: PStrInfo2; Output: _PrecompOutput; Funcs: PPrecompFuncs): Boolean;
var
  Buffer: PByte;
  Params: String;
  I: Integer;
  X, Y: Integer;
  Res1: Integer;
  Res2: NativeUInt;
  COptions: TOodleLZ_CompressOptions;
begin
  Result := False;
  X := GetBits(StreamInfo^.Option, 0, 5);
  if BoolArray(CodecAvailable, False) or (CodecAvailable[X] = False) then
    exit;
  Y := GetOodleCodec(X);
  Buffer := Funcs^.Allocator(Instance, OodleLZ_GetCompressedBufferSizeNeeded(Y,
    StreamInfo^.NewSize));
  SOList[Instance][X].Index := 0;
  while SOList[Instance][X].Get(I) >= 0 do
  begin
    if StreamInfo^.Status = TStreamStatus.Predicted then
      if GetBits(StreamInfo^.Option, 5, 7) <> I then
        continue;
    Move(OodleLZ_CompressOptions_GetDefault(Y, I)^, COptions,
      SizeOf(TOodleLZ_CompressOptions));
    COptions.sendQuantumCRCs := GetBits(StreamInfo^.Option, 12, 1) = 1;
    COptions.spaceSpeedTradeoffBytes := GetBits(StreamInfo^.Option, 13, 11);
    Params := 'l' + I.ToString + ':' + 'c' + GetBits(StreamInfo^.Option, 12, 1)
      .ToString + ':' + 't' + GetBits(StreamInfo^.Option, 13, 11).ToString;
    Res1 := OodleLZ_Compress(Y, NewInput, StreamInfo^.NewSize, Buffer, I,
      @COptions);
    Result := (Res1 = StreamInfo^.OldSize) and CompareMem(OldInput, Buffer,
      StreamInfo^.OldSize);
    Funcs^.LogProcess(OodleCodecs[GetBits(StreamInfo^.Option, 0, 5)],
      PChar(Params), StreamInfo^.OldSize, StreamInfo^.NewSize, Res1, Result);
    if Result or (StreamInfo^.Status = TStreamStatus.Predicted) then
      break;
  end;
  if (Result = False) and ((StreamInfo^.Status = TStreamStatus.Predicted) or
    (SOList[Instance][X].Count = 1)) and (DIFF_TOLERANCE > 0) then
  begin
    Buffer := Funcs^.Allocator(Instance, Res1 + Max(StreamInfo^.OldSize, Res1));
    Res2 := PrecompEncodePatch(OldInput, StreamInfo^.OldSize, Buffer, Res1,
      Buffer + Res1, Max(StreamInfo^.OldSize, Res1));
    Funcs^.LogPatch1(StreamInfo^.OldSize, Res1, Res2,
      Funcs^.AcceptPatch(StreamInfo^.OldSize, Res1, Res2));
    if Funcs^.AcceptPatch(StreamInfo^.OldSize, Res1, Res2) then
    begin
      Output(Instance, Buffer + Res1, Res2);
      SetBits(StreamInfo^.Option, 1, 31, 1);
      SOList[Instance][X].Add(I);
      Result := True;
    end;
  end;
  if Result then
  begin
    SetBits(StreamInfo^.Option, I, 5, 7);
    SOList[Instance][X].Add(I);
  end;
end;

function OodleRestore(Instance, Depth: Integer; Input, InputExt: Pointer;
  StreamInfo: _StrInfo3; Output: _PrecompOutput; Funcs: PPrecompFuncs): Boolean;
var
  Buffer: PByte;
  Params: String;
  X, Y: Integer;
  Res1: Integer;
  Res2: NativeUInt;
  COptions: TOodleLZ_CompressOptions;
begin
  Result := False;
  X := GetBits(StreamInfo.Option, 0, 5);
  if CodecAvailable[X] = False then
    exit;
  Y := GetOodleCodec(X);
  Buffer := Funcs^.Allocator(Instance, OodleLZ_GetCompressedBufferSizeNeeded(Y,
    StreamInfo.NewSize));
  Move(OodleLZ_CompressOptions_GetDefault(Y, GetBits(StreamInfo.Option, 5, 7))^,
    COptions, SizeOf(TOodleLZ_CompressOptions));
  COptions.sendQuantumCRCs := GetBits(StreamInfo.Option, 12, 1) = 1;
  COptions.spaceSpeedTradeoffBytes := GetBits(StreamInfo.Option, 13, 11);
  Params := 'l' + GetBits(StreamInfo.Option, 5, 7).ToString + ':' + 'c' +
    GetBits(StreamInfo.Option, 12, 1).ToString + ':' + 't' +
    GetBits(StreamInfo.Option, 13, 11).ToString;
  Res1 := OodleLZ_Compress(Y, Input, StreamInfo.NewSize, Buffer,
    GetBits(StreamInfo.Option, 5, 7), @COptions);
  Funcs^.LogRestore(OodleCodecs[GetBits(StreamInfo.Option, 0, 5)],
    PChar(Params), StreamInfo.OldSize, StreamInfo.NewSize, Res1, True);
  if GetBits(StreamInfo.Option, 31, 1) = 1 then
  begin
    Buffer := Funcs^.Allocator(Instance, Res1 + StreamInfo.OldSize);
    Res2 := PrecompDecodePatch(InputExt, StreamInfo.ExtSize, Buffer, Res1,
      Buffer + Res1, StreamInfo.OldSize);
    Funcs^.LogPatch2(StreamInfo.OldSize, Res1, StreamInfo.ExtSize, Res2 > 0);
    if Res2 > 0 then
    begin
      Output(Instance, Buffer + Res1, StreamInfo.OldSize);
      Result := True;
    end;
    exit;
  end;
  if Res1 = StreamInfo.OldSize then
  begin
    Output(Instance, Buffer, StreamInfo.OldSize);
    Result := True;
  end;
end;

var
  I: Integer;

initialization

Codec.Names := [];
for I := Low(OodleCodecs) to High(OodleCodecs) do
begin
  Codec.Names := Codec.Names + [OodleCodecs[I]];
  StockMethods.Add(OodleCodecs[I]);
end;
Codec.Initialised := False;
Codec.Init := @OodleInit;
Codec.Free := @OodleFree;
Codec.Parse := @OodleParse;
Codec.Scan1 := @OodleScan1;
Codec.Scan2 := @OodleScan2;
Codec.Process := @OodleProcess;
Codec.Restore := @OodleRestore;
SetLength(CodecAvailable, Length(Codec.Names));
SetLength(CodecEnabled, Length(Codec.Names));

end.
