/* MSNKBOO.C
 *
 * This program takes a file and encodes it into printable characters.
 * These printable files can then be decoded by the programs MSPCBOOT.BAS
 * or MSPCTRAN.BAS as the need may be.  The file is encoded by taking
 * three consecutive eight bit bytes and dividing them into four six bit
 * bytes.  An ASCII zero was then added to the resulting four characters.
 * to make them all printable ASCII characters in the range of the
 * character zero to the character underscore.  In order to reduce the
 * size of the file null repeat count was used.  The null repeat count
 * compresses up to 78 consecutive nulls into only two characters.  This
 * is done by using the character tilde (~) as an indication that a group
 * of repetitive nulls has occured.  The character following the tilde is
 * number of nulls in the group.  The number is also converted in to a
 * printable character by adding an ASCII zero.  The highest number of
 * nulls is therefore the highest printable character tilde.  This is
 * equal to tilde minus zero nulls or 78 nulls.  Because of the three
 * byte to four byte encoding the repeat counting can only start with
 * the first character of a three byte triplet.
 *
 * This C program was written specifically for the DEC-20 and as such
 * will not easily be transported to another system.  The main problem
 * lies in the file I/O routines.  It is necessary to make sure that
 * untranslated eight bit bytes are input from the input file.  The 
 * main change would be to make the OPEN statement reflect this for 
 * your particular system and brand of UNIX and C.  The rest of the
 * program should be transportable with little or no problems.
 *
 */


#include <stdio.h>		/* Standard UNIX i/o definitions */
#include <file.h>

/* Symbol Definitions */

#define MAXPACK		80	/* Maximum packet size */

#define MYRPTQ		'~'	/* Repeat count prefix I will use */
#define DATALEN		78	/* Length of data buffer */

#define TRUE		-1	/* Boolean constants */
#define FALSE		0

/* Macros */

#define tochar(ch)  ((ch) + '0')

/* Global Variables */

int	size,			/* Size of present data */
	maxsize,		/* Max size for data field */
        nc,			/* Number of input chars */
        oc,			/* Number of output chars */
	fd,			/* File pointer of file to read/write */
	ofd,
	rpt,			/* repeat count */
	rptq,			/* repeat quote */
	rptflg,			/* repeat processing flag */
	eoflag,			/* Set when file is empty. */
	otot;			/* What char number we are processing. */

char	t,			/* Current character */
	one,
	two,
	three,
	*filnam,		/* Current file name */
        *ofile,
	packet[MAXPACK];	/* Packet buffer */

main(argc,argv)				/* Main program */
int argc;				/* Command line argument count */
char **argv;				/* Pointers to args */
{
    char sfile();		        /* Send file routine & ret code */
    if (--argc != 2) usage();		/* Make sure there's a command line. */
    rptq = MYRPTQ;			/* Repeat Quote */
    rptflg = TRUE;			/* Repeat Count Processing Flag */

    filnam = *++argv;			/* Get file to send */
    ofile = *++argv;			/* Output file to create */
    sfile();
    printf("Done, in: %d, out: %d, efficiency: %.2f%%\n",nc,oc,(100.0*nc)/oc);
}

/*
   S F I L E - Send a whole file
*/

char sfile()				/* Send a file */
{
    char *i;

    fd = open(filnam,FATT_RDONLY|FATT_BINARY|FATT_DEFSIZE);
    if (fd < 0)				/* Report any errors */
    {
        printf("\n?Error opening file \"%s\"\n",filnam);
        exit(1);
    }

    ofd = open(ofile,FATT_WRONLY|FATT_CREATE|FATT_BINARY);
    if (ofd < 0)
    {
        printf("\n?error opening file \"%s\"\n",ofile);
        exit(1);
    }

    oc = strlen(filnam);		/* Get the string length. */
    for (i=filnam; *i != '\0'; i++)	/* Uppercase the file name. */
	if (*i >= 'a' && *i <= 'z') *i ^= 040;
    write(ofd,filnam,oc);	 	/* Write the file name in the file. */
    write(ofd,"\r\n",2);

    maxsize = DATALEN - 5;
    rpt = 0;				/* Zero the repeat count. */
    oc = nc = 0;			/* Output & input character counts. */
    otot = 1;				/* Start with first char of triplet. */
    while (getbuf() > 0)		/* While not EOF, get a packet. */
    {
	while (size < DATALEN - 1) packet[size++] = ' ';
	packet[size++] = '\r';		/* Explicit CRLF. */
	packet[size++] = '\n';
	packet[size] = '\0';
        oc += size;			/* Count output size. */
        write(ofd,packet,size);		/* Write the packet to the file. */
        printf("%d: %s",size,packet);	/* Print on the screen for testing. */
    }
}
/*
   G E T B U F -- Do one packet.
*/

