#ifndef XADMASTER_ACE_C
#define XADMASTER_ACE_C

/*  $Id: Ace.c,v 1.9 2005/06/23 14:54:40 stoecker Exp $
    Ace file archiver client

    XAD library system for archive handling
    Copyright (C) 1998 and later by Dirk Stöcker <soft@dstoecker.de>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/


#include "../unix/xadClient.h"

#ifndef XADMASTERVERSION
  #define XADMASTERVERSION      8
#endif

XADCLIENTVERSTR("Ace 1.2 (22.02.2004)")

#define ACE_VERSION             1
#define ACE_REVISION            2
#define ACEEXE_VERSION          ACE_VERSION
#define ACEEXE_REVISION         ACE_REVISION

#define ACETYPE_MAIN            0
#define ACETYPE_FILE            1
#define ACETYPE_RECOVERY        2

#define ACEFLAG_ADDSIZE         (1<< 0)
#define ACEFLAG_COMMENT         (1<< 1)

#define ACEMFLAG_SFXARCHIVE     (1<<( 9-8))
#define ACEMFLAG_DICTLIM256     (1<<(10-8))
#define ACEMFLAG_MULTIVOLUME    (1<<(11-8))
#define ACEMFLAG_AVSTRING       (1<<(12-8))
#define ACEMFLAG_RECOVERY       (1<<(13-8))
#define ACEMFLAG_LOCKED         (1<<(14-8))
#define ACEMFLAG_SOLID          (1<<(15-8))

#define ACEFFLAG_CONTPREVIOUS   (1<<(12-8))     /* file is continued from last volume */
#define ACEFFLAG_CONTNEXT       (1<<(13-8))     /* file is continued in next volume */
#define ACEFFLAG_PASSWORD       (1<<(14-8))
#define ACEFFLAG_SOLID          (1<<(15-8))

#define ACEHOST_MSDOS           0
#define ACEHOST_OS2             1
#define ACEHOST_WIN32           2
#define ACEHOST_UNIX            3
#define ACEHOST_MAC_OS          4
#define ACEHOST_WIN NT          5
#define ACEHOST_PRIMOS          6
#define ACEHOST_APPLE_GS        7
#define ACEHOST_ATARI           8
#define ACEHOST_VAX_VMS         9
#define ACEHOST_AMIGA           10
#define ACEHOST_NEXT            11

#define ACECOMP_STORED          0
#define ACECOMP_LZ77            1
#define ACECOMP_BLOCKED         2

#define ACEBLOCK_LZ77_NORM         0
#define ACEBLOCK_LZ77_DELTA        1
#define ACEBLOCK_LZ77_EXE          2
#define ACEBLOCK_SOUND_8           3
#define ACEBLOCK_SOUND_16          4
#define ACEBLOCK_SOUND_32_1        5
#define ACEBLOCK_SOUND_32_2        6
#define ACEBLOCK_PIC               7

#define ACECOMPQUALITY_FASTEST  0
#define ACECOMPQUALITY_FAST     1
#define ACECOMPQUALITY_NORMAL   2
#define ACECOMPQUALITY_GOOD     3
#define ACECOMPQUALITY_BEST     4

/* AceHead:
  xadUINT16 HeaderCRC;          // start at HeaderType!
  xadUINT16 HeaderSize;

  xadUINT8 HeaderType;          // 00           // ACETYPE_MAIN
  xadUINT8 HeaderFlags[2];      // 01
  xadUINT8 AceSign[7];          // 03           // **ACE**
  xadUINT8 ExtractVersion;      // 10
  xadUINT8 CreaterVersion;      // 11
  xadUINT8 Host;                // 12
  xadUINT8 VolumeNumber;        // 13
  xadUINT32 Date;               // 14
  xadUINT8 Reserved[8];         // 18

  xadUINT8 AVSize;              // 26
  ...   Advert string

  xadUINT16 CommentSize;        // compressed
  ...   Comment string
*/

/* AceFile:
  xadUINT16 HeaderCRC;          // start at HeaderType!
  xadUINT16 HeaderSize;

  xadUINT8 HeaderType;          // 00           // ACETYPE_FILE
  xadUINT8 HeaderFlags[2];      // 01
  xadUINT32     PackSize;       // 03
  xadUINT32 OrigSize;           // 07
  xadUINT32 Date;               // 11
  xadUINT32 Attributes;         // 15
  xadUINT32 CRC32;              // 19
  xadUINT8 Compression;         // 23           // ACECOMP_xxx
  xadUINT8 CompQuality;         // 24           // ACECOMPQUALITY_xxx
  xadUINT16 DecompParam;        // 25
  xadUINT16     Reserved;

  xadUINT16 FilenameSize;       // 29
  ...   filename string

  xadUINT16 CommentSize;        // compressed
  ...   Comment string
*/

