/*  Dump files, similar to OD, but in a different format
 *  Files are displayed sixteen bytes to a line, offset from the beginning
 *  of the file on the right, hex values in the middle,
 *  ascii values on the right.
 *  -c option displays char value above hex value
 *  13 May 1985 David Elins
 */

/* modifications
 * 3-28-86  uses different formats depending on how bytes are ordered
 *      on the machine
 *      little-endian (e.g. vax)
 *      hex ..... hex addr ascii
 *      big endian (e.g. 68000)
 *      addr hex ascii
 *
 *      idea came from an article by Mike Higgins
 *      in "Computer Language" April, 1986
 *
 * 3-30-86  -x option reverses natural display format
 *
 * 4-14-86  Amiga port for both Lattice and Manx (under 32 bit)
 *
 *	screen width needs to be a define
 *	fputs not documented to return valid value except on error
 *	printf return value cannot be relied on under Lattice
 *	modify isprint semantics to limit to "normal" ascii
 *
 */
#include <stdio.h>
#include <ctype.h>
extern char *strrchr();
extern void exit();
#define EQS(str1,str2) (strcmp((str1),(str2))==0)

/*
 *  On the Amiga, the normal CLI window is only 77 characters wide.
 *  I decided to trim the line length by recovering 3 characters from
 *  the offset field.  Fred Fish   14-Apr-86
 */

#ifdef AMIGA
#define OFFSETFMT "%05lx"
#define OFFSETSIZ (5)		/* Should match length in OFFSETFMT */
#define WIDTH (77)		/* Screen width in char positions */
#define BLANKS "     "
#define ISPRINT(x) (((x) < 0200) && isprint(x))	/* Limit to 000-177 */
extern int Enable_Abort;
#else
#define OFFSETFMT "%08lx"
#define OFFSETSIZ (8)
#define WIDTH (80)
#define BLANKS "        "
#define ISPRINT(x) isprint(x)
#endif

static int showchar=0;
static int manyfile;

main(argc, argv)
int argc;
char *argv[];
{
#ifdef MSDOS
    char *pgmname="hd"; /* Microsoft C may not support argv[0] */
#else
    extern char *basename(); /* pathnames allowed on commands */
    char *pgmname=basename(argv[0]);
#endif
    int lowfirst(),highfirst(); /* dump formatting routines */
    static int reverse = 0; /* reverse natural display format */
    register char *cp, c;

#ifdef AMIGA
    Enable_Abort = 1;	/* Stupid Lattice default is 0 */
#endif
    /* parse options */
    for (; argc > 1 && (*argv[1] == '-'); argv++, argc--) {
        cp = &argv[1][1];   /* - alone is standard input */
        while ((c = *cp++) && argc) {
            switch (c) {
                case 'c':   /* like od -c option */
                    showchar = 1;
                    break;
                case 'x':   /* reverse natural display format */
                    reverse = 1;
                    break;
                default:
                    argc=0; /* modify argument to indicate error */
                    break;
            }
        }
    }

    if (argc < 1) {
        (void)fprintf(stderr,"Usage: %s [-cx] [files]\n", pgmname);
        exit(1);
    }

    manyfile = (argc > 2);              /* more than one file to display? */
    /*  loop through filename arguments
        if reverse == 1, exclusive or'ing with it
        will reverse the return value of bytorder()
     */
    if (bytorder() ^ reverse)   /* display in natural or reversed order */
        exit(filter(argc,argv,lowfirst));
    else
        exit(filter(argc,argv,highfirst));
}