getbuf()				/* Fill one packet buffer. */
{
    if (eoflag != 0) return(-1);	/* If at the end of file, stop. */
    size = 0;
    while((t = getch()) >= 0)		/* t == -1 means EOF. */
    {
	nc++;				/* Count the character. */
        process(t);			/* Process the character. */
	if (size >= maxsize)		/* If the packet is full, */
	{
            packet[size] = '\0';	/*  terminate the string. */
            return(size);
        }
    }
    eoflag = -1;			/* Say we hit the end of the file. */
    process(0);				/* Clean out any remaining chars. */
    process(0);
    process(' ');
    packet[size] = '\0';		/* Return any partial final buffer. */
    return(size);
}

/* P R O C E S S -- Do one character. */

process(a)
char a;
{
    if (otot == 1)			/* Is this the first of three chars? */
    {
        if (a == 0)			/* Is it a null? */
        {
	    if (++rpt < 78)		/* Below max nulls, just count. */
                return;
	    else if (rpt == 78)		/* Reached max number, must output. */
	    {
                packet[size++] = rptq;	/* Put in null repeat char and */
                packet[size++] = tochar(rpt); /* number of nulls. */
                packet[size] = '\0';
                rpt = 0;
	        return;
	    }
        }
	else
        {
	    if (rpt == 1)		/* Just one null? */
	    {
		one = 0;		/* Say the first char was a null. */
		two = a;		/* This char is the second one. */
		otot = 3;		/* Look for the third char. */
		rpt = 0;		/* Restart null count. */
		return;
	    }
	    if (rpt > 1)		/* Some number of nulls? */
	    {
                packet[size++] = rptq;	/* Insert the repeat prefix */
                packet[size++] = tochar(rpt); /* and count. */
                packet[size] = '\0';
                rpt = 0;		/* Reset repeat counter. */
 	    }
	    one = a;			/* Set first character. */
	    otot = 2;			/* Say we are at the second char. */
	}
    }
    else if (otot == 2)
    {
	two = a;			/* Set second character. */
	otot = 3;			/* Say we are at the third char. */
    }
    else
    {
        three = a;
	otot = 1;			/* Start over at one. */
	pack(one,two,three);		/* Pack in the three characters. */
    }
}

/* This routine does the actual three character to four character encoding.
 * The concept is relatively straight forward.  The first output character
 * consists of the first (high order or most significant) six bits of the
 * first input character.  The second output character is made from the
 * remaining two low order bits of the first input character and the first
 * four high order bits of the second input character.  The third output
 * character is built from the last four low order bits of the second input
 * character and the two high order bits of the third input character.  The
 * fourth and last output character consists of the six low order bit of
 * the third input character.  In this way the three eight bit input char-
 * acters (for a total of 24 bits) are divided into four six bit output
 * characters (also for a total of 24 bits).  In order to make the four
 * output characters printable an ASCII zero is then added to each of them.
 *
 */

pack(x,y,z)
char x,y,z;
{
    packet[size++] = tochar((x >> 2) & 077);
    packet[size++] = tochar(((x & 003) << 4) | ((y >> 4) & 017));
    packet[size++] = tochar(((y & 017) << 2) | ((z >> 6) & 003));
    packet[size++] = tochar(z & 077);
    packet[size] = '\0';
}

getch()					/* Get next (or pushed) char. */
{
   char a;

   return((read(fd,&a,1) > 0) ? a : -1); /* (or -1 if EOF) */
}

usage()					/* Give message if user makes */
{					/* a mistake in the command. */
    fprintf(stderr,"usage: msmkboo inputfile outputfile\n");
    exit(1);
}
