/*
** unarc.c
**
** =================================
**  Extract C64/C128 style archives
** =================================
**
**  Derived from Chris Smeets' MS-DOS utility a2l that converts Commodore
**  ARK, ARC and SDA files to LZH using LHARC 1.12. Optimized for speed,
**  converted to standard C and adapted to the cbmconvert package by
**  Marko Mäkelä.
**
**  Original version: a2l.c       March   1st, 1990   Chris Smeets
**  Unix port:        unarc.c     August 28th, 1993   Marko Mäkelä
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "scan.h"

/* Determine endianess */

#ifdef __STDC__
#include <endian.h>
#endif

#ifndef LITTLE_ENDIAN
#define BUILD_WORDS
#endif

/* Function prototypes */

#ifdef __STDC__
void ssort (unsigned n, int (*cmp)(int, int),
	    void (*swp)(int, int));
int hcomp (int x, int y);
void hswap (int x, int y);
__inline int GetBit (void);
unsigned char GetByte (void);
unsigned int GetWord (void);
long GetThree (void);
int Huffin (void);
int GetHeader (void);
int GetStartPos	(void);
void push (int c);
int pop (void);
int unc	(void);
int getcode (void);
void UpdateChecksum (int c);
int UnPack (void);
int PutC (int c);
int main (int argc, char **argv);
#else /* ! __STDC__ */
void ssort ();
int hcomp ();
void hswap ();
int GetBit ();
unsigned char GetByte ();
unsigned int GetWord ();
long GetThree ();
int Huffin ();
int GetHeader ();
int GetStartPos	();
void push ();
int pop ();
int unc ();
int getcode ();
void UpdateChecksum ();
int UnPack ();
int PutC ();
int main ();
#endif /* __STDC__ */

/*  Globals */

int           Status;     /* I/O status. 0=ok, or EOF */
unsigned long FilePos;    /* Current offset from ARC or SDA file's beginning */
FILE *        fp;         /* archive */
unsigned int  BitBuf;     /* Bit Buffer */
unsigned int  crc;        /* checksum */
unsigned char crc2;       /* used in checksum calculation */
unsigned long hc[256];    /* Huffman codes */
unsigned char hl[256];    /* Lengths of huffman codes */
unsigned char hv[256];    /* Character associated with Huffman code */
unsigned int  hcount;     /* Number of Huffman codes */
unsigned int  ctrl;       /* Run-Length control character */
unsigned int  loadaddr;   /* Load address if CBM PRG file */
long          truesize;   /* True file size */


/*  C64 Archive entry header */

struct ARC64Header {
  unsigned char version;  /* Version number, must be 1 or 2 */
  unsigned char mode;     /* 0=store, 1=pack, 2=squeeze, 3=crunch,
			     4=squeeze+pack, 5=crunch in one pass */
  unsigned int  check;    /* Checksum */
  long          size;     /* Original size. Only three bytes are stored */
  unsigned int  blocks;   /* Compressed size in CBM disk blocks */
  unsigned char type;     /* File type. P,S,U or R */
  unsigned char fnlen;    /* Filename length */
  unsigned char name[17]; /* Filename. Only fnlen bytes are stored */
  unsigned char rl;       /* Record length if relative file */
  unsigned int  date;     /* Date archive was created. Same format 
			     as in MS-DOS directories */
} entry;

/*  Lempel Zev compression string table entry */

struct LZ {
  unsigned int  prefix;     /* Prefix code */
  unsigned char ext;        /* Extension character */
};

/*  LZ globals */

#define LZSTKSIZ 512        /* Actually only 40 or 50 bytes is sufficient */

int           State = 0;      /* Set to 0 to reset un-crunch */
struct LZ     lztab[4096];    /* String table */
unsigned char stack[LZSTKSIZ];/* Stack for push/pop */
int           lzstack;        /* Stack pointer */
int           cdlen,          /* Current code size */
              code,           /* Last code rec'd */
              wtcl;           /* Bump cdlen when code reaches this value */
int           ncodes,         /* Current # of codes in table */
              wttcl;          /* Copy of wtcl */


/*  --------------------------------------------------------------------------
    Shell Sort.  From "C Programmer's Library" by Purdum, Leslie & Stegemoller

    'swap' and 'comp' functions vary depending on the data type being sorted.

    swp(i,j)  - simply swaps array elements i and j.
    cmp(i,j)  - returns 0 if elements are equal.
                      >0 if element[i] > element[j]
                      <0 if    ''      <    ''
    --------------------------------------------------------------------------
    n   = Number of elements to be sorted. (0 is 1st, n-1 is last)
    cmp = Compare two elements. Return result like strcmp()
    swp = Swap to elements
*/

