

/* ABE 'Ascii-Binary Encoding' Decoder 
   This is the full decoder which detects all errors in input files,
   and can handle groups of input blocks in any order, without
   sorting.  This program was written with Unix(TM-Bell Labs) in mind, but it
   can be easily ported to other systems, notably MS-DOS using the
   Microsoft C compiler conventions for text/binary files. 

   This program was written by Brad Templeton, and is a companion to
   the ABE encoder and the simple ABE decoder.  While the simple
   ABE decoder was released to the public domain, this decoder is not.

   This decoder is quite general.  It includes the ability to decode
   ABE files of just about any type, including 3-set ascii ones, 4-set
   EBCDIC ones and general ABE style files of up to MAX_SETS sets.  It
   can also handle UUENCODE format placed inside an ABE file.

   It is Copyright 1989 by Brad Templeton.  I hereby grant a licence
   for unlimited use of binaries generated by compiling this source
   code.  The source code may be distributed and modified for non-commercial
   purposes, but all copyright messages must be retained.  Changes which
   alter the file format of ABE files are not permitted without
   the explicit consent of Brad Templeton.  I mean it.

   No fee is required for the use of the program.
   See the man page for a potential way to contribute.

   Changes which move this program to other machines and operating
   systems are encouraged.  Please send any port to a new machine to
   me at abe@looking.UUCP.

 */

#include <stdio.h>
#include "abe.h"





	/* the big array that maps all the printables in each of the
	   three character sets to actual bytes */

int whatbyte[MAX_SETS][MAX_PRINTS];

char linbuf[MAX_LLEN];		/* input line */
char lbcopy[MAX_LLEN];		/* copy of input line for error msgs */

int current_block;		/* block we are decoding */
bool in_file;			/* are we in a ABE file */
bool in_block;			/* are we in a ABE block */
bool system_noted;		/* did we note the OS? */
bool has_blocking;		/* does this file have blocks at all */
int got_coding;			/* which lines of the encoding have we got */
bool got_checksum;		/* did we get the file's checksum */
bool same_os;			/* file from same OS as ours? */
bool got_main_head;		/* got the main header */
bool got_main_end;		/* got the EOF header */

int total_blocks;		/* number of blocks to expect */
long this_line;			/* line number for this line */
long expected_line;		/* what line number we expect next */
long block_checksum;		/* running checksum for the block */
long total_checksum;		/* running checksum for the file */
long block_bytecount;		/* running count of bytes in block */
long total_bytecount;		/* running count of bytes in file */
long expected_checksum;		/* total checksum file should have */
long expected_bytecount;	/* size file should have */
long save_total_bytecount;	/* in case a block gets ignored */
long save_total_checksum;
bool skipping_block = FALSE;	/* are we skipping this block */

tcrc crc;			/* total file crc */
tcrc block_crc;			/* block crc */
tcrc crctab [TABSIZE];		/* big mother CRC-32 lookup table */

long file_date;			/* modification date for file */
int file_perm;			/* file permission bits.  Only the low 3
				   are significant across OSs */

char *cur_filename;		/* current universal filename */
char *real_filename;		/* real name of the file */
char *cur_sourcefile;		/* name of current source file */
bool block_gotmap[MAX_BLOCKS];	/* have we seen this block */

FILE *out_desc;			/* descriptor for current output file */
long remember_seek;		/* address to seek to after opening file */

int error_stat = 0;		/* exit status */
bool had_files = FALSE;		/* did we process files */

/* options */
bool only_stdout = FALSE;	/* output to stdout, ignore files */
bool error_ignore = FALSE;	/* ignore semi-fatal errors and continue */
bool is_verbose = TRUE;		/* give verbose output */
bool no_numbers = FALSE;	/* don't require line numbers */
int print_map[TOTAL_PRINTS];	/* map printable chars to actions */
				/* -1 means undefined.  0 to 128 means that
				   the printable maps to a character of the
				   given index in one of the sets.  Numbers
				   above 256 are shifting chars */

struct fseen_list *seen_list = 0;	/* list of file names seen */
int current_style = UNDEF;		/* decoding style */
int sets_per_char;			/* how many sets are packed into
					   an index byte in the code map */

/* The map of ABE2 characters and line number characters */

char safe_prints[] =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";


int num_sets = A1NSETS;		/* number of sets of characters */
int num_prints = A1NPRINTS;	/* number of characters per set */

/* characters that define the headers (normally never changed) */

int main_head_char = MAIN_HEAD;
int code_head_char = CODE_HEAD;
int sub_head_char = SUB_HEAD;

