[comp.binaries.apple2] C NuFX archive reader

labc-3dc@e260-3f.berkeley.edu (Andy McFadden) (05/13/89)

Well, I got bored one day, and decided to write a NuFX archive reader
in C.  This was intended to be used by people using a UNIX system who
want to see what is in a ShrinkIt archive, but the code should be
portable to Aztec or APW C.

This was totally my idea; no credit or blame should fall on Andy Nicholas.
Any bugs/glitches are my fault, although (as it states in the README) I'm
not going to be held responsible for them.

I'm posting this in shar format; if you aren't using UNIX, you will have
to do the separation by hand (sorry!)

This is a shell archive.  Cut off everything above the dotted line, and
then feed it to /bin/sh with either "sh filename" or "sh < filename"
-------------------------- cut here -------------------------------------
echo x - README
cat >README <<'!E!O!F!'
NuFX archive reader, version 1.0

First and foremost:
This program is totally in the public domain.  I reserve no rights to
the program; do with it what you will.  If you make lots of money by
modifying and selling my code, great.  Stick my name in a comment somewhere
and I'll be more than happy.

This being the case, I also give up any responsibility to update and maintain
this program.

In its present state, it will either produce a ShrinkIt-like listing (the
default), or a detailed breakdown of every known facet of the archive (run
it with the -l option).

When ShrinkIt packs a file, it always uses records with one thread (of type
data_thread).  I have tried to make the code flexible, but since I can't
generate proper archives with multiple threads and resource forks, I can't
honestly say that it will work when presented with such an archive.

I have tried to make this easy to extend for new advances.  While the
option parsing remains somewhat primitive, the program should be fairly solid.
I didn't feel like dealing with lseek as it relates to FILEs, so I just used
getc the required number of times... hurts performance somewhat, but it's
portable.

SYSTEM DEPENDENCIES
When compiling this on a Sun 3/50, I noticed something unfortunate: the byte
ordering for the //gs and the Sun are backward.  The header file "nuread.h"
currently contains a definition of "HILO"; when this is defined, the code
generated while reverse the byte ordering automatically.  You should clear
this if you start getting really weird results from valid archives (or if
you are sure that your machine works in LO->HI byte order).

There are definitions for one byte, two byte, and four byte variable types;
my compiler uses char, short, and int.  If these are different for your
compiler, be sure to change the typedefs.

Good luck...

					- Andy

-- 
fadden@cory.berkeley.edu (Andy McFadden)
...!ucbvax!cory!fadden
labc-3dc@widow.berkeley.edu (expiring soon)
!E!O!F!
echo x - Makefile
cat >Makefile <<'!E!O!F!'
# Makefile for NuView
# ATM  13-May-89
CFLAGS=
LIBS=
CC=cc

nuview: nuread.o nuview.o nuread.h nufx.h nustr.h
	$(CC) $(CFLAGS) nuread.o nuview.o -o nuview $(LIBS)

!E!O!F!
echo x - nufx.h
cat >nufx.h <<'!E!O!F!'
/*
 * nufx.h - struct definitions for NuFX reader
 *
 * Public domain - fadden@cory.berkeley.edu (Andy McFadden)
 */

/* SYSTEM DEPENDENCIES */
typedef unsigned char onebyt;
typedef unsigned short twobyt;
typedef unsigned int fourbyt;
#define HILO

#define UNKNOWNSTR "[ unknown ]"

/* "NuFile" in alternating ASCII */
static onebyt MasterID[7] = { 0x4e, 0xf5, 0x46, 0xe9, 0x6c, 0xe5, 0x0 };

/* structure definitions */

/* Time structure */
typedef struct {
    onebyt second;
    onebyt minute;
    onebyt hour;
    onebyt year;
    onebyt day;
    onebyt month;
    onebyt filler;
    onebyt weekDay;
} Time;

