#ifndef XADMASTER_DMS_C
#define XADMASTER_DMS_C

/*  $Id: DMS.c,v 1.11 2006/06/01 06:55:03 stoecker Exp $
    DMS and related disk/file archiver clients

    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
*/

//#define CRACK_PWD

/* This format seems to have really lots of problems. Append option
does not work correctly, because the crunching algorithm start data
seems to be handled wrong. ENCRYPTION over APPEND borders does not
work correctly as well. DIZ text sometimes are encrypted, sometimes
are not. Generally all is possible and all may fail. Hopefully there
are not too much archives out there doing such a strange defective
stuff. */

/* This client is lossely based (mainly decrunch stuff) on xDMS
source made by Andre R. de la Rocha. Thanks for your work. The code
has been made reentrant to fit requirements of shared libraries and
also lots of strange files are handled correctly now. Crunch Type
reinitialisation has been fixed as well. This client does not use
DMS header information (except password flag), but always the track
data. This should reduce influence of modified DMS files.

DIZ texts seem to use easy compression (NONE or RLE) always, so they
can be decompressed without decompressiong all the data before.
APPENDED files which use a password, where the normal file does
not use one, will fail. The other way round should work.
*/

/* FMS format:
- pre 2.04 FMS files contain file name in block DMSTRTYPE_FILENAME
- 2.04 format contains additionally information:
  LONGWORD      protection bits
  3 LONGWORDs   DateStamp structure
  xadUINT8      size of following text, if bit 7 is set, the text
                is a comment
[ xadUINT8      size of filename, when there was a comment]
*/

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

#ifndef XADMASTERVERSION
  #define XADMASTERVERSION      10
#endif

XADCLIENTVERSTR("DMS 1.13 (31.05.2006)")

#define DMS_VERSION             1
#define DMS_REVISION            12
#define DMSSFX_VERSION          DMS_VERSION
#define DMSSFX_REVISION         DMS_REVISION
#define SDSSFX_VERSION          DMS_VERSION
#define SDSSFX_REVISION         DMS_REVISION

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

#define DMSINFO_NOZERO          (1<<0)
#define DMSINFO_ENCRYPT         (1<<1)
#define DMSINFO_APPENDS         (1<<2)
#define DMSINFO_BANNER          (1<<3)
#define DMSINFO_HIGHDENSITY     (1<<4)
#define DMSINFO_PC              (1<<5)
#define DMSINFO_DMS_DEVICE_FIX  (1<<6)
#define DMSINFO_REGIST_VERSION  (1<<7)
#define DMSINFO_DIZ             (1<<8)
/* InfoFlags seem to be valid for first part. Appended part may have
   other flags! */

#define DMSOSV_AMIGA_AGA        (1<<15)

#define DMSCPU_68000            0
#define DMSCPU_68010            1
#define DMSCPU_68020            2
#define DMSCPU_68030            3
#define DMSCPU_68040            4
#define DMSCPU_68060            5
#define DMSCPU_6086             6
#define DMSCPU_8088             7
#define DMSCPU_80188            8
#define DMSCPU_80186            9
#define DMSCPU_80286            10
#define DMSCPU_80386SX          11
#define DMSCPU_80386            12
#define DMSCPU_80486            13
#define DMSCPU_80586            14

#define DMSFPU_NONE             0
#define DMSFPU_68881            1
#define DMSFPU_68882            2
#define DMSFPU_8087             3
#define DMSFPU_80287SX          4
#define DMSFPU_80387            5

#define DMSMACH_UNKNOWN         0
#define DMSMACH_AMIGA           1
#define DMSMACH_PC              2
#define DMSMACH_ATARI           3
#define DMSMACH_MACINTOSH       4

#define DMSTYPE_UNKNOWN         0
#define DMSTYPE_AOS1OFS         1
#define DMSTYPE_AOS1FFS         2
#define DMSTYPE_AOS3INT         3
#define DMSTYPE_AOS3INTFFS      4
#define DMSTYPE_AOS3DIR         5
#define DMSTYPE_AOS3DIRFFS      6
#define DMSTYPE_FMS             7

#define DMSCOMP_NOCOMP          0
#define DMSCOMP_SIMPLE          1
#define DMSCOMP_QUICK           2
#define DMSCOMP_MEDIUM          3
#define DMSCOMP_DEEP            4
#define DMSCOMP_HEAVY1          5
#define DMSCOMP_HEAVY2          6

#define DMSCFLAG_NOINIT         (1<<0)
#define DMSCFLAG_HEAVY_C        (1<<1)
#define DMSCFLAG_HEAVYRLE       (1<<2)
#define DMSCFLAG_HEAVY2         (1<<3)  /* own flag */

#define DMSTRTYPE_DIZ           80
#define DMSTRTYPE_BANNER        -1
#define DMSTRTYPE_FILENAME      0x03E7
#define DMSTRTYPE_FILESTART     0x03E8

#define DMSPWD_USE      1
#define DMSPWD_NOUSE    2

struct DMSHeader {
  xadUINT8 DMSID[4];          /* "DMS!" */
  xadUINT8 ID[4];             /* " PRO" or "FILE" */
  xadUINT8 InfoFlags[4];      /* DMSINFO_ defines */
  xadUINT8 Date[4];           /* UNIX format */
  xadUINT8 LowTrack[2];
  xadUINT8 HighTrack[2];
  xadUINT8 PackedSize[4];
  xadUINT8 UnpackedSize[4];
  xadUINT8 OSVersion[2];      /* DMSOSV_ defines */
  xadUINT8 OSRevision[2];
  xadUINT8 CPU[2];            /* DMSCPU_ defines */
  xadUINT8 FPU[2];            /* DMSFPU_ defines */
  xadUINT8 Machine[2];        /* DMSMACH_ defines */
  xadUINT8 DiskType2[2];      /* DMSTYPE_ defines */
  xadUINT8 CPUSpeed[2];       /* multiplied (in 0.01 MHz) */
  xadUINT8 CreationTime[4];
  xadUINT8 CreatorVersion[2];
  xadUINT8 NeededVersion[2];
  xadUINT8 DiskType[2];       /* DMSTYPE_ defines */
  xadUINT8 CompMode[2];       /* DMSCOMP_ defines */
  xadUINT8 CheckSum[2];
  /* checksums exclude DMSID and checksum word itself */
};

struct DMSTrack {
  xadUINT8 TRID[2];        /* "TR" */
  xadUINT8 TrackNumber[2]; /* signed */
  xadUINT8 pad[2];
  xadUINT8 CMODE_Packed[2];
  xadUINT8 RuntimePacked[2];
  xadUINT8 UnpackedSize[2];
  xadUINT8 CFlag;
  xadUINT8 CModeTrk;       /* DMSCOMP_ defines */
  xadUINT8 UncrunchedCRC[2];
  xadUINT8 CrunchedCRC[2];
  xadUINT8 CheckSum[2];
};

#define DMSNC 510
#define DMSNPT 30
#define DMSN1 510
#define DMSOFFSET 253

#define DMSF            60              /* lookahead buffer size */
#define DMSTHRESHOLD    2
#define DMSN_BYTE       (256 - DMSTHRESHOLD + DMSF)     /* kinds of characters (character code = 0..DMSN_BYTE-1) */
#define DMST            (DMSN_BYTE * 2 - 1)             /* size of table */
#define DMSR            (DMST - 1)      /* position of root */
#define DMSMAX_FREQ     0x8000          /* DMSupdates tree when the */

