/*
 * MSBPCT.C
 *
 * Howie Kaye -- Columbia University 3/11/86
 *
 * Sibling program to MSBMKB.C.  It is used to unpack BOO files, used for
 * encoding binary files into text, and back.  This program does the decoding.
 * It is meant to replace the program "MSBPCT.BAS", and runs approximately
 * 200 times faster.
 *
 * For documentation on BOO file format, see MSBMKB.C.
 * This program runs, as is, under Microsoft C on MSDOS and under UNIX.
 *
 * Modification history:
 *
 * 10-AUG-90, Robert Weiner (robert%progplus.uucp@uunet.uu.net)
 *   Look for [rw] comments
 *   Moved strlen() out of loop, makes MSBPCT quicker (I've seen 20% quicker)
 *   Added ferror() check for outputfile, else things like out-of-disk-space
 *   and write errors go unnoticed.  I think ferror() should be a reasonably
 *   machine independent call.
 *   There was a "final ^Z" outputted for MSDOS, removed it since we are
 *   extracting executables here and what we extract should be exactly what
 *   we put in.  If there was a final ^Z in the inputfile (ie. possibly a text
 *   file) it should have been encoded in the boo file to begin with and
 *   therefore would be automatically extracted.
 *   Added proper exit status to go along with the ferror() test.
 *   exit(0)=good, exit(1)=bad.  Added exit status variable 'ret'.
 *   Tested under MSC 5.1, MSDOS 3.10
 *
 * 1/26/92, Christian Hemsing. Added OS-9 support. Omit trailing nulls.
 *            after an idea from Charles Lasner.
 * 1/21/92, Frank da Cruz.  Fix #ifdef's, remove empty #else clause.
 * 6/29/90, Frank da Cruz.  Lowercase output filename for Unix.
 *
 * 1/27/89, Frank da Cruz.  Comment out #define MSDOS, since this is already
 *   predefined for Microsoft C and probably other MS-DOS C compilers.
 *   put it back if you have trouble.
 *   Also comment out #define CI86.  Uncomment if you're really using CI-86,
 *   or define CI86 on the compiler command line.
 *
 * 1/1/88, Frank da Cruz.  Remove default input file names -- too
 *   confusing.  Add exit(0) for good return code upon success. 
 *
 * 3/23/86 - Davide P. Cervone -- University of Rochester
 *   added AMIGA and VAX11C support
 *
 * 3/24/86 - Martin Knoblauch -- TH-Darmstadt              (MK001)
 *   test if 1. line of inputfile is delimited by "\r\n" instead of "\n"
 *
 * 5/5/86 - John Matthews, U of Delaware.
 *   Explicitly close the files.
 *
 * 5/8/86 - L. John Junod, DTNSRDC.
 *   Adapt for Computer Innovations MS-DOS CI-86 Compiler
 *   Improve too many args error message
 *
 */
#include <stdio.h>

#ifdef AMIGA
#include <fcntl.h>
#endif /* AMIGA */

#ifdef MSDOS 
#ifndef CI86
#include <fcntl.h>
#endif /* CI86 */
#else
#ifdef vax11c
#include <file.h>
#else
#ifndef OSK
#include <sys/file.h>
#endif /* not OSK */
#endif /* vax11c */
#endif /* MSDOS */

#define fixchr(x) ((x) -'0')
#define MYRPTQ		'~'     /* Repeat count prefix */

yes_or_no_p(arg) char *arg; {
    int c,x;
    while (1) {
	printf("%s",arg);
	c = getchar();
	if (c == '\n') continue;
	while ((x = getchar()) != '\n')
	  if (x == EOF) return(0);
	if ((c == 'Y') || (c == 'y')) return(1);
	if ((c == 'N') || (c == 'n') || (c == EOF)) return(0);
	printf("Please answer 'Y' or 'N'\n");
    }
}