#define ACEPI(a)        ((struct AcePrivate *) ((a)->xfi_PrivateInfo))

struct AcePrivate {
  xadUINT32 CRC32;
  xadUINT8 Compression;
  xadUINT8 Quality;
  xadUINT16 DecompParam;
  struct xadFileInfo *Solid;
  xadUINT8 Flags;                               /* only password+solid needed */
};

#define ACEBUFSIZE 10240

/****************************************************************************/

#define ACEmaxdic      22
#define ACEmaxwd_mn    11
#define ACEmaxwd_lg    11
#define ACEmaxwd_svwd   7
#define ACEmaxlength  259
#define ACEmaxdis2    255
#define ACEmaxdis3   8191
#define ACEmaxcode   (255+4+ACEmaxdic)
#define ACEsvwd_cnt    15
#define ACEmax_cd_mn (256+4+(ACEmaxdic+1)-1)
#define ACEmax_cd_lg (256-1)

struct AceData {
  xadINT32   err;
  struct xadArchiveInfo *ai;
  struct xadMasterBase *xadMasterBase;
  struct xadFileInfo *lastfile;
  xadUINT32  insize;
  xadUINT32  inbufsize;
  xadSTRPTR inbuffer;
  xadUINT32 crc32;

  xadUINT8 *dcpr_text;
  xadUINT32 code_rd;
  xadUINT32 dcpr_dicsiz;
  xadUINT32 dcpr_dican;
  xadUINT32 dcpr_size;
  xadUINT32 dcpr_dpos;
  xadUINT32 rpos;

  xadUINT16 bits_rd;
  xadINT16  dcpr_dic;

  xadUINT16 dcpr_code_mn[1 << ACEmaxwd_mn];
  xadUINT16 sort_org[ACEmaxcode + 2];
  xadUINT16 dcpr_code_lg[1 << ACEmaxwd_lg];
  xadUINT8 dcpr_wd_mn[ACEmaxcode + 2];
  xadUINT8 wd_svwd[ACEsvwd_cnt];
  xadUINT8 sort_freq[(ACEmaxcode + 2) * 2];
  xadUINT8 dcpr_wd_lg[ACEmaxcode + 2];
};

static const xadINT8 ACEswapdata[4] = {3,1,-1,-3};

/* ACEaddbits(ad,0); initializes the stuff (together with ad->rpos = ad->inbufsize) */
static void ACEaddbits(struct AceData *ad, xadUINT16 bits)
{
  ad->code_rd <<= bits;
  ad->bits_rd -= bits;
  while(ad->bits_rd <= 24 && ad->insize) /* 3 byte */
  {
    if(ad->rpos == ad->inbufsize)
    {
      ad->rpos = 0;
      if(!ad->err && ad->ai)
      {
        xadUINT32 i;
        struct xadMasterBase *xadMasterBase = ad->xadMasterBase;

        if((i = ad->inbufsize) > ad->insize)
          i = ad->insize;
        ad->err = xadHookTagAccess(XADM XADAC_READ, i, ad->inbuffer, ad->ai, XAD_USESKIPINFO, XADTRUE, TAG_DONE);
      }
    }

    ad->code_rd |= ad->inbuffer[ad->rpos+ACEswapdata[ad->rpos&3]]<<(24-ad->bits_rd);
    ad->insize--;
    ad->rpos++;
    ad->bits_rd += 8;
  }
}