#ifndef __STDC__
void
ssort (n, cmp, swp)
     unsigned int n;
     int (*cmp) ();
     void (*swp) ();
#else /* __STDC__ */
void ssort (unsigned int n, int (*cmp)(int,int), void (*swp)(int,int))
#endif
{
  int m;
  int h,i,j,k;

  m=n;

  while( (m /= 2) != 0) {
    k = n-m;
    j = 1;
    do {
      i=j;
      do {
	h=i+m;
	if ((*cmp)(i-1,h-1) >0 ) {
	  (*swp)(i-1,h-1);
	  i -= m;
	}
	else
	  break;
      } while (i >= 1);
      j += 1;
    } while(j <= k);
  }
}


/*  ------------------------------------------------
    Compare function for Shell Sort of Huffman codes
    Set up to sort them in reverse order by length
    ------------------------------------------------
*/

#ifndef __STDC__    
int
hcomp (x, y)
     int x;
     int y;
#else
int hcomp (int x, int y)
#endif
{
  return ( hl[y] - hl[x] );
}

#ifndef __STDC__
void
hswap (x, y)
     int x;
     int y;
#else
void hswap (int x, int y)
#endif
{
  unsigned long t0;
  unsigned char t1, t2;

  t0    = hc[x];
  t1    = hv[x];
  t2    = hl[x];
  hc[x] = hc[y];
  hv[x] = hv[y];
  hl[x] = hl[y];
  hc[y] = t0;
  hv[y] = t1;
  hl[y] = t2;
}


/*  -----------------
    Input Subroutines
    -----------------
*/

#ifndef __STDC__
unsigned char
GetByte ()
#else
unsigned char GetByte (void)
#endif
{
  if (Status == EOF)
    return 0;

  if (feof(fp) || ferror(fp)) {
    Status = EOF;
    return EOF;
  }
  else {
    Status = 0;
  }

  return (unsigned char)(fgetc (fp) & 0xff);
}

#ifndef __STDC__
unsigned int
GetWord ()
#else
unsigned int GetWord (void)
#endif
{
  unsigned int u = 0;

  if (Status == EOF)
    return 0;

  if (feof(fp) || ferror(fp)) {
    Status = EOF;
    return EOF;
  }
  else {
    Status = 0;
  }

#ifdef BUILD_WORDS
  u = (unsigned int)(fgetc (fp) & 0xff);
  u |= (unsigned int)(fgetc (fp) & 0xff) << 8;
#else
  if (1 != fread (&u, 2, 1, fp)) {
    Status = EOF;
    return EOF;
  }
#endif

  return u;
}

#ifndef __STDC__
long
GetThree ()
#else
long GetThree (void)
#endif
{
  long u = 0;

  if (Status == EOF)
    return 0;

  if (feof(fp) || ferror(fp)) {
    Status = EOF;
    return EOF;
  }
  else {
    Status = 0;
  }

#ifdef BUILD_WORDS
  u = (unsigned int)(fgetc (fp) & 0xff);
  u |= (unsigned int)(fgetc (fp) & 0xff) << 8;
  u |= (unsigned int)(fgetc (fp) & 0xff) << 16;
#else
  if (1 != fread (&u, 3, 1, fp)) {
    Status = EOF;
    return EOF;
  }
#endif

  return u;
}


#ifndef __STDC__
int
GetBit ()
#else
__inline int GetBit (void)
#endif
{
  register int result = (BitBuf >>= 1);

  if (result == 1)
    return ((BitBuf = GetByte() | 0x0100) & 1);
  else
    return (result & 1);
}




/*  --------------------------------------------------------
    Fetch huffman code and convert it to what it represents
    --------------------------------------------------------
*/

#ifndef __STDC__
int
Huffin ()
#else
int Huffin (void)
#endif
{
  long hcode = 0;
  long mask  = 1;
  int  size  = 1;
  int  now;

  now = hcount;       /* First non=zero Huffman code */

  do {
    if (GetBit())
      hcode |= mask;

    while( hl[now] == size) {

      if (hc[now] == hcode)
	return hv[now];

      if (--now < 0) {         /* Error in decode table */
	Status = EOF;
	return EOF;
      }
    }
    size++;
    mask = mask << 1;
  } while(size < 24);

  Status = EOF;
  return EOF;                      /* Error. Huffman code too big */
}

/*  ------------------------------------------------------------------
    Fetch ARC64 header. Returns 1 if header is ok, otherwise returns 0
    ------------------------------------------------------------------
*/