main(argc,argv)
int argc;
char **argv;
{
	int argnum;
	char *strchr();



	/* scan the option arguments */
	for( argnum = 1; argnum < argc; argnum++ ) {
		char *argline, *argstr;
		int isplus;		/* boolean tells +arg vs -arg */
		argline = argv[argnum];

		if (argstr = strchr(argline, '=')) {
			int len;
			argstr++;
			switch( argline[0] ) {
				case 'h':	/* header chars */
					/* undocumented option allows an
					   encoding to use different header
					   characters.  For future encoders
					   and unforseen problems */
					len = strlen( argstr );
					if( len > 2 )
						code_head_char = argstr[2];
					if( len > 1 )
						sub_head_char = argstr[1];
					if( len > 0 )
						main_head_char = argstr[0];
					break;
				default:
					do_options();
					fatal( "Bad Option %s", argline );
				}
			}
		else if( (isplus = argline[0] == '+') || argline[0] == '-' ) {
			switch( argline[1] ) {
				case 's': /* output to stdout */
					only_stdout = isplus;
					break;
				case 'i': 
					error_ignore = isplus;
					break;
				case 'v':
					is_verbose = isplus;
					break;
				default:
					do_options();
					fatal( "Bad Option %s", argline );
				}
			}
		}

	mkcrctab();		/* init crc-32 table */

	/* Don't even think of removing or changing the copyright */
	verbose( 1, "ABE decoder v%d Copyright 1989 by Brad Templeton",
			OUR_VERSION );

	/* now scan the non-tagged arguments */
	for( argnum = 1; argnum < argc; argnum++ ) {
		FILE *indesc;
		char *argline;

		argline = argv[argnum];
		if( strchr( "+-", argline[0] ) == NULL && strchr(argline,'=')
								== NULL ) {

			indesc = fopen( argline, "r" );
			if( !indesc )
				fatal( "Could not open file '%s'", argline );
			cur_sourcefile = argline;
			givemsg( dofile(indesc), argline );
			had_files = TRUE;
			fclose( indesc );
			}
		}
	cur_sourcefile = "(STDIN)";
	if( !had_files )
		givemsg( dofile( stdin ), "STDIN" );
	if( in_file )
		end_current_file();

	return error_stat;
}

dofile(desc)
FILE *desc;
{
	bool try_uu;			/* flag to try a uudecode */
	bool got_abe;			/* got some ABE data */
	char *ldata;			/* data on the line */
	char command;
	int i, c, len;
	extern bool seen_file( );

	try_uu = got_abe = FALSE;
	lbcopy[0] = 0;

	while( fgets( linbuf, MAX_LLEN, desc ) ){
		int dummyo;		/* dummies for scanf */
		char dummystr[8];
		int sum;		/* checksum for this line */

		len = strlen( linbuf );
		if( !in_file && !try_uu && sscanf(linbuf, "begin %o %7s",
				&dummyo, dummystr ) == 2 )
			try_uu = TRUE;

		if( no_numbers ) {
			ldata = linbuf;
			}
		 else {
			/* ignore any line not starting with T or later, or
		   	with fewer than 5 characters */
			if( len < 5 || linbuf[0] < safe_prints[SFBYTE] )
				continue;
			ldata = linbuf+4;
			len -= 4;
			}
		/* extract the command character, 0 if none */

		command = ldata[0] == ldata[1] ? ldata[0] : 0;
		/* eliminate the newline from the official line length,
		   and if it isn't there, the line's too long, so ignore it. */
		if( ldata[len-1] == '\n' )
			ldata[--len] = '\0';
		 else
			continue;	/* line too long */
		/* deal with files from ms-dos */
		if( ldata[len-1] == '\r' )
			ldata[--len] = 0;
		strcpy( lbcopy, linbuf );
		/* checksum this line */
		sum = 0;
		for( i = 0; i < len; i++ )
			sum += ldata[i];

		if( !no_numbers ) {
			/* see if the checksum matches */
			if( (sum % NUM_SAFE) != lnconv(linbuf[3]) )
					continue;
			/* get a real line number from first 3 characters */
		
			this_line = ( (long)(lnconv(linbuf[0])-SFBYTE)*NUM_SAFE+
					lnconv(linbuf[1]))*NUM_SAFE +
					lnconv(linbuf[2]);

			if( in_file && in_block && !skipping_block &&
						this_line != expected_line ) {
				semifatal("Out of sequence.  Should have got line '%s'",
						lineconv( expected_line ) );
				expected_line = this_line + 1;
				}
			else
				expected_line++;
			}

		if( command == main_head_char ) {
			int file_version;
			int early_ver;		/* earlist decoder */
			char style[MAX_STYLE+1];
			/* check start or end of file */
			if( ldata[2] == 'S' ) {
				sscanf(ldata+3, "%*d,%u,%u,%8s",
					&file_version,
					&early_ver, style);

				if( !in_file )
					begin_file(file_version,early_ver);
				init_style( style );

				got_main_head = TRUE;
				}
			 else {
				got_main_end = TRUE;
				expected_checksum = atol(ldata+3);
				got_checksum = TRUE;
				if( in_block && has_blocking )
					fatal( "End of ABE while still inside block" );
				consider_closing();
				}
			}
		else if( command == sub_head_char ) /* general sub-header */
			sub_header( ldata + 2 );
		else if( command == code_head_char ) /* mapping entry */
			code_head( ldata + 2, len-2 );
		else if( command && (print_map[command] >> 8) != MCHAR ) {
			/* unknown header entry */
			nonfatal( "Unknown header type %c", command );
			}
		else if( in_file && in_block && !skipping_block ) {
			/* a line of data */
			if( got_coding != FULL_MAP ) {
				if( !no_numbers )
					verbose( -1,"You may wish to sort the input files" );
				fatal( "Unable to decode line without full character map." );
				}
			/* add to the checksum for this line */
			total_checksum = (total_checksum + sum)
						% CSUM_MOD;
			got_abe = TRUE;	/* we got something */
			if( out_desc == (FILE *)0 )
				init_output();
			/* decode the line */
			decode( ldata, len );
			}
		/* these checksums are added after, as the encoder calculates
		   a checksum before sending out a line, and it thus does
		   not include the checksum of the checksum line itself */
		block_checksum = (block_checksum + sum) % CSUM_MOD;
		}

	consider_closing();
	

	/* indicate how this file went */
	if( got_abe )
		return GOOD_FILE;
	else
		return try_uu ? UUDECODE : NO_DATA;

}

