#include "mactypes.h"

/* HQXBUFLEN must be large enough to hold a complete hqx header */
#define HQXBUFLEN 512
byte hqxbuf[HQXBUFLEN + 1], *buf_ptr, *buf_end, *buf_start = hqxbuf + 1;

/* Note: line[0] stores the run length character,
 * so line is one greater than MAXLINE  */
#define MAXLINE 2048
byte line[MAXLINE + 1], *line_ptr, *line_end, *line_start = line + 1;

/* keep the compiler happy */
#define LINE_START ((char*)(line_start))

extern FILE *convert;
extern FILE *debug;
extern FILE *verbose;
extern FILE *devnull;
#define DEBUG (debug != devnull)

extern char *cmdname;

int line_count, file_count;
int save_state, total_bytes, save_run_length;
word save_accum;
char binfname[BINNAMELEN], hqxfname[BINNAMELEN];
FILE *hqxfile, *binfile;


/*
 * hqxsuspect caches whether or not we have gotten past all suspect data
 * at the head of a file, for example, a mail header.  hqxsuspect is set
 * on every new hqx file read, and reset only in non_suspect()
 */
int hqxsuspect;
extern suspect_shorter;
extern suspect_same;

extern int info_only;

/* This routine reads the header of a hqxed file and appropriately twiddles it,
    determines if it has CRC problems, creates the .bin file, and puts the info
    into the .bin file.
    Output is hqx_datalen, hqx_rsrclen, type, binfname, binfile.
	 Returns 0 iff failed to read header.
 */


int
hqx_to_bin_hdr(type, hqx_datalen, hqx_rsrclen, same_file_only)
	char *type;
	ulong *hqx_datalen, *hqx_rsrclen;
	int same_file_only;
{
	register byte *hqx_ptr, *hqx_end;
	register ulong calc_crc;
	hqx_buf *hqx_block;
	hqx_header *hqx;
	info_header info;
	ulong mtim;
	short crc;

	extern word magic[];
	extern char *dir, *ext;
	extern short calc_mb_crc();

	/* read the hqx header, 
	 * assuming that I won't exhaust hqxbuf in so doing --
	 * that is, that hqxbuf is always large enough (it is)
	 */
	(void)fill_hqxbuf(same_file_only);
	/*
	 * If we are reading multiple files, then we could have the last
	 * unread line of the "previous" file be just a colon (since we are
	 * length driven, and stopped when we processed the expected
	 * lengths), and the prior fill_hqxbuf() call would find it and
	 * return 0, leaving the buffer unfilled.  So, if we have zero
	 * bytes so far, just fill it again.
	 */
	if (total_bytes == 0) {
		DEBUG && fprintf(debug,
			"%s: Note: had to call fill_hqxbuf again in hqx_to_bin_hdr\n",
			cmdname);
		(void)fill_hqxbuf(same_file_only);
	}
	if (same_file_only == 1 && total_bytes == 0)
		return 0;
	if (total_bytes < MIN_HQX_HDR) {
		fprintf(verbose, "%s: %s (%d < %d): bad file format? -- exiting\n",
			cmdname, "error: hqx_header too short", total_bytes, MIN_HQX_HDR);
		exit(2);
	}

	hqx_block = (hqx_buf *) buf_ptr;
	hqx = (hqx_header *) (hqx_block->name + hqx_block->nlen);
	hqx_ptr = buf_ptr;
	hqx_end = (byte *) hqx + sizeof(hqx_header) - 1;
	calc_crc = 0;
	while (hqx_ptr < hqx_end)
		calc_crc = (((calc_crc & 0xff) << 8) | *hqx_ptr++) ^ magic[calc_crc >> 8];
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	buf_ptr = hqx_ptr;

	/* stuff the hqx header data into the info header */
	bzero((char*)&info, sizeof(info_header));
	info.nlen = hqx_block->nlen;
	strncpy((char*)info.name, (char*)hqx_block->name, (int)info.nlen);/* name */
	bcopy((char*)hqx->type, (char*)info.type, 9);       /* type, author, flag */
	info.flags &= 0x7e;		       /* reset lock bit, init bit */
	if (hqx->protect & 0x40)
		info.protect = 1;	       /* copy protect bit */
	bcopy((char*)hqx->dlen, (char*)info.dlen, 8);	       /* dlen, rlen */
	mtim = unix2mac((ulong)time((long*)0));
	bcopy((char*)&mtim, (char*)info.mtim, 4);
	bcopy((char*)&mtim, (char*)info.ctim, 4);
	info.uploadvers = '\201';
	info.readvers = '\201';

	/* calculate MacBinary CRC */
	crc = calc_mb_crc((unsigned char*)&info, 124L, 0);
	info.crc[0] = (char) (crc >> 8);
	info.crc[1] = (char) crc;

	/* Create the .bin file and write the info to it */

	unixify((char*)hqx_block->name);
	converting((char*)hqx_block->name, info.type, info.auth);
	print_bin_hdr("Creating", &info);
	sprintf(binfname, "%s/%s%s", dir, hqx_block->name, ext);
	binfile = mopen(binfname, "", "w");

	check_hqx_crc((word)calc_crc, "File header CRC mismatch in %s", binfname);
	if (1 != fwrite((char*)&info, sizeof(info), 1, binfile))
		error("fwrite failed on binfile", "");

	/* Get a couple of items we'll need later */
	bcopy((char*)info.dlen, (char*)hqx_datalen, 4);
	*hqx_datalen = mac2long(*hqx_datalen);
	bcopy((char*)info.rlen, (char*)hqx_rsrclen, 4);
	*hqx_rsrclen = mac2long(*hqx_rsrclen);
	bcopy((char*)info.type, (char*)type, 4);

	/* emit useful debugging info */
	DEBUG && fprintf(debug, "\tdata_len=%10ld\t\trsrc_len=%10ld\n",
		*hqx_datalen, *hqx_rsrclen);

	return 1;
}