/* master header block */
typedef struct {
    onebyt ID[6];
    twobyt master_crc;
    fourbyt total_records;
    Time arc_create_when;
    Time arc_mod_when;
    onebyt reserved[20];
} MHblock;

/* record header block */
typedef struct {
    onebyt ID[4];
    twobyt header_crc;
    twobyt attrib_count;
    twobyt version_number;
    fourbyt total_threads;
    twobyt file_sys_id;
    onebyt sparse;
    onebyt fssep;
    fourbyt access;
    fourbyt file_type;
    fourbyt extra_type;
    twobyt storage_type;
    Time create_when;
    Time mod_when;
    Time archive_when;
    /* future expansion here... */
} RHblock;

/* thread record */
typedef struct {
    twobyt thread_class;
    twobyt thread_format;
    twobyt thread_kind;
    twobyt reserved;
    fourbyt thread_eof;
    fourbyt comp_thread_eof;
} THblock;

!E!O!F!
echo x - nuread.h
cat >nuread.h <<'!E!O!F!'
/*
 * nuread.h - linked list structures used for holding NuFX header data
 * A linked list of Record headers, with linked lists of Threads attached
 *
 * Public domain - fadden@cory.berkeley.edu (Andy McFadden)
 */

#define MAXFILENAME	1024

/* thread nodes */
typedef struct TNode_s {
    THblock *THptr;  /* points to thread info */
    struct TNode_s *TNext;  /* points to next thread node */
} TNode;

/* record nodes */
typedef struct RNode_s {
    RHblock *RHptr;  /* points to the record header block */
    char *filename;
    TNode *TNodePtr;  /* points to first thread node */
    unsigned int unc_len;  /* total uncompressed length of all threads */
    unsigned int comp_len;  /* total compressed length of all threads */
    struct RNode_s *RNext;  /* points to next record node */
} RNode;

/* head of list */
typedef struct {
    MHblock *MHptr;  /* points to master header */
    RNode *RNodePtr;  /* points to first record node */
} ListHdr;

extern ListHdr *NuRead();
!E!O!F!
echo x - nustr.h
cat >nustr.h <<'!E!O!F!'
/*
 * nustr.h - string definitions for the various structure entries
 *
 * Public Domain - fadden@cory.berkeley.edu (Andy McFadden)
 */

/* weekDay values */
static char *WD[8] = { "[ null ]", "Sunday", "Monday", "Tuesday", "Wednesday",
		"Thursday", "Friday", "Saturday" };

/* month values */
static char *MO[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
		"Aug", "Sep", "Oct", "Nov", "Dec" };

/* thread_class */
#define TCn 4
static char *TC[TCn] = { "Message_thread", "Control_thread", "Data_thread",
		"Sparse_thread" };

#define TKn 3  /* max #of thread_kinds in a thread_class */
static char *TK[TCn][TKn] = {
		{ "ASCII text", "<undef>", "<undef>" },
		{ "Create directory", "<undef>", "<undef>" },
		{ "File data_fork", "Disk image", "File resource_fork" },
		{ "<undef>", "<undef>", "<undef>" } };

/* thread_format */
#define TFn 3
static char *TF[TFn] = { "Uncompressed", "SQueezed (SQ/USQ)",
		"Dynamic LZW (ShrinkIt)" };

/* quick thread_format */
#define QTFn 3
static char *QTF[QTFn] = { "unc", "squ", "shk" };

/* file_sys_id */
#define FIDn 12
static char *FID[FIDn] = { "Reserved ($00)", "ProDOS/SOS", "DOS 3.3", "DOS 3.2",
		"Apple II Pascal", "Macintosh (HFS)", "Macintosh (MFS)",
		"LISA file system", "Apple CP/M", "Reserved ($09)", "MS-DOS",
		"High-Sierra/ISO 9660" };

