/* Original Header from BBC BASIC version.

   This program will unpack Spark or arc style archives on the BBC
   and Archimedes. To produce an archive that can be unpacked using it
   you must set arc or Spark to not use squashing.
   Although you can use this on the Archie, a much better solution,
   is to use !SparkPlug. If you have an Archie, and would like to make
   your own archives and manipulate them in style from the desktop,
   you need a copy of Spark. This is obtainable for M-#5.99 from:
   David Pilling,
   P.O. Box 22,
   Thornton Cleveleys,
   Blackpool.
   FY5 1LR.

   You are encouraged to add your own bits to this program and pass it on.
   If you do modify it, add your name and details below.

   V0.00 20th September 1989 -- David Pilling
   V0.01 25th September 1989 -- Philip Colmer
                                 Changed BASIC V usage to BASIC II
   V0.02 21st February 1990  -- Philip Colmer
                                 Improved support for DFS
   V0.03 22nd April 1991     -- Philip Colmer
                                 Fixed bugs in directory handling


   This version, renamed Cark (for want of a better name !) is a port
   of Bark to Unix C.  You are GREATLY encouraged to improve it...
   my knowledge of C is very limited and I suspect that the coding
   could be much better ! - Alun.


   V0.01 4th  June  1991     -- Alun Jones
                                 Ported (messily!) to Unix style C.    

   V0.02 2nd Dec 1991		-- Andy Duplain (duplain@rtf.bt.co.uk)
   								Further porting to 4.2BSD (and provision made
								for System V).  Define either 'BSD' or 'SYSV'
								when compiling.							   

   V0.03 17th Dec 1991		-- Andy Duplain (duplain@rtf.bt.co.uk)						   
				Fixed errors in function prototypes for PCC
				C compilers.											   

   V0.04 20th Dec 1991		-- Andy Duplain (duplain@rtf.bt.co.uk)
   				Changed '-t' option output from stderr to stdout.											   
				
   V0.05 8th Jan 1992		-- Andy Duplain (duplain@rtf.bt.co.uk)
   				Added patch posted to NEWS by Martin Percival
				(martin@thed.uk22.bull.com) to unpack type 127 archives.
*/

#include <stdio.h>

#ifdef	BSD						/* Berkeley */
#	include <strings.h>
#	define	remove	unlink			
#	define	UNIX		  										 
#	undef	SYSV
#endif

#ifdef	SYSV					/* System V */
#	include <sys/types.h>
#	include <sys/stat.h>
#	include <string.h>
#	include <stdlib.h> 
#	define	remove 	unlink
#	define	UNIX
#	undef	BSD
#endif

#ifndef UNIX					/* Not UNIX */
#	include <string.h>
#	include <stdlib.h>
#endif

#define MAXCODE(n) ((1<<n)-1)

/* Used by many functions */
static  FILE * fp,
       *fo,
       *logfile;
static char rmask[9] = {
    0, 1, 3, 7, 15, 31, 63, 127, 255
};

static int  buf[128];
static char filename[255];
char	compname[270];			/* Ready for system() uncompress call */
static int  isdir,
            earc;
static int  run,
            c,
            rc;

/* permissions of files, lengths, etc. */
static long load,
            exec,
            attr,
            type,
            clen,
            olen;
static int  date,
            time,
            crc;

/* Used by uncrunch/getcode */
static int  n_bits,
            clear_flg,
            maxcode,
            free_ent,
            offset,
            size;

int			testonly = 0;	/* Used by -t option */

/* Used to guard function prototypes for PCC and ANSI compilers... */
			
#if defined(__STDC__) || defined(__cplusplus)
# define P_(s) s
#else
# define P_(s) ()
#endif

int main P_((int argc, char **argv));
int rdhdr P_((void));
long word P_((void));
int dble P_((void));
void unpack P_((char *root, char *file));
void unstore P_((void));
void unpck P_((void));
void putc_ncr P_((int b));
int get_c P_((void));
void uncrunch P_((void));
int getcode P_((void));