/* This routine reads the header of a bin file and appropriately twiddles it,
    creates the .hqx file, and puts the info into the .hqx file.
    Output is hqx_datalen, hqx_rsrclen, type, hqxfname, hqxfile */

bin_to_hqx_hdr(hqx_datalen, hqx_rsrclen)
	ulong *hqx_datalen, *hqx_rsrclen;
{
	register byte *hqx_ptr, *hqx_end;
	register ulong calc_crc;
	hqx_buf *hqx_block;
	hqx_header *hqx;
	info_header info;
	extern word magic[];
	extern char **hqxnames_left;
	extern char *ext;

	strcpy(binfname, *hqxnames_left++);
	binfile = mopen(binfname, ext, "r");

	if (!fread((char*)&info, sizeof(info), 1, binfile))
		error("Unexpected EOF in header of %s", binfname);

	/* stuff the info header into the hqx header */
	hqx_block = (hqx_buf *) buf_ptr;
	hqx_block->nlen = info.nlen;
	strncpy((char*)hqx_block->name, (char*)info.name, (int)info.nlen);
	hqx = (hqx_header *) (hqx_block->name + hqx_block->nlen);
	hqx->version = 0;
	bcopy((char*)info.type, (char*)hqx->type, 9);   /* type, author, flags */
	hqx->flags &= 0x7e;		       /* reset lock bit, init bit */
	if (info.protect == 1)
		hqx->protect = 0;	       /* protect bit: 0x40 */
	else
		hqx->protect = 0;
	bcopy((char*)info.dlen, (char*)hqx->dlen, 8);	       /* dlen, rlen */

	/* Create the .hqx file and write the info to it */
	strncpy(hqxfname, (char*)info.name, (int)info.nlen);
	hqxfname[info.nlen] = '\0';
	unixify(hqxfname);
	converting(hqxfname, info.type, info.auth);
	print_bin_hdr("Reading", &info);

	calc_crc = 0;
	hqx_ptr = (byte *) hqx_block;
	hqx_end = hqx_ptr + hqx_block->nlen + sizeof(hqx_header);
	while (hqx_ptr < hqx_end)
		calc_crc = (((calc_crc & 0xff) << 8) | *hqx_ptr++) ^ magic[calc_crc >> 8];
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	buf_ptr = hqx_end;
	write_hqx_crc((word)calc_crc);

	/* Get a couple of items we'll need later */
	bcopy((char*)info.dlen, (char*)hqx_datalen, 4);
	*hqx_datalen = mac2long(*hqx_datalen);
	bcopy((char*)info.rlen, (char*)hqx_rsrclen, 4);
	*hqx_rsrclen = mac2long(*hqx_rsrclen);
}