/* storage_type */
#define STn 14
static char *ST[STn] = { "Standard file", "Standard file", "Standard file",
		"Standard file", "-", "Extended file", "-", "-", "-", "-",
		"-", "-", "-", "Subdirectory" };

/* file type names */
static char *FT[256] = {
	"NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "CHR",
	"PIC", "BA3", "DA3", "WPD", "SOS", "$0D", "$0E", "DIR",
	"RPD", "RPI", "$12", "OUT", "$14", "RPT", "$16", "$17",
	"$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F",
	"$20", "$21", "$22", "$23", "$24", "$25", "$26", "$27",
	"$28", "$29", "$2A", "$2B", "$2C", "$2D", "$2E", "$2F",
	"$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37",
	"$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F",
	"$40", "$41", "$42", "$43", "$44", "$45", "$46", "$47",
	"$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F",
	"$50", "$51", "$52", "$53", "$54", "$55", "$56", "$57",
	"$58", "$59", "$5A", "$5B", "$5C", "$5D", "$5E", "$5F",
	"PRE", "$61", "$62", "$63", "$64", "$65", "$66", "$67",
	"$68", "$69", "$6A", "NIO", "$6C", "DVR", "$6E", "HDV",
	"$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77",
	"$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F",
	"$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87",
	"$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F",
	"$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97",
	"$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F",
	"WPF", "MAC", "HLP", "DAT", "$A4", "LEX", "$A6", "$A7",
	"$A8", "$A9", "$AA", "GSB", "ARC", "$AD", "$AE", "$AF",
	"SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "STR", "TSF",
	"NDA", "CDA", "TOL", "DRV", "$BC", "FST", "$BE", "DOC",
	"PNT", "SCR", "ANI", "$C3", "$C4", "$C5", "$C6", "$C7",
	"FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF",
	"$D0", "$D1", "$D2", "$D3", "$D4", "$D5", "$D6", "$D7",
	"$D8", "$D9", "$DA", "$DB", "$DC", "DDD", "$DE", "$DF",
	"LBR", "$E1", "ATI", "$E3", "$E4", "$E5", "$E6", "$E7",
	"$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "$EE", "PAS",
	"CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7",
	"$F8", "IMG", "INT", "IVR", "BAS", "VAR", "REL", "SYS" };

!E!O!F!
echo x - nuread.c
cat >nuread.c <<'!E!O!F!'
/*
 * nuread.c - read NuFX archives (header info only) into structures
 *
 * Public domain - fadden@cory.berkeley.edu (Andy McFadden)
 */

#include <stdio.h>
#include "nufx.h"
#include "nuread.h"

#define TRUE 1
#define FALSE 0

typedef int BOOLEAN;


/* swap two bytes */
void Swap(ptr, a, b)
onebyt *ptr;
onebyt a, b;
{
    register onebyt tmp;

    tmp = ptr[a], ptr[a] = ptr[b], ptr[b] = tmp;
}


/* seek forward in a FILE in a somewhat lame (but portable) manner */
int LameSeek(fi, dist)
FILE *fi;
unsigned int dist;
{
    int i;

    for (i = 0; i < dist; i++)
	getc(fi);
}