#ifndef __STDC__
int
GetHeader ()
#else
int GetHeader (void)
#endif
{
  unsigned int  w, i;
  char          *LegalTypes = "SPUR";
  unsigned long mask;

  if (feof(fp) || ferror(fp))
    return 0;
  else
    Status = 0;

  BitBuf        = 2;              /* Clear Bit buffer */
  crc           = 0;              /* checksum */
  crc2          = 0;              /* Used in checksum calculation */
  State         = 0;              /* LZW state */
  ctrl          = 254;            /* Run-Length control character */

  entry.version = GetByte();
  entry.mode    = GetByte();
  entry.check   = GetWord();
  entry.size    = GetThree();
  entry.blocks  = GetWord();
  entry.type    = GetByte();
  entry.fnlen   = GetByte();

  /* Check for invalid header, If invalid, then we've input past the end */
  /* Possibly due to XMODEM padding or whatever */

  if  (entry.fnlen > 16)
    return 0;

  for (w=0; w < entry.fnlen; w++)
    entry.name[w] = GetByte();

  entry.name[entry.fnlen] = 0;

  if (entry.version > 1) {
    entry.rl  = GetByte();
    entry.date= GetWord();
  }

  if (Status == EOF)
    return 0;

  if ( (entry.version == 0) || (entry.version >2) )
    return 0;

  if ( entry.version == 1) {  /* If ARC64 version 1.xx */
    if (entry.mode > 2)     /* Only store, pack, squeeze */
      return 0;
  }
  if (entry.mode == 1)        /* If packed get control char */
    ctrl = GetByte();       /* V2 always uses 0xfe V1 varies */

  if (entry.mode > 5)
    return 0;

  if (entry.blocks > 4133)    /* Largest CBM disk is 8250 */
    return 0;

  if ( (entry.mode == 2) || (entry.mode == 4) ) { /* if squeezed or squashed */
    hcount = 255;                                 /* Will be first code */

    for (w=0; w<256; w++) {                       /* Fetch Huffman codes */
      hv[w] = w;

      hl[w]=0;
      mask = 1;
      for (i=1; i<6; i++) {
	if (GetBit())
	  hl[w] |= mask;
	mask <<= 1;
      }

      if (hl[w] > 24)
	return 0;                             /* Code too big */

      hc[w] = 0;
      if (hl[w]) {
	i = 0;
	mask = 1;
	while (i<hl[w]) {
	  if (GetBit())
	    hc[w] |= mask;
	  i++;
	  mask <<= 1;
	}
      }
      else
	hcount--;
    }
    ssort(256,hcomp,hswap);
  }

  if (strchr(LegalTypes, entry.type) == 0)
    return 0;

  return 1;
}





/*  --------------------------------------------------------------------------
     Get start of data. Ignores SDA header, and returns -1 if not an archive.
     Otherwise return value is the starting position of useful data within the
     file. (Normally 0)
    --------------------------------------------------------------------------
*/

#ifndef __STDC__
int
GetStartPos ()
#else
int GetStartPos (void)
#endif
{
  int c;                      /* Temp */
  int cpu;                    /* C64 or C128 if SDA */
  int linenum;                /* Sys line number */
  int skip;                   /* Size of SDA header in bytes */

  fseek(fp,(long) 0,0);       /* Goto start of file */

  if ( (c=GetByte()) == 2)    /* Probably type 2 archive */
    return 0;                 /* Data starts at offset 0 */

  if (c != 1)                 /* IBM archive, or not an archive at all */
    return -1;

  /* Check if its an SDA */

  GetByte();           /* Skip to line number (which is # of header blocks) */
  GetWord();
  linenum = GetWord();
  c = GetByte();

  if (c != 0x9e)              /* Must be BASIC SYS token */
    return 0;                 /* Else probably type 1 archive */

  c = GetByte();              /* Get SYS address */
  cpu = GetByte();            /* '2' for C64, '7' for C128 */

  skip = (linenum-6)*254;     /* True except for SDA232.128 */

  if ( (linenum==15) && (cpu=='7') )   /* handle the special case */
    skip -= 1;

  return skip;
}


/*  ----------------------------------------------------------------------
    Un-Crunch a byte
    ----------------------------------------------------------------------
    This is pretty straight forward if you have Terry Welch's article
    "A Technique for High Performance Data Compression" from IEEE Computer
    June 1984

    This implemention reserves code 256 to indicate the end of a crunched
    file, and code 257 was reserved for future considerations. Codes grow
    up to 12 bits and then stay there. There is no reset of the string
    table.
*/

    /* PUSH/POP LZ stack */