/* Decode a line of data */

decode( line, len )
char *line;		/* pointer to start of data */
int len;		/* length of data region */
{
	register int c;		/* lower byte of action word */
	int i;
	int cset;		/* current decoding set - (always 0 currently)*/
	int lastc;
	register int wmap;

	if( current_style == UUENCODE ) {
		uudecode( line, len );
		return;
		}

	cset = 0;
	wmap = 0;

	for( i = 0; i < len; i++ ) {
		c = print_map[line[i]] & 0xff;
		switch(print_map[line[i]] >> 8 ){
		    case SHIFTX:
			if( (wmap = print_map[line[++i]]) >= 0 )
				outbyte(lastc = whatbyte[1+c][wmap]);
			break;
		    case SHIFTXX:
			outbyte(whatbyte[1 + c/(num_sets-1)]
						[print_map[line[++i]]]);
			outbyte(whatbyte[1 + c%(num_sets-1)]
						[print_map[line[++i]]]);
			break;
		    case SHIFTXcX:
			outbyte(whatbyte[1+c/(num_sets-1)]
						[print_map[line[++i]]]);
			outbyte(whatbyte[cset][print_map[line[++i]]]);
			outbyte(whatbyte[1+c%(num_sets-1)]
						[print_map[line[++i]]]);
			break;
		    case MCHAR:	/* plain old data character */
			outbyte(lastc = whatbyte[cset][c]);
			break;
		    case CHANGE_SET:		/* currently unused */
			/* change current set for rest of line */
			/* some more efficient future decoder may decide to
			   use this some day */
			cset = c;
			break;
		    case RUNLENGTH: {	/* not currently used */
			int mapc;
			int rlen;

			rlen = 1+print_map[line[i++]];
			for( mapc = 0; mapc < rlen; mapc++ )
				outbyte( lastc );
			}
		    default:	/* probably a -1 */
			semifatal( "Unknown character %c\n", print_map[i] );
			}
	    }
	/* do the newline in TEXT mode if not escaped */
	if( current_style == TEXT && wmap != -1 )
		outbyte( '\n' );
}
	/* process a sub header item */