/* read thread header data, and skip data field */
TNode *ReadThreads(fi, RHptr, tu_ptr, tc_ptr)
FILE *fi;
RHblock *RHptr;
unsigned int *tu_ptr;  /* pointer to int holding len of uncomp. threads */
unsigned int *tc_ptr;  /* same for sum of comp_thread_eof */
{
    int i, size;
    BOOLEAN first;
    TNode *TNodePtr, *THeadPtr = (TNode *) NULL;
    THblock *THptr;

    first = TRUE;
    for (i = 0; i < RHptr->total_threads; i++) {
	if (first) {  /* create first block, or... */
	    TNodePtr = (TNode *) malloc(sizeof(TNode));
	    THeadPtr = TNodePtr;
	    first = FALSE;
	} else {  /* create next block and go on */
	    TNodePtr->TNext = (TNode *) malloc(sizeof(TNode));
	    TNodePtr = TNodePtr->TNext;
	}
	TNodePtr->TNext = (TNode *) NULL;

	TNodePtr->THptr = (THblock *) malloc(sizeof(THblock));
	THptr = TNodePtr->THptr;
	size = fread(THptr, 1, sizeof(THblock), fi);  /* should be 16 */
	if (size < 0) {
	    perror("ReadThread (THblock)");
	    exit (-1);
	}

#ifdef HILO
	Swap(THptr, 0, 1);
	Swap(THptr, 2, 3);
	Swap(THptr, 4, 5);
	Swap(THptr, 6, 7);
	Swap(THptr, 8, 11);  Swap(THptr, 9, 10);
	Swap(THptr, 12, 15);  Swap(THptr, 13, 14);
#endif HILO

	*tu_ptr += THptr->thread_eof;
	*tc_ptr += THptr->comp_thread_eof;
	LameSeek(fi, THptr->comp_thread_eof);
    }
    return (THeadPtr);
}


/*
 * Read header data from a NuFX archive into memory
 */

ListHdr *NuRead(filename)
char *filename;
{
    FILE *fi;
    char namebuf[MAXFILENAME];
    int size, i;
    unsigned int dist;
    BOOLEAN first;
    twobyt namelen;
    ListHdr *ListPtr;  /* List Header struct */
    MHblock *MHptr;  /* Master Header block */
    RNode *RNodePtr;  /* Record Node */
    RHblock *RHptr;  /* Record Header block */

    ListPtr = (ListHdr *) malloc(sizeof(ListHdr));  /* create head of list */
    ListPtr->MHptr = (MHblock *) malloc(sizeof(MHblock));  /* master block */

    if ((fi = fopen(filename, "r")) == (FILE *) NULL) {
	fprintf(stderr, "Unable to open archive\n");
	perror("NuRead");
	exit (-1);
    }

    MHptr = ListPtr->MHptr;  /* read in the master header */
    size = fread(MHptr, 1, sizeof(MHblock), fi);  /* should be 48 */

#ifdef HILO
    /* correct for machine-dependent byte ordering */
    Swap(MHptr, 6, 7);
    Swap(MHptr, 8, 11); Swap(MHptr, 9, 10);
#endif HILO

    if (strncmp(MHptr->ID, MasterID, 6)) {
	fprintf(stderr, "File '%s' is not a NuFX archive\n", filename);
	exit (-1);
    }

    first = TRUE;
    for (i = 0; i < MHptr->total_records; i++) {
	if (first) {  /* allocate first, or... */
	    ListPtr->RNodePtr = (RNode *) malloc(sizeof(RNode));
	    RNodePtr = ListPtr->RNodePtr;
	    first = FALSE;
	} else {  /* allocate next, and go on */
	    RNodePtr->RNext = (RNode *) malloc(sizeof(RNode));  /* next Rnode */
	    RNodePtr = RNodePtr->RNext;  /* move on to next record */
	}
	RNodePtr->RNext = (RNode *) NULL;

	RNodePtr->RHptr = (RHblock *) malloc(sizeof(RHblock)); /* alloc blk */
	RHptr = RNodePtr->RHptr;
	size = fread(RHptr, 1, sizeof(RHblock), fi);  /* get known stuff */
	if (size < 0) {
	    perror("NuRead (RHblock)");
	    exit (-1);
	}
#ifdef HILO
	Swap(RHptr, 4, 5);
	Swap(RHptr, 6, 7);
	Swap(RHptr, 8, 9);
	Swap(RHptr, 10, 13); Swap(RHptr, 11, 12);
	Swap(RHptr, 14, 15);
	Swap(RHptr, 16, 17);
	Swap(RHptr, 18, 21); Swap(RHptr, 19, 20);
	Swap(RHptr, 22, 25); Swap(RHptr, 23, 24);
	Swap(RHptr, 26, 29); Swap(RHptr, 27, 28);
	Swap(RHptr, 30, 31);
#endif HILO
	/* now, seek past remaining attributes to filename length */
	dist = RHptr->attrib_count - sizeof(RHblock) - 2;
	LameSeek(fi, dist);

	size = fread(&namelen, 2, 1, fi);  /* read filename len */
	if (size < 0) {
	    perror("NuRead (namelen)");
	    exit (-1);
	}
#ifdef HILO
	Swap(&namelen, 0, 1);
#endif HILO
	size = fread(namebuf, 1, namelen, fi);  /* read filename */
	RNodePtr->filename = (char *) malloc(namelen+1);
	bcopy(namebuf, RNodePtr->filename, namelen);  /* store filename */
	RNodePtr->filename[namelen] = '\0';

	RNodePtr->TNodePtr = ReadThreads(fi, RHptr, &RNodePtr->unc_len,
		&RNodePtr->comp_len);
    }

    if (fclose(fi) != 0) {
	perror("NuRead (close)");
	exit (-1);
    }
    return (ListPtr);
}