/* This routine copies bytes from the decoded input stream to the output.  
    It also pads to a multiple of 128 bytes on the output, which is part
    of the .bin format */
word
hqx_to_bin_fork(nbytes)
	register ulong nbytes;
{
	register byte *cp;
	register ulong calc_crc;
	register int c_length;
	ulong extra_bytes;
	extern word magic[];
	long avail = 0;	/* used for internal consistency checking */
	int wrote;

	extra_bytes = 127 - (nbytes + 127) % 128;	/* pad fork to mult of
							 * 128 bytes */
	calc_crc = 0;
	for (;;) {
		cp = buf_ptr;
		c_length = (cp + nbytes > buf_end) ? buf_end - cp : nbytes;
		/* we can only check readily if we read it here */
		if (avail && c_length > avail)
			error("hqx_to_bin_fork: writing %ld too many bytes",
				(char*)c_length - avail);
		nbytes -= c_length;
		wrote = fwrite((char*)cp, sizeof(byte), c_length, binfile);
		if (wrote != c_length)
			error("hqx_to_bin_fork: fwrite on binfile wrote %ld bytes too few",
				(char*)c_length-wrote);
		while (c_length--)
			calc_crc = (((calc_crc & 0xff) << 8) | *cp++) ^ magic[calc_crc >> 8];
		if (!nbytes)
			break;
		avail = fill_hqxbuf(0);
	}
	buf_ptr = cp;
	while (extra_bytes--)
		putc(0, binfile);
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	return (word) calc_crc;
}

/* This routine copies bytes from the input stream to the encoded output.  
    It also pads to a multiple of 128 bytes on the input, which is part
    of the .bin format */
word
bin_to_hqx_fork(nbytes)
	register ulong nbytes;
{
	register byte *cp;
	register ulong calc_crc;
	register int c_length;
	ulong extra_bytes;
	extern word magic[];

	extra_bytes = 127 - (nbytes + 127) % 128;	/* pad fork to mult of
							 * 128 bytes */
	calc_crc = 0;
	for (;;) {
		cp = buf_ptr;
		c_length = (cp + nbytes > buf_end) ? buf_end - cp : nbytes;
		nbytes -= c_length;
		if (c_length != fread((char*)cp, sizeof(byte), c_length, binfile))
			error("fread failed on binfile", "");
		buf_ptr += c_length;
		while (c_length--)
			calc_crc = (((calc_crc & 0xff) << 8) | *cp++) ^ magic[calc_crc >> 8];
		if (!nbytes)
			break;
		empty_hqxbuf();
	}
	buf_ptr = cp;

	fseek(binfile, (long)extra_bytes, 1);
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	calc_crc = ((calc_crc & 0xff) << 8) ^ magic[calc_crc >> 8];
	return (word) calc_crc;
}

/* Essentials for Binhex 8to6 run length encoding */
#define RUNCHAR 0x90
#define MAXRUN 255
#define IS_LEGAL <0x40
#define ISNT_LEGAL >0x3f
#define DONE 0x7F		/* tr68[':'] = DONE, since Binhex terminator is ':' */
#define SKIP 0x7E		/* tr68['\n'|'\r'] = SKIP, i. e. end of line char.  */
	/* We also treat '\0' as SKIP to handle files without trailing newline */
#define FAIL 0x7D		/* character illegal in binhex file */

byte tr86[] =
"!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr";
byte tr68[] = {
/* 0x00 */
    SKIP, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, SKIP, FAIL, FAIL, SKIP, FAIL, FAIL,
/* 0x10 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0x20 */
    FAIL, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, FAIL, FAIL,
/* 0x30 */
    0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, FAIL,
    0x14, 0x15, DONE, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0x40 */
    0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
    0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, FAIL,
/* 0x50 */
    0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, FAIL,
    0x2C, 0x2D, 0x2E, 0x2F, FAIL, FAIL, FAIL, FAIL,
/* 0x60 */
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, FAIL,
    0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, FAIL, FAIL,
/* 0x70 */
    0x3D, 0x3E, 0x3F, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0x80 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0x90 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0xA0 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0xB0 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0xC0 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0xD0 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0xE0 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
/* 0xF0 */
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
    FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
};