sub_header( shcom )
char *shcom;
{
	char *arg, *strchr();
	char *thecom;

	thecom = shcom;
	if( arg = strchr( shcom, '=' ) ) {
		*arg++ = 0;		/* point at the argument */
		}

	if( comeq( thecom, "os" ) ) {
		same_os = arg && comeq( arg, OUROS );
		if( !system_noted ) {
			verbose(2,"File was encoded on a %s system (%slike ours)",
					arg, same_os ? "" : "un" );
			system_noted = TRUE;
			}
		}
	else if( comeq( thecom, "blocking" ) ) {
		has_blocking = comeq( arg, "true" );
		in_block = !has_blocking;
		expected_line = this_line+1;
		}
	else if( comeq( thecom, "fname" ) ) {
		init_outfile( NULL, arg );
		}
	else if( comeq( thecom, "linenumbers" ) ) {
		no_numbers = comeq( arg, "false" );
		}
	else if( comeq( thecom, "startblock" ) ) {
		startblock( arg );
		}
	else if( comeq( thecom, "closeblock" ) ) {
		closeblock( arg );
		}
	else if( comeq( thecom, "uname" ) ) {
		init_outfile( arg, NULL );
		}
	else if( comeq( thecom, "owner" ) ) {
		/* do nothing, for now */;
		}
	else if( comeq( thecom, "filecrc32" ) ) {
		if( !has_blocking && ~crc != (tcrc)atol(arg) )
			semifatal( "Bad CRC on file %s", cur_filename );
		}
	else if( comeq( thecom, "filecount" ) ) {
		/* do nothing, for now */;
		}
	else if( comeq( thecom, "xshifts" ) ) {
		set_shifts( SHIFTX, arg );
		}
	else if( comeq( thecom, "xxshifts" ) ) {
		set_shifts( SHIFTXX, arg );
		}
	else if( comeq( thecom, "xcxshifts" ) ) {
		set_shifts( SHIFTXcX, arg );
		}
	else if( comeq( thecom, "runlength" ) ) {
		set_shifts( RUNLENGTH, arg );
		}
	else if( comeq( thecom, "changeset" ) ) {
		set_shifts( CHANGE_SET, arg );
		}
	else if( comeq( thecom, "style" ) ) {
		init_style( arg );
		}
	else if( comeq( thecom, "prints1" ) ) {
		set_prints( 0, arg );
		}
	else if( comeq( thecom, "prints48" ) ) {
		set_prints( 48, arg );
		}
	else if( comeq( thecom, "numsets" ) ) {
		num_sets = atoi(arg);
		if( num_sets > MAX_SETS )
			fatal( "Too many character sets" );
		}
	else if( comeq( thecom, "setgroup" ) ) {
		/* we should insist that this divide 32 */
		sets_per_char = atoi(arg);
		if( 32 % sets_per_char != 0 )
			fatal( "Set Grouping must divide into 32" );
		}
	else if( comeq( thecom, "total-blocks" ) ) {
		total_blocks = atoi( arg );
		}
	else if( comeq( thecom, "end_file" ) ) {
		if( !cur_filename || strcmp( arg, cur_filename ) != 0 )
			semifatal( "Termination of wrong file" );
		}
	else if( comeq( thecom, "date" ) ) {
		file_date = atol(arg);
		}
	else if( comeq( thecom, "perm" ) ) {
		file_perm = (int)atol(arg);
		}
	else if( comeq( thecom, "size" ) ) {
		expected_bytecount = atol(arg);
		}
	else
		nonfatal( "Unknown header command %s", thecom );
}

	/* begin a new file */

begin_file(vnum,early_ver)
int vnum;		/* version number of file */
int early_ver;		/* earliest decoder that can read this file */
{
	int i;

	if( OUR_VERSION < early_ver )
		fatal( "File version %d needs a more recent ABE decoder",
					vnum );

	if( in_file ) {	
		nonfatal( "New file begins while still in file '%s' -- terminating that file", cur_filename ? cur_filename : "**Unknown**" );
		end_current_file();
		}
	in_file = TRUE;
	in_block = FALSE;
	got_coding = 0;
	/* clear out printable character action map */
	for( i = 0; i < TOTAL_PRINTS; i++ )
		print_map[i] = -1;
	current_style = UNDEF;
	got_checksum = FALSE;
	system_noted = FALSE;
	no_numbers = FALSE;
	got_main_head = got_main_end = TRUE;
	remember_seek = 0;
	file_date = 0;
	file_perm = -1;
	cur_filename = NULL;
	/* this store may throw away a few bytes of memory, but it
	   isn't important */
	real_filename = NULL;

	total_checksum = 0;
	total_bytecount = 0;
	block_crc = crc = 0xffffffffL;


	/* mark no blocks as having been seen */
	for( i = 0; i < MAX_BLOCKS; i++ )
		block_gotmap[i] = FALSE;
}

	/* terminate processing of the current file and close it */

end_current_file()
{
	int i;
	int success;

	success = TRUE;
	if( !cur_filename )
		fatal( "No name ever found for file in this encoding" );

	if( !got_checksum ) {
		warning( "No checksum found for '%s'", cur_filename );
		success = FALSE;
		}

	if( has_blocking )
		if( !total_blocks )
			fatal( "No count of total blocks found for '%s'",
							cur_filename );
		 else {
			for( i = 0; i < total_blocks; i++ ) {
				if( !block_gotmap[i] ) {
					semifatal( "Missing block %d from '%s'",
							i, cur_filename );
					success = FALSE;
					}
				}
			}
	if( expected_bytecount != total_bytecount ) {
		semifatal( "Expected %ld bytes, got %ld bytes on file '%s'",
			expected_bytecount, total_bytecount, cur_filename );
		success = FALSE;
		}
	else if( expected_checksum != (total_checksum % CSUM_MOD) ) {
		semifatal( "Whole file checksum error on '%s' we had %ld", cur_filename, total_checksum );
		success = FALSE;
		}
#ifndef msdos
	close_output();	/* under dos, file must be open to change date */
#endif
	if( !only_stdout ) {
		if( file_date != 0 )
			setfiledate( file_date );
		if( file_perm != -1 )
			setfileperm( file_perm );
		}
#ifdef msdos
	close_output();
#endif
	if( success )
		verbose( 0, "File '%s' decoded successfully, %ld bytes",
				cur_filename, total_bytecount );
	 else
		warning( "Error decoding file '%s'", cur_filename );
	cur_filename = NULL;
	in_file = FALSE;
	no_numbers = FALSE;

}


	/* set of OS dependent routines to set file dates and permissions */
