[net.sources.mac] new xbin, much faster

guido@mcvax.uucp (Guido van Rossum) (11/15/86)

Annoyed and amazed by xbin's lack of speed, I ran a profile on it.  It
turned out that the CRC calculation is very costly, and the 6-bit to
8-bit decoding also takes up a lot of time.  Consequently, I have
completely rewritten the 6-to-8 bit decoding and added a flag to turn
off the CRC calculations (since I have never seen a Usenet posting with
a failing CRC).

This is a beta test version, please send me back your comments or
improvements so I can prepare a definitive version for mod.mac.sources.
I am especially interested in a faster CRC calculation.

	Guido van Rossum, CWI, Amsterdam <guido@mcvax.uucp>

: This is a shell archive.
: Extract with 'sh this_file'.
echo 'Start of pack.out, part 01 out of 01:'
echo 'x - README'
sed 's/^X//' > 'README' << 'EOF'
XThis is a beta test copy of version 2.4 of xbin.  I have completely
Xrewritten the .hqx file decoding to speed it up, and added a -f option
Xto turn off CRC calculations.  Usenet generally guarantees error-free
Xtransmission (except for line eaters :-) and I have never seen the CRC
Xcheck fail except on truncated files.  With CRC calculations it is about
Xtwice as fast as xbin 2.3; without CRC's you have a tenfold speed
Ximprovement!
X
XI have only checked this on a VAX running 4.3BSD; comments on
Xportability are welcome.
X
XAs this is a beta test, I have changed most occurrences of the name
X'xbin' to 'nxbin'.
X
XUse at own risk.  My part of the code is placed in the public domain.
X
X	Guido van Rossum
X	CWI, dept. AA
X	Kruislaan 413
X	1098 SJ  Amsterdam
X	The Netherlands
X	
X	phone: 020-5924138
X	usenet: guido@mcvax.uucp
X
X(The rest of this file is the original README for xbin 2.3).
X
X------------------------------------------------------------------------
X
XThis is version 2.3 of xbin.  The major changes include
Xperfomance improvements from Dan LaLiberte of UIUC, fixes
Xfor 16-bit machines from Jim Budler of AMD, and a fix for
Xa bug in the run-length encoding code.
X
XThis version of "xbin" can handle all three BinHex formats
X(so far).  Thanks to Darin Adler at TMQ Software for providing
Xthe code to compute and check the CRC values for all three formats.
X(There are no plans to support binhex5.0, as its use of binary
Xencoding makes it useless for sending programs through e-mail).
X
XOther new features include "list" and "verbose" modes, the
Xability to convert several binhex files at one time, the ability
Xto read standard input, somewhat better error handling, and a
Xmanual page.
X
XAny extraneous mail or news headers are ignored, but xbin relies
Xon finding a line which starts with "(This file" to know when
Xthe header ends and the good stuff begins.  You can add one
Xof these by hand if it's been lost.
X
XTo compile it on USG systems, type:
X	cc -o xbin xbin.c
X
Xor on Berkeley systems:
X	cc -o xbin xbin.c -DBSD
X
XAs usual, please report any problems, suggestions, or
Ximprovements to me.
X
X	Dave Johnson
X	Brown University Computer Science
X	ddj%brown@csnet-relay.ARPA
X	{ihnp4,decvax,allegra,ulysses,linus}!brunix!ddj
X
X===================
XHere's an informal description of the HQX format as I understand it:
X-----
XThe first and last characters are each a ':'.  After the first ':',
Xthe rest of the file is just string of 6 bit encoded characters.
XAll newlines and carriage returns are to be ignored.
X
XThe tricky part is that there are holes in the translation string
Xso you have to look up each file character to get its binary 6 bit
Xvalue.  I found the string by looking at a hex dump of BinHex:
X
X	!"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr
X
XI can't see how this aids or abets any kind of error recovery, but
Xif you ran into a char not in the list, you would know something's
Xwrong and give up.
X
XThere is some run length encoding, where the character to be repeated
Xis followed by a 0x90 byte then the repeat count.  For example, ff9004
Xmeans repeat 0xff 4 times.  The special case of a repeat count of zero
Xmeans it's not a run, but a literal 0x90.  2b9000 => 2b90.
X
X*** Note: the 9000 can be followed by a run, which means to repeat the
X0x90 (not the character previous to that).  That is, 2090009003 means
Xa 0x20 followed by 3 0x90's.
X
XOnce you've turned the 6 bit chars into 8, you can parse the header.
XThe header format consists of a one byte name length, then the mac
Xfile name, then a null.  The rest of the header is 20 bytes long,
Xand contains the usual file type, creator/author, file flags, data
Xand resource lengths, and the two byte crc value for the header.
X
XThe data fork and resource fork contents follow in that order.
XThere is a two byte file crc at the end of each fork.  If a fork
Xis empty, there will be no bytes of contents and the checksum
Xwill be two bytes of zero.
X
XSo the decoded data between the first and last ':' looks like:
X
X	 1       n       4    4    2    4    4   2	(length)
X	+-+---------+-+----+----+----+----+----+--+
X	|n| name... |0|TYPE|AUTH|FLAG|DLEN|RLEN|HC|	(contents)
X	+-+---------+-+----+----+----+----+----+--+
X
X			DLEN			 2	(length)
X	+--------------------------------------+--+
X	|	DATA FORK		       |DC|	(contents)
X	+--------------------------------------+--+
X
X			RLEN			 2	(length)
X	+--------------------------------------+--+
X	|	RESOURCE FORK		       |RC|	(contents)
X	+--------------------------------------+--+
X
X------
EOF
echo 'x - nxbin.1'
sed 's/^X//' > 'nxbin.1' << 'EOF'
X.TH NXBIN local "15 Nov 1986"
X.UC 4
X.SH NAME
Xnxbin \- convert mailable format BinHex file into binary before downloading
Xto MacTerminal
X.SH SYNOPSIS
X.B nxbin
X[
X.B \-o
X]
X[
X.B \-v
X]
X[
X.B \-l
X]
X[
X.B \-f
X]
X[[
X.B \-n
Xname
X] file] ...
X.SH DESCRIPTION
X.I Nxbin
Xconverts a file created by BinHex (usually
Xnamed with one of the extensions ".hex", ".hcx", or ".hqx")
Xinto three host-system files suitable for downloading to a
XMacintosh via macput.
XThis program is designed for use with the 1.1 Release
Xversion of MacTerminal, but includes a compatibility option for the
Xold -0.15X Almost-Alpha version.
X.PP
XThe
X.B -l
X(list) option reads the header information and
Xprints out all the useful information there,
Xwithout creating any converted output files.
X.PP
XThe
X.B -v
X(verbose) option prints a line for each file to be converted, indicating
Xthe input and output file names.
X.PP
XThe
X.B -f
X(fast) option skips the CRC calculation; this is about five times
Xas fast as normal operation, but transmission errors may go undetected.
X.PP
XThe
X.B -n
Xname
Xoption allows the user to specify the name to use when creating
Xthe host files and the eventual name to use on the mac.
XThis option must precede the input file name it is to affect.
X.PP
XIf this option is not used, the names will be derived from
Xeither the input file name (.hex or .hcx files),
Xor the name encoded in the header information (.hqx files).
XSpaces and slashes will be converted to underscores, and
Xthe .h?x extension will be deleted, if one is included in the
Xinput file name.
X.PP
XA file name of "-" indicates that the input should be taken from stdin.
XIf no mac file name is specified, the default name (for .hex or .hcx files)
Xis "stdin".
X.PP
XMail or news headers and signatures need not be manually
Xstripped -- nxbin will ignore pretty much anything
Xit doesn't need.
X.PP
X.I Nxbin
Xcreates three host-system files from each input file:
X.IB name .info ,
X.IB name .data ,
Xand
X.IB name .rsrc .
X.PP
XThe
X.B \-o
Xflag specifies "old" (version -0.15X) MacTerminal compatibility mode.
X.SH BUGS
XThe "LOCKED" bit in the flags cannot be set by nxbin.
XThis is due to a bug in MacTerminal, which sets the flags
Xwhen the file is created, rather than after it has been
Xtransfered, resulting in it not being able to write the
Xfile.
X.PP
XInput files must contain a line starting with "(This file"
Xto detect the beginning of the BinHex information.
X.SH SEE ALSO
Xmacput(1), macget(1)
X.SH AUTHOR
XDave Johnson, Brown 12/16/84;
XCRC handling code by Darin Adler, TMQ Software 3/16/85
EOF
echo 'x - nxbin.c'
sed 's/^X//' > 'nxbin.c' << 'EOF'
X#ifndef lint
Xstatic char version[] = "@(#)nxbin.c Version 2.4, Nov. 86 (guido@mcvax.uucp)";
X#endif lint
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X
X#ifdef MAXNAMLEN	/* 4.2 BSD */
X#define FNAMELEN MAXNAMLEN
X#else
X#define FNAMELEN DIRSIZ
X#endif
X
X#ifdef BSD
X#include <sys/time.h>
X#include <sys/timeb.h>
X#define search_last rindex
Xextern char *rindex();
X#else
X#include <time.h>
Xextern long timezone;
X#define search_last strrchr
Xextern char *strrchr();
X#endif
X
X/* Mac time of 00:00:00 GMT, Jan 1, 1970 */
X#define TIMEDIFF 0x7c25b080
X
X#define DATABYTES 128
X
X#define BYTEMASK 0xff
X#define BYTEBIT 0x100
X#define WORDMASK 0xffff
X#define WORDBIT 0x10000
X
X#define NAMEBYTES 63
X#define H_NLENOFF 1
X#define H_NAMEOFF 2
X
X/* 65 <-> 80 is the FInfo structure */
X#define H_TYPEOFF 65
X#define H_AUTHOFF 69
X#define H_FLAGOFF 73
X
X#define H_LOCKOFF 81
X#define H_DLENOFF 83
X#define H_RLENOFF 87
X#define H_CTIMOFF 91
X#define H_MTIMOFF 95
X
X#define H_OLD_DLENOFF 81
X#define H_OLD_RLENOFF 85
X
X#define F_BUNDLE 0x2000
X#define F_LOCKED 0x8000
X
Xstruct macheader {
X	char m_name[NAMEBYTES+1];
X	char m_type[4];
X	char m_author[4];
X	short m_flags;
X	long m_datalen;
X	long m_rsrclen;
X	long m_createtime;
X	long m_modifytime;
X} mh;
X
Xstruct filenames {
X	char f_info[256];
X	char f_data[256];
X	char f_rsrc[256];
X} files;
X
Xint pre_beta;	/* options */
Xint listmode;
Xint verbose;
Xint no_crc;
X
Xint compressed;	/* state variables */
Xint qformat;
XFILE *ifp;
X
X/*
X * xbin -- unpack BinHex format file into suitable
X * format for downloading with macput
X * Dave Johnson, Brown University Computer Science
X *
X * (c) 1984 Brown University
X * may be used but not sold without permission
X *
X * created ddj 12/16/84
X * revised ddj 03/10/85 -- version 4.0 compatibility, other minor mods
X * revised ddj 03/11/85 -- strip LOCKED bit from m_flags
X * revised ahm 03/12/85 -- System V compatibility
X * revised dba 03/16/85 -- (Darin Adler, TMQ Software)  4.0 EOF fixed,
X *			   4.0 checksum added
X * revised ddj 03/17/85 -- extend new features to older formats: -l, stdin
X * revised ddj 03/24/85 -- check for filename truncation, allow multiple files
X * revised ddj 03/26/85 -- fixed USG botches, many problems w/multiple files
X * revised jcb 03/30/85 -- (Jim Budler, amdcad!jimb), revised for compatibility
X *			   with 16-bit int machines
X * revised dl  06/16/85 -- (Dan LaLiberte, liberte@uiucdcs) character
X *			   translation speedup
X * revised ddj 09/30/85 -- fixed problem with run of RUNCHAR
X * revised gvr 11/15/86 -- speed-up: rewrote .hqx decoding (mcvax!guido)
X */
X
Xchar usage[] = "usage: \"nxbin [-v] [-l] [-o] [-f] [-n name] [-] filename\"\n";
X
Xmain(ac, av)
Xchar **av;
X{
X	char *filename, *macname;
X
X	filename = ""; macname = "";
X	ac--; av++;
X	while (ac) {
X		if (av[0][0] == '-') {
X			switch (av[0][1]) {
X			case '\0':
X				filename = "-";
X				break;
X			case 'v':
X				verbose++;
X				break;
X			case 'l':
X				listmode++;
X				break;
X			case 'o':
X				pre_beta++;
X				break;
X			case 'f':
X				no_crc++;
X				break;
X			case 'n':
X				if (ac > 1) {
X					ac--; av++;
X					macname = av[0];
X					filename = "";
X					break;
X				}
X				else
X					goto bad_usage;
X			default:
X				goto bad_usage;
X			}
X		}
X		else
X			filename = av[0];
X		if (filename[0] != '\0') {
X			setup_files(filename, macname);
X			if (listmode) {
X				print_header();
X			}
X			else {
X				process_forks();
X				/* now that we know the size of the forks */
X				forge_info();
X			}
X			if (ifp != stdin)
X				fclose(ifp);
X			macname = "";
X			ifp = NULL;		/* reset state */
X			qformat = 0;
X			compressed = 0;
X		}
X		ac--; av++;
X	}
X	if (*filename == '\0') {
Xbad_usage:
X		fprintf(stderr, usage);
X		exit(1);
X	}
X}
X
Xstatic char *extensions[] = {
X	".hqx",
X	".hcx",
X	".hex",
X	"",
X	NULL
X};
X
Xsetup_files(filename, macname)
Xchar *filename;		/* input file name -- extension optional */
Xchar *macname;		/* name to use on the mac side of things */
X{
X	char namebuf[256];
X	char **ep;
X	struct stat stbuf;
X	long curtime;
X
X	if (filename[0] == '-') {
X		ifp = stdin;
X		filename = "stdin";
X	}
X	else {
X		/* find input file and open it */
X		for (ep = extensions; *ep != NULL; ep++) {
X			sprintf(namebuf, "%s%s", filename, *ep);
X			if (stat(namebuf, &stbuf) == 0)
X				break;
X		}
X		if (*ep == NULL) {
X			perror(namebuf);
X			exit(-1);
X		}
X		ifp = fopen(namebuf, "r");
X		if (ifp == NULL) {
X			perror(namebuf);
X			exit(-1);
X		}
X	}
X	if (ifp == stdin) {
X		curtime = time(0);
X		mh.m_createtime = curtime;
X		mh.m_modifytime = curtime;
X	}
X	else {
X		mh.m_createtime = stbuf.st_mtime;
X		mh.m_modifytime = stbuf.st_mtime;
X	}
X	if (listmode || verbose) {
X		fprintf(stderr, "%s %s%s",
X			listmode ? "\nListing" : "Converting",
X			namebuf, listmode ? ":\n" : " ");
X	}
X
X	qformat = find_header(); /* eat mailer header &cetera, intuit format */
X
X	if (qformat)
X		do_q_everything(macname, namebuf);
X	else {
X		do_o_header(macname, filename);
X		name_stuffing(macname);
X	}
X}
X
Xname_stuffing(namebuf)
Xchar *namebuf;
X{
X	int n;
X	char *np;
X	/* make sure host file name doesn't get truncated beyond recognition */
X	n = strlen(mh.m_name);
X	if (n > FNAMELEN - 2)
X		n = FNAMELEN - 2;
X	strncpy(namebuf, mh.m_name, n);
X	namebuf[n] = '\0';
X
X	/* get rid of troublesome characters */
X	for (np = namebuf; *np; np++)
X		if (*np == ' ' || *np == '/')
X			*np = '_';
X
X	sprintf(files.f_data, "%s.data", namebuf);
X	sprintf(files.f_rsrc, "%s.rsrc", namebuf);
X	sprintf(files.f_info, "%s.info", namebuf);
X	if (verbose)
X		fprintf(stderr, "==> %s.{info,data,rsrc}\n", namebuf);
X}
X
X/* print out header information in human-readable format */
Xprint_header()
X{
X	char *ctime();
X
X	printf("macname: %s\n", mh.m_name);
X	printf("filetype: %.4s, ", mh.m_type);
X	printf("author: %.4s, ", mh.m_author);
X	printf("flags: 0x%x\n", mh.m_flags);
X	if (qformat) {
X		printf("data length: %ld, ", mh.m_datalen);
X		printf("rsrc length: %ld\n", mh.m_rsrclen);
X	}
X	if (!pre_beta) {
X		printf("create time: %s", ctime(&mh.m_createtime));
X	}
X}
X
Xprocess_forks()
X{
X	if (qformat) {
X		/* Forks have already been read. */
X	}
X	else
X		do_o_forks();
X}
X
X/* write out .info file from information in the mh structure */
Xforge_info()
X{
X	static char buf[DATABYTES];
X	char *np;
X	FILE *fp;
X	int n;
X	long tdiff;
X	struct tm *tp;
X#ifdef BSD
X	struct timeb tbuf;
X#else
X	long bs;
X#endif
X
X	for (np = mh.m_name; *np; np++)
X		if (*np == '_') *np = ' ';
X
X	buf[H_NLENOFF] = n = np - mh.m_name;
X	strncpy(buf + H_NAMEOFF, mh.m_name, n);
X	strncpy(buf + H_TYPEOFF, mh.m_type, 4);
X	strncpy(buf + H_AUTHOFF, mh.m_author, 4);
X	put2(buf + H_FLAGOFF, mh.m_flags & ~F_LOCKED);
X	if (pre_beta) {
X		put4(buf + H_OLD_DLENOFF, mh.m_datalen);
X		put4(buf + H_OLD_RLENOFF, mh.m_rsrclen);
X	}
X	else {
X		put4(buf + H_DLENOFF, mh.m_datalen);
X		put4(buf + H_RLENOFF, mh.m_rsrclen);
X
X		/* convert unix file time to mac time format */
X#ifdef BSD
X		ftime(&tbuf);
X		tp = localtime(&tbuf.time);
X		tdiff = TIMEDIFF - tbuf.timezone * 60;
X		if (tp->tm_isdst)
X			tdiff += 60 * 60;
X#else
X		/* I hope this is right! -andy */
X		time(&bs);
X		tp = localtime(&bs);
X		tdiff = TIMEDIFF - timezone;
X		if (tp->tm_isdst)
X			tdiff += 60 * 60;
X#endif
X		put4(buf + H_CTIMOFF, mh.m_createtime + tdiff);
X		put4(buf + H_MTIMOFF, mh.m_modifytime + tdiff);
X	}
X	fp = fopen(files.f_info, "w");
X	if (fp == NULL) {
X		perror("info file");
X		exit(-1);
X	}
X	fwrite(buf, 1, DATABYTES, fp);
X	fclose(fp);
X}
X
X/* eat characters until header detected, return which format */
Xfind_header()
X{
X	int c, at_bol;
X	char ibuf[BUFSIZ];
X
X	/* look for "(This file ...)" line */
X	while (fgets(ibuf, BUFSIZ, ifp) != NULL) {
X		if (strncmp(ibuf, "(This file", 10) == 0)
X			break;
X	}
X	at_bol = 1;
X	while ((c = getc(ifp)) != EOF) {
X		switch (c) {
X		case '\n':
X		case '\r':
X			at_bol = 1;
X			break;
X		case ':':
X			if (at_bol)	/* q format */
X				return 1;
X			break;
X		case '#':
X			if (at_bol) {	/* old format */
X				ungetc(c, ifp);
X				return 0;
X			}
X			break;
X		default:
X			at_bol = 0;
X			break;
X		}
X	}
X
X	fprintf(stderr, "unexpected EOF\n");
X	exit(2);
X	/* NOTREACHED */
X}
X
Xstatic unsigned int crc;
X
Xshort get2q();
Xlong get4q();
X
X/* read header of .hqx file */
Xdo_q_header(macname)
Xchar *macname;
X{
X	char namebuf[256];		/* big enough for both att & bsd */
X	int n;
X	unsigned int calc_crc, file_crc;
X
X	crc = 0;			/* compute a crc for the header */
X	/* q_init();			/* reset static variables */
X
X	n = getq();			/* namelength */
X	n++;				/* must read trailing null also */
X	getqbuf(namebuf, n);		/* read name */
X	if (macname[0] == '\0')
X		macname = namebuf;
X
X	n = strlen(macname);
X	if (n > NAMEBYTES)
X		n = NAMEBYTES;
X	strncpy(mh.m_name, macname, n);
X	mh.m_name[n] = '\0';
X
X	getqbuf(mh.m_type, 4);
X	getqbuf(mh.m_author, 4);
X	mh.m_flags = get2q();
X	mh.m_datalen = get4q();
X	mh.m_rsrclen = get4q();
X
X	comp_q_crc(0);
X	comp_q_crc(0);
X	calc_crc = crc;
X	file_crc = get2q();
X	verify_crc(calc_crc, file_crc);
X}
X
X/* verify_crc(); -- check if crc's check out */
Xverify_crc(calc_crc, file_crc)
Xunsigned int calc_crc, file_crc;
X{
X	calc_crc &= WORDMASK;
X	file_crc &= WORDMASK;
X
X	if (calc_crc != file_crc) {
X		fprintf(stderr, "CRC error\n---------\n");
X		fprintf(stderr, "CRC in file:\t0x%x\n", file_crc);
X		fprintf(stderr, "calculated CRC:\t0x%x\n", calc_crc);
X		exit(3);
X	}
X}
X
X/* New stuff which hopes to improve the speed. */
X
X#define RUNCHAR 0x90
X
X#define DONE 0x7F
X#define SKIP 0x7E
X#define FAIL 0x7D
X
Xchar lookup[256] = {
X/* 0*/	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X/*	            \n                \r		*/
X	FAIL, FAIL, SKIP, FAIL, FAIL, SKIP, FAIL, FAIL,
X/* 2*/	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X/* 4*/	FAIL, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
X	0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, FAIL, FAIL,
X/* 6*/	0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, FAIL,
X/*	            :					*/
X	0x14, 0x15, DONE, FAIL, FAIL, FAIL, FAIL, FAIL,
X/* 8*/	0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
X	0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, FAIL,
X/*10*/	0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, FAIL,
X	0x2C, 0x2D, 0x2E, 0x2F, FAIL, FAIL, FAIL, FAIL,
X/*12*/	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, FAIL,
X	0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, FAIL, FAIL,
X/*14*/	0x3D, 0x3E, 0x3F, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X/*16*/	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X	FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL,
X};
X
Xchar *g_macname;
Xchar *g_namebuf;
X
Xint stop= 0;
X
Xget_header()
X{
X	do_q_header(g_macname);
X	name_stuffing(g_namebuf);
X	if (listmode) {
X		stop= 1;
X		return;
X	}
X}
X
Xunsigned char obuf[BUFSIZ];
Xunsigned char *op= obuf;
Xunsigned char *oq;
X
X#define S_HEADER	0
X#define S_DATAOPEN	1
X#define S_DATAWRITE	2
X#define S_DATAC1	3
X#define S_DATAC2	4
X#define S_RSRCOPEN	5
X#define S_RSRCWRITE	6
X#define S_RSRCC1	7
X#define S_RSRCC2	8
X#define S_EXCESS	9
X
Xint ostate= S_HEADER;
X
Xunsigned int calc_crc;
Xunsigned int file_crc;
X
Xlong todo;
XFILE *ofp;
X
Xoflush()
X{
X	int n;
X	
X	oq= obuf;
X	while (oq < op && !stop) {
X		switch (ostate) {
X		case S_HEADER:
X			get_header();
X			++ostate;
X			break;
X		case S_DATAOPEN:
X			ofp= fopen(files.f_data, "w");
X			todo= mh.m_datalen;
X			crc= 0;
X			++ostate;
X			break;
X		case S_RSRCOPEN:
X			ofp= fopen(files.f_rsrc, "w");
X			todo= mh.m_rsrclen;
X			crc= 0;
X			++ostate;
X			break;
X		case S_DATAWRITE:
X		case S_RSRCWRITE:
X			n= op-oq;
X			if (n > todo)
X				n= todo;
X			if (fwrite(oq, 1, n, ofp) != n) {
X				perror("fwrite");
X				exit(1);
X			}
X			if (!no_crc) {
X				register unsigned char *end= oq+n;
X				register unsigned char *p= oq;
X				while (p < end)
X					comp_q_crc(*p++);
X			}
X			oq += n;
X			todo -= n;
X			if (todo <= 0) {
X				fclose(ofp);
X				++ostate;
X			}
X			break;
X		case S_DATAC1:
X		case S_RSRCC1:
X			comp_q_crc(0);
X			comp_q_crc(0);
X			calc_crc= crc;
X			file_crc= getq() << 8;
X			++ostate;
X			break;
X		case S_DATAC2:
X		case S_RSRCC2:
X			/* Skip crc bytes */
X			file_crc |= getq();
X			if (!no_crc)
X				verify_crc(calc_crc, file_crc);
X			++ostate;
X			break;
X		case S_EXCESS:
X			fprintf(stderr, "%d excess bytes ignored\n", op-oq);
X			oq= op;
X			break;
X		}
X	}
X	op= obuf;
X}
X
Xgetq()
X{
X	int c;
X	
X	if (oq >= op) {
X		fprintf(stderr, "premature EOF\n");
X		exit(1);
X	}
X	c= *oq++;
X	comp_q_crc((unsigned)c);
X	return c;
X}
X
X/* get2q(); q format -- read 2 bytes from input, return short */
Xshort
Xget2q()
X{
X	short high= getq() << 8;
X	return high | getq();
X}
X
X/* get4q(); q format -- read 4 bytes from input, return long */
Xlong
Xget4q()
X{
X	int i;
X	long value= 0;
X
X	for (i= 0; i < 4; i++)
X		value= (value<<8) | getq();
X	return value;
X}
X
X/* getqbuf(); q format -- read n characters from input into buf */
Xgetqbuf(buf, n)
X	char *buf;
X	int n;
X{
X	int i;
X
X	for (i = 0; i < n; i++)
X		*buf++ = getq();
X}
X
X#define output(c) { *op++ = (c); if (op >= &obuf[BUFSIZ]) oflush(); }
X
Xdo_q_everything(macname, namebuf)
Xchar *macname;
Xchar *namebuf;
X{
X	unsigned char buf[BUFSIZ];
X	int n;
X	register unsigned char *in, *out;
X	register int b6, b8, data, lastc= 0;
X	char state68= 0, run= 0;
X	
X	g_macname= macname;
X	g_namebuf= namebuf;
X	
X	ostate= S_HEADER;
X	stop= 0;
X	
X	while (!stop && (n= fread(buf, 1, BUFSIZ, ifp)) > 0) {
X		in= buf;
X		out= buf+n;
X		do {
X			if ((b6= lookup[*in]) >= 64) {
X				switch (b6) {
X				case DONE:
X					goto done;
X				case SKIP:
X					break;
X				default:
X					fprintf(stderr,
X						"bad char '%c'(%d)\n",
X						*in, *in);
X					goto done;
X				}
X			}
X			else {
X				/* Pack 6 bits to 8 bits */
X				switch (state68++) {
X				case 0:
X					b8= b6<<2;
X					continue; /* No data byte */
X				case 1:
X					data= b8 | (b6>>4);
X					b8= (b6&0xF) << 4;
X					break;
X				case 2:
X					data= b8 | (b6>>2);
X					b8= (b6&0x3) << 6;
X					break;
X				case 3:
X					data= b8 | b6;
X					state68= 0;
X					break;
X				}
X				if (!run) {
X					if (data == RUNCHAR)
X						run= 1;
X					else
X						output(lastc= data);
X				}
X				else {
X					if (data == 0) {
X						output(lastc= RUNCHAR);
X					}
X					else {
X						while (--data > 0) {
X							output(lastc);
X						}
X					}
X					run= 0;
X				}
X			}
X		} while (++in < out);
X	}
X done:	oflush();
X 	if (!stop && ostate != S_EXCESS) {
X 		fprintf(stderr, "premature EOF\n");
X 		exit(1);
X 	}
X}
X
X/* Ahh, too bad.  The following routine takes up half of the CPU time.
X   But in this form it's far to complicated to turn it into a macro! */
X
X#define CRCCONSTANT 0x1021
X
Xcomp_q_crc(c)
Xregister unsigned int c;
X{
X	register int i;
X	register unsigned long temp = crc;
X
X/* Never mind why I call it WOP... */
X#define WOP { \
X		c <<= 1; \
X		if ((temp <<= 1) & WORDBIT) \
X			temp = (temp & WORDMASK) ^ CRCCONSTANT; \
X		temp ^= (c >> 8); \
X		c &= BYTEMASK; \
X	}
X	WOP;
X	WOP;
X	WOP;
X	WOP;
X	WOP;
X	WOP;
X	WOP;
X	WOP;
X	crc = temp;
X}
X
X/* old format -- process .hex and .hcx files */
Xdo_o_header(macname, filename)
Xchar *macname, *filename;
X{
X	char namebuf[256];		/* big enough for both att & bsd */
X	char ibuf[BUFSIZ];
X	int n;
X
X	/* set up name for output files */
X	if (macname[0] == '\0') {
X		strcpy(namebuf, filename);
X
X		/* strip directories */
X		macname = search_last(namebuf, '/');
X		if (macname == NULL)
X			macname = namebuf;
X		else
X			macname++;
X
X		/* strip extension */
X		n = strlen(macname);
X		if (n > 4) {
X		    n -= 4;
X		    if (macname[n] == '.' && macname[n+1] == 'h'
X					    && macname[n+3] == 'x')
X			    macname[n] = '\0';
X		}
X	}
X	n = strlen(macname);
X	if (n > NAMEBYTES)
X		n = NAMEBYTES;
X	strncpy(mh.m_name, macname, n);
X	mh.m_name[n] = '\0';
X
X	/* read "#TYPEAUTH$flag"  line */
X	if (fgets(ibuf, BUFSIZ, ifp) == NULL) {
X		fprintf(stderr, "unexpected EOF\n");
X		exit(2);
X	}
X	n = strlen(ibuf);
X	if (n >= 7 && ibuf[0] == '#' && ibuf[n-6] == '$') {
X		if (n >= 11)
X			strncpy(mh.m_type, &ibuf[1], 4);
X		if (n >= 15)
X			strncpy(mh.m_author, &ibuf[5], 4);
X		sscanf(&ibuf[n-5], "%4hx", &mh.m_flags);
X	}
X}
X
Xdo_o_forks()
X{
X	char ibuf[BUFSIZ];
X	int forks = 0, found_crc = 0;
X	unsigned int calc_crc, file_crc;
X	extern long make_file();
X
X
X	crc = 0;	/* calculate a crc for both forks */
X
X	/* create empty files ahead of time */
X	close(creat(files.f_data, 0666));
X	close(creat(files.f_rsrc, 0666));
X
X	while (!found_crc && fgets(ibuf, BUFSIZ, ifp) != NULL) {
X		if (forks == 0 && strncmp(ibuf, "***COMPRESSED", 13) == 0) {
X			compressed++;
X			continue;
X		}
X		if (strncmp(ibuf, "***DATA", 7) == 0) {
X			mh.m_datalen = make_file(files.f_data, compressed);
X			forks++;
X			continue;
X		}
X		if (strncmp(ibuf, "***RESOURCE", 11) == 0) {
X			mh.m_rsrclen = make_file(files.f_rsrc, compressed);
X			forks++;
X			continue;
X		}
X		if (compressed && strncmp(ibuf, "***CRC:", 7) == 0) {
X			found_crc++;
X			calc_crc = crc;
X			sscanf(&ibuf[7], "%x", &file_crc);
X			break;
X		}
X		if (!compressed && strncmp(ibuf, "***CHECKSUM:", 12) == 0) {
X			found_crc++;
X			calc_crc = crc & BYTEMASK;
X			sscanf(&ibuf[12], "%x", &file_crc);
X			file_crc &= BYTEMASK;
X			break;
X		}
X	}
X
X	if (found_crc)
X		verify_crc(calc_crc, file_crc);
X	else {
X		fprintf(stderr, "missing CRC\n");
X		exit(3);
X	}
X}
X
Xlong
Xmake_file(fname, compressed)
Xchar *fname;
Xint compressed;
X{
X	char ibuf[BUFSIZ];
X	FILE *outf;
X	register long nbytes = 0L;
X
X	outf = fopen(fname, "w");
X	if (outf == NULL) {
X		perror(fname);
X		exit(-1);
X	}
X
X	while (fgets(ibuf, BUFSIZ, ifp) != NULL) {
X		if (strncmp(ibuf, "***END", 6) == 0)
X			break;
X		if (compressed)
X			nbytes += comp_to_bin(ibuf, outf);
X		else
X			nbytes += hex_to_bin(ibuf, outf);
X	}
X
X	fclose(outf);
X	return nbytes;
X}
X
Xcomp_c_crc(c)
Xunsigned char c;
X{
X	crc = (crc + c) & WORDMASK;
X	crc = ((crc << 3) & WORDMASK) | (crc >> 13);
X}
X
Xcomp_e_crc(c)
Xunsigned char c;
X{
X	crc += c;
X}
X
X#define SIXB(c) (((c)-0x20) & 0x3f)
X
Xcomp_to_bin(ibuf, outf)
Xchar ibuf[];
XFILE *outf;
X{
X	char obuf[BUFSIZ];
X	register char *ip = ibuf;
X	register char *op = obuf;
X	register int n, outcount;
X	int numread, incount;
X
X	numread = strlen(ibuf);
X	ip[numread-1] = ' ';		/* zap out the newline */
X	outcount = (SIXB(ip[0]) << 2) | (SIXB(ip[1]) >> 4);
X	incount = ((outcount / 3) + 1) * 4;
X	for (n = numread; n < incount; n++)	/* restore lost spaces */
X		ibuf[n] = ' ';
X
X	n = 0;
X	while (n <= outcount) {
X		*op++ = SIXB(ip[0]) << 2 | SIXB(ip[1]) >> 4;
X		*op++ = SIXB(ip[1]) << 4 | SIXB(ip[2]) >> 2;
X		*op++ = SIXB(ip[2]) << 6 | SIXB(ip[3]);
X		ip += 4;
X		n += 3;
X	}
X
X	for (n=1; n <= outcount; n++)
X		comp_c_crc((unsigned)obuf[n]);
X
X	fwrite(obuf+1, 1, outcount, outf);
X	return outcount;
X}
X
Xhex_to_bin(ibuf, outf)
Xchar ibuf[];
XFILE *outf;
X{
X	register char *ip = ibuf;
X	register int n, outcount;
X	int c;
X
X	n = strlen(ibuf) - 1;
X	outcount = n / 2;
X	for (n = 0; n < outcount; n++) {
X		c = hexit(*ip++);
X		comp_e_crc((unsigned)(c = (c << 4) | hexit(*ip++)));
X		fputc(c, outf);
X	}
X	return outcount;
X}
X
Xhexit(c)
Xint c;
X{
X	if ('0' <= c && c <= '9')
X		return c - '0';
X	if ('A' <= c && c <= 'F')
X		return c - 'A' + 10;
X
X	fprintf(stderr, "illegal hex digit: %c", c);
X	exit(4);
X	/* NOTREACHED */
X}
X
Xput2(bp, value)
Xchar *bp;
Xshort value;
X{
X	*bp++ = (value >> 8) & BYTEMASK;
X	*bp++ = value & BYTEMASK;
X}
X
Xput4(bp, value)
Xchar *bp;
Xlong value;
X{
X	register int i, c;
X
X	for (i = 0; i < 4; i++) {
X		c = (value >> 24) & BYTEMASK;
X		value <<= 8;
X		*bp++ = c;
X	}
X}
EOF
echo 'Part 01 out of 01 of pack.out complete.'
exit 0