/*
 *  This procedure transparently reads and decodes the hqx input.
 *  It does run length and 6 to 8 decoding.
 *  As many lines as required are read to fill up buf.
 * Global Input
 *  buf_start
 *  line_ptr
 *  save_*
 * Global Output
 *  buf_ptr end of buffer used
 *  line_ptr
 *  save_*
 * Internal
 *  line     holds the encoded incoming ascii line.
 *  buf      holds the decoding binary binary.
 *  buf[-1]  holds the character to be repeated for run length encoding.
 */
#define READING 0
#define SKIPPING 1
#define FIND_START_COLON 2
#define FINDING_COLON 3

/*

 * It's too bad we can't look for ``(This file ..'' when hunting for a
 * starting colon, but we don't emit such a line at the head of every
 * segment, so we can't expect to find one, now, can we?

 * We were really too brittle when scanning, since we only
 * accepted lines which were exactly 64 (HQXLINELEN) characters long.
 * Fixed in version 1.83.  But allowing any length valid line
 * permitted the false-match-in-mail-headers-problem for lines like "---".
 * Better header skipping provided in 1.84.

 * XXX: rather than decode line inplace, it would be better
 * to leave the original intact to enable better warning messages

 */

/* returns the number of bytes read */
fill_hqxbuf(same_file_only)
	int same_file_only;
{
	register ulong c, accum;
	register int not_in_a_run = TRUE, state68;
	register byte *fast_buf, *fast_line;
	static int phase = FIND_START_COLON;
	long avail;

	buf_ptr = fast_buf = buf_start;
	fast_line = line_ptr;
	state68 = save_state;
	accum = save_accum;
	if (save_run_length > 0) {
		c = save_run_length;
		save_run_length = 0;
		goto continue_run;
	}

	while (fast_buf < buf_end) {

next_char:
		if ((c = *fast_line++) ISNT_LEGAL) {
			if (c == DONE) {
				/* done processing the file, so get out */
				/* phase has already been set by read processing */
				break;
		  }

	next_line:
			if (!fgets(LINE_START, MAXLINE, hqxfile)) {
				if (same_file_only) {
					avail = fast_buf - buf_ptr;
					if (avail != 0) {
						DEBUG && fprintf(debug, "DEBUG: avail: %d\n", avail);
						error("Premature EOF reading non-initial header from %s",
							hqxfname);
					} else {
						/* this routine may be called many times in a row,
						 * and we must ensure that we don't allow fast_line
						 * to march beyond the end of line (it did), so we jam
						 * fast_line to the start of the line, and salt the
						 * line to mark it invalid, so a new line will be fetched.
						 * Jeepers, how I hate the (il)logic of this routine!
						 */
						fast_line = line_start;
						*fast_line = SKIP;
						break;
					}
					/*NOTREACHED*/
				} else if (new_in_hqx_file() == 0) {
					/*
					 * Used to assume if no more input available through error or
					 * exhaustion while looking for valid data then must be done.
					 * But we are demand driven (now) and if called, must
					 * find data.  So we don't silently exit any more.
					 */
					error("Premature EOF while reading %s", hqxfname);
				}
			}
			line_ptr = line_start;

			DEBUG && fprintf(debug, "DEBUG: input: %s", line_start);

	scan_line:

			/* ensure the the entire line is valid */

			fast_line = line_ptr;
			while ((*fast_line = tr68[*fast_line]) IS_LEGAL)
				fast_line++;

			/* grab the stopper */
			c = *fast_line;

			/* check for validity, transition phases as required */
			switch (phase) {

			case READING:
			case SKIPPING:
			case FINDING_COLON:
				if (SKIP == c && fast_line > line_ptr &&
					( 0 == hqxsuspect ||
						non_suspect((char *)line_ptr, (char *)fast_line) )
				) {
					/* the entire line is valid (again), so (resume) reading */
					/* hack: require the line to be non-empty to simplify logic */
					/* require line to non suspect [outside [mail] header] */
					phase = READING;
					break;
				}
				if (c == DONE && tr68[fast_line[1]] == SKIP) {
					/*
					 * valid encoded last line, so
					 * set phase for next line, and process this line --
					 * we exit the fill-the-buffer loop when the terminal
					 * colon is encountered during line processing
					 */
					phase = FIND_START_COLON;
					break;
				}

				/* line is not entirely valid, so do some flavor of skipping */
				phase = (phase == FINDING_COLON) ? FIND_START_COLON : SKIPPING;
				/* we're suspicious again, since we could be reading a
				 * concatenated multi-segmented file */
				hqxsuspect = 1;
				goto next_line;

			case FIND_START_COLON:
				if (*line_start == DONE) {
					/* can't transition to READING
					 * until know that entire line is valid
					 * so transition to intermediate state
					 */
					phase = FINDING_COLON;
					/* skip the initial colon */
					line_ptr++;
					goto scan_line;
				}
				goto next_line;

			}

			/* we've read in a valid line, so start processing it */
			fast_line = line_ptr;
			c = *fast_line++;

			/*
			 * Jskud 15Jul92: fix bug reported by Info-Mac Moderator Bill
			 * regarding case of last line just :
			 * The line is valid, but it has no data, so don't ingest it.
			 */

			if (c == DONE)
				break;

			DEBUG && fprintf(debug, "DEBUG: processing above line\n\n");

		}

		/* Finally, we have the next 6 bits worth of data in "c" as input. */
		/* Note: we use "c" as the output of this processing too */
		switch (state68++) {
		case 0:
			accum = c;
			goto next_char;
		case 1:
			accum = (accum << 6) | c;
			c = accum >> 4;
			break;
		case 2:
			accum = (accum << 6) | c;
			c = (accum >> 2) & 0xff;
			break;
		case 3:
			/* we avoid any miniscule optimizations here
			 * to maintain parallelism and clarity
			 * which should enhance program maintainability
			 */
			accum = (accum << 6) | c;
			c = accum & 0xff;
			state68 = 0;
			break;
		}
		if (not_in_a_run)
			if (c != RUNCHAR)
				*fast_buf++ = c;
			else {
				not_in_a_run = FALSE;
				goto next_char;
			}
		else {
			/* "c" has the run total length, not just the incremental,
			   hence the post decrement is correct */
			if (c--) {
				avail = buf_end - fast_buf;
				if (c > avail) {
					save_run_length = c - avail;
					c = avail;
				}
		continue_run:
				{
					register char ch = fast_buf[-1];

					while (c--)
						*fast_buf++ = ch;
				}

			} else
				/* handle special case of 0x9000 => 0x90 */
				*fast_buf++ = RUNCHAR;

			not_in_a_run = TRUE;
		}
	}

/* exit: */
	avail = fast_buf - buf_ptr;
	total_bytes += avail;
	buf_start[-1] = fast_buf[-1];
	line_ptr = fast_line;
	save_state = state68;
	save_accum = accum;

	return avail;

}