#ifdef unix

#include <sys/types.h>

setfiledate(arg)
long arg;
{
	struct utimbuf {
		time_t actime;
		time_t modtime;
		} otime;

	otime.actime = otime.modtime = arg;
	if( utime( real_filename, &otime ) )
		warning( "Could not set date/time on '%s'", real_filename );
}

#include <sys/stat.h>
setfileperm(arg)
int arg;
{
	struct stat sbuf;
	int failure;


	if( same_os )
		failure = chmod( real_filename, arg );
	 else 
		/* only the lower 3 bits are portable */
		failure = stat( real_filename, &sbuf ) ||
			chmod( real_filename, (arg & 7) | (sbuf.st_mode & ~7) );
	if( failure )
		warning( "Could not set permissions on '%s'", real_filename );
}

#else

# include <sys/types.h>

# ifdef msdos
	/* using unix compat library */
setfiledate(arg)
long arg;
{
	struct utimbuf {
		time_t actime;
		time_t modtime;
		} otime;

	otime.actime = otime.modtime = arg;
	if( utime( real_filename, &otime ) )
		warning( "Could not set date/time on '%s'", real_filename );
}
	/* setting file permissions in DOS is not a great idea, anyway.
	   If is currently commented out, but people can add it if
	   desired */

setfileperm(arg)
int arg;
{
	int failure;


	/*if( chmod( real_filename, same_os ? arg : (arg & 7) << 6 ) )
		warning( "Could not set permissions on '%s'", real_filename );*/
}


# else

setfiledate(arg)
long arg;
{
}

setfileperm(arg)
int arg;
{
}

# endif /* else */

#endif

	/* initialize the output file */

init_outfile( uname, fullname )
char *uname;		/* name to init, or null */
char *fullname;		/* full name of the file */
{
	if( fullname ) {
		if( !real_filename ) {
			if( same_os ) { 
				if( out_desc != (FILE *) 0 ) /* dead code ?*/
					verbose( 2, "File's true name '%s' ignored due to block re-ordering", fullname );
				real_filename = allocstring(fullname);
				}
		 	else
				verbose( 2, "Real name '%s' ignored due to operating system differences.", fullname );
			}
		}
	 else {
		if( !cur_filename ) {
			struct fseen_list *ourseen;
			cur_filename = real_filename = allocstring(uname);
			/* add this to the chain of filenames seen */
			ourseen = (struct fseen_list *)malloc(sizeof(
							struct fseen_list));
			if( !ourseen )
				fatal( "Out of memory" );
			ourseen->next_fs = seen_list;
			ourseen->name_seen = cur_filename;
			/* put on the front of the chain */
			seen_list = ourseen;
			}
		}
}
	
	/* open the output descriptor.  Return stdout if requested */

FILE *
openout( fname )
char *fname;
{
	FILE *mydesc;

	if( only_stdout ) {
#ifdef msdos
		setmode( fileno(stdout), O_BINARY );
#endif
		return stdout;
		}

	mydesc = fopen( fname, WRITEMODE );

	if( !mydesc )
		fatal( "Unable to open file '%s' for writing", fname );
	return mydesc;
}

outbyte( byte )
int byte;
{
	putc( byte, out_desc );
	total_bytecount++;
	block_bytecount++;
	crc = UPDC32( byte, crc );
	block_crc = UPDC32( byte, block_crc );
}

close_output()
{
	fclose( out_desc );
	out_desc = (FILE *)0;
}


seekto( adr )
long adr;
{
	if( out_desc ) {
		/* don't try to seek on stdout unless necessary */
		if( !only_stdout || adr != total_bytecount )
			if( fseek( out_desc, adr, 0 ) )
				semifatal( "Seek to address %ld failed", adr );
		}
	 else
		remember_seek = adr;
		
}

static char lstr[] = " - line: ";


/* error message routines */

/* DANGER, NON-PORTABLE FUNCTION */
/*VARARGS*/
fatal(form,a,b,c,d,e,f,g)
char *form;
{
	fprintf( stderr, "Fatal Error%s%s\n", lbcopy[0] ? lstr : "", lbcopy );
	fprintf( stderr, form, a,b,c,d,e,f,g );
	fprintf( stderr, "\n" );
	exit(1);
}