struct DMSData {
  xadUINT32             bitbuf;
  xadSTRPTR             indata;
  xadUINT8              bitcount;
  xadUINT8              DidInit;
  xadUINT16             RTV_Medium;
  xadUINT8              Text[32768];
  /* the above are accessed by Medium decruncher and supplied as a short
    data structure from SDS SFX */

  xadUINT16             UsePwd;
  xadUINT16             PassCRC;
  xadUINT16             RTV_Pass; /* RunTimeVariable */
  xadUINT16             RTV_Quick;
  xadUINT16             RTV_Deep;
  xadUINT16             RTV_Heavy;
  xadUINT16             c_table[4096];
  xadUINT16             pt_table[256];
  xadUINT16             lastlen;
  xadUINT16             np;
  xadUINT16             left[2*DMSNC - 1];
  xadUINT16             right[2*DMSNC -1 + 9];
  xadUINT16             freq[DMST + 1];         /* d->frequency table */
  xadUINT16             prnt[DMST + DMSN_BYTE]; /* pointers to parent nodes, except for the */
                /* elements [DMST..DMST + DMSN_BYTE - 1] which are used to get */
                /* the positions of leaves corresponding to the codes. */
  xadUINT16             son[DMST];      /* pointers to child nodes (son[], son[] + 1) */

  xadUINT8              DidInitDEEP;
  xadUINT8              c_len[DMSNC];
  xadUINT8              pt_len[DMSNPT];
};

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

static const xadUINT32 DMS_mask_bits[25]={
  0x000000,0x000001,0x000003,0x000007,0x00000f,0x00001f,
  0x00003f,0x00007f,0x0000ff,0x0001ff,0x0003ff,0x0007ff,
  0x000fff,0x001fff,0x003fff,0x007fff,0x00ffff,0x01ffff,
  0x03ffff,0x07ffff,0x0fffff,0x1fffff,0x3fffff,0x7fffff,
  0xffffff
};

static const xadUINT8 DMS_d_code[256] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
    0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
    0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
    0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
    0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D,
    0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F,
    0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11,
    0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13,
    0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15,
    0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17,
    0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B,
    0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F,
    0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23,
    0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27,
    0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B,
    0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
    0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
};

static const xadUINT8 DMS_d_len[256] = {
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
};

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