non_suspect(start, beyond)
	char *start;
	char *beyond;
{
	int looking_good;
	int len;
	register char *cp;
	register char *last;
	char *skip_msg = "Warning: skipping legal but suspect line in hqx file";

	/* ensure it's long enough */
	len = beyond - start;
	looking_good = len >= suspect_shorter;
	DEBUG && fprintf(debug, "DEBUG: non_suspect: long enough: %d\n", looking_good);
	if (!looking_good)
		fprintf(verbose, "%s -- too short (%d < %d)\n",
			skip_msg, len, suspect_shorter);

	/* ensure it's different enough */
	if (suspect_same && looking_good) {
		last = beyond - 1;
		for (cp = start; cp < last; cp++)
			if (*cp != *last) {
				break;
			}
		/* is different */
		looking_good = cp != last;
		DEBUG && fprintf(debug, "DEBUG: non_suspect: different enough: %d\n",
			looking_good);
		if (!looking_good)
			fprintf(verbose, "%s -- all same char ('%c')\n", skip_msg, *last);
	}

	hqxsuspect = !looking_good;

	return looking_good;
}

new_in_hqx_file()
{
	extern char **hqxnames_left;
	int result;

	DEBUG && fprintf(debug, "entering new_in_hqx_file ...\n");

	if (*hqxnames_left[0] == '\0' || *hqxnames_left[0] == '-') {
		result = FALSE;
		goto exit;
	}

	strcpy(hqxfname, *hqxnames_left++);
	/*
	 * we used to use freopen,
	 * but now suffer the slight inefficiency of close/open
	 * to provide info_only and consistent handling
	 * with good software engineering methods
	 */
	mclose(&hqxfile, "hqxfile");
	hqxfile = mopen(hqxfname, ".hqx", "r");
	hqxsuspect = 1;
	(void)fgets(LINE_START, MAXLINE, hqxfile);
	result = TRUE;

exit:
	if (result == TRUE)
		DEBUG && fprintf(debug, "... opened %s\n", hqxfname);
	else
		DEBUG && fprintf(debug, "... nothing to open\n");

	return result;
}