!E!O!F!
echo x - nuview.c
cat >nuview.c <<'!E!O!F!'
/*
 * nuview.c - prints the contents of a NuFX archive
 *
 * Public domain - fadden@cory.berkeley.edu (Andy McFadden)
 */

#include <stdio.h>
#include <strings.h>
#include "nufx.h"
#include "nuread.h"
#include "nustr.h"

typedef int BOOLEAN;
#define TRUE	1
#define FALSE	0


/*
 * Usage:  nuview -v			print version number
 *	   nuview archive-name		print archive like ShrinkIt would
 *	   nuview -l archive-name	print full archive info
 */
void Usage(argv0)
char *argv0;
{
    printf("Usage: %s [-v | -l] archive-name\n", argv0);
}


/* print date from Time structure */
char *PrintDate(tptr, brief)
Time *tptr;
BOOLEAN brief;
{
    static char buf[64], buf2[64];

    if ( (tptr->day > 31) || (tptr->month > 11) || (tptr->hour > 24) ||
	(tptr->minute > 59) ) {
	strcpy(buf, "   <invalid>   ");
	return (buf);
    }

    if (!brief && tptr->weekDay) {
	(void) sprintf(buf, "%s, ", WD[tptr->weekDay]);
    } else {
	buf[0] = '\0';
    }
    (void) sprintf(buf2, "%.2d-%s-%.2d %.2d:%.2d",
	tptr->day, MO[tptr->month], tptr->year,
	tptr->hour, tptr->minute);
    (void) strcat(buf, buf2);
    if (!brief) {
	(void) sprintf(buf2, ":%.2d", tptr->second);
	(void) strcat(buf, buf2);
    }
    return (buf);
}


/* Dump contents of the threads */
DumpThreads(RNodePtr)
RNode *RNodePtr;
{
    int i;
    int count = RNodePtr->RHptr->total_threads;
    static char ind[4] = "   ";  /* indentation */
    THblock *THptr;
    TNode *TNodePtr;

    TNodePtr = RNodePtr->TNodePtr;
    for (i = 0; i < count; i++) {
	if (TNodePtr == (TNode *) NULL) {
	    fprintf(stderr, "WARNING: fewer threads than expected\n");
	    return;
	}
	THptr = TNodePtr->THptr;

	printf("%s --> Information for thread %d\n", ind, i);
	printf("%s thread_class: %s\n", ind, THptr->thread_class < TCn ?
		TC[THptr->thread_class] : UNKNOWNSTR);
	printf("%s thread_format: %s\n", ind, THptr->thread_format < TFn ?
		TF[THptr->thread_format] : UNKNOWNSTR);
	printf("%s thread_kind: %s ($%.2X)\n", ind,
		(THptr->thread_kind < TKn && THptr->thread_class < TCn) ?
		TK[THptr->thread_class][THptr->thread_kind] : UNKNOWNSTR,
		THptr->thread_kind);
	printf("%s thread_eof: %u   ", ind, THptr->thread_eof);
	printf("comp_thread_eof: %u\n", THptr->comp_thread_eof);

	TNodePtr = TNodePtr->TNext;
    }
    printf("%s * total thread_eof: %u   ", ind, RNodePtr->unc_len);
    printf("total comp_thread_eof: %u\n", RNodePtr->comp_len);
}