static xadINT32 DMSUnpRLE(xadSTRPTR *res, xadUINT32 size, struct xadMasterBase *xadMasterBase)
{
  xadSTRPTR out, outstore, outend, in = *res;
  xadUINT16 n;
  xadUINT8 a,b;
  xadINT32 err = 0;

  if(!(out = outstore = (xadSTRPTR) xadAllocVec(XADM size+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    return XADERR_NOMEMORY;

  outend = out+size;

  while(out < outend && !err)
  {
    if((a = *(in++)) != 0x90)
      *(out++) = a;
    else if(!(b = *(in++)))
      *(out++) = a;
    else
    {
      a = *(in++);
      if(b == 0xFF)
      {
        n = *(in++);
        n = ((n<<8) + *(in++));
      }
      else
        n = b;
      if(out+n > outend)
        err = XADERR_ILLEGALDATA;
      else
      {
        if(!n)
          break;
        memset(out,a,n);
        out += n;
      }
    }
  }

  xadFreeObjectA(XADM *res, 0);
  *res = outstore;
  return err;
}

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

#define DMSGETBITS(n) ((xadUINT16)(d->bitbuf >> (d->bitcount-(n))))
#define DMSDROPBITS(n) {d->bitbuf &= DMS_mask_bits[d->bitcount-=(n)]; \
        while (d->bitcount<16) {d->bitbuf = (d->bitbuf << 8) | \
        *d->indata++;  d->bitcount += 8;}}

static void DMSinitbitbuf(xadSTRPTR in, struct DMSData *d)
{
  d->bitbuf = 0;
  d->bitcount = 0;
  d->indata = in;
  DMSDROPBITS(0);
}

static xadINT32 DMSUnpQUICK(xadSTRPTR *res, xadUINT32 size, struct DMSData *d, struct xadMasterBase *xadMasterBase)
{
  xadSTRPTR out, outstore, outend, in = *res;
  xadUINT16 i,j;
  xadINT32 err = 0;

  if(!(out = outstore = (xadSTRPTR) xadAllocVec(XADM size+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    return XADERR_NOMEMORY;

  d->DidInit = 0;       /* clear Init flag */

  DMSinitbitbuf(in, d);
  outend = out + size;
  while (out < outend)
  {
    if(DMSGETBITS(1))
    {
      DMSDROPBITS(1);
      *out++ = d->Text[d->RTV_Quick++ & 0xFF] = (xadUINT8) DMSGETBITS(8);
      DMSDROPBITS(8);
    }
    else
    {
      DMSDROPBITS(1);
      j = DMSGETBITS(2)+2;
      DMSDROPBITS(2);
      i = d->RTV_Quick - DMSGETBITS(8) - 1;
      DMSDROPBITS(8);
      if(j + out > outend)
        err = XADERR_ILLEGALDATA;
      else
        while(j--)
          *out++ = d->Text[d->RTV_Quick++ & 0xFF] = d->Text[i++ & 0xFF];
    }
  }
  d->RTV_Quick = (d->RTV_Quick+5) & 0xFF;

  xadFreeObjectA(XADM *res, 0);
  *res = outstore;
  return err;
}

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

static xadINT32 DMSUnpMEDIUM(xadSTRPTR *res, xadUINT32 size, struct DMSData *d, struct xadMasterBase *xadMasterBase)
{
  xadSTRPTR out, outstore, outend, in = *res;
  xadUINT16 i,j, c;
  xadINT32 err = 0;
  xadUINT8 u;

  if(!(out = outstore = (xadSTRPTR) xadAllocVec(XADM size+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    return XADERR_NOMEMORY;

  d->DidInit = 0;       /* clear Init flag */

  DMSinitbitbuf(in, d);
  outend = out + size;

  while(out < outend)
  {
    if(DMSGETBITS(1))
    {
      DMSDROPBITS(1);
      *out++ = d->Text[d->RTV_Medium++ & 0x3FFF] = (xadUINT8)DMSGETBITS(8);
      DMSDROPBITS(8);
    }
    else
    {
      DMSDROPBITS(1);
      c = DMSGETBITS(8);
      DMSDROPBITS(8);
      j = (xadUINT16) (DMS_d_code[c]+3);
      u = DMS_d_len[c];
      c = (xadUINT16) (((c << u) | DMSGETBITS(u)) & 0xff);
      DMSDROPBITS(u);
      u = DMS_d_len[c];
      c = (xadUINT16) ((DMS_d_code[c] << 8) | (((c << u) | DMSGETBITS(u)) & 0xff));
      DMSDROPBITS(u);
      i = (xadUINT16) (d->RTV_Medium - c - 1);

      if(j + out > outend)
        err = XADERR_ILLEGALDATA;
      else
        while(j--)
          *out++ = d->Text[d->RTV_Medium++ & 0x3FFF] = d->Text[i++ & 0x3FFF];
    }
  }
  d->RTV_Medium = (xadUINT16)((d->RTV_Medium+66) & 0x3FFF);

  xadFreeObjectA(XADM *res,0);
  *res = outstore;
  return err;
}

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

/* reconstruction of tree */
static void DMSreconst(struct DMSData *d)
{
  xadUINT16 i, j, k, f, l;

  /* collect leaf nodes in the first half of the table */
  /* and replace the d->freq by (d->freq + 1) / 2. */
  j = 0;
  for (i = 0; i < DMST; i++)
  {
    if(d->son[i] >= DMST)
    {
      d->freq[j] = (xadUINT16) ((d->freq[i] + 1) / 2);
      d->son[j] = d->son[i];
      j++;
    }
  }
  /* begin constructing tree by connecting d->sons */
  for(i = 0, j = DMSN_BYTE; j < DMST; i += 2, j++)
  {
    k = (xadUINT16) (i + 1);
    f = d->freq[j] = (xadUINT16) (d->freq[i] + d->freq[k]);
    for(k = (xadUINT16)(j - 1); f < d->freq[k]; k--)
      ;
    k++;
    for(l = j; l > k; --l)
    {
      d->freq[l] = d->freq[l-1];
      d->son[l] = d->son[l-1];
    }
    d->freq[k] = f;
    d->son[k] = i;
  }
  /* connect d->prnt */
  for(i = 0; i < DMST; i++)
  {
    if((k = d->son[i]) >= DMST)
      d->prnt[k] = i;
    else
      d->prnt[k] = d->prnt[k + 1] = i;
  }
}

/* increment d->frequency of given code by one, and update tree */
static void DMSupdate(xadUINT16 c, struct DMSData *d)
{
  xadUINT16 i, j, k, l;

  if(d->freq[DMSR] == DMSMAX_FREQ)
    DMSreconst(d);
  c = d->prnt[c + DMST];
  do
  {
    k = ++d->freq[c];

    /* if the order is disturbed, exchange nodes */
    if(k > d->freq[l = (xadUINT16)(c + 1)])
    {
      while(k > d->freq[++l])
        ;
      l--;
      d->freq[c] = d->freq[l];
      d->freq[l] = k;

      i = d->son[c];
      d->prnt[i] = l;
      if(i < DMST)
        d->prnt[i + 1] = l;

      j = d->son[l];
      d->son[l] = i;

      d->prnt[j] = c;
      if(j < DMST)
        d->prnt[j + 1] = c;
      d->son[c] = j;

      c = l;
    }
  } while((c = d->prnt[c]) != 0); /* repeat up to root */
}

static xadUINT16 DMSDecodeChar(struct DMSData *d)
{
  xadUINT16 c;

  c = d->son[DMSR];

  /* travel from root to leaf, */
  /* choosing the smaller child node (d->son[]) if the read bit is 0, */
  /* the bigger (d->son[]+1) if 1 */
  while(c < DMST)
  {
    c = d->son[c + DMSGETBITS(1)];
    DMSDROPBITS(1);
  }
  c -= DMST;
  DMSupdate(c, d);
  return c;
}

static xadUINT16 DMSDecodePosition(struct DMSData *d)
{
  xadUINT16 i, j, c;

  i = DMSGETBITS(8);
  DMSDROPBITS(8);
  c = (xadUINT16) (DMS_d_code[i] << 8);
  j = DMS_d_len[i];
  i = (xadUINT16) (((i << j) | DMSGETBITS(j)) & 0xff);
  DMSDROPBITS(j);

  return (xadUINT16) (c | i);
}

static xadINT32 DMSUnpDEEP(xadSTRPTR *res, xadUINT32 size, struct DMSData *d, struct xadMasterBase *xadMasterBase)
{
  xadSTRPTR out, outstore, outend, in = *res;
  xadUINT16 i,j, c;
  xadINT32 err = 0;

  if(!(out = outstore = (xadSTRPTR) xadAllocVec(XADM size+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    return XADERR_NOMEMORY;

  d->DidInitDEEP = d->DidInit = 0;      /* clear Init flag */

  DMSinitbitbuf(in, d);
  outend = out + size;

  while(out < outend)
  {
    c = DMSDecodeChar(d);
    if(c < 256)
      *out++ = d->Text[d->RTV_Deep++ & 0x3FFF] = (xadUINT8)c;
    else
    {
      j = (xadUINT16) (c - 255 + DMSTHRESHOLD);
      i = (xadUINT16) (d->RTV_Deep - DMSDecodePosition(d) - 1);
      if(j + out > outend)
        err = XADERR_ILLEGALDATA;
      else
        while (j--)
          *out++ = d->Text[d->RTV_Deep++ & 0x3FFF] = d->Text[i++ & 0x3FFF];
    }
  }

  d->RTV_Deep = (xadUINT16)((d->RTV_Deep+60) & 0x3FFF);

  xadFreeObjectA(XADM *res, 0);
  *res = outstore;
  return err;
}

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

struct DMSTableData {
  xadINT16              c;
  xadUINT16             n;
  xadUINT16             tblsiz;
  xadUINT16             len;
  xadUINT16             depth;
  xadUINT16             maxdepth;
  xadUINT16             avail;
  xadUINT16             codeword;
  xadUINT16             bit;
  xadUINT16     *       tbl;
  xadUINT16     TabErr;
  xadSTRPTR     blen;

  xadUINT16     *       left;  /* copy of data in DMSData */
  xadUINT16     *       right; /* copy of data in DMSData */
};

static xadUINT16 DMSmktbl(struct DMSTableData *t)
{
  xadUINT16 i=0;

  if(t->TabErr)
    return 0;

  if(t->len == t->depth)
  {
    while(++t->c < t->n)
      if(t->blen[t->c] == t->len)
      {
        i = t->codeword;
        t->codeword += t->bit;
        if(t->codeword > t->tblsiz)
        {
          t->TabErr=1;
          return 0;
        }
        while(i < t->codeword)
          t->tbl[i++] = (xadUINT16) t->c;
        return (xadUINT16) t->c;
      }
      t->c = -1;
      t->len++;
      t->bit >>= 1;
  }
  t->depth++;
  if(t->depth < t->maxdepth)
  {
    (void) DMSmktbl(t);
    (void) DMSmktbl(t);
  }
  else if(t->depth > 32)
  {
    t->TabErr = 2;
    return 0;
  }
  else
  {
    if((i = t->avail++) >= 2 * t->n - 1)
    {
      t->TabErr = 3;
      return 0;
    }
    t->left[i] = DMSmktbl(t);
    t->right[i] = DMSmktbl(t);
    if(t->codeword >= t->tblsiz)
    {
      t->TabErr = 4;
      return 0;
    }
    if(t->depth == t->maxdepth)
      t->tbl[t->codeword++] = i;
  }
  t->depth--;
  return i;
}

static xadUINT16 DMSmake_table(xadUINT16 nchar, xadUINT8 bitlen[], xadUINT16 tablebits, xadUINT16 table[],
struct DMSData *d)
{
  struct DMSTableData t;

  t.left = d->left;
  t.right = d->right;

  t.n = t.avail = nchar;
  t.blen = (xadSTRPTR) bitlen;
  t.tbl = table;
  t.tblsiz = (xadUINT16) (1 << tablebits);
  t.bit = (xadUINT16) (t.tblsiz / 2);
  t.maxdepth = (xadUINT16)(tablebits + 1);
  t.depth = t.len = 1;
  t.c = -1;
  t.codeword = 0;
  t.TabErr = 0;
  (void) DMSmktbl(&t);  /* left subtree */
  (void) DMSmktbl(&t);  /* right subtree */
  if(t.TabErr)
    return t.TabErr;
  if(t.codeword != t.tblsiz)
    return 5;
  return 0;
}

static xadUINT16 DMSdecode_c(struct DMSData *d)
{
  xadUINT16 i, j, m;

  j = d->c_table[DMSGETBITS(12)];
  if(j < DMSN1)
  {
    DMSDROPBITS(d->c_len[j]);
  }
  else
  {
    DMSDROPBITS(12);
    i = DMSGETBITS(16);
    m = 0x8000;
    do
    {
      if(i & m)
        j = d->right[j];
      else
        j = d->left[j];
      m >>= 1;
    } while (j >= DMSN1);
    DMSDROPBITS(d->c_len[j] - 12);
  }
  return j;
}

static xadUINT16 DMSdecode_p(struct DMSData *d)
{
  xadUINT16 i, j, m;

  j = d->pt_table[DMSGETBITS(8)];
  if(j < d->np)
  {
    DMSDROPBITS(d->pt_len[j]);
  }
  else
  {
    DMSDROPBITS(8);
    i = DMSGETBITS(16);
    m = 0x8000;
    do
    {
      if(i & m)
        j = d->right[j];
      else
        j = d->left[j];
      m >>= 1;
    } while (j >= d->np);
    DMSDROPBITS(d->pt_len[j] - 8);
  }

  if(j != d->np-1)
  {
    if(j > 0)
    {
      j = (xadUINT16)(DMSGETBITS(i=(xadUINT16)(j-1)) | (1 << (j-1)));
      DMSDROPBITS(i);
    }
    d->lastlen=j;
  }

  return d->lastlen;
}

static xadUINT16 DMSread_tree_c(struct DMSData *d)
{
  xadUINT16 i,n;

  n = DMSGETBITS(9);
  DMSDROPBITS(9);
  if(n > 0)
  {
    for(i=0; i<n; i++)
    {
      d->c_len[i] = (xadUINT8)DMSGETBITS(5);
      DMSDROPBITS(5);
    }
    for(i=n; i<510; i++)
      d->c_len[i] = 0;
    if(DMSmake_table(510,d->c_len,12,d->c_table, d))
      return 1;
  }
  else
  {
    n = DMSGETBITS(9);
    DMSDROPBITS(9);
    for(i=0; i<510; i++)
      d->c_len[i] = 0;
    for(i=0; i<4096; i++)
      d->c_table[i] = n;
  }
  return 0;
}

static xadUINT16 DMSread_tree_p(struct DMSData *d)
{
  xadUINT16 i,n;

  n = DMSGETBITS(5);
  DMSDROPBITS(5);
  if(n > 0)
  {
    for(i=0; i<n; i++)
    {
      d->pt_len[i] = (xadUINT8) DMSGETBITS(4);
      DMSDROPBITS(4);
    }
    for(i=n; i<d->np; i++)
    d->pt_len[i] = 0;
    if(DMSmake_table(d->np,d->pt_len,8,d->pt_table, d))
      return 1;
  }
  else
  {
    n = DMSGETBITS(5);
    DMSDROPBITS(5);
    for(i=0; i<d->np; i++)
      d->pt_len[i] = 0;
    for (i=0; i<256; i++)
      d->pt_table[i] = n;
  }
  return 0;
}

static xadINT32 DMSUnpHEAVY(xadSTRPTR *res, xadUINT32 size, xadUINT8 flags, struct DMSData *d, struct xadMasterBase *xadMasterBase)
{
  xadSTRPTR out, outstore, outend, in = *res;
  xadUINT16 i,j, c, bitmask;
  xadINT32 err = 0;

  if(!(out = outstore = (xadSTRPTR) xadAllocVec(XADM size+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    return XADERR_NOMEMORY;

  d->DidInit = 0;       /* clear Init flag */

  /*  Heavy 1 uses a 4Kb dictionary,  Heavy 2 uses 8Kb  */

  if(flags & DMSCFLAG_HEAVY2)
  {
    d->np = 15;
    bitmask = 0x1fff;
  }
  else
  {
    d->np = 14;
    bitmask = 0x0fff;
  }

  DMSinitbitbuf(in, d);
  outend = out + size;

  if(flags & DMSCFLAG_HEAVY_C)
  {
    if(DMSread_tree_c(d))
    {
      xadFreeObjectA(XADM outstore, 0);
      return 1;
    }
    if(DMSread_tree_p(d))
    {
      xadFreeObjectA(XADM outstore, 0);
      return 2;
    }
  }

  while(out < outend)
  {
    c = DMSdecode_c(d);
    if(c < 256)
      *out++ = d->Text[d->RTV_Heavy++ & bitmask] = (xadUINT8)c;
    else
    {
      j = (xadUINT16) (c - DMSOFFSET);
      i = (xadUINT16) (d->RTV_Heavy - DMSdecode_p(d) - 1);
      if(j + out > outend)
      {
        err = XADERR_ILLEGALDATA; break;
      }
      else
      {
        while(j--)
          *out++ = d->Text[d->RTV_Heavy++ & bitmask] = d->Text[i++ & bitmask];
      }
    }
  }

  xadFreeObjectA(XADM *res,0);
  *res = outstore;
  return err;

}

/****************************************************************************/
/*  track 80 is FILEID.DIZ, track 0xffff (-1) is Banner  */
/*  and track 0 with 1024 bytes only is a fake boot block with more advertising */

static xadINT32 testDMSTrack(struct DMSTrack *t, struct xadMasterBase *xadMasterBase)
{
  if(EndGetM16(t->TRID) != 0x5452)
    return XADERR_ILLEGALDATA;
  if(xadCalcCRC16(XADM XADCRC16_ID1, 0, sizeof(struct DMSTrack)-2,
  (xadUINT8 *)t) != EndGetM16(t->CheckSum))
    return XADERR_CHECKSUM;

  return 0;
}

static xadUINT16 CheckSumDMS(xadSTRPTR mem, xadUINT32 size)
{
  xadUINT16 u = 0;

  while(size--)
    u += *mem++;

  return u;
}

static void DecryptDMS(xadSTRPTR p, xadUINT32 size, struct DMSData *d)
{
  xadUINT16 t;

  while(size--)
  {
    t = (xadUINT16) *p;
    *p++ ^= (xadUINT8) d->RTV_Pass;
    d->RTV_Pass = (xadUINT16)((d->RTV_Pass >> 1) + t);
  }
}

static void DMSInitData(struct DMSData *d)
{
  d->RTV_Quick = 251;
  d->RTV_Medium = 0x3FBE;
  d->RTV_Deep = 0x3fc4;

  if(!d->DidInitDEEP)
  {
    xadUINT16 i, j;
    for(i = 0; i < DMSN_BYTE; i++)
    {
      d->freq[i] = 1;
      d->son[i] = (xadUINT16)(i + DMST);
      d->prnt[i + DMST] = i;
    }
    i = 0;
    j = DMSN_BYTE;
    while(j <= DMSR)
    {
      d->freq[j] = (xadUINT16) (d->freq[i] + d->freq[i + 1]);
      d->son[j] = i;
      d->prnt[i] = d->prnt[i + 1] = j;
      i += 2;
      j++;
    }
    d->freq[DMST] = 0xffff;
    d->prnt[DMSR] = 0;
  }

  d->DidInit = d->DidInitDEEP = 1;

  memset(d->Text,0,0x3fc8);
}

/* always make buffer 1 byte larger, which is filled with 0! */
static xadINT32 DecrunchDMS(struct DMSTrack *t, struct xadArchiveInfo *ai,
struct xadMasterBase *xadMasterBase, xadSTRPTR *res, struct DMSData *d)
{
  xadPTR inbuf;
  xadINT32 err;
  xadUINT16 cmode = EndGetM16(t->CMODE_Packed);
  xadUINT16 upsize = EndGetM16(t->UnpackedSize);
  xadUINT16 rtsize = EndGetM16(t->RuntimePacked);

  *res = 0;

  if(!(inbuf = xadAllocVec(XADM cmode+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    return XADERR_NOMEMORY;

  if((err = xadHookAccess(XADM XADAC_READ, cmode, inbuf, ai)))
  {
    xadFreeObjectA(XADM inbuf,0);
    return err;
  }

  *res = inbuf;

  if(d->UsePwd == DMSPWD_USE && d->PassCRC)
    DecryptDMS(inbuf, cmode, d);

  switch(t->CModeTrk)
  {
  case DMSCOMP_NOCOMP: break;
  case DMSCOMP_SIMPLE: err = DMSUnpRLE(res, upsize, xadMasterBase); break;
  case DMSCOMP_QUICK:
    if(!(err = DMSUnpQUICK(res, rtsize, d, xadMasterBase)))
      err = DMSUnpRLE(res, upsize, xadMasterBase);
    break;
  case DMSCOMP_MEDIUM:
    if(!(err = DMSUnpMEDIUM(res, rtsize, d, xadMasterBase)))
      err = DMSUnpRLE(res, upsize, xadMasterBase);
    break;
  case DMSCOMP_DEEP:
    if(!(err = DMSUnpDEEP(res, rtsize, d, xadMasterBase)))
      err = DMSUnpRLE(res, upsize, xadMasterBase);
    break;
  case DMSCOMP_HEAVY1: case DMSCOMP_HEAVY2:
    if(t->CModeTrk == DMSCOMP_HEAVY1)
      err = DMSUnpHEAVY(res, rtsize, t->CFlag & ~DMSCFLAG_HEAVY2, d, xadMasterBase);
    else
      err = DMSUnpHEAVY(res, rtsize, t->CFlag | DMSCFLAG_HEAVY2, d, xadMasterBase);
    if(t->CFlag & DMSCFLAG_HEAVYRLE && !err)
      err = DMSUnpRLE(res, upsize, xadMasterBase);
    break;
  default: err = XADERR_DECRUNCH; break;
  }

  if(!err && CheckSumDMS(*res, upsize) != EndGetM16(t->UncrunchedCRC))
  {
    /* try again without password */
    if(d->UsePwd == DMSPWD_USE && d->PassCRC && !(err = xadHookAccess(XADM XADAC_INPUTSEEK,
    -cmode, 0, ai)))
    {
      xadFreeObjectA(XADM *res, 0);
      d->UsePwd = DMSPWD_NOUSE;
      return DecrunchDMS(t, ai, xadMasterBase, res, d);
    }
    if(!err)
      err = d->UsePwd ? XADERR_PASSWORD : XADERR_CHECKSUM;
  }

  if(!(t->CFlag & DMSCFLAG_NOINIT) && !(d->DidInit))
    DMSInitData(d);

  if(err)
  {
    if(*res)
    {
      xadFreeObjectA(XADM *res, 0);
      *res= 0;
    }
  }

  return err;
}

static struct DMSData *GetDMSData(struct xadMasterBase *xadMasterBase, xadSTRPTR pwd)
{
  struct DMSData *d;
  if((d = (struct DMSData *) xadAllocVec(XADM sizeof(struct DMSData), XADMEMF_CLEAR)))
  {
    DMSInitData(d);
    if(pwd)
    {
#ifdef CRACK_PWD
      if(pwd[0] == '0' && pwd[1] == 'x')
        d->RTV_Pass = d->PassCRC = 0;
      else
#endif
      d->RTV_Pass = d->PassCRC = xadCalcCRC16(XADM XADCRC16_ID1, 0, strlen((const char *)pwd), (xadUINT8 *)pwd);
    }
  }
  return d;
}

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

XADRECOGDATA(DMS)
{
  if(EndGetM32(((struct DMSHeader *)data)->DMSID) == 0x444D5321 && xadCalcCRC16(XADM XADCRC16_ID1, 0,
  sizeof(struct DMSHeader)-6, (xadUINT8 *) ((xadSTRPTR)data)+4) == EndGetM16(((struct DMSHeader *)data)->CheckSum))
    return 1;
  else
    return 0;
}

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

/* Only the crypted information is used form DMS header. All the other
   information is taken from data directly, to allow these lots of
   modified headers. */

static struct xadDiskInfo *DMSOneArc(struct xadMasterBase *xadMasterBase,
struct xadArchiveInfo *ai, xadINT32 *more, xadINT32 crypted, xadINT32 *ret)
{
  struct xadDiskInfo *di;
  struct xadTextInfo *ti = 0, *ti2;
  struct DMSData *d;
  struct DMSTrack t;
  xadINT32 err = 0, stop = 0, tracksize = 0;
  xadINT32 lowcyl = -1, highcyl = -1;
  xadSTRPTR zerotxt = 0;
  xadUINT32 zerosize = 0;

  *more = 0;

  if((di = (struct xadDiskInfo *) xadAllocObject(XADM XADOBJ_DISKINFO, TAG_DONE)))
  {
    di->xdi_Flags |= XADDIF_SEEKDATAPOS;
    if((d = GetDMSData(xadMasterBase, ai->xai_Password)))
    {
      if(crypted)
        d->UsePwd = DMSPWD_USE;

      di->xdi_DataPos = ai->xai_InPos;

      while(!stop && !err && ai->xai_InPos < ai->xai_InSize)
      {
        if(xadHookAccess(XADM XADAC_READ, sizeof(struct DMSTrack), &t, ai))
          stop = 1;
        else
        {
          if(testDMSTrack(&t, xadMasterBase))
            stop = 2;   /* stop > 1 means seek back one Track */
          else
          {
            xadINT16 tr = EndGetM16(t.TrackNumber);
            xadUINT16 upsize = EndGetM16(t.UnpackedSize);
            if(zerotxt)
            {
              while(zerosize && !zerotxt[zerosize-1])
                --zerosize;
              if(tr != 1 && zerosize && zerosize <= 2048) /* was a information text */
              {
                highcyl = lowcyl = -1; /* reset these two */
                if((ti2 = (struct xadTextInfo *) xadAllocObjectA(XADM XADOBJ_TEXTINFO, 0)))
                {
                  if(ti)
                    ti->xti_Next = ti2;
                  else
                    di->xdi_TextInfo = ti2;
                  ti = ti2;
                  if(!(ti2->xti_Text = (xadSTRPTR) xadAllocVec(XADM zerosize+1, XADMEMF_PUBLIC|XADMEMF_CLEAR)))
                    err = XADERR_NOMEMORY;
                  else
                  {
                    ti2->xti_Size = zerosize;
                    xadCopyMem(XADM zerotxt, ti->xti_Text, zerosize);
                  }
                }
                else
                  err = XADERR_NOMEMORY;
              }
              xadFreeObjectA(XADM zerotxt,0);
              zerotxt = 0;
            }
            /* Normally only -1 is allowed as banner, but I found at least
               one file using -2. */
            if(tr < 0 || tr == DMSTRTYPE_DIZ || (!tr && upsize == 1024))
            {
              if((ti2 = (struct xadTextInfo *) xadAllocObjectA(XADM XADOBJ_TEXTINFO, 0)))
              {
                if(tr == DMSTRTYPE_DIZ)
                  ti2->xti_Flags |= XADTIF_FILEDIZ;
                if(!DecrunchDMS(&t, ai, xadMasterBase, &ti2->xti_Text, d))
                  ti2->xti_Size = upsize;
                else if(crypted && !d->PassCRC)
                  ti2->xti_Flags |= XADTIF_CRYPTED;
                if(ti)
                  ti->xti_Next = ti2;
                else
                  di->xdi_TextInfo = ti2;
                ti = ti2;

                if(highcyl == -1)
                  di->xdi_DataPos = ai->xai_InPos;
              }
              else
                err = XADERR_NOMEMORY;
            }
            else
            {
              xadUINT16 cmode = EndGetM16(t.CMODE_Packed);
              if(highcyl == -1)
              {
                highcyl = lowcyl = tr;
                tracksize = upsize;
                /* store first track to extract */
/* FIXME - make real pointer! */
                di->xdi_PrivateInfo = (xadPTR)(uintptr_t) (ai->xai_InPos-sizeof(struct DMSTrack));
                if(!tr)
                {
                  zerosize = upsize;
                  if((DecrunchDMS(&t, ai, xadMasterBase, &zerotxt, d)))
                    zerotxt = 0;        /* ignore it */
                  continue;
                }
              }
              else if(tr != highcyl+1 || tracksize != upsize)
              {
                stop = 3; break;
              }
              else
                ++highcyl;

              if(d->UsePwd)
              {
                xadSTRPTR a;
                if(!(a = xadAllocVec(XADM cmode, XADMEMF_ANY)))
                  err = XADERR_NOMEMORY;
                else
                {
                  if(!(err = xadHookAccess(XADM XADAC_READ, cmode, a, ai)))
                    DecryptDMS(a, cmode, d);   /* to get a valid pwd pointer */
                  xadFreeObjectA(XADM a, 0);
                }
              }
              else
                err = xadHookAccess(XADM XADAC_INPUTSEEK, cmode, 0, ai);
            }
          } /* testDMSTrack */
        } /* xadHookAccess */
      } /* while */

      if(zerotxt) /* in case there is only one zero track and nothing else */
      {
        xadFreeObjectA(XADM zerotxt,0);
        zerotxt = 0;
      }

      if(stop > 1)
        err = xadHookAccess(XADM XADAC_INPUTSEEK, -sizeof(struct DMSTrack), &t, ai);

      if(!tracksize || lowcyl == -1 || highcyl == -1 || tracksize % (2*512))
        err = XADERR_ILLEGALDATA;
      else
      {
        if(crypted & DMSINFO_ENCRYPT)
          di->xdi_Flags |= XADDIF_CRYPTED;

        tracksize /= (2*512);

        di->xdi_LowCyl = lowcyl;
        di->xdi_HighCyl = highcyl;
        di->xdi_SectorSize = 512;
        di->xdi_Heads = 2;
        di->xdi_Cylinders = 80;
        switch(tracksize)
        {
        case 18: case 9:
          di->xdi_EntryInfo = (xadSTRPTR)"MSDOS disk"; /* no break! */
        case 22: case 11:
          di->xdi_TrackSectors = tracksize;
          break;
        default: err = XADERR_ILLEGALDATA; break;
        }
        di->xdi_CylSectors = 2 * di->xdi_TrackSectors;
        di->xdi_TotalSectors = 80 * di->xdi_CylSectors;
      }

      xadFreeObjectA(XADM d, 0);
    } /* GetDMSData */
    else
      err = XADERR_NOMEMORY;

    if(err)
    {
      for(ti = di->xdi_TextInfo; ti; ti = ti2)
      {
        ti2 = ti->xti_Next;
        if(ti->xti_Text)
          xadFreeObjectA(XADM ti->xti_Text, 0);
        xadFreeObjectA(XADM ti, 0);
      }
      xadFreeObjectA(XADM di, 0);
      di = 0;
    }
  } /* xadAllocObjectA */
  else
    err = XADERR_NOMEMORY;

  *ret = err;
  if(stop == 3 && !err)
    *more = 1;

  return di;
}

XADGETINFO(DMS)
{
  xadINT32 err = 0;
  struct DMSHeader h;
  struct DMSTrack t;
  struct xadFileInfo *fi;
  struct xadDiskInfo *di;
  xadUINT32 i, j;

  /* appended stuff is treated as own archive, as there may be gaps and double
     parts due to the chaotic file format of DMS. */

  while(!err && ai->xai_InPos < ai->xai_InSize &&
  !xadHookAccess(XADM XADAC_READ, sizeof(struct DMSHeader), &h, ai))
  {
    if(!testDMSTrack((struct DMSTrack *) &h, xadMasterBase))
      err = xadHookAccess(XADM XADAC_INPUTSEEK, sizeof(struct DMSTrack)+
      sizeof(struct DMSHeader)+EndGetM16(((struct DMSTrack *)&h)->CMODE_Packed), 0, ai);
    else
    {
      if(!DMS_RecogData(sizeof(struct DMSHeader), (xadPTR) &h, xadMasterBase))
        return 0;

      if(EndGetM32(h.InfoFlags) & DMSINFO_ENCRYPT)
        ai->xai_Flags |= XADAIF_CRYPTED;
      if(EndGetM16(h.DiskType2) == DMSTYPE_FMS || EndGetM16(h.DiskType) == DMSTYPE_FMS)
      {
        if(!(err = xadHookAccess(XADM XADAC_READ, sizeof(struct DMSTrack), &t, ai)) &&
        !(err = testDMSTrack(&t, xadMasterBase)) && EndGetM16(t.TrackNumber) == DMSTRTYPE_FILENAME)
        {
          if(!(fi = (struct xadFileInfo *) xadAllocObject(XADM XADOBJ_FILEINFO,
          XAD_OBJNAMESIZE, EndGetM16(t.UnpackedSize)+1, TAG_DONE)))
            return XADERR_NOMEMORY;
          if(!(err = xadHookAccess(XADM XADAC_READ, EndGetM16(t.UnpackedSize), fi->xfi_FileName, ai)))
          {
            i = (xadUINT32)ai->xai_InPos;
            j = 0;
            while(j < EndGetM32(h.UnpackedSize) && !err)
            {
              if(!(err = xadHookAccess(XADM XADAC_READ, sizeof(struct DMSTrack), &t, ai)) &&
              !(err = testDMSTrack(&t, xadMasterBase)))
              {
                if(!(err = xadHookAccess(XADM XADAC_INPUTSEEK, EndGetM16(t.CMODE_Packed), 0, ai)))
                  j += EndGetM16(t.UnpackedSize);
              }
            }
            if(!err)
            {
              if(EndGetM32(h.InfoFlags) & DMSINFO_ENCRYPT)
                fi->xfi_Flags = XADFIF_CRYPTED;
              if(!fi->xfi_FileName[0]) /* This is a 2.04 FMS files */
              {
                struct FMSInfo {
                  xadUINT8 prot[4];
                  xadUINT8 date[12];
                  } inf;

                xadCopyMem(XADM fi->xfi_FileName, (xadPTR) &inf,
                sizeof(struct FMSInfo));
                fi->xfi_FileName += sizeof(struct FMSInfo);

                fi->xfi_Protection = EndGetM32(inf.prot);
                err = xadConvertDates(XADM XAD_DATEDATESTAMP, &inf.date,
                XAD_GETDATEXADDATE, &fi->xfi_Date, TAG_DONE);
                if(fi->xfi_FileName[0] & 0x80)
                {
                  fi->xfi_Comment = fi->xfi_FileName+1;
                  fi->xfi_FileName += (fi->xfi_FileName[0]&0x7F)+1;
                }
                *(fi->xfi_FileName++) = 0; /* clear size (make C pointer) */
              }
              else
                err = xadConvertDates(XADM XAD_DATEUNIX, EndGetM32(h.Date)-7*60*60,
                XAD_GETDATEXADDATE, &fi->xfi_Date, XAD_MAKELOCALDATE, 1, TAG_DONE);
              fi->xfi_DataPos = i;
              fi->xfi_Flags |= XADFIF_SEEKDATAPOS;
              fi->xfi_Size = EndGetM32(h.UnpackedSize);
              fi->xfi_CrunchSize = EndGetM32(h.PackedSize);
              err = xadAddFileEntry(XADM fi, ai, XAD_SETINPOS, ai->xai_InPos, TAG_DONE);
              fi = 0;
            }
          }
          if(fi)
            xadFreeObjectA(XADM fi, 0);
        }
      }
      else /* is a disk archive */
      {
        xadINT32 more = 1;

        while(more)
        {
          if((di = DMSOneArc(xadMasterBase, ai, &more, EndGetM32(h.InfoFlags) & DMSINFO_ENCRYPT, &err)))
            err = xadAddDiskEntry(XADM di, ai, XAD_SETINPOS, ai->xai_InPos, TAG_DONE);
        }
      } /* DISK ARCHIVE */
    } /* found correct track */
    if(err)
    {
      ai->xai_Flags |= XADAIF_FILECORRUPT;
      ai->xai_LastError = err;
    }
  } /* while */

  return 0;
}

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

XADUNARCHIVE(DMS)
{
  struct DMSTrack t;
  xadINT32 i;
  xadINT32 err = 0;
  xadSTRPTR pwd = ai->xai_Password;
  struct DMSData *d;

#ifdef CRACK_PWD
  xadINT32 j=1, k;

  if((ai->xai_CurFile && !(ai->xai_CurFile->xfi_Flags & XADFIF_CRYPTED)) ||
  !(ai->xai_CurDisk->xdi_Flags & XADDIF_CRYPTED))
    pwd = 0;

  if(pwd && pwd[0] == '0' && pwd[1] == 'x')
  {
    xadUINT8 a;
    for(j = i = 0; i < 4; ++i)
    {
      a = pwd[2+i];
      if(a >= 'A' && a <= 'F') a = a-('A'-10);
      else if(a >= 'a' && a <= 'f') a = a-('a'-10);
      else if(a >= '0' && a <= '9') a = a-'0';
      j = (j<<4)+a;
    }
    pwd = 0;
  }

  k = ai->xai_InPos;
  do
  {
    err = 0;
    if(k != ai->xai_InPos)
      xadHookAccess(XADM XADAC_INPUTSEEK, k-ai->xai_InPos, 0, ai);
    if(ai->xai_OutPos)
      xadHookAccess(XADM XADAC_OUTPUTSEEK, -ai->xai_OutPos, 0, ai);
#else

  if((ai->xai_CurFile && !(ai->xai_CurFile->xfi_Flags & XADFIF_CRYPTED)) ||
  !(ai->xai_CurDisk->xdi_Flags & XADDIF_CRYPTED))
    pwd = 0;
  else if(!pwd)
    return XADERR_PASSWORD;
#endif

  if(!(d = GetDMSData(xadMasterBase, pwd)))
    return XADERR_NOMEMORY;

#ifdef CRACK_PWD
  if(!pwd)
  {
    d->RTV_Pass = d->PassCRC = j;
    if(!(j & 0xFF))
    {
      DebugClient(ai, "CRACKPWD %04lx", j);
    }
  }
  else
    j = d->PassCRC;
#endif

  if(ai->xai_CurFile)
  {
    struct xadFileInfo *fi;

    fi = ai->xai_CurFile;

    if(fi->xfi_Flags & XADFIF_CRYPTED)
      d->UsePwd = DMSPWD_USE;

    for(i = DMSTRTYPE_FILESTART; !err && ai->xai_OutSize < fi->xfi_Size; ++i)
    {
      if(!(err = xadHookAccess(XADM XADAC_READ, sizeof(struct DMSTrack), &t, ai)) &&
      !(err = testDMSTrack(&t, xadMasterBase)))
      {
        xadSTRPTR res;

        if(i != EndGetM16(t.TrackNumber))
          err = XADERR_ILLEGALDATA;
        else if(!(err = DecrunchDMS(&t, ai, xadMasterBase, &res, d)))
        {
          err = xadHookAccess(XADM XADAC_WRITE, EndGetM16(t.UnpackedSize), res, ai);
          xadFreeObjectA(XADM res, 0);
        }
        else if((err == XADERR_PASSWORD) && i != DMSTRTYPE_FILESTART)
          err = XADERR_CHECKSUM; /* prevent wrong password errors */
      }
    }
  }
  else
  {
    struct xadDiskInfo *di;

    di = ai->xai_CurDisk;
    if(di->xdi_Flags & XADDIF_CRYPTED)
      d->UsePwd = DMSPWD_USE;

    for(i = ai->xai_LowCyl; !err && i <= ai->xai_HighCyl;)
    {
      if(!(err = xadHookAccess(XADM XADAC_READ, sizeof(struct DMSTrack), &t, ai)) &&
      !(err = testDMSTrack(&t, xadMasterBase)))
      {
        xadSTRPTR res;

        if(!(err = DecrunchDMS(&t, ai, xadMasterBase, &res, d)))
        { /* xdi_PrivateInfo contains start pos of useful data */
          if(ai->xai_InPos > (xadUINT32)(uintptr_t) di->xdi_PrivateInfo) /* skip unusable parts */
          {
            if(i == EndGetM16(t.TrackNumber))
            {
              err = xadHookAccess(XADM XADAC_WRITE, EndGetM16(t.UnpackedSize), res, ai); ++i;
            }
            else if(EndGetM16(t.TrackNumber) > i)
              err = XADERR_ILLEGALDATA;
          }
          xadFreeObjectA(XADM res,0);
        }
        else if((err == XADERR_PASSWORD) && i != ai->xai_LowCyl)
          err = XADERR_CHECKSUM; /* prevent wrong password errors */
      }
    }
  }

  xadFreeObjectA(XADM d, 0);

#ifdef CRACK_PWD
  } while(++j < 65536 && err && !pwd);

  if(!err)
    DebugClient(ai,"Final Pass CRC is %04lx\n", --j);
#endif

  return err;
}

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

XADRECOGDATA(DMSSFX)
{
  if(EndGetM32(data) == 0x3F3 /*HUNK_HEADER*/)
  { /* DMS 2.03 file */
    if(EndGetM32(data+5*4) == 0x1605 && EndGetM32(data+6*4) == 0x1C24
    && EndGetM32(data+16*4) == 0x303C05CD && EndGetM32(data+17*4) == 0x421B51C8
    && EndGetM32(data+18*4) == 0xFFFC47F9)
      return 1;
    /* DMS 2.04 file - defective */
    if(EndGetM32(data+10*4) == 0x24E2 && EndGetM32(data+9*4) == 0x60000006
    && EndGetM32(data+11*4) == 0x48E77EFE && EndGetM32(data+12*4) == 0x24482400
    && EndGetM32(data+16*4) == 0x3B61425B)
      return 1;
    /* DMS 2.03, 2.04 disk */
    if(EndGetM32(data+11*4) == 0xABCD && EndGetM32(data+19*4) == 0x48E7FFF6
    && EndGetM32(data+20*4) == 0x61000030 && EndGetM32(data+21*4) == 0x4CDF6FFF)
      return 1;
  }
  return 0;
}

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

XADGETINFO(DMSSFX)
{
  xadINT32 err, offs = 0;
  xadUINT8 data[6*4];

  if(!(err = xadHookAccess(XADM XADAC_READ, 6*4, data, ai)))
  {
    switch(EndGetM32(data+5*4))
    {
    case 0x1605: offs = 0x58C4; break; /* DMS 2.03 file */
    case 0x2462: offs = 0x45D0; break; /* DMS 2.03 disk */
    case 0x2466: offs = 0x45E0; break; /* DMS 2.04 disk */
    case 0x3269: offs = 0x537C; break; /* DMS 2.04 file */
    default:
      err = XADERR_ILLEGALDATA;
    }
    if(!err && !(err = xadHookAccess(XADM XADAC_INPUTSEEK, offs-6*4, 0, ai)))
      err = DMS_GetInfo(ai, xadMasterBase);
  }

  return err;
}

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

XADRECOGDATA(SDSSFX)
{
  if(EndGetM32(data) == 0x3F3 /*HUNK_HEADER*/)
  {
    if(EndGetM32(data+15*4) == 0xFDD823C0 && EndGetM32(data+17*4) == 0x2C404EAE
    && EndGetM32(data+19*4) == 0x405A && EndGetM32(data+20*4) == 0x207C0000
    && EndGetM32(data+21*4) == 0x020C7600)
      return 1;
  }
  return 0;
}

struct SDSSFXData {
  xadUINT8 Name[20];
  xadUINT8 CrSize[4];
  xadUINT8 Size[4];
  xadUINT8 CheckSum[2];
};

XADGETINFO(SDSSFX)
{
  xadINT32 err, i, j;
  struct SDSSFXData sd;
  struct xadFileInfo *fi;

  if((err = xadHookAccess(XADM XADAC_INPUTSEEK, 0x41C, 0, ai)))
    return err;
  while(!err)
  {
    j = (xadINT32)ai->xai_InPos;
    if(!(err = xadHookAccess(XADM XADAC_READ, sizeof(struct SDSSFXData), &sd, ai)))
    {
      if(!(i = strlen((char *)sd.Name)))
        break; /* last entry */
      else if((fi = (struct xadFileInfo *) xadAllocObject(XADM XADOBJ_FILEINFO,
      XAD_OBJNAMESIZE, i, TAG_DONE)))
      {
        fi->xfi_DataPos = j; /* file position */
        fi->xfi_Size = EndGetM32(sd.Size);
        fi->xfi_CrunchSize = EndGetM32(sd.CrSize);
        fi->xfi_Flags = XADFIF_NODATE|XADFIF_SEEKDATAPOS|XADFIF_EXTRACTONBUILD;
        for(j = 0; j < i; ++j)
          fi->xfi_FileName[j] = sd.Name[j];
        xadConvertDates(XADM XAD_DATECURRENTTIME, 1, XAD_GETDATEXADDATE,
        &fi->xfi_Date, TAG_DONE);

        err = xadAddFileEntry(XADM fi, ai, XAD_SETINPOS, ai->xai_InPos+fi->xfi_CrunchSize, TAG_DONE);
      }
      else
        err = XADERR_NOMEMORY;
    }
  }
  if(err)
  {
    ai->xai_Flags |= XADAIF_FILECORRUPT;
    ai->xai_LastError = err;
  }

  return ai->xai_FileInfo ? 0 : XADERR_ILLEGALDATA;
}

struct DMSDataShort {
  xadUINT32     bitbuf;
  xadSTRPTR     indata;
  xadUINT8      bitcount;
  xadUINT8      DidInit;
  xadUINT16     RTV_Medium;
  xadUINT8      Text[32768];
};

XADUNARCHIVE(SDSSFX)
{
  xadINT32 err;
  struct xadFileInfo *fi;
  struct SDSSFXData sd;

  fi = ai->xai_CurFile;
  if(!(err = xadHookAccess(XADM XADAC_READ, sizeof(struct SDSSFXData), &sd, ai)))
  {
    struct DMSData *d;

    if((d = (struct DMSData *) xadAllocVec(XADM sizeof(struct DMSDataShort), XADMEMF_PUBLIC|XADMEMF_CLEAR)))
    {
      xadSTRPTR buf;

      if((buf = (xadSTRPTR) xadAllocVec(XADM fi->xfi_CrunchSize, XADMEMF_PUBLIC)))
      {
        d->RTV_Medium = 0x3FBE;

        if(!(err = xadHookAccess(XADM XADAC_READ, fi->xfi_CrunchSize, buf, ai)))
        {
          if(!(err = DMSUnpMEDIUM(&buf, (xadUINT32)fi->xfi_Size, d, xadMasterBase)))
          {
            if(CheckSumDMS(buf, (xadUINT32)fi->xfi_Size) == EndGetM16(sd.CheckSum))
              err = xadHookAccess(XADM XADAC_WRITE, fi->xfi_Size, buf, ai);
            else
              err = XADERR_CHECKSUM;
          }
        }
        xadFreeObjectA(XADM buf, 0);
      }
      else
        err = XADERR_NOMEMORY;
      xadFreeObjectA(XADM d, 0);
    }
    else
      err = XADERR_NOMEMORY;
  }

  return err;
}

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

XADCLIENT(SDSSFX) {
  XADNEXTCLIENT,
  XADCLIENT_VERSION,
  XADMASTERVERSION,
  SDSSFX_VERSION,
  SDSSFX_REVISION,
  100,
  XADCF_FILEARCHIVER|XADCF_FREEFILEINFO,
  XADCID_SDSSFX,
  "SDS Software SFX",
  XADRECOGDATAP(SDSSFX),
  XADGETINFOP(SDSSFX),
  XADUNARCHIVEP(SDSSFX),
  NULL
};

XADCLIENT(DMSSFX) {
  (struct xadClient *) &SDSSFX_Client,
  XADCLIENT_VERSION,
  XADMASTERVERSION,
  DMSSFX_VERSION,
  DMSSFX_REVISION,
  100,
  XADCF_DISKARCHIVER|XADCF_FILEARCHIVER|XADCF_FREEFILEINFO|XADCF_FREEDISKINFO|
    XADCF_FREETEXTINFO|XADCF_FREETEXTINFOTEXT,
  XADCID_DMSSFX,
  "DMS SFX",
  XADRECOGDATAP(DMSSFX),
  XADGETINFOP(DMSSFX),
  XADUNARCHIVEP(DMS),
  NULL
};

XADFIRSTCLIENT(DMS) {
  (struct xadClient *) &DMSSFX_Client,
  XADCLIENT_VERSION,
  XADMASTERVERSION,
  DMS_VERSION,
  DMS_REVISION,
  sizeof(struct DMSHeader),
  XADCF_DISKARCHIVER|XADCF_FILEARCHIVER|XADCF_FREEFILEINFO|XADCF_FREEDISKINFO|
    XADCF_FREETEXTINFO|XADCF_FREETEXTINFOTEXT,
  XADCID_DMS,
  "DMS",
  XADRECOGDATAP(DMS),
  XADGETINFOP(DMS),
  XADUNARCHIVEP(DMS),
  NULL
};

#undef XADNEXTCLIENT
#define XADNEXTCLIENT XADNEXTCLIENTNAME(DMS)

#endif /* XADMASTER_DMS_C */