/*
 *  This procedure transparently encodes and writes the hqx output.  
 *  It does run length and 8 to 6 encoding.
 */
empty_hqxbuf()
{
	register ulong c, accum, last_c;
	register byte *fast_buf, *fast_line;
	register int state86, dont_look_for_runs = FALSE, run_length;
	extern int maxlines;

	run_length = save_run_length;
	last_c = buf_start[-1];
	fast_buf = buf_start;
	fast_line = line_ptr;
	state86 = save_state;
	accum = save_accum;
	while (fast_buf < buf_ptr) {
		c = *fast_buf++;
		if (dont_look_for_runs)
			dont_look_for_runs = FALSE;
		else if (last_c == c && run_length < MAXRUN) {
			run_length++;
			continue;
		} else {
			if (run_length > 1) {
				--fast_buf;
				if (run_length == 2 && last_c != RUNCHAR)
					c = last_c;
				else {
					c = RUNCHAR;
					*--fast_buf = run_length;
					dont_look_for_runs = TRUE;
				}
				run_length = 1;
			} else
				last_c = c;
			if (c == RUNCHAR && !dont_look_for_runs) {
				*--fast_buf = 0;
				dont_look_for_runs = TRUE;
			}
		}

		if (fast_line == line_end) {
			if (line_count++ == maxlines)
				new_out_hqx_file();
			fputs(LINE_START, hqxfile);
			fast_line = line_start;
		}
		switch (state86++) {
		case 0:
			*fast_line++ = tr86[c >> 2];
			accum = (c << 4) & 0x3f;
			break;
		case 1:
			*fast_line++ = tr86[(c >> 4) | accum];
			accum = (c << 2) & 0x3f;
			break;
		case 2:
			*fast_line++ = tr86[(c >> 6) | accum];
			if (fast_line == line_end) {
				if (line_count++ == maxlines)
					new_out_hqx_file();
				fputs(LINE_START, hqxfile);
				fast_line = line_start;
			}
			*fast_line++ = tr86[c & 0x3f];
			state86 = 0;
			break;
		}
	}
	save_run_length = run_length;
	buf_start[-1] = last_c;
	buf_ptr = buf_start;
	line_ptr = fast_line;
	save_state = state86;
	save_accum = accum;
}

new_out_hqx_file()
{
	char filename[NAMELEN + 7];
	extern int maxlines;

	fprintf(hqxfile, "<<< End of Part %2d >>>\n", file_count);
	mclose(&hqxfile, "hqxfile");
	file_count++;
	if (maxlines)
		sprintf(filename, "%s%02d.hqx", hqxfname, file_count);
	else
		sprintf(filename, "%s.hqx", hqxfname);
	hqxfile = mopen(filename, "", "w");
	if (file_count > 1)
		fprintf(hqxfile, "<<< Start of Part %2d >>>\n", file_count);
	else
		fprintf(hqxfile, "(This file must be converted with BinHex 4.0)\n\n");
	line_count = 3;
}

check_hqx_crc(calc_crc, msg, name)
	word calc_crc;
	char msg[], name[];

{
	word read_crc;

	if (buf_ptr >= buf_end)
		(void)fill_hqxbuf(0);
	read_crc = *buf_ptr++ << 8;
	if (buf_ptr >= buf_end)
		(void)fill_hqxbuf(0);
	read_crc |= *buf_ptr++;
	if (read_crc != calc_crc)
		error(msg, name);
}

write_hqx_crc(calc_crc)
	word calc_crc;
{
	if (buf_ptr == buf_end)
		empty_hqxbuf();
	*buf_ptr++ = calc_crc >> 8;
	if (buf_ptr == buf_end)
		empty_hqxbuf();
	*buf_ptr++ = calc_crc;
}

