/*
** general purpose file unpacker, Copyright (C) Randy Nevin 1989, 1990.
**
** you may give this software to anyone, make as many copies as you like, and
** post it on public computer bulletin boards and file servers. you may not
** sell it or charge any fee for distribution (except for media and postage),
** remove this comment or the copyright notice from the code, or claim that
** you wrote this code or anything derived from it. you may modify the code as
** much as you want (please document clearly with comments, and maintain the
** coding style), but programs which are derived from this one are subject to
** the conditions stated here. i am providing this code so that people can
** learn from it, so if you distribute it, please include source code, not
** just executables. contact me to report bugs or suggest enhancements; i do
** not guarantee support, but i will make an effort to help you, and i want to
** act as a central clearing house for future versions. you should contact me
** before undertaking a significant development effort, to avoid reinventing
** the wheel. if you come up with an enhancement you consider particularly
** useful, i would appreciate being informed so that it can be incorporated in
** future versions. my address is: Randy Nevin, 1731 211th PL NE, Redmond,
** WA 98053, USA. this code is available directly from the author; just send a
** 360k floppy and a self-addressed floppy mailer with sufficient postage.
**
** HISTORY
** (name		date		description)
** ----------------------------------------------------
** randy nevin		7/17/89		initial version
** randy nevin		8/6/89		changed to handle run-length encoding
**					in addition to huffman encoding
** randy nevin		8/15/89		released version 1.00
*/

/* BUGBUG: doesn't handle files with only one byte value */

/*
** unpack a file that has been packed with huffman or run-length encoding. the
** file contains a packing identification byte (1=huffman, 2=run-length). for
** huffman encoding, it also contains the number of bytes, a skeletal tree
** structure, the corresponding byte values, and the encoded bits. for
** run-length encoding, it contains the encoding byte, then the encoded file,
** with the encoding byte indicating the next two bytes are the number of
** occurrences, and the encoded byte. an exception (for run-length encoding)
** is if the number of occurrences is 0, in which case this represents just
** one (literal) encoded byte; there is no following 'encoded byte'.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 32	/* use a maximum of 32 bits per byte */

struct node { /* tree node for huffman encoding */
	int byteval;
	char buf[MAX+1];
	char pad;
	struct node *left, *right;
	} *head = NULL;

void main( int, char *[] );
void walk0( struct node *, FILE * );
void walk1( struct node *, char [], int );
void walk2( struct node *, FILE * );
void initbit( void );
int inbit( FILE * );