/* format and display contents of stdin */
/* low addr byte of multibyte values is MOST significant */
static int
highfirst(filename)     /* format and display contents of stdin */
char *filename;
{
    unsigned char   /* is unsigned char portable ? */
        buf[16],    /* portion of file read */
        *bufptr,    /* pointer into that portion */
        ascstr[17], /* ascii equivalents of digits */
        *pascstr;   /* pointer into above string */
    int ctr,        /* general counter */
        col,        /* current column printed */
        rlen;       /* length read */
    long offset=0L; /* offset from beginning of file */
    static char sep[]="::::::::::::::"; /* separates filenames */

    if (manyfile)
        (void)printf("%s\n%s\n%s\n", sep, filename, sep);

    /* read a display line's worth at a time, format and display it */
    while ((rlen = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) {

	col = 1;
        bufptr = buf;   /* point to beginning of bytes read */

        if (showchar) { /* print equivalent characters above hex values? */
            (void)fputs(BLANKS, stdout);    /* for offset into file; */
            for (ctr = 0; ctr < rlen; ctr++) {
                if (ctr % 4 == 0)
                    (void)fputs(" ",stdout);    /* separate each four bytes */
                (void)fputs(" ",stdout);
                if (*bufptr == '\b')
                    (void)fputs("\\b",stdout);
                else if (*bufptr == '\t')
                    (void)fputs("\\t",stdout);
                else if (*bufptr == '\n')
                    (void)fputs("\\n",stdout);
                else if (*bufptr == '\f')
                    (void)fputs("\\f",stdout);
                else if (*bufptr == '\r')
                    (void)fputs("\\r",stdout);
                else if (ISPRINT(*bufptr))
                    (void)printf(" %c", *bufptr);
                else
                    (void)fputs("  ",stdout);
                bufptr++;
            }
            (void)putc('\n',stdout);
        }
                
        bufptr = buf;   /* point to beginning of bytes read */
        pascstr = ascstr; /* point to beginning of ascii equivalents */

        (void) printf(OFFSETFMT, offset); /* count and print offset into file; */
	col += OFFSETSIZ;
        for (ctr = 0; ctr < rlen; ctr++) {
            if (ctr % 4 == 0) {
                col++;		/* fputs value defined only on error */
		(void)fputs(" ",stdout);   /* separate each four bytes */
	    }
            *pascstr++ = ISPRINT(*bufptr) ? *bufptr : '.';  /* save ascii val */
            (void) printf(" %02x", *bufptr++);  /* display hex value of byte */
	    col += 3;
        }
        *pascstr = '\0';    /* terminate ascii string */
	do {
	    (void)fputs(" ",stdout);	/* fputs value defined only on error */
	} while (col++ < (WIDTH-18));		/* align ascii values */
        (void)printf("|%s|\n",ascstr);              /* display ascii values */
        offset += rlen;                     /* bump offset into file */;
        }
    return (1);
}

/* format and display contents of stdin */
/* low addr byte of multibyte values is LEAST significant */
static int
lowfirst(filename)      /* format and display contents of stdin */
char *filename;
{
    unsigned char   /* is unsigned char portable ? */
        buf[16],    /* portion of file read */
        *bufptr,    /* pointer into that portion */
        *rbufptr,   /* backward pointer into that portion */
        ascstr[17], /* ascii equivalents of digits */
        *pascstr;   /* pointer into above string */
    int ctr,        /* general counter */
        col,        /* current column printed */
        rlen;       /* length read */
    long offset=0L; /* offset from beginning of file */
    static char sep[]="::::::::::::::"; /* separates filenames */

    if (manyfile)
        (void)printf("%s\n%s\n%s\n", sep, filename, sep);

    /* read a display line's worth at a time, format and display it */
    while ((rlen = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) {

	col = 1;    /* current printed column */
        rbufptr = buf + rlen - 1;   /* point to end of bytes read */

        if (showchar) { /* print equivalent characters above hex values? */
            for (ctr = 0; ctr < sizeof(buf); ctr++) {
                if ((ctr + rlen) < sizeof(buf))
                    (void)fputs("  ",stdout);
                else if (*rbufptr == '\b')
                    (void)fputs("\\b",stdout);
                else if (*rbufptr == '\t')
                    (void)fputs("\\t",stdout);
                else if (*rbufptr == '\n')
                    (void)fputs("\\n",stdout);
                else if (*rbufptr == '\f')
                    (void)fputs("\\f",stdout);
                else if (*rbufptr == '\r')
                    (void)fputs("\\r",stdout);
                else if (ISPRINT(*rbufptr))
                    (void)printf(" %c", *rbufptr);
                else
                    (void)fputs("  ",stdout);
                if (ctr % 4 == 3)
                    (void)fputs(" ",stdout);    /* separate each four bytes */
                (void)fputs(" ",stdout);
                if ((ctr + rlen) >= sizeof(buf))
                    rbufptr--;
            }
            (void)putc('\n',stdout);
        }
                
        bufptr = buf;   /* point to beginning of bytes read */
        rbufptr = buf + rlen - 1;   /* point to end of bytes read */
        pascstr = ascstr; /* point to beginning of ascii equivalents */

        for (ctr = 0; ctr < sizeof(buf); ctr++) {
            if ((ctr + rlen) < sizeof(buf)) { /* display hex value of byte */
		(void) fputs("   ",stdout); /* no value */
            } else {
                (void) printf("%02x ", *rbufptr);
	    }
	    col += 3;
            if (ctr % 4 == 3) {
                col++;
		(void) fputs(" ",stdout);   /* separate each four bytes */
	    }
            if ((ctr + rlen) >= sizeof(buf)) {  /* save ascii value */
                *pascstr++ = ISPRINT(*bufptr) ? *bufptr : '.';
                bufptr++;   /* advance, */
                rbufptr--;  /* decrement pointers */
            }
        }
        *pascstr = '\0';    /* terminate ascii string */
        (void) printf(OFFSETFMT,offset); /* count and print offset into file; */
	col += OFFSETSIZ;
	do {
	    (void)fputs(" ",stdout);	/* fputs value defined only on error */
	} while (col++ < (WIDTH-18));		/* align ascii values */
        (void)printf("|%s|\n",ascstr);              /* display ascii values */
        offset += rlen;                     /* bump offset into file */;
        }
    return (1);
}

/* is low addr byte of multi-byte values the least significant ? */
static int
bytorder()
{
    static union {  /* used to determine byte order */
        int x;
        char c[2];
    }   test;

    test.x=1;   /* find out how bytes are ordered */
    return(test.c[0] == test.x);
}

/*
 *      perform the basename(1) function from a C program
 */
static
char *
basename(name)  /* return pointer to filename portion of name */
char name[];
{
    char *cp=strrchr(name,'/');

    return(cp==NULL ? name : (cp+1));
}

/*
 *  loop through filenames on a command line
 *  reopening each as standard input
 */
extern int errno; /* system error variable */
extern void perror();

static
int
filter(argc,argv,process)
int argc;       /* how many command line arguments */
char *argv[];   /* command line arguments */
int (*process)();/* function that processes each file, called at least once */
                 /* the only argument is the filename (NULL if orig stdin) */
{
#ifdef unix
    register int fd = dup(0);   /* initial standard input */
#endif
    register int inorig;        /* 0=from stdin, else from file */
    register int firstime;      /* make sure *process is called once */
    register int retval=0;      /* return value */

    for (firstime = 1; firstime || (argc > 1); argc--,argv++,firstime=0) {
        /* arguments are filenames to filter, "-" means standard input */

        inorig = ((argc == 1) || EQS(argv[1],"-"));
        if (inorig) {
#ifdef unix
            (void)close(0); /* reset to initial standard input */
            (void)dup(fd);
#endif
        }
        /* set filename as standard input */
        else if (freopen(argv[1],"r",stdin) == NULL) {
            char string[48];
            retval = errno; /* return if error */
            (void)sprintf(string,"cannot open %s", argv[1]);
            perror(string);
            break;
        }

        (*process)(inorig?(char *)NULL:argv[1]);    /* process this file */
    }

    if (!inorig) {  /* leave stdin the same file as at entry */
#ifdef unix
        (void)close(0);
        (void)dup(fd);
#endif
    }

    return(retval);
}

#ifdef MANX

char *strrchr (s, c)
char *s;
char c;
{
	register char *scan;

	scan = s;
	while (*scan++ != '\000') {;}
	while (--scan > s && *scan != c) {;}
	return (*scan == c ? scan : NULL);
}

#endif	/* MANX */

#ifdef AMIGA	/* Neither Lattice or Manx has perror */

void perror (sp)
char *sp;
{
	if (sp && *sp) {
		fprintf (stderr, "%s: ", sp);
	}
	fprintf (stderr, "<unknown error>\n");
}

#endif /* AMIGA */

	