#define ACExchg_def(v1,v2) {tmp = v1; v1 = v2; v2 = tmp;}
static void ACEsortrange(struct AceData *ad, xadINT32 left, xadINT32 right)
{
  xadINT32 zl = left, zr = right, hyphen, tmp;

  hyphen = ad->sort_freq[right];

  /* divides by hyphen the given range into 2 parts */
  do
  {
    while(ad->sort_freq[zl] > hyphen)
      zl++;
    while(ad->sort_freq[zr] < hyphen)
      zr--;
    /* found a too small (left side) and a too big (right side) element-->exchange them */
    if(zl <= zr)
    {
      ACExchg_def(ad->sort_freq[zl], ad->sort_freq[zr]);
      ACExchg_def(ad->sort_org[zl], ad->sort_org[zr]);
      zl++;
      zr--;
    }
  } while(zl < zr);

  /* sort partial ranges - when very small, sort directly */
  if(left < zr)
  {
    if(left < zr - 1)
      ACEsortrange(ad, left, zr);
    else if(ad->sort_freq[left] < ad->sort_freq[zr])
    {
      ACExchg_def(ad->sort_freq[left], ad->sort_freq[zr]);
      ACExchg_def(ad->sort_org[left], ad->sort_org[zr]);
    }
  }

  if(right > zl)
  {
    if(zl < right - 1)
      ACEsortrange(ad, zl, right);
    else if(ad->sort_freq[zl] < ad->sort_freq[right])
    {
      ACExchg_def(ad->sort_freq[zl], ad->sort_freq[right]);
      ACExchg_def(ad->sort_org[zl], ad->sort_org[right]);
    }
  }
}

static xadINT32 ACEmakecode(struct AceData *ad, xadUINT32 maxwd, xadUINT32 size1_t, xadUINT8 *wd, xadUINT16 *code)
{
  xadUINT32 maxc, size2_t, l, c, i, max_make_code;
  struct xadMasterBase *xadMasterBase = ad->xadMasterBase;

  xadCopyMem(XADM wd, (xadPTR) &ad->sort_freq, (size1_t + 1) * sizeof(xadUINT8));
  if(size1_t)
  { /* quicksort */
    for(i = size1_t + 1; i--;)
      ad->sort_org[i] = i;
    ACEsortrange(ad, 0, size1_t);
  }
  else
    ad->sort_org[0] = 0;
  ad->sort_freq[size1_t + 1] = size2_t = c = 0;
  while(ad->sort_freq[size2_t])
    size2_t++;
  if(size2_t < 2)
  {
    i = ad->sort_org[0];
    wd[i] = 1;
    size2_t += (size2_t == 0);
  }
  size2_t--;

  max_make_code = 1 << maxwd;
  for(i = size2_t + 1; i-- && c < max_make_code;)
  {
    maxc = 1 << (maxwd - ad->sort_freq[i]);
    l = ad->sort_org[i];
    if(c + maxc > max_make_code)
      return 0;
    while(maxc--)
      code[c++] = l;
  }
  return 1;
}

static xadINT32 ACEread_wd(struct AceData *ad, xadUINT32 maxwd, xadUINT16 *code, xadUINT8 *wd, xadINT32 max_el)
{
  xadUINT32 c, i, j, num_el, l, uplim, lolim;

  memset(wd, 0, max_el * sizeof(xadINT8));
  memset(code, 0, (1 << maxwd) * sizeof(xadUINT16));

  num_el = ad->code_rd >> (32 - 9);
  ACEaddbits(ad, 9);
  if(num_el > max_el)
    num_el = max_el;

  lolim = ad->code_rd >> (32 - 4);
  ACEaddbits(ad, 4);
  uplim = ad->code_rd >> (32 - 4);
  ACEaddbits(ad, 4);

  for(i = -1; ++i <= uplim;)
  {
    ad->wd_svwd[i] = ad->code_rd >> (32 - 3);
    ACEaddbits(ad, 3);
  }
  if(!ACEmakecode(ad, ACEmaxwd_svwd, uplim, ad->wd_svwd, code))
    return 0;
  j = 0;
  while(j <= num_el)
  {
    c = code[ad->code_rd >> (32 - ACEmaxwd_svwd)];
    ACEaddbits(ad, ad->wd_svwd[c]);
    if(c < uplim)
      wd[j++] = c;
    else
    {
      l = (ad->code_rd >> 28) + 4;
      ACEaddbits(ad, 4);
      while(l-- && j <= num_el)
        wd[j++] = 0;
    }
  }
  if(uplim)
    for(i = 0; ++i <= num_el;)
      wd[i] = (wd[i] + wd[i - 1]) % uplim;
  for(i = -1; ++i <= num_el;)
    if(wd[i])
      wd[i] += lolim;

  return ACEmakecode(ad, maxwd, num_el, wd, code);
}