void main ( argc, argv )
	int argc;
	char *argv[];
	{
	char *self, *t;
	int c, enc, i;
	FILE *fin, *fout;
	register struct node *p;
	char buf[MAX+1];
	long total;
	int b1, b2, b3, b4;

	printf( "Copyright (C) Randy Nevin, 1989, 1990. Version 1.00\n" );
	printf( "See source code for rights granted.\n\n" );
	self = argv[0];
	/* get rid of initial part of path */
	if ((t = strrchr( self, '\\' )) || (t = strrchr( self, ':' )))
		self = ++t;
	/* get rid of extension */
	if ((t = strrchr( self, '.' )) && !stricmp( t, ".EXE" ))
		*t = 0;
	if (argc != 3) { /* need infile and outfile */
		fprintf( stderr, "usage: %s infile outfile\n", self );
		exit( -1 );
		}
	if (!(fin = fopen( argv[1], "rb" ))) {
		fprintf( stderr, "can't open %s\n", argv[1] );
		exit( -1 );
		}
	if (!(fout = fopen( argv[2], "wb" ))) {
		fprintf( stderr, "can't open %s\n", argv[2] );
		exit( -1 );
		}
	if ((c = getc( fin )) == 1) { /* huffman encoding */
		b1 = getc( fin );
		b2 = getc( fin );
		b3 = getc( fin );
		b4 = getc( fin );
		total = (long)b1 | ((long)b2<<8) | ((long)b3<<16)
			| ((long)b4 << 24);
		/* build the encoding tree */
		if (!(head = (struct node *)malloc( sizeof(struct node) ))) {
			fprintf( stderr, "out of memory\n" );
			exit( -1 );
			}
		head->left = head->right = NULL;
		initbit();
		walk0( head, fin ); /* construct encoded tree */
		buf[0] = 0;
		walk1( head, buf, 0 ); /* assign encoded bit strings */
		walk2( head, fin ); /* read in byte values */
		initbit();
		while (total--) { /* read in each "byte" */
			p = head;
			while (p->left && p->right)
				p = (inbit( fin )) ? p->right : p->left;
			putc( (char)(p->byteval), fout );
			}
		}
	else if (c == 2) { /* run-length encoding */
		if ((enc = getc( fin )) == EOF) {
			fprintf( stderr, "premature eof\n" );
			exit( -1 );
			}
		while ((c = getc( fin )) != EOF) {
			if (c == enc) {
				if ((i = getc( fin )) == EOF) {
					fprintf( stderr, "premature eof\n" );
					exit( -1 );
					}
				if (!i)
					putc( (char)enc, fout );
				else {
					if ((c = getc( fin )) == EOF) {
						fprintf( stderr,
							"premature eof\n" );
						exit( -1 );
						}
					while (i--)
						putc( (char)c, fout );
					}
				}
			else
				putc( (char)c, fout );
			}
		}
	else if (c == EOF) {
		fprintf( stderr, "premature eof\n" );
		exit( -1 );
		}
	else {
		fprintf( stderr, "unrecognized encoding type\n" );
		exit( -1 );
		}
	if (ferror( fout ))
		fprintf( stderr, "output error; disk might be full\n" );
	fclose( fout );
	exit( 0 );
	}

void walk0 ( p, fin ) /* input skeletal tree structure */
	struct node *p;
	FILE *fin;
	{
	/* a 1 bit indicates a node has children; 0 means no children */
	if (inbit( fin )) {
		if (!(p->left = (struct node *)malloc(
				sizeof(struct node) ))
			|| !(p->right = (struct node *)malloc(
				sizeof(struct node) ))) {
			fprintf( stderr, "out of memory\n" );
			exit( -1 );
			}
		p->left->left =
		  p->left->right =
		    p->right->left =
		      p->right->right = NULL;
		walk0( p->left, fin );
		walk0( p->right, fin );
		}
	}

void walk1 ( p, buf, level ) /* assign encoded bit strings */
	struct node *p;
	char buf[];
	int level;
	{
	if (!(p->left) && !(p->right)) {
		strcpy( p->buf, buf );
		/* printf( "str[0x%02X] = %s\n", p->byteval, buf ); */
		}
	else if (level < MAX) {
		buf[level] = '0';
		buf[level+1] = 0;
		walk1( p->left, buf, level+1 );
		buf[level] = '1';
		buf[level+1] = 0;
		walk1( p->right, buf, level+1 );
		buf[level] = 0;
		}
	else {
		fprintf( stderr, "error: skewed tree\n" );
		exit( -1 );
		}
	}

void walk2 ( p, fin ) /* read in byte values for the tree */
	struct node *p;
	FILE *fin;
	{
	if (p->left) {
		walk2( p->left, fin );
		walk2( p->right, fin );
		}
	else
		p->byteval = getc( fin );
	}

static int nbit; /* how many bits remaining in buffer */
static char byte; /* the byte buffer */

void initbit () { /* initialize bit output buffer */
	nbit = 0;
	byte = 0;
	}

int inbit( fp ) /* input a bit using byte buffering */
	FILE *fp;
	{
	int bit, newbyte;

	if (!nbit) {
		if ((newbyte = getc( fp )) == EOF) {
			fprintf( stderr, "unexpected EOF\n" );
			newbyte = 0; /* force zeros */
			}
		byte = (char)newbyte;
		nbit = 8;
		}

	bit = byte & 1;
	byte >>= 1;
	nbit--;
	return( bit );
	}