#undef P_

int     main (argc, argv)
int     argc;
char  **argv;
{
    char    cline[256],
            fullname[256];
    int     level = 0;
    int     l[32];
    l[0] = 0;
    *fullname = 0;

    /* Wrong # args */
    if (argc < 2) {
   		fprintf (stderr, "Usage : %s [-t] <filename>\n", *argv);
  		exit (1);
    }												   
										 
	/* Crappy option parsing... */
	
	if (argc == 3 && argv[1][0] == '-')
	{
		switch(argv[1][1])
		{
			case 't':			/* Test */
				testonly++;
				break;
			default:
				fprintf(stderr, "Unknown option '-%c'\n", argv[1][1]);
				exit(1);
		}				 
		*++argv;
	}

    /* Can't open file for reading */
    if ((fp = fopen (*++argv, "r")) == NULL) {
	fprintf (stderr, "\nCan't find file %s\n", *argv);
	exit (1);
    }

    /* Version number */
    puts("Cark V0.05 January 1992, based on Bark V0.03");

    /* File types */ 
	if (!testonly)
    	logfile = fopen ("settypes", "w");

    /* Main loop - rdhdr returns non-zero if EOF reached */
    while (!rdhdr ()) {
	/* End of top level dir */
	if ((earc) && (level == 0))
	    break;

	/* New directory in archive */
	if (isdir) {
	    l[level++] = strlen(fullname);/* Remember old pathname  */
	    strcat(fullname, filename);/* Add new name           */
		if (!testonly) 
		{
       		printf("Creating directory %s\n", fullname);
#ifdef	UNIX	 
	  		mkdir(fullname, 0755);		/* Ignore error */
#else	/* Not UNIX */
	  		sprintf (cline, "mkdir %s\n", fullname);
	  		system (cline);
#endif	/* UNIX */ 									  
		}
		else
			printf("Directory %s\n", fullname);
    	strcat (fullname, "/");/* Add the dir. separator */
	}
	else
	    if (earc) {
		fullname[l[--level]] = 0;/* Step back up a level   */
		if (strlen (fullname) != 0)
		    printf ("Directory:   %s\n", fullname);
	    }
	    else {								   
			if (testonly)
				printf ("Testing file : %s%s ... ", fullname, filename);
			else				
				printf ("Restoring file : %s%s ... ", fullname, filename);
			unpack (fullname, filename);/* Not a dir, so a file   */
	    }
    }
    /* End of archive, so close files */
    fclose (fp);
	if (!testonly)
    	fclose (logfile);
    exit (0);
}


/* Read header */
int     rdhdr () {
    int     i;

    /* archive flag */
    if (getc (fp) != 26) {	/* Missing flag, try and find another */
		fprintf (stderr, "Bad Header\n");
		while ((i = getc (fp)) != 26)
		    if (i == EOF)
			break;
		if (i == EOF) {
		    return (1);
		}
    }

    /* Compression type */
    type = getc (fp) & 0x7f;

    if (type == 0) {		/* End of archive */
		earc = 1;
		isdir = 0;
		return (0);
    }

    earc = 0;
    for (i = 0; i <= 12; ++i) {	/* Get filename */
		filename[i] = getc (fp);
			if (filename[i] <= 32)
			    filename[i] = 0;
    }

    clen = word ();		/* Compressed length                   */
    date = dble ();		/* File creation date                  */
    time = dble ();		/* File creation time                  */
    crc = dble ();		/* crc byte - unchecked                */
    if (type > 1)
		olen = word ();	/* Compressed, so find original length */
    else
		olen = clen;   	/* Not compressed                      */
    load = word ();		/* Load address                        */
    exec = word ();		/* Execution address                   */
    attr = word ();		/* File attributes                     */
    if ((type == 2) && ((load & 0xffffff00) == 0xfffddc00))
		isdir = 1;		/* File is a directory                 */
    else
		isdir = 0;		/* No it aint !                        */
    return (0);
}