/* DANGER, NON-PORTABLE FUNCTION */
/*VARARGS*/
nonfatal(form,a,b,c,d,e,f,g)
char *form;
{
	fprintf( stderr, "Error%s%s\n", lbcopy[0] ? lstr : "", lbcopy );
	fprintf( stderr, form, a,b,c,d,e,f,g );
	fprintf( stderr, "\n" );
}
/* DANGER, NON-PORTABLE FUNCTION */
/*VARARGS*/
semifatal(form,a,b,c,d,e,f,g)
char *form;
{
	fprintf( stderr, "Serious Error%s%s\n", lbcopy[0] ? lstr : "", lbcopy );
	fprintf( stderr, form, a,b,c,d,e,f,g );
	fprintf( stderr, "\n" );
	if( !error_ignore ) {
		fprintf(stderr, "Use +i option to DABE to ignore this error\n" );
		exit(1);
		}
	 else
		error_stat = 2;
}
/* DANGER, NON-PORTABLE FUNCTION */
/*VARARGS*/
warning(form,a,b,c,d,e,f,g)
char *form;
{
	fprintf( stderr, "Warning%s%s\n", lbcopy[0] ? lstr : "", lbcopy );
	fprintf( stderr, form, a,b,c,d,e,f,g );
	fprintf( stderr, "\n" );
}
/*VARARGS*/
verbose(level, form,a,b,c,d,e,f,g)
int level;
char *form;
{
	int i;
	if( is_verbose ) {
		for( i = 0; i < level; i++ )
			putc( '\t', stderr );
		fprintf( stderr, form, a,b,c,d,e,f,g );
		fprintf( stderr, "\n" );
		}
}

/* routine to alloc and copy a string */

char *
allocstring( str )
char *str;
{
	char *p, *malloc();

	p = malloc( strlen(str) + 1 );
	if( !p )
		fatal( "Out of memory!" );
	(void)strcpy( p, str );
	return p;
}

/* comeq - an alphanumeric string equality tester that ignores case */
#define CASEBIT 0x20
comeq( s1, s2 )
register char *s1;
register char *s2;
{
	while( *s1 )
		if( ( *s1++ | CASEBIT) != ( *s2++ | CASEBIT ) )
			return FALSE;
	/* zero in s1, if also end of s2 strings were equal */
	return *s2 == 0;
}

	/* convert a line number/checksum character to an index number */

lnconv( n )
int n;
{
	return n - (n > '9' ? (n > 'Z' ? 'a'-38 : 'A'-12) : '.');
}

	/* give a line number in character form, in static buffer */

static char lcbuf[4];

char *
lineconv( num )
long num;
{
	lcbuf[0] = safe_prints[ SFBYTE + num / (NUM_SAFE*NUM_SAFE) ];
	lcbuf[1] = safe_prints[ (num / NUM_SAFE) % NUM_SAFE ];
	lcbuf[2] = safe_prints[ num % NUM_SAFE ];
	lcbuf[3] = 0;
	return lcbuf;
}

givemsg( mstat, fname )
int mstat;
char *fname;
{
	lbcopy[0] = 0;
	if( mstat == NO_DATA )
		verbose( 2, "No valid data found in file '%s'", fname );
	else if ( mstat == UUDECODE )
		verbose( 1, "No data in file '%s' -- but try uudecode on it",
				fname );
	/* some day I should build in a uudecoder and just do it */
}

	/* has the given file been seen before? */
bool
seen_file( name )
char *name;
{
	struct fseen_list *osptr;

	for( osptr = seen_list; osptr; osptr = osptr->next_fs )
		if( strcmp( name, osptr->name_seen ) == 0 )
			return TRUE;
		
	return FALSE;
}

	/* check to see if we have all this file, and if so, close it off */

consider_closing()
{
	int i;

	if( !in_file || !(got_main_head && got_main_end && got_checksum) )
		return;			/* not enough header */
	if( has_blocking ) {
		if( total_blocks == 0 ) 
			return;		/* no block count yet */
		for( i = 0; i < total_blocks; i++ )
			if( !block_gotmap[i] )
				return;	/* missing block */
		}
	/* we seem to have a finished file */
	end_current_file();				
}

init_output()
{
	if( real_filename && same_os ) {
		out_desc = openout( real_filename );
		verbose( 1, "File's True Name: '%s'", real_filename );
		}
	 else {
		if( cur_filename ) {
			out_desc = openout( cur_filename );
			verbose( 1, "File Universal Name: '%s'", cur_filename );
			}
		 else
			fatal( "No name for output file found" );
		}
	if( remember_seek != 0 ) {
		seekto( remember_seek );
		remember_seek = 0;
		}
}

/* Handle the start of a block, possibly initializing the file */