#ifndef __STDC__
void
push (c)
     int c;
#else
void push (int c)
#endif
{
  if (lzstack > LZSTKSIZ-1) {
    fprintf (stderr, "Lempel Zev Stack Overflow\n");
    exit(1);
  }
  else
    stack[lzstack++] = c;
}

#ifndef __STDC__
int
pop ()
#else
int pop (void)
#endif
{
  if ( !lzstack ) {
    fprintf (stderr, "Lempel Zev stack underflow.\n");
    exit(1);
  }
  else
    return stack[--lzstack];
}


#ifndef __STDC__
int
unc ()
#else
int unc (void)
#endif
{
  static int  oldcode, incode;
  static unsigned char kay;
  static int  omega;
  static unsigned char finchar;

  switch (State) {

  case 0: {                /* First time. Reset. */
    lzstack = 0;
    ncodes  = 258;         /* 2 reserved codes */
    wtcl    = 256;         /* 256 Bump code size when we get here */
    wttcl   = 254;         /* 1st time only 254 due to resvd codes */
    cdlen   = 9;           /* Start with 9 bit codes */
    oldcode = getcode();

    if (oldcode == 256) {  /* Code 256 is EOF for this entry */
      Status = EOF;        /* (ie. a zero length file) */
      return EOF;
    }
    kay = oldcode & 0xff;
    finchar = kay;
    State = 1;
    return kay;
  }

  case 1: {
    incode = getcode();

    if (incode == 256) {
      State = 0;
      Status = EOF;
      return EOF;
    }

    if (incode >= ncodes) {     /* Undefined code, special case */
      kay = finchar;
      push(kay);
      code = oldcode;
      omega = oldcode;
      incode = ncodes;
    }
    while ( code > 255 ) {      /* Decompose string */
      push(lztab[code].ext);
      code = lztab[code].prefix;
    }
    kay = code;
    finchar = code;
    State = 2;
    return kay & 0xff;
  }

  case 2: {
    if (lzstack == 0) {         /* Empty stack */
      omega = oldcode;
      if (ncodes < 4096) {
	lztab[ncodes].prefix = omega;
	lztab[ncodes].ext = kay;
	ncodes++;
      }
      oldcode = incode;
      State = 1;
      return unc();
    }
    else
      return pop();
  }
  }

  return (Status = EOF);
}





/*  -------------
    Fetch LZ code
    -------------
*/

#ifndef __STDC__
int
getcode ()
#else
int getcode (void)
#endif
{
  register int i;
  long blocks;

  code = 0;
  i = cdlen;

  while(i--)
    code = (code << 1) | GetBit();

  /*  Special case of 1 pass crunch. Checksum and size are at the end */

  if ( (code == 256) && (entry.mode == 5) ) {
    i = 16;
    entry.check = 0;
    while(i--)
      entry.check = (entry.check << 1) | GetBit();
    i = 24;
    entry.size = 0;
    while(i--)
      entry.size = (entry.size << 1) | GetBit();
    i = 16;
    while(i--)                      /* This was never implemented */
      GetBit();
    blocks = ftell(fp)-FilePos;
    entry.blocks = blocks/254;
    if (blocks % 254)
      entry.blocks++;
  }

  /* Get ready for next time */

  if ( (cdlen<12) ) {
    if ( !(--wttcl) ) {
      wtcl = wtcl << 1;
      cdlen++;
      wttcl = wtcl;
    }
  }

  return code;
}

#ifndef __STDC__
void UpdateChecksum (c)
     int c;
#else
void UpdateChecksum (int c)
#endif
{
  truesize++;

  c &= 0xff;

  if (entry.version == 1)     /* Simple checksum for version 1 */
    crc += c;
  else
    crc += (c ^ (++crc2));    /* A slightly better checksum for version 2 */
}


#ifndef __STDC__
int
UnPack ()
#else
int UnPack (void)
#endif
{
  switch (entry.mode) {

  case 0:             /* Stored */
  case 1:             /* Packed (Run-Length) */
    return GetByte();

  case 2:             /* Squeezed (Huffman only) */
  case 4:             /* Squashed (Huffman + Run-Length) */
    return Huffin();

  case 3:             /* Crunched */
  case 5:             /* Crunched in one pass */
    return unc();

  default:            /* Otherwise ERROR */
    Status = EOF;
    return EOF;
  }
}


long   FileSize;              /* Used by main() and PutC() */
FILE * outputfile;

#ifndef __STDC__
int
PutC (c)
     int c;