/* Scan contents of the threads for certain things (for ShrinkIt) */
twobyt ScanThreads(RNodePtr, format)
RNode *RNodePtr;
twobyt *format;
{
    int i;
    int count = RNodePtr->RHptr->total_threads;
    THblock *THptr;
    TNode *TNodePtr;

    *format = 65535;
    TNodePtr = RNodePtr->TNodePtr;
    for (i = 0; i < count; i++) {
	if (TNodePtr == (TNode *) NULL) {
	    fprintf(stderr, "WARNING: fewer threads than expected\n");
	    return (65535);
	}
	THptr = TNodePtr->THptr;

	if (THptr->thread_class == 2) {  /* data thread? */
	    *format = THptr->thread_format;
	    return (THptr->thread_kind);
	}
    }
    return (65535);  /* no data thread found */
}


/*
 * View archive contents
 *
 * When "longout" is false, the output resembles a ShrinkIt archive listing
 */

void NuView(archive, filename, longout)
ListHdr *archive;
char *filename;
BOOLEAN longout;
{
    MHblock *MHptr = archive->MHptr;
    RHblock *RHptr;
    RNode *RNodePtr;
    static char tmpbuf[64];
    twobyt format, datakind;
    int percent, i;

    if (!longout) {
	printf("%-16s ", sprintf(tmpbuf, "%.15s", filename));
	printf("Created:%s   ", PrintDate(&MHptr->arc_create_when, TRUE));
	printf("Mod:%s     ", PrintDate(&MHptr->arc_mod_when, TRUE));
	printf("Recs:%5u\n\n", MHptr->total_records);
	printf(" Name                  Kind  Typ  Auxtyp Archived");
	printf("        Fmat Size  Un-Length\n");
	printf("-------------------------------------------------");
	printf("----------------------------\n");
    } else {
	printf("Now processing archive '%s'\n", filename);
	printf("---> Master header information:\n");
	printf("master ID: '%.6s'     ", MHptr->ID);
	printf("master_crc: $%.4X\n", MHptr->master_crc);
	printf("total_records: %u\n", MHptr->total_records);
	printf("created: %s   ", PrintDate(&MHptr->arc_create_when, FALSE));
	printf("mod: %s\n", PrintDate(&MHptr->arc_mod_when, FALSE));
    }

    RNodePtr = archive->RNodePtr;
    for (i = 0; i < MHptr->total_records; i++) {
	if (RNodePtr == (RNode *) NULL) {
	    fprintf(stderr, "WARNING: fewer records than expected\n");
	    return;
	}
	RHptr = RNodePtr->RHptr;
	if (!longout) {
	    printf("%c", (RHptr->access == 0xE3 || RHptr->access == 0xC3) ?
		' ' : '+');
	    printf("%-21s ", sprintf(tmpbuf, "%.21s", RNodePtr->filename));
	    datakind = ScanThreads(RNodePtr, &format);  /* data_thread info */
	    if (datakind == 65535) {  /* no data thread... */
		printf("????  ");
	        printf("%s  ", RHptr->file_type < 256 ? FT[RHptr->file_type] :
			"???");
	        printf("$%.4X  ", RHptr->extra_type);
	    } else if (datakind == 1) {  /* disk */
		printf("Disk  ");
		printf("---  ");
		printf("%-5s  ", sprintf(tmpbuf, "%dk", RHptr->extra_type / 2));
	    } else {  /* must be a file */
		printf("File  ");
	        printf("%s  ", RHptr->file_type < 256 ? FT[RHptr->file_type] :
			"???");
	        printf("$%.4X  ", RHptr->extra_type);
	    }
	    printf("%s  ", PrintDate(&RHptr->archive_when, TRUE));
	    printf("%s  ", format < QTFn ? QTF[format] : "???");
	    if (!RNodePtr->unc_len && !RNodePtr->comp_len) {
		printf("100%%  ");  /* file is 0 bytes long */
	    } else if ((!RNodePtr->unc_len && RNodePtr->comp_len) ||
			(RNodePtr->unc_len && !RNodePtr->comp_len)) {
		printf("????  ");  /* something weird happened */
	    } else if (RNodePtr->unc_len < RNodePtr->comp_len) {
		printf(">100%% ");  /* compression failed?!? */
	    } else {  /* compute from sum of thread lengths (use only data?) */
	        percent = (((float) RNodePtr->comp_len /
			(float) RNodePtr->unc_len) * 100.0) + 0.5;
		printf("%-4s  ", sprintf(tmpbuf, "%.2d%%", percent));
	        /*printf("%.3d%%  ", percent);*/
	    }
	    printf("%8d\n", RNodePtr->unc_len);
	} else {
	    printf("\n---> Information for record %d:\n", i);
	    printf("Filename: %s\n", RNodePtr->filename);
	    printf("header ID: '%.4s'   ", RHptr->ID);
	    printf("header_crc: $%.4X\n", RHptr->header_crc);
	    printf("attrib_count: %u   ", RHptr->attrib_count);
	    printf("version_number: %u   ", RHptr->version_number);
	    printf("total_threads: %u\n", RHptr->total_threads);
	    printf("file_sys_id: %s   ", RHptr->file_sys_id < FIDn ?
		FID[RHptr->file_sys_id] : UNKNOWNSTR);
	    printf("sep: '%c'\n", RHptr->fssep);
	    printf("sparse: %s (%u)   ", RHptr->sparse ? "Yes" : "No",
		RHptr->sparse);
	    printf("access: %s ($%.2X)\n", (RHptr->access == 0xE3 ||
		RHptr->access == 0xC3) ? "Unlocked" : "Locked", RHptr->access);
	    printf("file_type: %s ($%.4X)   ", RHptr->file_type < 256 ?
	   	 FT[RHptr->file_type] : "???", RHptr->file_type);
	    printf("extra_type: $%.4X\n", RHptr->extra_type);
	    printf("storage_type: %s\n", RHptr->storage_type < STn ?
		ST[RHptr->storage_type] : UNKNOWNSTR);
	    printf("created: %s   ", PrintDate(&RHptr->create_when, FALSE));
	    printf("mod: %s\n", PrintDate(&RHptr->mod_when, FALSE));
	    printf("archived: %s\n", PrintDate(&RHptr->archive_when,
	    	FALSE));
	    /* future expansion... */
	}

	if (longout) DumpThreads(RNodePtr);
	RNodePtr = RNodePtr->RNext;
    }
}


/* parse args, call functions.  Not idiot-proof by any stretch. */
main(argc, argv)
int argc;
char **argv;
{
    ListHdr *archive;
    BOOLEAN longout = FALSE;

    if (argc < 2) {
	Usage(argv[0]);
	exit (0);
    }
    if (!strcmp(argv[1], "-v")) {
	printf("Version 1.0  12-May-89  Public Domain  by Andy McFadden\n");
	exit (0);
    }
    if (!strcmp(argv[1], "-l")) {
	longout = TRUE;
	argv++;
    }

    archive = NuRead(argv[1]);
    NuView(archive, argv[1], longout);
}

!E!O!F!

echo "Done"