startblock(args)
char *args;
{

	char tuname[MAX_UNAME+1];	/* universal name of block */
	long seekadr; 			/* seek address to start block */
	int early_ver;			/* earliest decoder that understands */
	extern bool seen_file();

	if( in_block )
		fatal( "Already in block %d", current_block );
	sscanf( args, "%d,%ld,%u,%14s", &current_block, &seekadr, 
					&early_ver, tuname );

	if( current_block >= MAX_BLOCKS )
		fatal( "Illegal block number %d", current_block );

	/* if not in a file, or in a different file,
	   start a new file for this block */

	if( !in_file || (cur_filename && strcmp(tuname,cur_filename)!=0)){
		if( seen_file( tuname ) ) {
			semifatal("Got block %d from previously encountered file '%s'",
				current_block,tuname );
			verbose( -1,"Skipping over it");
			skipping_block = TRUE;
			return;
			}
		 else
			begin_file(0,early_ver);
		}

	init_outfile( tuname, NULL );
	has_blocking = TRUE;
	/* set line we next expect to see */
	expected_line = this_line + 1;

	if( block_gotmap[current_block] ) {
		semifatal( "Duplicate of block %d detected",
			current_block );
		verbose( -1, "Skipping over it" );
		skipping_block = TRUE;
		return;
		}
	 else
		verbose( 1, "Decoding block %d of file '%s' from input '%s'",
			current_block, tuname,
			cur_sourcefile );
	in_block = TRUE;
	block_gotmap[current_block] = TRUE;

	seekto( seekadr );
	block_checksum = 0;
	block_bytecount = 0;
	block_crc = 0xffffffffL;
	save_total_checksum = total_checksum;
	save_total_bytecount = total_bytecount;
}

/* Close off a block */

closeblock( arg )
char *arg;
{
	int ended_block;	/* what block we're ending */
	long checksum;		/* expected checksum for block */
	long bytecount;		/* expected bytecount for block */
	bool failed;		/* did the block fail */
	unsigned long blcrc;	/* block crc */


	failed = FALSE;
	sscanf( arg, "%d,%lu,%lu,%lu", &ended_block, &checksum, &bytecount,
					&blcrc);

	/* if we were skipping this block, just go on */
	no_numbers = FALSE;
	if( skipping_block ) {
		skipping_block = FALSE;
		return;
		}
		
	if( !in_block ) {
		semifatal( "Attempt to close unopened block %d", ended_block );
		return;
		}
	if( ended_block != current_block )
		fatal( "Attempt to close block %d while in block %d",
			ended_block, current_block );
	in_block = FALSE;
	if( bytecount != block_bytecount ) {
		semifatal( "Expected %ld bytes, got %ld in block %d",
				bytecount, block_bytecount, current_block );
		failed = TRUE;
		}
	else if( (block_checksum % CSUM_MOD) != checksum ) {
		semifatal( "Checksum mismatch on block %d %d",
				current_block, block_checksum % CSUM_MOD );
		failed = TRUE;
		}
	else if( blcrc != ~block_crc ) {
		semifatal( "Block CRC mismatch on block %d", current_block );
		failed = TRUE;
		}
	if( failed ) {
		block_gotmap[ended_block] = FALSE;
		total_checksum = save_total_checksum;
		total_bytecount = save_total_bytecount;
		}
}

code_head( codeline, len )
char *codeline;		/* the code header line */
int len;		/* length of code header line from codeline */
{
	int cline;	/* which line of code */
	int i;
	int setby, by;
	extern int sets_per_char;	/* number of chars in a set grouping,
					   and also the number of sets grouped
					   into the trailing 'sets' byte */
	int wprint;			/* which printable char */
	int grp;			/* size of set groupings */


	cline = print_map[ codeline[0] ];
	grp = sets_per_char+1;

	/* ABE1 code header lines have 41 bytes */
	if( (len != 33 + 32/sets_per_char) || cline < 0 || cline > 7)
		fatal( "Invalid code map line" );
	for( i = 0; i < 32/sets_per_char; i++ ) {
		setby = print_map[codeline[1+sets_per_char+i*grp]];
		for( by = sets_per_char-1; by >= 0; by-- ) {
			wprint = print_map[codeline[1+i*grp+by]];
			if( wprint < 0 || wprint > 128 )
				fatal( "Invalid char in print map" );
			whatbyte[setby%num_sets][wprint] =
					cline*32+i*sets_per_char+by;
			setby /= num_sets;
			}
		}
	/* mark we got this row of the coding */
	got_coding |= 1 << cline;
}

/* initialize the encoding style */

char *styles[] = {
"ABE1",
"ABE2",
"UUENCODE",
"TEXT",
0
};

/* special predefined mappings for 'TEXT' secondary set */

char tmap1[] = { 'G', 'H', 'r', 'n', 'E', '@', 0 };
char tmap2[] = { 7,    8, '\r', '\n', 27, '#', 0 };