/* Get 4 bytes */
long    word () {
    long    n;
    n = getc (fp);
    n |= (getc (fp) << 8);
    n |= (getc (fp) << 16);
    n |= (getc (fp) << 24);
    return (n);
}


/* Get 2 bytes */
int     dble () {
    int     i;
    i = getc (fp);
    i |= (getc (fp) << 8);
    return (i);
}


/* Unpack file */
void unpack (root, file)
char   *root,
       *file;
{
    char    fullpath[255];
    int     i;


    /* Open file and report any error */
    sprintf (fullpath, "%s%s", root, file);
	if (!testonly && type != 127)		/* type 127 uncompressed seperately */
    	if ((fo = fopen (fullpath, "w")) == NULL) {
			fprintf (stderr, "Can't open file %s\n", fullpath);
			exit (1);
    	}

    /* Decide appropriate compression action */
    switch (type) {
	case 1: 
	case 2: 
	    /* Not compressed */
	    unstore ();
	    break;
	case 8: 
	    /* Crunched */
	    uncrunch ();
	    break;
	case 3: 
	    /* I dunno */
	    unpck ();
	    break;
	case 127:		/* Compressed file, save as *.Z and use compress utility */
		if (testonly)
			puts("unix compress file");
		else
			puts("Uncompressing");
		strcpy(compname, "compress -d ");	/* Perferable to uncompress */
		strcat(fullpath, ".Z");
		strcat(compname, fullpath);
		if (!testonly)
		{
			if ((fo = fopen(fullpath, "w")) == NULL)
			{
				fprintf(stderr, "Can't open file %s\n", fullpath); 
				exit(1);
			}
		
			putc(31, fo);				/* Write magic header for compress file */
			putc(157, fo);
			putc((getc(fp) + 0x80), fo); /* and adjust byte 1 of file to be UNIX
								bit indicator */
			while (clen-- > 1)
				putc(getc(fp), fo);		/* Copy bytes (-1) */
			
			fclose(fo);

			if (system(compname))		/* Do the uncompress */
			{
		   		fprintf(stderr, "%s failed!\n", compname);
		   		exit(1);
			}
		}	
		else							/* Testing only */
			while(clen--)
				getc(fp);				/* Throw away */
				
		break;
			  
	default: 
	    {						   
		if (!testonly)
			fprintf (stderr, "Can't unpack %s - compression type %d\n", file, type);
		else
			printf("Can't unpack, compression type %d\n", type);
		/* Skip to end of file */
		for (i = 1; i <= clen; ++i)
		    getc (fp);
		/* Remove output file */
		if (!testonly)
			remove (fullpath);
	    }
    }
		
	if (!testonly && type != 127)
    	fclose (fo);
    /* Save appropriate SYS "OS_File" call to set catalogue info */
    if (!testonly)
	{
		fprintf (logfile, "SYS %cOS_File%c, 1, %c", 34, 34, 34);
	    for (i = 0; root[i] != 0; ++i)
		if (root[i] == '/')
		    putc ('.', logfile);
		else
		    putc (root[i], logfile);
	    fprintf (logfile, "%s%c, &%x, &%x,, &%x\n", file, 34, load, exec, attr);
	}
}


/* No compression */
void unstore () {
    int     i;
    putchar('\n');
    for (i = 0; i < clen; ++i)
		if (testonly)
			getc(fp);
		else
			putc (getc (fp), fo);
}


/* Looks like run length of some sort to me */
void unpck () {
    int     i;
    run = 0;
    c = 0;				  
	if (testonly)
		putchar('\n');
	else
    	puts("Unpacking");
    for (i = 0; i < clen; ++i)
		putc_ncr (getc (fp));
}