main(argc,argv) char **argv; {
    char *infile = "";			/* input file name, with default */
    char outfile[100];			/* output file name */
    FILE *ifp, *ofp;			/* i/o files */
    char inline[100];
    int f, i, n;
    int ret=0;				/* [rw] exit status assumed ok */
    char last,before_last;  /* two byte buffer to omit trailing nulls */
	char first=1;
	char null_cnt=0;        /* #of trailing nulls */

    if (argc > 2) {			/* check for too many args */
	printf("Too many args. Usage: msbpct input-boo-file-name\n");
	exit(1);
    }
    if (argc > 1) {			/* check for input file */
	infile = argv[1];
    } else if (argc < 2) {
	printf("Usage: msbpct input-boo-file-name\n");
	exit(1);
    }
    if ((ifp = fopen(infile,"r")) == NULL) { /* open input file */
	printf("%s not found.\n",infile); /* failure? */
	exit(1);
    }

    fgets(outfile,100,ifp);		/* get output file name */
    n = strlen(outfile);
    if ((outfile[n-2] == '\r')|		/* MK001 */
	(outfile[n-2] == '\n')) {
	outfile[n-2] = '\0';
    } else {
	outfile[n-1] = '\0';
    }

#ifndef MSDOS
#ifndef vms
    /* For UNIX and OSK, convert name to lowercase. */
    for (i = 0; i < n; i++) {
	if (outfile[i] >= 'A' && outfile[i] <= 'Z') outfile[i] += 32;
    }
#endif /* vms */
#endif /* MSDOS */

    if ((ofp = fopen(outfile,"r")) != NULL) {
	char msg[100];
	sprintf(msg,
		"output file '%s' already exists.  continue (y/n)? ",outfile);
	if (!yes_or_no_p(msg)) {
	    printf("ok.	 bye\n");
	    exit(0);
	} else {
	    fclose(ofp);
	}
    }

#ifndef MSDOS
#ifndef O_BINARY
#define O_BINARY 0
#endif /* O_BINARY */
#endif /* MSDOS */

#ifdef AMIGA
    if ((ofp = fopen(outfile,"w")) == NULL) {
	printf("could not open %s\n",outfile); /* failure */
	exit(0);
    }
#else
#ifdef CI86
    if((ofp = fopen(outfile,"wb")) == NULL){
	printf("could not open %s\n",outfile); /* failure */
	exit(0);
    }
#else
#ifdef OSK
    if ((ofp = fopen(outfile,"w")) == NULL) { /* open it */
#else
    f = open(outfile,O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,0x1ff);
    if ((ofp = fdopen(f,"w")) == NULL) { /* open it */
#endif
	printf("failure to open %s\n",outfile); /* failure? */
	exit(1);
  }
#endif /* CI86 */
#endif /* AMIGA */

    printf("%s ==> %s\n",infile,outfile); /* announce our intentions */

    while(fgets(inline,100,ifp) != NULL) { /* till EOF */
	int index=0,outindex=0,len;	/* [rw] added len */
	len=strlen(inline);		/* [rw] moved out of loop below */
	while (index < len && inline[index] != '\n' &&
	       inline[index] != '\r') {	/* end of line? */
	    if (inline[index] == MYRPTQ) { /* null compress char... */
		int rptcnt;
		int i;

		index++;
		rptcnt = fixchr(inline[index]); /* get repeat count */
		index++;
		if (rptcnt == 0) {	/* additional nulls to be omitted */
		    null_cnt = null_cnt +1;
		    continue;
		}
		if (!first) {
		    putc(before_last,ofp);
		    putc(last,ofp);
		    before_last = '\0';
		    last = '\0';
		} else {
		    first = 0;
		    before_last = '\0';
		    last = '\0';
		}
		rptcnt = rptcnt - 2;
		for (i=0; i<rptcnt; i++) /* output rest of the nulls */
		  putc('\0',ofp);
	    } else {			/* a quad to decode... */
		int a, b, c ,d;

		a = fixchr(inline[index]);
		index++;
		b = fixchr(inline[index]);
		index++;
		c = fixchr(inline[index]);
		index++;
		d = fixchr(inline[index]);
		index++;

		if (!first) {
		    putc(before_last,ofp);
		    putc(last,ofp);
		    putc(((a*4)+(b/16)) & 255,ofp); /* output the bytes */
		    before_last = ((b*16)+(c/4)) & 255;
		    last = ((c*64)+d) & 255;
		} else {
		    first = 0;
		    putc(((a*4)+(b/16)) & 255,ofp); /* output the bytes */
		    before_last = ((b*16)+(c/4)) & 255;
		    last = ((c*64)+d) & 255;
		}
	    }
	}
	if ( ferror(ofp) )		/* [rw] must check for errors here! */
	  break;
    }

    if (null_cnt == 0) {
	printf("There might be one additional null at eof, because\n");
	printf("the BOO file had been encoded with an old encoder.\n");
	putc(before_last,ofp);
    } else if (null_cnt == 1) {
	putc(before_last,ofp);
    } else {} /* nothing to do here; just don't output the last two nulls */
#ifdef AMIGA
    putc('\032',ofp);			/* final ^Z for the Amiga */
#endif /* AMIGA */

    if ( ferror(ofp) ) {		/* [rw] must check for errors */
	printf("error writing output file %s\n",outfile); /* failure */
	ret = 1 ;			/* [rw] exit status is now bad */
    }
    fclose(ofp);			/* Close the files */
    fclose(ifp);
    exit(ret);				/* [rw] exit with success/failed */
}					/* End of program */