init_style( style )
char *style;
{
	int i;
	int newstyle;

	for( i = 0; styles[i]; i++ )
		if( comeq( styles[i], style ) )
			break;
	if( styles[i] == NULL )
		fatal( "Unknown encoding style %s", style );
	/* if same style as before, just return */
	if( i == current_style )
		return;

	/* A new style.  Initialize the tables for it */
	/* This means setting up printable character map tables, including
	   shift characters, and the number of sets and mappable chars, plus
	   the number of sets per character in a code header line */

	current_style = i;
	switch( current_style ) {
		case ABE1:
			/* mark results of printables */
			for( i = 0; i < A1NPRINTS; i++ )
				print_map[ A1FPRINT+i ] = i;
			num_sets = A1NSETS;
			num_prints = A1NPRINTS;
			set_shifts( SHIFTX, "{|" );
			set_shifts( SHIFTXX, "!\"#$" );
			set_shifts( SHIFTXcX, "}~00" );
			sets_per_char = 4;
			break;
		case ABE2:
			num_sets = 4;
			set_shifts( SHIFTX, "+,-" );
			set_shifts( SHIFTXX, "\"#$%&'()*" );
			set_shifts( SHIFTXcX, ":;<=>?@_0" );
			set_prints( 0, safe_prints );
			sets_per_char = 2;
			break;
		case UUENCODE:
			got_coding = FULL_MAP;	/* no coding needed */
			for( i = 0; i < 64; i++ )
				print_map[ ' '+i ] = i;
			break;
		case TEXT:
			{
			/* special text mapping */
			num_sets = 2;
			sets_per_char = 4;
			got_coding = FULL_MAP;	/* no map needed */
			for( i = 0; i < 128; i++ ) {
				print_map[ i ] = i;
				whatbyte[0][i] = whatbyte[1][i] = i;
				}
			print_map[0] = -1;
			set_shifts( SHIFTX, "#" );
			/* define proper maps in set 1 */
			for( i = 0; tmap1[i]; i++ )
				whatbyte[1][tmap1[i]] = tmap2[i];
			break;
			}
		}
			
}

/* set the mappings of the general printable characters */

set_prints( base, str )
int base;
char *str;
{
	int i;
	for( i = 0; i < 128 && str[i]; i++ )
		print_map[str[i]] = i+base;
	num_prints = i+base;
}

/*
 * This routine stores the shifting characters into the printable
 * character map.  Shifting characters all start with a base which is
 * multiplied by 256, and get an index added to them which tells which
 * sort of shifting character it is.
 * The character '0' indicates that this shift is not defined.  Thus
 * '0' can never be a shifting character.
 */

set_shifts( mainshift, shchars )
int mainshift;		
char *shchars;
{
	int i;
	char c;

	for( i = 0; c = shchars[i]; i++ )
		if( c != '0' )
			print_map[c] = (mainshift << 8) + i;
}

#define UUD(x) ((x-' ')&0x3f)

/* do uudecode on a line */

/* This is far from the best uudecoder in the world, it's just here to
 * support files with the old format in them.
 */

uudecode( line, len )
char *line;
int len;
{
	int blen;			/* number of bytes to put out */
	char *p;			/* pointer into line */
	int i;
	
	if( *line >= 'a' )
		return;		/* control line of some sort */
	blen = UUD( *line );
	if( blen <= 0 || (blen+2)/3 != (len-1)/4 )  /* null or invalid line */
		return;
	line++;			/* point to start of decode area */

	/* no need to space pad, as we use grave instead of space */

	/* fix the possible bitnet munge */
	for( i = 0; i < len; i++ )
		if( line[i] == '~' )
			line[i] = '^';

	p = line;

        while( blen > 0 ) {
		outbyte( UUD(p[0]) << 2 | UUD(p[1]) >> 4 );
		if (blen >= 2)
			outbyte( UUD(p[1]) << 4 | UUD(p[2]) >> 2 );
		if (blen >= 3)
			outbyte( UUD(p[2]) << 6 | UUD(p[3]) );
		p += 4;
		blen -= 3;
		}
}

do_options() {
	fprintf( stderr, "Usage:\n" );
	fprintf( stderr, "\tdabe [options] files\n" );
	fprintf( stderr, "or to read from stdin:\n" );
	fprintf( stderr, "\tdabe [options]\n" );
	fprintf( stderr, "\nDabe options:\n" );
	fprintf( stderr, "\t-v\tTurn off verbose mode -- errors only\n" );
	fprintf( stderr, "\t+i\tIgnore errors and keep going\n" );
	fprintf( stderr, "\t+s\tOutput file to standard output (use of -v advised)\n" );
}

/* Crc-32 builder, thanks to Rahul Dhesi */
#define CRC_32          0xedb88320L    /* CRC-32 polynomial */

/* calculates CRC of one item */
tcrc
onecrc (item)
int item;
{
   int i;
   tcrc accum = 0;
   item <<= 1;
   for (i = 8;  i > 0;  i--) {
      item >>= 1;
      if ((item ^ accum) & 0x0001)
         accum = (accum >> 1) ^ CRC_32;
      else
         accum >>= 1;
   }
   return (accum);
}

/* generates CRC table, calling onecrc() to make each term */
mkcrctab()
{
   int i;
   for (i = 0;  i < TABSIZE;  i++)
      crctab[i] = onecrc (i);
}