static xadINT32 AceDecrComment(xadUINT8 *src, xadUINT8 *dest, xadINT32 comm_size, struct xadMasterBase *xadMasterBase)
{
  xadINT32 err = XADERR_NOMEMORY;
  xadUINT16 *hash;
  struct AceData *ad;
  xadINT32 dpos = 0, c, pos = 0, len, hs;

  if((hash = (xadUINT16 *) xadAllocVec(XADM (255+255+1)*sizeof(xadUINT16), XADMEMF_PUBLIC|XADMEMF_CLEAR)))
  {
    if((ad = (struct AceData *) xadAllocVec(XADM sizeof(struct AceData), XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    {
      ad->insize = ad->inbufsize = comm_size;
      ad->inbuffer = (xadSTRPTR) src;
      ad->xadMasterBase = xadMasterBase;

      ACEaddbits(ad,0); /* initialize */
      len = ad->code_rd >> (32 - 15);
      ACEaddbits(ad,15);
      if(ACEread_wd(ad, ACEmaxwd_mn, ad->dcpr_code_mn, ad->dcpr_wd_mn, ACEmax_cd_mn))
      {
        do
        {
          if(dpos > 1)
          {
            pos = hash[hs = dest[dpos - 1] + dest[dpos - 2]];
            hash[hs] = dpos;
          }
          ACEaddbits(ad, ad->dcpr_wd_mn[(c = ad->dcpr_code_mn[ad->code_rd >> (32 - ACEmaxwd_mn)])]);
          if(c > 255)
          {
             c = c - 256 + 2;
             while(c--)
               dest[dpos++] = dest[pos++];
          }
          else
            dest[dpos++] = c;
        } while(dpos < len);
        err = 0;
      }

      xadFreeObjectA(XADM ad, 0);
    }
    xadFreeObjectA(XADM hash, 0);
  }
  return err;
}

static xadINT32 ACEdecompress(struct AceData *ad, xadUINT32 save)
{
  xadUINT32 startpos, num = 0;
  xadINT32  c, lg, i, k;
  xadUINT32 dist, mpos, olddist[4];
  xadINT16  oldnum = 0, blocksize = 0;
  struct xadMasterBase *xadMasterBase = ad->xadMasterBase;

  startpos = ad->dcpr_dpos;
  memset(&olddist, 0, 4*4);
  while(num < ad->dcpr_size && !ad->err)
  {
    if(!blocksize)
    {
      if(!ACEread_wd(ad, ACEmaxwd_mn, ad->dcpr_code_mn, ad->dcpr_wd_mn, ACEmax_cd_mn) ||
      !ACEread_wd(ad, ACEmaxwd_lg, ad->dcpr_code_lg, ad->dcpr_wd_lg, ACEmax_cd_lg))
        return XADERR_DECRUNCH;
      blocksize = ad->code_rd >> (32 - 15);
      ACEaddbits(ad, 15);
    }

    ACEaddbits(ad, ad->dcpr_wd_mn[(c = ad->dcpr_code_mn[ad->code_rd >> (32 - ACEmaxwd_mn)])]);
    blocksize--;
    if(c > 255)
    {
      if(c > 259)
      {
        if((c -= 260) > 1)
        {
          dist = (ad->code_rd >> (33 - c)) + (1L << (c - 1));
          ACEaddbits(ad, c - 1);
        }
        else
          dist = c;
        olddist[(oldnum = (oldnum + 1) & 3)] = dist;
        i = 2;
        if(dist > ACEmaxdis2)
        {
          i++;
          if(dist > ACEmaxdis3)
            i++;
        }
      }
      else
      {
        dist = olddist[(oldnum - (c &= 255)) & 3];
        for(k = c + 1; k--;)
          olddist[(oldnum - k) & 3] = olddist[(oldnum - k + 1) & 3];
        olddist[oldnum] = dist;
        i = 2;
        if(c > 1)
          i++;
      }
      ACEaddbits(ad, ad->dcpr_wd_lg[(lg = ad->dcpr_code_lg[ad->code_rd >> (32 - ACEmaxwd_lg)])]);
      lg += i;
      mpos = ad->dcpr_dpos - ++dist;
      num += lg;
      while(lg--)
      {
        mpos &= ad->dcpr_dican;
        ad->dcpr_text[ad->dcpr_dpos++] = ad->dcpr_text[mpos++];
        if(ad->dcpr_dpos > ad->dcpr_dican)
        {
          ad->crc32 = xadCalcCRC32(XADM XADCRC32_ID1, ad->crc32, ad->dcpr_dpos-startpos, ad->dcpr_text+startpos);
          if(save)
            ad->err = xadHookAccess(XADM XADAC_WRITE, ad->dcpr_dpos-startpos, ad->dcpr_text+startpos, ad->ai);
          startpos = (ad->dcpr_dpos &= ad->dcpr_dican);
        }
      }
    }
    else
    {
      ad->dcpr_text[ad->dcpr_dpos++] = c;
      ++num;
      if(ad->dcpr_dpos > ad->dcpr_dican)
      {
        ad->crc32 = xadCalcCRC32(XADM XADCRC32_ID1, ad->crc32, ad->dcpr_dpos-startpos, ad->dcpr_text+startpos);
        if(save)
          ad->err = xadHookAccess(XADM XADAC_WRITE, ad->dcpr_dpos-startpos, ad->dcpr_text+startpos, ad->ai);
        startpos = (ad->dcpr_dpos &= ad->dcpr_dican);
      }
    }
  }
  ad->crc32 = xadCalcCRC32(XADM XADCRC32_ID1, ad->crc32, ad->dcpr_dpos-startpos, ad->dcpr_text+startpos);
  if(!ad->err && save)
    ad->err = xadHookAccess(XADM XADAC_WRITE, ad->dcpr_dpos-startpos, ad->dcpr_text+startpos, ad->ai);
  return ad->err;
}

static xadINT32 AceExtractEntry(struct AceData *ad, struct xadFileInfo *fi)
{
  struct xadMasterBase *xadMasterBase = ad->xadMasterBase;
  xadINT32 err;
  xadUINT32 i;
  struct xadFileInfo *fis;

  if(ACEPI(fi)->Flags & ACEFFLAG_PASSWORD)
    return XADERR_NOTSUPPORTED;

  fis = fi;
  if(ad->lastfile)
  {
    while(ACEPI(fis)->Solid && fis != ad->lastfile->xfi_Next)
      fis = ACEPI(fis)->Solid;
  }
  else
    while(ACEPI(fis)->Solid)
      fis = ACEPI(fis)->Solid;

  if(!ACEPI(fis)->Solid)
    ad->dcpr_dpos = 0;

  do
  {
    ad->crc32 = ~0;
    ad->insize = (xadUINT32)fis->xfi_CrunchSize;

    if(!(err = xadHookAccess(XADM XADAC_INPUTSEEK, fis->xfi_DataPos-ad->ai->xai_InPos, 0, ad->ai)))
    {
      switch(ACEPI(fis)->Compression)
      {
      case ACECOMP_STORED:
        while(!err && ad->insize)
        {
          i = ad->insize;
          if(ad->dcpr_dpos + i > ad->dcpr_dicsiz)
            i = ad->dcpr_dicsiz - ad->dcpr_dpos;
          if(!(err = xadHookTagAccess(XADM XADAC_READ, i, ad->dcpr_text+ad->dcpr_dpos,  ad->ai, XAD_GETCRC32, &ad->crc32,
          XAD_USESKIPINFO, 1, TAG_DONE)))
          {
            if(fis == fi)
              err = xadHookAccess(XADM XADAC_WRITE, i, ad->dcpr_text+ad->dcpr_dpos, ad->ai);
          }
          ad->dcpr_dpos = (ad->dcpr_dpos+i) & ad->dcpr_dican;
          ad->insize -= i;
        }
        break;
      case ACECOMP_LZ77:
        if((ACEPI(fis)->DecompParam & 15) + 10 > ad->dcpr_dic)
         err = XADERR_NOMEMORY;
        else
        {
          ad->dcpr_size = (xadUINT32)fis->xfi_Size;
          ad->bits_rd = 0;
          ad->rpos = ad->inbufsize; /* enforce start read */
          ACEaddbits(ad,0);
          err = ACEdecompress(ad, fis == fi ? 1 : 0);
        }
        break;
      default: err = XADERR_DATAFORMAT;
      }
      if(!err && ad->crc32 != ACEPI(fis)->CRC32)
        err = XADERR_CHECKSUM;
    }
    fis = fis->xfi_Next;
  } while(!err && fis != fi->xfi_Next);

  ad->lastfile = fi;

  return err;
}

/****************************************************************************/

static const xadSTRPTR acetypes[2] = {(xadSTRPTR)"stored", (xadSTRPTR)"lz77"};

XADGETINFO(Ace)
{
  xadINT32 err = 0;
  xadUINT8 blockdata[256], *bptr;
  struct xadFileInfo *fi = 0, *fi2;
  xadUINT32 i, num = 1, lastpos = 0;
  xadUINT8 ac[4];

  while(ai->xai_InPos + 3 < ai->xai_InSize && !err)
  {
    if(!(err = xadHookAccess(XADM XADAC_READ, 4, &ac, ai)))
    {
      i = EndGetI16(ac+2); /* Size */
      if(i <= 256)
        bptr = blockdata;
      else if(!(bptr = (xadUINT8 *) xadAllocVec(XADM i+1, XADMEMF_PUBLIC)))
        err = XADERR_NOMEMORY;

      if(!err && !(err = xadHookAccess(XADM XADAC_READ, i, bptr, ai)))
      {
        if(EndGetI16(ac) != (xadUINT16) xadCalcCRC32(XADM XADCRC32_ID1, ~0, i, bptr))
          err = XADERR_CHECKSUM;
        else
        {
          if(bptr[0] == ACETYPE_MAIN)
          {
            if(bptr[1] & ACEFLAG_COMMENT)
            {
              if((fi2 = xadAllocObjectA(XADM XADOBJ_FILEINFO, 0)))
              {
                fi2->xfi_FileName = (xadSTRPTR)"AceInfo.TXT";
                fi2->xfi_EntryNumber = num++;
                fi2->xfi_DataPos = ai->xai_InPos-i+29+bptr[26];
                fi2->xfi_CrunchSize = EndGetI16(bptr+bptr[26]+27);
                fi2->xfi_Size = EndGetI16(bptr+31+bptr[26])>>1;
                fi2->xfi_Flags = XADFIF_NODATE|XADFIF_SEEKDATAPOS|XADFIF_INFOTEXT|XADFIF_NOFILENAME;
                xadConvertDates(XADM XAD_DATECURRENTTIME, 1, XAD_GETDATEXADDATE, &fi2->xfi_Date, TAG_DONE);
                if(!fi)
                  ai->xai_FileInfo = fi2;
                else
                  fi->xfi_Next = fi2;
                fi = fi2;
              }
              else
                err = XADERR_NOMEMORY;
            }
          }
          else if(bptr[0] == ACETYPE_FILE)
          {
            xadUINT32 namesize, commentsize = 0;

            namesize = EndGetI16(bptr+29);
            if(bptr[1] & ACEFLAG_COMMENT)
              commentsize = EndGetI16(bptr+35+namesize)>>1;

            if((bptr[2] & ACEFFLAG_CONTPREVIOUS) && fi && (ACEPI(fi)->Flags & ACEFFLAG_CONTNEXT) &&
            fi->xfi_Size == EndGetI32(bptr+7))
            {
              struct xadSkipInfo *si;
              if((si = (struct xadSkipInfo *) xadAllocObjectA(XADM XADOBJ_SKIPINFO, 0)))
              {
                si->xsi_Next = ai->xai_SkipInfo;
                ai->xai_SkipInfo = si;
                si->xsi_SkipSize = ai->xai_InPos - lastpos;
                si->xsi_Position = lastpos;
                ACEPI(fi)->CRC32 = EndGetI32(bptr+19);
                fi->xfi_CrunchSize += EndGetI32(bptr+3);
                lastpos = (xadUINT32)ai->xai_InPos + EndGetI32(bptr+3);
              }
              else
                err = XADERR_NOMEMORY;
            }
            else if((fi2 = xadAllocObject(XADM XADOBJ_FILEINFO, XAD_OBJNAMESIZE, namesize+1,
            commentsize ? XAD_OBJCOMMENTSIZE : TAG_IGNORE, commentsize+1,
            XAD_OBJPRIVINFOSIZE, sizeof(struct AcePrivate), TAG_DONE)))
            {
              xadCopyMem(XADM bptr+31, fi2->xfi_FileName, namesize);
              for(i = 0; i < namesize; ++i)
              {
                if(fi2->xfi_FileName[i] == '\\')
                  fi2->xfi_FileName[i] = '/';
              }
              if(commentsize)
                AceDecrComment((xadUINT8 *)bptr+33+namesize, (xadUINT8 *)fi2->xfi_Comment, EndGetI16(bptr+31+namesize), xadMasterBase);

              fi2->xfi_EntryNumber = num++;
              fi2->xfi_Size = EndGetI32(bptr+7);
              fi2->xfi_CrunchSize = EndGetI32(bptr+3);
              ACEPI(fi2)->CRC32 = EndGetI32(bptr+19);
              ACEPI(fi2)->Compression = bptr[23];
              ACEPI(fi2)->Quality = bptr[24];
              ACEPI(fi2)->Flags = bptr[2];
              ACEPI(fi2)->DecompParam = EndGetI16(bptr+25);
              if(bptr[23] <= 1)
                fi2->xfi_EntryInfo = acetypes[bptr[23]];
              i = EndGetI32(bptr+15);
              if(i & 0x10)
                fi2->xfi_Flags |= XADFIF_DIRECTORY;
              //xadConvertProtection(XADM XAD_PROTMSDOS, EndGetI32(bptr+15), XAD_GETPROTAMIGA, &fi2->xfi_Protection, TAG_DONE);
              xadConvertDates(XADM XAD_DATEMSDOS, EndGetI32(bptr+11), XAD_GETDATEXADDATE, &fi2->xfi_Date, TAG_DONE);
              if(bptr[2] & ACEFFLAG_PASSWORD)
              {
                fi2->xfi_Flags |= XADFIF_CRYPTED;
                ai->xai_Flags |= XADAIF_CRYPTED;
              }
              fi2->xfi_DataPos = ai->xai_InPos;
              if(bptr[2] & ACEFFLAG_SOLID)
              {
                ACEPI(fi2)->Solid = fi;
              }

              if(!fi)
                ai->xai_FileInfo = fi2;
              else
                fi->xfi_Next = fi2;
              fi = fi2;
              lastpos = (xadUINT32)(fi2->xfi_DataPos + fi->xfi_CrunchSize);
            }
            else
              err = XADERR_NOMEMORY;
          }
          if(bptr[1] & ACEFLAG_ADDSIZE)
            err = xadHookAccess(XADM XADAC_INPUTSEEK, EndGetI32(bptr+3), 0, ai);
        }
      }

      if(bptr && bptr != blockdata)
        xadFreeObjectA(XADM bptr, 0);
    }
  }

  if(err && ai->xai_FileInfo)
  {
    ai->xai_Flags |= XADAIF_FILECORRUPT;
    ai->xai_LastError = err;
    err = 0;
  }

  return err;
}

XADRECOGDATA(Ace)
{
  if(data[7] == '*' && data[8] == '*' && data[9] == 'A' && data[10] == 'C'
  && data[11] == 'E' && data[12] == '*' && data[13] == '*')
    return 1;
  else
    return 0;
}

/****************************************************************************/

XADUNARCHIVE(Ace)
{
  xadINT32 err = 0;
  struct xadFileInfo *fi;

  fi = ai->xai_CurFile;

  if(!fi->xfi_PrivateInfo) /* comment */
  {
    xadSTRPTR buf;

    if((buf = (xadSTRPTR) xadAllocVec(XADM fi->xfi_CrunchSize+fi->xfi_Size, XADMEMF_PUBLIC)))
    {
      if(!(err = xadHookAccess(XADM XADAC_READ, fi->xfi_CrunchSize, buf, ai)))
      {
        if(!(err = AceDecrComment((xadUINT8 *)buf, (xadUINT8 *)(buf+fi->xfi_CrunchSize), (xadUINT32)fi->xfi_CrunchSize, xadMasterBase)))
          err = xadHookAccess(XADM XADAC_WRITE, fi->xfi_Size, buf+fi->xfi_CrunchSize, ai);
      }
      xadFreeObjectA(XADM buf, 0);
    }
    else
      err = XADERR_NOMEMORY;
  }
  else if(ACEPI(fi)->Compression == ACECOMP_STORED && !(ACEPI(fi)->Flags & ACEFFLAG_PASSWORD))
  {
    /* this ensures we can always extract stored files, even if solid extraction fails */
    xadUINT32 crc32 = ~0;
    if(!(err = xadHookAccess(XADM XADAC_INPUTSEEK, fi->xfi_DataPos-ai->xai_InPos, 0, ai)))
      if(!(err = xadHookTagAccess(XADM XADAC_COPY, fi->xfi_Size, 0, ai, XAD_GETCRC32, &crc32, XAD_USESKIPINFO, 1, TAG_DONE)))
        if(crc32 != ACEPI(fi)->CRC32)
          err = XADERR_CHECKSUM;
  }
  else
  {
    struct AceData *ad;
    if(!(ad = (struct AceData *) ai->xai_PrivateClient))
    {
      if((ad = (struct AceData *) xadAllocVec(XADM sizeof(struct AceData)+ACEBUFSIZE, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
      {
        ai->xai_PrivateClient = ad;
        ad->ai = ai;
        ad->inbufsize = ACEBUFSIZE;
        ad->inbuffer = (xadSTRPTR) (ad+1);
        ad->xadMasterBase = xadMasterBase;
        ad->dcpr_dic = 20;
        while(!(ad->dcpr_text = xadAllocVec(XADM ad->dcpr_dicsiz = (xadINT32) 1 << ad->dcpr_dic, XADMEMF_PUBLIC)))
          ad->dcpr_dic--;
        ad->dcpr_dican = ad->dcpr_dicsiz - 1;
      }
      else
        err = XADERR_NOMEMORY;
    }
    if(ad)
    {
      if((err = AceExtractEntry(ad, fi)))
      { /* allow restart for next entry */
        xadFreeObjectA(XADM ad->dcpr_text, 0);
        xadFreeObjectA(XADM ad, 0);
        ai->xai_PrivateClient = 0;
      }
    }
  }

  return err;
}

XADFREE(Ace)
{
  if(ai->xai_PrivateClient) /* decrunch buffer */
  {
    if(((struct AceData *) ai->xai_PrivateClient)->dcpr_text)
      xadFreeObjectA(XADM ((struct AceData *) ai->xai_PrivateClient)->dcpr_text, 0);
    xadFreeObjectA(XADM ai->xai_PrivateClient, 0);
    ai->xai_PrivateClient = 0;
  }
}

/****************************************************************************/

XADRECOGDATA(AceEXE)
{
  xadUINT32 i;

  if(size < 14 || data[0] != 0x4D || data[1] != 0x5A)
    return 0;

  data += 7;
  for(i = 7; i < size-6; ++i)
  {
    if(data[0] == '*' && data[1] == '*' && data[2] == 'A' && data[3] == 'C'
    && data[4] == 'E' && data[5] == '*' && data[6] == '*')
      return 1;
    ++data;
  }
  return 0;
}

XADGETINFO(AceEXE)
{
  xadINT32 i= 0, err = 0, found = 0;
  xadSTRPTR buf;
  xadUINT32 bufsize, fsize, spos = 0;

  if((fsize = (xadUINT32)ai->xai_InSize) < 20)
    return 0;

  if((bufsize = ACEBUFSIZE) > fsize)
    bufsize = fsize;

  if(!(buf = xadAllocVec(XADM bufsize, XADMEMF_PUBLIC)))
    return XADERR_NOMEMORY;

  while(!err && !found && fsize >= 20)
  {
    if(!(err = xadHookAccess(XADM XADAC_READ, bufsize-spos, buf+spos, ai)))
    {
      for(i = 0; i < bufsize - 15 && !found; ++i)
      {
        if(buf[i+7] == '*' && buf[i+8] == '*' && buf[i+9] == 'A' && buf[i+10] == 'C'
        && buf[i+11] == 'E' && buf[i+12] == '*' && buf[i+13] == '*')
          found = 1;
      }
      if(!found)
      {
        xadCopyMem(XADM buf+i, buf, 15);
        spos = 15;
        fsize -= bufsize - 15;
        if(fsize < bufsize)
          bufsize = fsize;
      }
    }
  }

  xadFreeObjectA(XADM buf, 0);

  if(found && !(err = xadHookAccess(XADM XADAC_INPUTSEEK, i-1-bufsize, 0, ai)))
    err = Ace_GetInfo(ai, xadMasterBase);

  return err;
}

/****************************************************************************/

XADCLIENT(AceEXE) {
  XADNEXTCLIENT,
  XADCLIENT_VERSION,
  XADMASTERVERSION,
  ACEEXE_VERSION,
  ACEEXE_REVISION,
  0x10000,
  XADCF_FILEARCHIVER|XADCF_FREEFILEINFO|XADCF_NOCHECKSIZE|XADCF_FREESKIPINFO,
  XADCID_ACEEXE,
  "Ace MS-EXE",
  XADRECOGDATAP(AceEXE),
  XADGETINFOP(AceEXE),
  XADUNARCHIVEP(Ace),
  XADFREEP(Ace)
};

XADFIRSTCLIENT(Ace) {
  (struct xadClient *) &AceEXE_Client,
  XADCLIENT_VERSION,
  XADMASTERVERSION,
  ACE_VERSION,
  ACE_REVISION,
  14,
  XADCF_FILEARCHIVER|XADCF_FREEFILEINFO|XADCF_FREESKIPINFO,
  XADCID_ACE,
  "Ace",
  XADRECOGDATAP(Ace),
  XADGETINFOP(Ace),
  XADUNARCHIVEP(Ace),
  XADFREEP(Ace)
};

#undef XADNEXTCLIENT
#define XADNEXTCLIENT XADNEXTCLIENTNAME(Ace)

#endif /* XADMASTER_ACE_C */
