#ifndef XADMASTER_HA_C
#define XADMASTER_HA_C

/* Programmheader

	Name:		HA.c
	Main:		xadmaster
	Versionstring:	$VER: HA.c 1.1 (23.12.2000)
	Author:		SDI
	Distribution:	GPL
	Description:	HA file archiver client

 1.0   05.11.00 : first version
 1.1   23.12.00 : added missing TAG_DONE
*/
#define DEBUG

#include <proto/xadmaster.h>
#include <exec/memory.h>
#include "SDI_compiler.h"
#include "ConvertE.c"
#include "xadCPU.h"
#include "xadIO.c"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

#ifndef XADMASTERFILE
#define HA_Client		FirstClient
#define NEXTCLIENT		0
#define XADMASTERVERSION	9
UBYTE version[] = "$VER: HA 1.1 (23.12.2000)" CPUTEXT " GPL by Dirk Stöcker";
#endif
#define HA_VERSION		1
#define HA_REVISION		1

#define HATYPE_CPY	0
#define HATYPE_ASC	1
#define HATYPE_HSC	2
#define HATYPE_DIR	14
#define HATYPE_SPECIAL	15

#define HAMTYPE_MSDOS	1
#define HAMTYPE_UNIX	2

#define HA_ISVTX     0x0200
#define HA_ISGID     0x0400
#define HA_ISUID     0x0800

#define HA_IRUSR     0x0100
#define HA_IWUSR     0x0080 
#define HA_IXUSR     0x0040

#define HA_IRGRP     0x0020
#define HA_IWGRP     0x0010
#define HA_IXGRP     0x0008

#define HA_IROTH     0x0004
#define HA_IWOTH     0x0002
#define HA_IXOTH     0x0001

#define HA_IFMT      0xf000
#define HA_IFIFO     0x1000
#define HA_IFCHR     0x2000
#define HA_IFDIR     0x4000
#define HA_IFBLK     0x6000
#define HA_IFREG     0x8000
#define HA_IFLNK     0xa000
#define HA_IFSOCK    0xc000

#define HA_ISDIR(m)  ((m&HA_IFMT)==HA_IFDIR)
#define HA_ISCHR(m)  ((m&HA_IFMT)==HA_IFCHR)
#define HA_ISBLK(m)  ((m&HA_IFMT)==HA_IFBLK)
#define HA_ISLNK(m)  ((m&HA_IFMT)==HA_IFLNK)
#define HA_ISFIFO(m) ((m&HA_IFMT)==HA_IFIFO)
#define HA_ISSOCK(m) ((m&HA_IFMT)==HA_IFSOCK)

struct HAPrivate {
  ULONG CRC32;
  UBYTE Method;
};

#define HAPI(a)	((struct HAPrivate *) ((a)->xfi_PrivateInfo))
#define HASCANSIZE	2048

static const STRPTR hatypes[] = {"CPY", "ASC", "HSC"};

/*
  archive header (intel storage):
  UBYTE[2]	ID = 'HA'
  UWORD		number of entries

  file header:
  UBYTE		(version<<4) + compression type
  ULONG		Compressed file size
  ULONG		Original file size
  ULONG		CRC32
  ULONG		timestamp (unix)
  UBYTE[..]	pathname, filename
  UBYTE		Length of machine specific information
  UBYTE[..]	Machine specific information
*/

ASM(BOOL) HA_RecogData(REG(d0, ULONG size), REG(a0, UBYTE *data),
REG(a6, struct xadMasterBase *xadMasterBase))
{ CPUCHECK
  if(data[0] == 'H' && data[1] == 'A' && (data[4]>>4) == 2 &&
  (data[4]&0xF) <= HATYPE_SPECIAL && EndGetI32(data+5) <= 
  EndGetI32(data+9))
    return 1;
  return 0;
}