#else
int PutC (int c)
#endif
{
  if (FileSize > 0) {
    FileSize--;
    UpdateChecksum (c);
    return fputc (c, outputfile);
  }
  return -1;
}


#ifndef __STDC__
int
main (argc, argv)
     int argc;
     char **argv;
#else
int main (int argc, char **argv)
#endif
{
  int           temp, count, Flag = 0;
  unsigned int  c;
  unsigned char prev;                   /* previous byte for Run Length */

  for (c = strlen (*argv); c && (*argv)[c] != '/'; c--);
  if ((*argv)[c] == '/') *argv += c + 1;

  FilePos = 0;
  Status  = 0;

  if (argc < 2 || argc > 3) {
    fprintf (stderr, "\
Usage: %s filename{.sda | .arc} [directory]\n\n\
Purpose: Extracts archives made by ARC64/128 on the Commodore 64/128\n",
	     *argv);
    return 1;
  }

  if (scan (*argv, argc == 3 ? argv[2] : NULL))
    return 1;

  if ( !(fp = fopen (argv[1], "rb")) ) {
    fprintf (stderr, "%s: can't open `%s'\n", *argv, argv[1]);
    return 1;
  }

  if ( (temp = GetStartPos()) < 0 ) {
    fprintf (stderr,
	     "%s: `%s' does not look like a Commodore archive or an SDA.\n",
	     *argv, argv[1]);
    fclose (fp);
    return 2;
  }
  else {
    fseek(fp,(long) temp, 0);
    FilePos = temp;
  }

  while(GetHeader()) {
    FileSize = entry.size;

    if (entry.mode == 5)        /* If 1 pass crunch size is unknown */
      FileSize = 10000000;

    if ( !(outputfile = fopen(nexttemp(), "wb")) ) {
      fclose(fp);
      fprintf(stderr, "%s: File `%s': I/O Error\n", *argv, argv[1]);
      return 3;
    }

    while( (FileSize > 0) && (Status != EOF) && !feof(fp) ) {
      c = UnPack();

      if (Status == EOF)
	break;

      /* If Run Length is needed */

      if ( (entry.mode != 0) && (entry.mode != 2) ) {

	if ( (c & 0xff) == ctrl) {
	  count = UnPack();
	  prev  = UnPack();
	  c     = prev;

	  if (count == 0) {
	    count = 256;
	    if (entry.version == 1)  /* version 1 problem */
	      count--;
	  }

	  while(count > 1) {
	    PutC (c);
	    count--;
	  }
	}
      }
      PutC (c);
    }

    if ((crc ^ entry.check) & 0xffff) {
      fprintf (stderr, "%s: Checksum error in file `%s' of archive `%s'\n",
	      *argv, entry.name, argv[1]);
      Flag=2;
    }

    fclose (outputfile);
    if (!Flag) Flag=1;

    fprintf (stdout, "; \"");

    for (count = 0; count < entry.fnlen; count++)
      fprintf (stdout, ((entry.name[count] & 127) < 32) ?
	       (count == entry.fnlen - 1 || entry.name[count + 1] < '0' ||
		entry.name[count + 1] > '9' ? "\\%o" : "\\%03o") :
	       (entry.name[count] == '"' || entry.name[count] == '\\' ?
		"\\%c" : "%c"), entry.name[count]);

    fprintf (stdout, "\", %c\n\"%s\"\t\"", entry.type, tempname);

    for (count = 0; count < entry.fnlen; count++) {
      temp = entry.name[count];

      temp = temp < 65 ? temp : (temp > 90 ? (temp < 193 || temp > 218 ?
						  temp : temp & ~128) :
				 temp | 32);

      fprintf(stdout, (temp & 127) < 32 ?
	      (count == entry.fnlen - 1 || entry.name[count + 1] < '0' ||
	       entry.name[count + 1] > '9' ? "\\%o" : "\\%03o") :
	      (temp == '"' || temp == '\\' ? "\\%c" : "%c"), temp);
    }

    if (entry.type != 'P') {
      fprintf (stdout, "\\0%c", entry.type);

      if (entry.type == 'R')
	fprintf (stdout, "\\%o", entry.rl);
    }

    fprintf (stdout, "\"\n");

    FilePos += 254L*(long)entry.blocks;
    fseek(fp,FilePos,0);
  }

  fclose(fp);

  if(!Flag) {
    fprintf (stderr,
	     "%s: Archive `%s': No file extracted (Is this an archive??)\n",
	     *argv, argv[1]);

    return 2;
  }

  return Flag == 2 ? 2 : 0;
}