un_hqx(unpit_flag)
	int unpit_flag;
{
	char type[5];	/* stuff EOS */
	ulong hqx_datalen, hqx_rsrclen;
	word un_pit();
	int unpitting, bytes_read;
	word calc_crc;
	extern char **hqxnames_left;
	int same_file_only = 0;
	int active;

	/* we read EOF on this to transision, so the stream must be valid */
	hqxfile = devnull;
	line_end = line_start + HQXLINELEN;
	buf_end = buf_start + HQXBUFLEN;

	while (1) {
		total_bytes = 0;
		line_ptr = line_start;
		/* ensure that the line buffer is considered empty */
		/* why we use SKIP and not newline, I'm not sure */
		line_ptr[0] = SKIP;
		save_state = 0;
		save_run_length = 0;

		active = hqx_to_bin_hdr(type, &hqx_datalen, &hqx_rsrclen, same_file_only);
		if (active == 0)
			break;
		same_file_only = 1;
		type[4] = 0; /* set EOS */

		unpitting = unpit_flag && !strcmp(type, "PIT ");
		DEBUG && fprintf(debug,
			"DEBUG: unpit_flag=%d type=%s unpitting=%d\n",
			unpit_flag, type, unpitting);
		if (unpitting) {
			mclose(&binfile, "binfile");
			/* Do not unlink files we did not open */
		   if (!info_only)
				unlink(binfname);
			bytes_read = total_bytes - (buf_end - buf_ptr);
			calc_crc = un_pit();
			bytes_read = total_bytes - (buf_end - buf_ptr) - bytes_read;
			if (bytes_read != hqx_datalen)
				fprintf(verbose,
				    "Warning - Extraneous characters ignored in %s\n", binfname);
		} else {
			calc_crc = hqx_to_bin_fork(hqx_datalen);
		}
		check_hqx_crc(calc_crc, "File data CRC mismatch in %s", binfname);

		calc_crc = hqx_to_bin_fork(hqx_rsrclen);
		check_hqx_crc(calc_crc, "File rsrc CRC mismatch in %s", binfname);

		if (!unpitting) {
			mclose(&binfile, "binfile");
		}

	}
	mclose(&hqxfile, "hqxfile");
}

re_hqx()
{
	word calc_crc;
	ulong hqx_datalen, hqx_rsrclen;
	extern char **hqxnames_left;
	extern int maxlines;

	line_end = line_start + HQXLINELEN;
	buf_end = buf_start + HQXBUFLEN;
	while (*hqxnames_left[0] != '-') {
		/* we write the trailer, so the stream must be valid */
		hqxfile = devnull;

		/*
       * We use the trick of setting our line_count at the limit to
       * force an immediate transition on overflow.
		 */
		line_count = maxlines;

		file_count = 0;
		line_ptr = line_start;
		*line_ptr++ = ':';
		strcpy((char*)line_end, "\n");
		buf_ptr = buf_start;
		save_state = 0;
		save_run_length = 1;

		bin_to_hqx_hdr(&hqx_datalen, &hqx_rsrclen);	/* calculates hqxfname */

		/*
       * Now that we have hqxfname, start the new file if
		 * not yet started.  We no longer wait until the output
		 * buffer overflows, since empty files with short names didn't overflow!
		 */

		if (file_count == 0)
			new_out_hqx_file();

		calc_crc = bin_to_hqx_fork(hqx_datalen);
		write_hqx_crc(calc_crc);

		calc_crc = bin_to_hqx_fork(hqx_rsrclen);
		write_hqx_crc(calc_crc);
		/*
       * To end a run and to get the last stray bits,
		 * temporarily add a char.
		 */
		*buf_ptr = !buf_ptr[-1];
		buf_ptr++;
		empty_hqxbuf();
		/* now toss any extra output character generated */
		if (save_state != 2)
			--line_ptr;

		/* if we're at the end of the line now, write it out */
		if (line_ptr == line_end) {
			fputs(LINE_START, hqxfile);
			line_ptr = line_start;
		}

		/* paste the trailing colon onto the end of the line */
		/* recall that line_ptr points into the line, not at the line */
		strcpy((char*)line_ptr, ":\n");
		
		/* and flush the output buffer */
		fputs(LINE_START, hqxfile);

		mclose(&hqxfile, "hqxfile");
		mclose(&binfile, "binfile");

	}
}