ASM(LONG) HA_GetInfo(REG(a0, struct xadArchiveInfo *ai),
REG(a6, struct xadMasterBase *xadMasterBase))
{
  LONG err, s, i, nsize, psize, msize;
  ULONG num = 1;
  struct xadFileInfo *fi = 0, *fi2;
  STRPTR hdr, machine;

  if(!(err = xadHookAccess(XADAC_INPUTSEEK, 4, 0, ai)))
  {
    if((hdr = xadAllocVec(HASCANSIZE, MEMF_PUBLIC)))
    {
      while(ai->xai_InPos < ai->xai_InSize && !err)
      {
        if((s = ai->xai_InSize - ai->xai_InPos) > HASCANSIZE)
          s = HASCANSIZE;
        if(!(err = xadHookAccess(XADAC_READ, s, hdr, ai)))
        {
          msize = 0;
          for(psize = 0; psize <= s-20 && hdr[17+psize]; ++psize)
            ;
          for(nsize = 0; nsize+psize <= s-20 && hdr[17+psize+1+nsize]; ++nsize)
            ;
          if(nsize+psize <= s-20)
            msize = hdr[17+psize+1+nsize+1];
	  if(hdr[0] >> 4 == 2 && (hdr[0]&0xF) <= HATYPE_SPECIAL &&
	  EndGetI32(hdr+1) <= EndGetI32(hdr+5) && psize + nsize + msize <= s-20)
	  {
	    machine = hdr+20+psize+nsize;

	    if((hdr[0]&0xF) == HATYPE_SPECIAL)
	    {
	      if(machine[0] == HAMTYPE_UNIX && (machine[2]>>4) == 0xA) /* check fo link */
	      {
	        if((fi2 = xadAllocObject(XADOBJ_FILEINFO, XAD_OBJNAMESIZE, nsize+1+psize+1+EndGetI32(hdr+1)+1, TAG_DONE)))
	        {
	          i = 0;
	          if(psize)
	          {
		    for(i = 0; i < psize; ++i)
		    {
		      fi2->xfi_FileName[i] = hdr[17+i] == 0xFF ? '/' : hdr[17+i];
	            }
	            if(fi2->xfi_FileName[i-1] != '/')
	              fi2->xfi_FileName[i++] = '/';
	          }
	          xadCopyMem(hdr+17+psize+1, fi2->xfi_FileName+i, nsize+1); i += nsize+1;

	          if(!(err = xadHookAccess(XADAC_INPUTSEEK, 20+nsize+psize+msize-s, 0, ai)))
	          {
	            fi2->xfi_LinkName = fi2->xfi_FileName+i;
	            err = xadHookAccess(XADAC_READ, EndGetI32(hdr+1), fi2->xfi_LinkName, ai);
		    for(i = 0; fi2->xfi_LinkName[i]; ++i)
		    {
		      if(fi2->xfi_LinkName[i] == 0xFF)
		        fi2->xfi_LinkName[i] = '/';
		    }
	          }
	          
                  fi2->xfi_EntryNumber = num++;

                  xadConvertProtection(XAD_PROTUNIX, EndGetI16(machine+1), XAD_GETPROTAMIGA, &fi2->xfi_Protection, TAG_DONE);

	          fi2->xfi_Flags |= XADFIF_LINK;
                  xadConvertDates(XAD_DATEUNIX, EndGetI32(hdr+13), 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
	        err = xadHookAccess(XADAC_INPUTSEEK, 20+nsize+psize+msize+EndGetI32(hdr+1)-s, 0, ai);
	    }
	    else if((fi2 = xadAllocObject(XADOBJ_FILEINFO, XAD_OBJNAMESIZE, nsize+1+psize+1,
	    XAD_OBJPRIVINFOSIZE, sizeof(struct HAPrivate), TAG_DONE)))
	    {
	      i = 0;
	      if(psize)
	      {
		for(i = 0; i < psize; ++i)
		{
		  fi2->xfi_FileName[i] = hdr[17+i] == 0xFF ? '/' : hdr[17+i];
	        }
	        if(fi2->xfi_FileName[i-1] != '/')
	          fi2->xfi_FileName[i++] = '/';
	      }
	      xadCopyMem(hdr+17+psize+1, fi2->xfi_FileName+i, nsize+1);

              fi2->xfi_EntryNumber = num++;
              fi2->xfi_CrunchSize = EndGetI32(hdr+1);
              fi2->xfi_Size = EndGetI32(hdr+5);

	      switch(machine[0])
	      {
	      case HAMTYPE_MSDOS: xadConvertProtection(XAD_PROTMSDOS, machine[1],
	        XAD_GETPROTAMIGA, &fi2->xfi_Protection, TAG_DONE); break;
	      case HAMTYPE_UNIX: xadConvertProtection(XAD_PROTUNIX, EndGetI16(machine+1),
	        XAD_GETPROTAMIGA, &fi2->xfi_Protection, TAG_DONE);
	        break;
	      }

	      fi2->xfi_Flags |= XADFIF_SEEKDATAPOS;
	      fi2->xfi_DataPos = ai->xai_InPos-s+20+nsize+psize+msize;

              xadConvertDates(XAD_DATEUNIX, EndGetI32(hdr+13), XAD_GETDATEXADDATE, &fi2->xfi_Date, TAG_DONE);

	      HAPI(fi2)->CRC32 = EndGetI32(hdr+9);
	      HAPI(fi2)->Method = hdr[0]&15;
	      if(HAPI(fi2)->Method == HATYPE_DIR)
	        fi2->xfi_Flags |= XADFIF_DIRECTORY;
	      else if(HAPI(fi2)->Method <= HATYPE_HSC)
	        fi2->xfi_EntryInfo = hatypes[HAPI(fi2)->Method];
	      err = xadHookAccess(XADAC_INPUTSEEK, fi2->xfi_DataPos+fi2->xfi_CrunchSize-ai->xai_InPos, 0, ai);

	      if(!fi)
                ai->xai_FileInfo = fi2;
              else
                fi->xfi_Next = fi2;
              fi = fi2;
	    }
	    else
	      err = XADERR_NOMEMORY;
	  }
	  else
	    err = XADERR_ILLEGALDATA;
	}
      }
      xadFreeObjectA(hdr,0);
    }
    else
      err = XADERR_NOMEMORY;

    if(!err && ai->xai_InPos < ai->xai_InSize)
      err = XADERR_ILLEGALDATA;
    if(err)
    {
      ai->xai_Flags |= XADAIF_FILECORRUPT;
      ai->xai_LastError = err;
    }
  }
  return (ai->xai_FileInfo ? 0 : err);
}


/* Arithmetic Decoding *******************************************************/

struct HaArithData {
 struct xadInOut *io;
 UWORD h;
 UWORD l;
 UWORD v;
 WORD  s;
 WORD  gpat;
 WORD  ppat;
};

static void HA_ac_init_decode(struct HaArithData *dat)
{
  dat->h = 0xffff;
/*  dat->l = 0; */
/*  dat->gpat = 0; */
  dat->v = xadIOGetChar(dat->io)<<8;
  dat->v |= 0xff&xadIOGetChar(dat->io);
}

UWORD HA_ac_threshold_val(struct HaArithData *dat, UWORD tot)
{
  ULONG r;
    
  r = (ULONG)(dat->h - dat->l)+1;
  return (UWORD)((((ULONG)(dat->v-dat->l)+1)*tot-1)/r);
}

#define HA_getbit(b) 	{ dat->gpat <<= 1;				\
			  if(!(dat->gpat&0xff)) {			\
			    dat->gpat=xadIOGetChar(dat->io);		\
				if(dat->gpat&0x100) dat->gpat=0x100;	\
				else {					\
					dat->gpat <<= 1;		\
					dat->gpat |= 1;			\
				}					\
			  }						\
			  b |= (dat->gpat&0x100)>>8;			\
			}

static void HA_ac_in(struct HaArithData *dat, UWORD low, UWORD high, UWORD tot)
{
  ULONG r;

  r = (ULONG)(dat->h - dat->l)+1;
  dat->h = (UWORD)(r*high/tot-1)+dat->l;
  dat->l += (UWORD)(r*low/tot);
  while(!((dat->h^dat->l)&0x8000))
  {
    dat->l <<= 1;
    dat->h <<= 1;
    dat->h |= 1;
    dat->v <<= 1;
    HA_getbit(dat->v);
  }
  while((dat->l&0x4000)&&!(dat->h&0x4000))
  {
    dat->l <<= 1;
    dat->l &= 0x7fff;
    dat->h <<= 1;
    dat->h |= 0x8001;
    dat->v <<= 1;
    dat->v ^= 0x8000;
    HA_getbit(dat->v);
  }
}

/* ASC ***********************************************************************/

#define HA_POSCODES	31200
#define HA_CTCODES	256
#define HA_PTCODES	16
#define HA_SLCODES	16
#define HA_LLCODES	48
#define HA_TTORD	4
#define HA_LTSTEP	8
#define HA_CTSTEP	1
#define HA_TTSTEP	40
#define HA_PTSTEP	24
#define HA_MAXPT	(250*HA_PTSTEP)
#define HA_MAXLT	(750*HA_LTSTEP)
#define HA_MAXTT	(150*HA_TTSTEP)
#define HA_MAXCT	(1000*HA_CTSTEP)
#define HA_TTOMASK	(HA_TTORD-1);
#define HA_LPLEN	4
#define HA_CPLEN	8
#define HA_LLLEN	16
#define HA_LLBITS	4
#define HA_CCUTOFF	(3*HA_CTSTEP)
#define HA_LCUTOFF	(3*HA_LTSTEP)
#define HA_LTCODES	(HA_SLCODES+HA_LLCODES)
#define HA_LENCODES	(HA_SLCODES+HA_LLCODES*HA_LLLEN)

struct HaAscData {
  struct HaArithData arith;
  UWORD cblen;
  UWORD bbf;
  UWORD ccnt;
  UWORD pmax;
  UWORD npt;
  UWORD ces;
  UWORD les;
  UWORD ttcon;
  UWORD ltab[2*HA_LTCODES];
  UWORD eltab[2*HA_LTCODES];
  UWORD ptab[2*HA_PTCODES];
  UWORD ctab[2*HA_CTCODES];
  UWORD ectab[2*HA_CTCODES];
  UWORD ttab[HA_TTORD][2];
  UBYTE b[HA_POSCODES];
};

static void HA_tabinit(UWORD *t, UWORD tl, UWORD ival)
{
  UWORD i,j;
    
  for(i = tl; i < 2*tl; ++i)
    t[i] = ival;
  for(i = tl-1, j = (tl<<1)-2; i; --i, j-=2)
  {
    t[i] = t[j] + t[j+1];
  }
}

static void HA_tscale(UWORD *t, UWORD tl)
{
  UWORD i,j;
    
  for(i = (tl<<1)-1; i>=tl; --i)
  {
    if(t[i]>1)
      t[i]>>=1;
  }
  for(i = tl-1, j = (tl<<1)-2; i; --i, j-=2)
  {
    t[i] = t[j] + t[j+1];
  }
}

static void HA_ttscale(struct HaAscData *dat, UWORD con)
{
  dat->ttab[con][0]>>=1;
  if(dat->ttab[con][0]==0)
    dat->ttab[con][0]=1;
  dat->ttab[con][1]>>=1;
  if(dat->ttab[con][1]==0)
    dat->ttab[con][1]=1;
}

static void HA_tupd(UWORD *t, UWORD tl, UWORD maxt, UWORD step, UWORD p)
{
  WORD i;
    
  for(i = p+tl; i; i>>=1)
    t[i] += step;
  if(t[1] >= maxt)
    HA_tscale(t,tl);
}

static void HA_tzero(UWORD *t, UWORD tl, UWORD p)
{
  WORD i, step;
    
  for(i = p+tl, step = t[i]; i; i>>=1)
    t[i] -= step;
}

static LONG HA_asc(struct xadInOut *io)
{
  struct xadMasterBase *xadMasterBase = io->xio_xadMasterBase;
  UWORD l,p,tv,i,lt;
  struct HaAscData *dat;

  if((dat = (struct HaAscData *) xadAllocVec(sizeof(struct HaAscData), MEMF_PUBLIC|MEMF_CLEAR)))
  {
    dat->arith.io = io;
    dat->cblen = HA_POSCODES;
    dat->ces=HA_CTSTEP;
    dat->les=HA_LTSTEP;
/*    dat->ccnt=0; */
/*    dat->ttcon=0; */
    dat->npt = dat->pmax = 1;
    for(i = 0; i < HA_TTORD; ++i)
      dat->ttab[i][0] = dat->ttab[i][1] = HA_TTSTEP;
    HA_tabinit(dat->ltab, HA_LTCODES,0);
    HA_tabinit(dat->eltab,HA_LTCODES,1);
    HA_tabinit(dat->ctab, HA_CTCODES,0);
    HA_tabinit(dat->ectab,HA_CTCODES,1);
    HA_tabinit(dat->ptab, HA_PTCODES,0);
    HA_tupd(dat->ptab,HA_PTCODES,HA_MAXPT,HA_PTSTEP,0);

    HA_ac_init_decode(&dat->arith);
    for(;;)
    {
      tv = HA_ac_threshold_val(&dat->arith, dat->ttab[dat->ttcon][0]+dat->ttab[dat->ttcon][1]+1);
      i = dat->ttab[dat->ttcon][0]+dat->ttab[dat->ttcon][1];
      if(dat->ttab[dat->ttcon][0] > tv)
      {
	HA_ac_in(&dat->arith, 0, dat->ttab[dat->ttcon][0], i+1);
	dat->ttab[dat->ttcon][0] += HA_TTSTEP;

	if(i >= HA_MAXTT)
	  HA_ttscale(dat, dat->ttcon);

	dat->ttcon = (dat->ttcon<<1) & HA_TTOMASK;
	tv = HA_ac_threshold_val(&dat->arith, dat->ctab[1] + dat->ces);

	if(tv >= dat->ctab[1])
	{
	  HA_ac_in(&dat->arith, dat->ctab[1], dat->ctab[1]+dat->ces, dat->ctab[1]+dat->ces);
	  tv = HA_ac_threshold_val(&dat->arith, dat->ectab[1]);
	  for(l=2,lt=0;;)
	  {
	    if(lt+dat->ectab[l]<=tv)
	    {
	      lt += dat->ectab[l];
	      ++l;
	    }
	    if(l >= HA_CTCODES)
	    {
	      l -= HA_CTCODES;
	      break;
	    }
	    l <<= 1;
	  }
	  HA_ac_in(&dat->arith, lt,lt+dat->ectab[HA_CTCODES+l], dat->ectab[1]);
	  HA_tzero(dat->ectab, HA_CTCODES, l);
	  if(dat->ectab[1] != 0)
	    dat->ces += HA_CTSTEP;
	  else
	    dat->ces = 0;
	  for(i = l < HA_CPLEN ? 0 : l-HA_CPLEN; i < (l+HA_CPLEN >= HA_CTCODES-1 ? HA_CTCODES-1:l+HA_CPLEN); ++i)
	  {
	    if(dat->ectab[HA_CTCODES+i])
	      HA_tupd(dat->ectab, HA_CTCODES, HA_MAXCT, 1, i);
	  }
	}
	else
	{
	  for(l=2,lt=0;;)
	  {
	    if(lt+dat->ctab[l] <= tv)
	    {
	      lt += dat->ctab[l];
	      l++;
	    }
	    if(l >= HA_CTCODES)
	    {
	      l -= HA_CTCODES;
	      break;
	    }
	    l <<= 1;
	  }
	  HA_ac_in(&dat->arith, lt, lt+dat->ctab[HA_CTCODES+l],dat->ctab[1]+dat->ces);
	}

	HA_tupd(dat->ctab, HA_CTCODES, HA_MAXCT, HA_CTSTEP, l);
	if(dat->ctab[HA_CTCODES+l] == HA_CCUTOFF)
	  dat->ces -= HA_CTSTEP < dat->ces ? HA_CTSTEP : dat->ces-1; 

	dat->b[dat->bbf] = xadIOPutChar(io, l);
    	if(++dat->bbf == dat->cblen)
    	  dat->bbf=0;

	if(dat->ccnt < HA_POSCODES)
	  ++dat->ccnt;
      }
      else if (i > tv)
      {
	HA_ac_in(&dat->arith, dat->ttab[dat->ttcon][0],i,i+1);
	dat->ttab[dat->ttcon][1] += HA_TTSTEP;
	if(i >= HA_MAXTT)
	  HA_ttscale(dat, dat->ttcon);
	dat->ttcon = ((dat->ttcon<<1)|1)&HA_TTOMASK;
	while(dat->ccnt > dat->pmax)
	{
	  HA_tupd(dat->ptab, HA_PTCODES, HA_MAXPT, HA_PTSTEP, dat->npt++);
	  dat->pmax <<= 1;	
	}
	tv = HA_ac_threshold_val(&dat->arith, dat->ptab[1]);
	for(p=2,lt=0;;)
	{
	  if(lt + dat->ptab[p] <= tv)
	  {
	    lt += dat->ptab[p];
	    p++;
	  }
	  if(p >= HA_PTCODES)
	  {
	    p -= HA_PTCODES;
	    break;
	  }
	  p <<= 1;
	}
	HA_ac_in(&dat->arith, lt, lt+dat->ptab[HA_PTCODES+p], dat->ptab[1]);
	HA_tupd(dat->ptab, HA_PTCODES, HA_MAXPT, HA_PTSTEP, p);
	if(p > 1)
	{
	  for(i=1; p; i <<= 1)
	    --p;
	  i >>= 1;
	  if(i==(dat->pmax>>1))
	    l = dat->ccnt-(dat->pmax>>1);
	  else
	    l=i;
	  p = HA_ac_threshold_val(&dat->arith, l);
	  HA_ac_in(&dat->arith, p, p+1, l);
	  p += i;
	}
	tv = HA_ac_threshold_val(&dat->arith, dat->ltab[1]+dat->les);
	if(tv >= dat->ltab[1])
	{
	  HA_ac_in(&dat->arith, dat->ltab[1], dat->ltab[1]+dat->les, dat->ltab[1]+dat->les);
	  tv = HA_ac_threshold_val(&dat->arith, dat->eltab[1]);
	  for(l=2,lt=0;;)
	  {
	    if(lt+dat->eltab[l] <= tv)
	    {
	      lt += dat->eltab[l];
	      ++l;
	    }
	    if(l >= HA_LTCODES)
	    {
	      l -= HA_LTCODES;
	      break;
	    }
	    l <<= 1;
	  }
	  HA_ac_in(&dat->arith, lt, lt+dat->eltab[HA_LTCODES+l], dat->eltab[1]);
	  HA_tzero(dat->eltab, HA_LTCODES, l);
	  if(dat->eltab[1] != 0)
	    dat->les += HA_LTSTEP;
	  else
	    dat->les = 0;
	  for(i = l < HA_LPLEN ? 0 : l-HA_LPLEN; i<(l+HA_LPLEN >= HA_LTCODES-1 ? HA_LTCODES-1:l+HA_LPLEN);++i)
	  {
	    if(dat->eltab[HA_LTCODES+i])
	      HA_tupd(dat->eltab,HA_LTCODES,HA_MAXLT,1,i);
	  }
	}
	else
	{
	  for(l=2,lt=0;;)
	  {
	    if(lt+dat->ltab[l] <= tv)
	    {
	      lt += dat->ltab[l];
	      ++l;
	    }
	    if(l >= HA_LTCODES)
	    {
	      l -= HA_LTCODES;
	      break;
	    }
	    l <<= 1;
	  }
	  HA_ac_in(&dat->arith, lt, lt+dat->ltab[HA_LTCODES+l], dat->ltab[1]+dat->les);
	}
	HA_tupd(dat->ltab, HA_LTCODES, HA_MAXLT, HA_LTSTEP, l);
	if(dat->ltab[HA_LTCODES+l] == HA_LCUTOFF)
	  dat->les -= HA_LTSTEP < dat->les ? HA_LTSTEP : dat->les-1; 
	if(l == HA_SLCODES-1)
	  l = HA_LENCODES-1;
	else if(l >= HA_SLCODES)
	{
	  i = HA_ac_threshold_val(&dat->arith, HA_LLLEN);
	  HA_ac_in(&dat->arith, i, i+1, HA_LLLEN);
	  l = ((l-HA_SLCODES) << HA_LLBITS)+i+HA_SLCODES-1;
	}
	l += 3;
	if(dat->ccnt < HA_POSCODES)
	{
	  dat->ccnt += l;
	  if(dat->ccnt > HA_POSCODES)
	    dat->ccnt = HA_POSCODES;
	}
        if(dat->bbf > p)
          p = dat->bbf-1-p;
        else
          p=dat->cblen-1-p+dat->bbf;
        while (l--)
        {
	  dat->b[dat->bbf] = xadIOPutChar(io, dat->b[p]);
	  if(++dat->bbf == dat->cblen)
	    dat->bbf=0;
	  if(++p == dat->cblen)
	    p=0;
        }
      }
      else
      {
	HA_ac_in(&dat->arith, i, i+1, i+1);
        break;
      }
    }

    xadFreeObjectA(dat, 0);
  }
  return io->xio_Error;
}

/* HSC ***********************************************************************/

#define HA_IECLIM	32	/* initial escape counter upper limit */
#define HA_NECLIM	5	/* no escape expected counter limit */
#define HA_NECTLIM	4
#define HA_NECMAX	10	/* no escape expected counter maximum */
#define HA_MAXCLEN	4	/* assumed to be 4 in several places */
#define HA_NUMCON	10000	/* number of contexts to remember */ 
#define HA_NUMCFB	32760	/* number of frequencies to remember */
#define HA_ESCTH	3	/* threshold for escape calculation */
#define HA_MAXTVAL	8000	/* maximum frequency value */
#define HA_RFMINI	4	/* initial refresh counter value */
#define HA_HTLEN	16384	/* length of hash table */
#define HA_NIL		0xffff	/* NIL pointer in lists */
#define HA_ESC		256	/* escape symbol */

struct HaHscData {
  struct HaArithData arith;
  UWORD usp;			/* stack pointer */
  WORD  cslen;			/* length of context to search */
  WORD  cmsp;			/* pointer to cmstack */
  WORD  dropcnt;		/* counter for context len drop */
  UWORD nrel;			/* context for frequency block release */
  UWORD elf;			/* first of expire list */
  UWORD	ell;			/* last of expire list */
  UWORD fcfbl;			/* pointer to free frequency blocks */
  UWORD elp[HA_NUMCON];		/* expire list previous pointer array */
  UWORD eln[HA_NUMCON];		/* expire list next pointer array */
  UWORD hrt[HA_HTLEN];		/* semi random data for hashing */
  UWORD hs[HA_MAXCLEN+1];	/* hash stack for context search */ 
  UWORD hp[HA_NUMCON];		/* hash list pointer array */
  UWORD as[HA_MAXCLEN+1];	/* indexes to frequency array */
  UWORD cps[HA_MAXCLEN+1];	/* context pointers */
  UWORD nb[HA_NUMCFB];		/* next pointer for frequency array */
  UWORD fa[HA_NUMCFB];		/* frequency array */
  UWORD ft[HA_NUMCON];		/* total frequency of context */
  UWORD ht[HA_HTLEN];		/* hash table */
  UBYTE nec;			/* counter for no escape expected */
  UBYTE maxclen;		/* current maximum length for context */ 
  UBYTE curcon[4];		/* current context */
  UBYTE cmask[256];		/* masked characters */
  UBYTE cmstack[256];		/* stack of cmask[] entries to clear */ 
  UBYTE cl[HA_NUMCON];		/* context length array */
  UBYTE con[HA_NUMCON][4];	/* context array */
  UBYTE fe[HA_NUMCON];		/* frequencys under ESCTH in context */
  UBYTE fc[HA_NUMCFB];		/* character for frequency array */
  UBYTE cc[HA_NUMCON];		/* character counts */
  UBYTE rfm[HA_NUMCON];		/* refresh counter array */
  UBYTE iec[HA_MAXCLEN+1];	/* initial escape counters */
};

static UWORD HA_find_next(struct HaHscData *dat)
{
  WORD i,k;
  UWORD cp;
    
  for(i = dat->cslen-1; i >= 0; --i)
  {
    k = dat->hs[i];
    for(cp = dat->ht[k]; cp != HA_NIL; cp = dat->hp[cp])
    {
      if(dat->cl[cp] == i)
      {
	switch(i)
	{
	case 4: if(dat->curcon[3] != dat->con[cp][3]) break;
        case 3: if(dat->curcon[2] != dat->con[cp][2]) break;
        case 2: if(dat->curcon[1] != dat->con[cp][1]) break;
        case 1: if(dat->curcon[0] != dat->con[cp][0]) break;
        case 0: dat->cslen = i; return cp;
	}
      }	
    }
  }

  return HA_NIL;
}

static UWORD HA_find_longest(struct HaHscData *dat)
{
  dat->hs[1] = dat->hrt[dat->curcon[0]];
  dat->hs[2] = dat->hrt[(dat->curcon[1]+dat->hs[1])&(HA_HTLEN-1)];
  dat->hs[3] = dat->hrt[(dat->curcon[2]+dat->hs[2])&(HA_HTLEN-1)];
  dat->hs[4] = dat->hrt[(dat->curcon[3]+dat->hs[3])&(HA_HTLEN-1)];
  dat->usp = 0;
  while(dat->cmsp)
    dat->cmask[dat->cmstack[--dat->cmsp]] = 0;
  dat->cslen = HA_MAXCLEN+1;

  return HA_find_next(dat);
}

static UWORD HA_adj_escape_prob(struct HaHscData *dat, UWORD esc, UWORD cp)
{
  if(dat->ft[cp] == 1)
    return (UWORD) (dat->iec[dat->cl[cp]] >= (HA_IECLIM >> 1) ? 2 : 1);
  if(dat->cc[cp] == 255)
    return 1;
  if(dat->cc[cp] && ((dat->cc[cp]+1) << 1) >= dat->ft[cp])
  {
    esc = (WORD)((LONG)esc*((dat->cc[cp]+1) << 1) / dat->ft[cp]);
    if(dat->cc[cp] + 1 == dat->ft[cp])
      esc += (dat->cc[cp]+1)>>1;
  }
  return (UWORD) (esc?esc:1);
}

static WORD HA_decode_first(struct HaHscData *dat, UWORD cp)
{
  UWORD c, tv, i;
  WORD sum,tot,esc,cf = 0;
  UBYTE sv;

  esc = HA_adj_escape_prob(dat, dat->fe[cp], cp);
  tot = dat->ft[cp];
  if(dat->nec >= HA_NECLIM)
  {
    if(tot <= HA_NECTLIM && dat->nec == HA_NECMAX)
      sv=2;
    else
      sv=1;
    tot <<= sv;
    tv = HA_ac_threshold_val(&dat->arith, tot+esc) >> sv;
    for(c = cp, sum = 0; c != HA_NIL; c = dat->nb[c])
    {
      if(sum+dat->fa[c] <= tv)
        sum += dat->fa[c];
      else
      {
	cf = dat->fa[c] << sv;
	break;
      }
    }
    sum <<= sv;
  }
  else
  {
    tv = HA_ac_threshold_val(&dat->arith, tot+esc);
    for(c = cp, sum = 0; c != HA_NIL; c = dat->nb[c])
    {
      if(sum+dat->fa[c] <= tv)
        sum += dat->fa[c];
      else
      {
	cf = dat->fa[c];
	break;
      }
    }
  }
  dat->usp = 1;
  if(c != HA_NIL)
  {
    HA_ac_in(&dat->arith, sum, sum+cf, tot+esc);
    if(dat->ft[cp] == 1 && dat->iec[dat->cl[cp]])
      --dat->iec[dat->cl[cp]];
    dat->as[0] = c;
    dat->cps[0] = cp;
    c = dat->fc[c];
    if(dat->nec < HA_NECMAX)
      ++dat->nec;
  }
  else
  {
    HA_ac_in(&dat->arith, tot,tot+esc,tot+esc);
    if(dat->ft[cp] == 1 && dat->iec[dat->cl[cp]] < HA_IECLIM)
      ++dat->iec[dat->cl[cp]];
    for(i = cp; i != HA_NIL; sum=i,i=dat->nb[i])
    {
      dat->cmstack[dat->cmsp++] = dat->fc[i];
      dat->cmask[dat->fc[i]]=1;
    }
    dat->cps[0]=0x8000|cp;
    dat->as[0]=sum;
    c = HA_ESC;
    dat->nec = 0;
  }
  return (WORD) c;
}

static WORD HA_decode_rest(struct HaHscData *dat, UWORD cp)
{
  UWORD c, tv, i;
  WORD sum,tot,esc,cf = 0;
    
  esc=tot=0;
  for(i = cp; i != HA_NIL; i=dat->nb[i])
  {
    if(!dat->cmask[dat->fc[i]])
    {
      tot += dat->fa[i];
      if(dat->fa[i] < HA_ESCTH)
        ++esc;
    }
  }
  esc = HA_adj_escape_prob(dat, esc, cp);
  tv = HA_ac_threshold_val(&dat->arith, tot+esc);
  for(c = cp, sum = 0; c != HA_NIL; c = dat->nb[c])
  {
    if(!dat->cmask[dat->fc[c]])
    {
      if(sum+dat->fa[c] <= tv)
        sum += dat->fa[c];
      else
      {
	cf = dat->fa[c];
	break;
      }
    }
  }
  if(c != HA_NIL)
  {
    HA_ac_in(&dat->arith, sum,sum+cf,tot+esc);
    if(dat->ft[cp]==1 && dat->iec[dat->cl[cp]])
      --dat->iec[dat->cl[cp]];
    dat->as[dat->usp] = c;
    dat->cps[dat->usp++] = cp;
    c = dat->fc[c];
    ++dat->nec;
  }
  else
  {
    HA_ac_in(&dat->arith, tot,tot+esc,tot+esc);
    if(dat->ft[cp] == 1 && dat->iec[dat->cl[cp]] < HA_IECLIM)
      ++dat->iec[dat->cl[cp]];
    for(i = cp; i != HA_NIL; sum=i,i=dat->nb[i])
    {
      if(!dat->cmask[dat->fc[i]])
      {
	dat->cmstack[dat->cmsp++] = dat->fc[i];
	dat->cmask[dat->fc[i]] = 1;
      }
    }
    dat->cps[dat->usp] = 0x8000|cp;
    dat->as[dat->usp++]=sum;		/* sum holds last i !! */
    c = HA_ESC;
  }

  return (WORD) c;
}

static WORD HA_decode_new(struct HaHscData *dat)
{
  WORD c;
  UWORD tv,sum,tot;
    
  tot = 257 - dat->cmsp;
  tv = HA_ac_threshold_val(&dat->arith, tot);
  for(c = sum = 0; c < 256; ++c)
  {
    if(dat->cmask[c])
      continue;
    if(sum+1 <= tv)
      ++sum;
    else
      break;
  }
  HA_ac_in(&dat->arith, sum, sum+1, tot);	
  return c;
}

static void HA_el_movefront(struct HaHscData *dat, UWORD cp)
{
  if(cp == dat->elf)
    return;
  if(cp == dat->ell)
    dat->ell = dat->elp[cp];
  else
  {
    dat->elp[dat->eln[cp]] = dat->elp[cp];
    dat->eln[dat->elp[cp]] = dat->eln[cp];
  }	
  dat->elp[dat->elf] = cp;
  dat->eln[cp] = dat->elf;
  dat->elf = cp;
}

static void HA_release_cfblocks(struct HaHscData *dat)
{
  UWORD i,j,d;
    
  do
  {
    do
    {
      if(++dat->nrel == HA_NUMCON)
        dat->nrel = 0;
    } while(dat->nb[dat->nrel] == HA_NIL);
    for(i=0; i <= dat->usp; ++i)
      if((dat->cps[i]&0x7fff) == dat->nrel)
        break;
  } while(i <= dat->usp);

  for(i = dat->nb[dat->nrel], d = dat->fa[dat->nrel]; i != HA_NIL; i = dat->nb[i])
    if(dat->fa[i] < d)
      d = dat->fa[i];
  ++d;
  if(dat->fa[dat->nrel] < d)
  {
    for(i = dat->nb[dat->nrel]; dat->fa[i] < d && dat->nb[i] != HA_NIL; i = dat->nb[i])
      ;
    dat->fa[dat->nrel] = dat->fa[i];
    dat->fc[dat->nrel] = dat->fc[i];
    j = dat->nb[i];
    dat->nb[i] = dat->fcfbl;
    dat->fcfbl = dat->nb[dat->nrel];
    if((dat->nb[dat->nrel] = j) == HA_NIL)
    {
      dat->cc[dat->nrel] = 0;
      dat->fe[dat->nrel] = (dat->ft[dat->nrel] = dat->fa[dat->nrel]) < HA_ESCTH ? 1 : 0;
      return;
    }
  }
  dat->fe[dat->nrel] = (dat->ft[dat->nrel] = dat->fa[dat->nrel] /= d) < HA_ESCTH ? 1 : 0;
  dat->cc[dat->nrel] = 0;
  for(j = dat->nrel, i = dat->nb[j]; i != HA_NIL;)
  {
    if(dat->fa[i] < d)
    {
      dat->nb[j] = dat->nb[i];
      dat->nb[i] = dat->fcfbl;
      dat->fcfbl = i;
      i = dat->nb[j];
    }
    else
    {
       ++dat->cc[dat->nrel];
       dat->ft[dat->nrel] += dat->fa[i] /= d;
       if(dat->fa[i] < HA_ESCTH)
         dat->fe[dat->nrel]++;
       j=i;
       i = dat->nb[i];
    }
  }
}

static void HA_add_model(struct HaHscData *dat, WORD c)
{
  UWORD i;
  WORD cp;
    
  while(dat->usp != 0)
  {
    i = dat->as[--dat->usp];
    cp = dat->cps[dat->usp];
    if(cp&0x8000)
    {
      cp &= 0x7fff;
      if(dat->fcfbl == HA_NIL)
        HA_release_cfblocks(dat);
      dat->nb[i] = dat->fcfbl;
      i = dat->nb[i];
      dat->fcfbl = dat->nb[dat->fcfbl];
      dat->nb[i] = HA_NIL;
      dat->fa[i] = 1;
      dat->fc[i] = c;
      ++dat->cc[cp];
      ++dat->fe[cp];
    }
    else if(++dat->fa[i] == HA_ESCTH)
      --dat->fe[cp];
    if((dat->fa[i]<<1) < ++dat->ft[cp] / (dat->cc[cp]+1))
      --dat->rfm[cp];
    else if(dat->rfm[cp] < HA_RFMINI)
      ++dat->rfm[cp];
    if(!dat->rfm[cp] || dat->ft[cp] >= HA_MAXTVAL)
    {
      ++dat->rfm[cp];
      dat->fe[cp] = dat->ft[cp]=0;
      for(i = cp; i != HA_NIL; i = dat->nb[i])
      {
	if(dat->fa[i] > 1)
	{
	  dat->ft[cp] += dat->fa[i] >>= 1;
	  if(dat->fa[i] < HA_ESCTH)
	    ++dat->fe[cp];
	}
	else
	{
	  ++dat->ft[cp];
	  ++dat->fe[cp];
	}
      }
    }
  }
}

#define HA_HASH(s,l,h)	{ h = 0; if (l) h=dat->hrt[s[0]];             \
			  if (l>1) h=dat->hrt[(s[1]+h)&(HA_HTLEN-1)]; \
			  if (l>2) h=dat->hrt[(s[2]+h)&(HA_HTLEN-1)]; \
			  if (l>3) h=dat->hrt[(s[3]+h)&(HA_HTLEN-1)];}

static UWORD HA_make_context(struct HaHscData *dat, UBYTE conlen, WORD c)
{
  WORD i;
  UWORD nc,fp;
    
  nc = dat->ell;
  dat->ell = dat->elp[nc];
  dat->elp[dat->elf] = nc;
  dat->eln[nc] = dat->elf;
  dat->elf = nc;
  if(dat->cl[nc] != 0xff)
  {
    if(dat->cl[nc] == HA_MAXCLEN && --dat->dropcnt==0)
      dat->maxclen = HA_MAXCLEN-1;
    HA_HASH(dat->con[nc], dat->cl[nc], i);
    if(dat->ht[i] == nc)
      dat->ht[i] = dat->hp[nc];
    else
    {
      for(i = dat->ht[i]; dat->hp[i] != nc; i = dat->hp[i])
        ;
      dat->hp[i] = dat->hp[nc];
    }
    if(dat->nb[nc] != HA_NIL)
    {
      for(fp = dat->nb[nc]; dat->nb[fp] != HA_NIL; fp = dat->nb[fp])
        ;
      dat->nb[fp] = dat->fcfbl;
      dat->fcfbl = dat->nb[nc];
    }
  }
  dat->nb[nc] = HA_NIL;
  dat->fe[nc] = dat->ft[nc] = dat->fa[nc] = 1;
  dat->fc[nc] = c;
  dat->rfm[nc] = HA_RFMINI;
  dat->cc[nc] = 0;
  dat->cl[nc] = conlen;
  dat->con[nc][0] = dat->curcon[0];
  dat->con[nc][1] = dat->curcon[1];
  dat->con[nc][2] = dat->curcon[2];
  dat->con[nc][3] = dat->curcon[3];
  HA_HASH(dat->curcon, conlen, i);
  dat->hp[nc] = dat->ht[i];
  dat->ht[i] = nc;
  return nc;
}

static LONG HA_hsc(struct xadInOut *io)
{
  struct xadMasterBase *xadMasterBase = io->xio_xadMasterBase;
  struct HaHscData *dat;
  WORD c = 0, i;
  UWORD cp;
  LONG z,h,l,t;
  UBYTE ncmax,ncmin;

  if((dat = (struct HaHscData *) xadAllocVec(sizeof(struct HaHscData), MEMF_PUBLIC|MEMF_CLEAR)))
  {
    dat->arith.io = io;

    dat->maxclen = HA_MAXCLEN;
    dat->iec[0] = (HA_IECLIM>>1);
    for(i=1; i <= HA_MAXCLEN; ++i)
      dat->iec[i] = (HA_IECLIM>>1)-1;
    dat->dropcnt = HA_NUMCON/4;
/*    nec=0; */
/*    nrel=0; */
/*    dat->hs[0] = 0; */
    for(i=0; i < HA_HTLEN; ++i)
      dat->ht[i] = HA_NIL;
    for(i=0; i < HA_NUMCON; ++i)
    {
      dat->eln[i] = i+1;
      dat->elp[i] = i-1;
      dat->cl[i] = 0xff;
      dat->nb[i] = HA_NIL;
    }
/*    dat->elf=0; */
    dat->ell = HA_NUMCON-1;
    for(i = HA_NUMCON; i < HA_NUMCFB-1; ++i)
      dat->nb[i] = i+1;
    dat->nb[i] = HA_NIL;
    dat->fcfbl = HA_NUMCON;
/*    dat->curcon[3] = dat->curcon[2] = dat->curcon[1] = dat->curcon[0] = 0; */
/*    dat->cmsp = 0; */
/*    for(i=0; i < 256; ++i) */
/*      dat->cmask[i] = 0; */
    for(z=10, i=0; i < HA_HTLEN; ++i)
    {
      h = z/(2147483647L/16807L);
      l = z%(2147483647L/16807L);
      if((t = 16807L*l-(2147483647L%16807L)*h)>0)
        z=t;
      else
        z=t+2147483647L;
      dat->hrt[i] = z&(HA_HTLEN-1);
    }

    HA_ac_init_decode(&dat->arith);
    while(c != HA_ESC)
    {
      cp = HA_find_longest(dat);
      ncmin = cp == HA_NIL ? 0 : dat->cl[cp]+1;
      ncmax = dat->maxclen+1;
      for(;;)
      {
	if(cp == HA_NIL)
	{
          c = HA_decode_new(dat);
	  break;
        }			
	if((c = dat->cmsp ? HA_decode_rest(dat, cp) : HA_decode_first(dat, cp)) != HA_ESC)
	{
	  HA_el_movefront(dat, cp);
	  break;
        }		
        cp = HA_find_next(dat);
      }
      if(c != HA_ESC)
      {
        HA_add_model(dat, c);
        while(ncmax > ncmin)
          HA_make_context(dat, --ncmax, c);
        dat->curcon[3] = dat->curcon[2];
        dat->curcon[2] = dat->curcon[1];
	dat->curcon[1] = dat->curcon[0];
	dat->curcon[0] = c;
	xadIOPutChar(io, c);
      }
    }

    xadFreeObjectA(dat, 0);
  }
  return io->xio_Error;
}

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

ASM(LONG) HA_UnArchive(REG(a0, struct xadArchiveInfo *ai),
REG(a6, struct xadMasterBase *xadMasterBase))
{
  struct xadFileInfo *fi;
  struct xadInOut *io;
  LONG err;

  fi = ai->xai_CurFile;

  if((io = xadIOAlloc(XADIOF_ALLOCINBUFFER|XADIOF_ALLOCOUTBUFFER|XADIOF_NOCRC16|XADIOF_NOINENDERR, ai, xadMasterBase)))
  {
    io->xio_InSize = fi->xfi_CrunchSize;
    io->xio_OutSize = fi->xfi_Size;
    io->xio_CRC32 = ~0;

    switch(HAPI(fi)->Method)
    {
    case HATYPE_CPY:
      while(!(io->xio_Flags & (XADIOF_LASTOUTBYTE|XADIOF_ERROR)))
        xadIOPutChar(io, xadIOGetChar(io));
      err = io->xio_Error;
      break;
    case HATYPE_ASC: err = HA_asc(io); break;
    case HATYPE_HSC: err = HA_hsc(io); break;
    default: err = XADERR_DATAFORMAT;
    }

    if(!err)
      err = xadIOWriteBuf(io);
    if(!err && ~io->xio_CRC32 != HAPI(fi)->CRC32)
      err = XADERR_CHECKSUM;
    xadFreeObjectA(io, 0);
  }
  else
    err = XADERR_NOMEMORY;

  return err;
}

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

const struct xadClient HA_Client = {
NEXTCLIENT, XADCLIENT_VERSION, XADMASTERVERSION, HA_VERSION, HA_REVISION,
22, XADCF_FILEARCHIVER|XADCF_FREEFILEINFO, XADCID_HA, "HA",
(BOOL (*)()) HA_RecogData, (LONG (*)()) HA_GetInfo,
(LONG (*)()) HA_UnArchive, 0};

#endif /* XADMASTER_HA_C */