/* Dunno */
void putc_ncr (b)
int     b;
{
    int     k;
		
	if (testonly)
		return;
		
    if (c == 1) {
		if (b == 0) {
	   		putc (0x90, fo);
	    	c = 0;
	    	return;
		}
		else {
	    	for (k = 1; k < b; ++k)
			putc (run, fo);
	    	c = 0;
	    	return;
		}
    }
    if (b == 0x90) {
		c = 1;
		return;
    }
    run = b;
    putc (run, fo);
}


int     get_c () {
    if      (rc > 0) {
		--rc;
		return (getc (fp));
    }
    else
		return (-1);
}


/* Crunched */
void uncrunch () {
    int     i,
            finchar,
            incode,
            code,
            oldcode;
    int     stack[4096],
           *stackp;
    int     suffix[4096];
    int     prefix[4096];
		   
	if (testonly)
		putchar('\n');
	else
    	puts("Uncrunching");
		
    c = 0;
    offset = 0;
    size = 0;
    rc = clen;
    code = get_c ();
    if (code != 12) {
		fprintf (stderr, "Can't unpack file - wrong number of bits\n");
		exit (1);
    }
    n_bits = 9;
    clear_flg = 0;
    maxcode = MAXCODE (n_bits);
    for (i = 0; i <= 256; ++i)
		prefix[i] = 0;
    for (code = 0; code < 256; ++code)
		suffix[code] = code;			  
    free_ent = 257;
    oldcode = getcode ();
    finchar = oldcode;
    if (oldcode == -1)
		return;
    putc_ncr (finchar);
    stackp = stack;

    while (1) {
		code = getcode ();
		if (code < 0)
	    	return;
		if (code == 256) {
	    	for (i = 0; i <= 256; ++i)
				prefix[i] = 0;
	    	clear_flg = 1;
	    	free_ent = 256;
	    	code = getcode ();
	    	if (code == -1)
				return;
		}
		incode = code;
		if (code >= free_ent) {
	    	*stackp++ = finchar;
	    	code = oldcode;
		}
		while (code >= 256) {
	    	*stackp++ = suffix[code];
	    	code = prefix[code];
		}
		finchar = suffix[code];
		*stackp++ = finchar;
		while (stackp > stack)
	    	putc_ncr (*--stackp);
			code = free_ent;
			if (code < 4096) {
	    	prefix[code] = oldcode;
	    	suffix[code] = finchar;
	    	free_ent = code + 1;
		}
		oldcode = incode;
    }
}

int     getcode () {
    int     code,
            temp,
            r_off,
            bits;
    int     bp = 0;

    if ((clear_flg > 0) || (offset >= size) || (free_ent > maxcode)) {
		if (free_ent > maxcode) {
	    	++n_bits;
	    	if (n_bits == 12)
				maxcode = 4096;
	    	else
				maxcode = MAXCODE (n_bits);
		}
		if (clear_flg > 0) {
   		    n_bits = 9;
   		    maxcode = MAXCODE (n_bits);
		    clear_flg = 0;
   		}
 		for (size = 0; size < n_bits; ++size) {
		    code = get_c ();
		    if (code == -1) {
	 			temp = size;
	   	 		size = n_bits;
	 	   }
 		    else
		  		buf[size] = code;
  		}
		if (size == (n_bits + 1)) {
		    size = temp;
		    if (size <= 0)
		   		return (-1);
		}
		offset = 0;
 		size = (size << 3) - (n_bits - 1);
    }
    r_off = offset;
    bits = n_bits;
    bp += r_off >> 3;
    r_off = r_off & 7;
    code = buf[bp++] >> r_off;
    bits = bits - 8 + r_off;
    r_off = 8 - r_off;
    if (bits >= 8) {
 		code = code | (buf[bp++] << r_off);
 		r_off += 8;
 		bits -= 8;
    }
    code = code | ((buf[bp] & rmask[bits]) << r_off);
    offset += n_bits;
    return (code & 4095);
}
