/*
 * nuread.c - read NuFX archives (header info only) into structures
 *
 * By Andy McFadden (fadden@cory.berkeley.edu)
 * NuLib v2.2  April 1990  Freeware (distribute, don't sell)
 */

#include "nudefs.h"
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#ifdef MSDOS     /* For file IO */
# include <stdlib.h>        /* errno, among others */
# include <string.h>
# include <io.h>
# include <sys\types.h>
# include <sys\stat.h>
#endif

#ifdef CRC_TAB
# include "crc.h"     /* fast CRC lookup */
#endif
#include "nuread.h"
#include "nupak.h"  /* uses PAKBUFSIZ */
#include "nuetc.h"

#define UNKNOWN_FN    "<No Filename>"

/* quick proc to save x00 bytes of static storage */
void OtherArc(char *str1, char *str2)
{
    fprintf(stderr, "File may be %s; try \"%s\".\n", str1, str2);
}

/* swap two bytes if HiLo is TRUE */
void HiSwap(ptr, a, b)
onebyt *ptr;
register onebyt a, b;
{
    register onebyt tmp;

    if (HiLo) {
    tmp = ptr[a], ptr[a] = ptr[b], ptr[b] = tmp;
    }
}


/* copy bytes from buffer to buffer, reversing byte order if necessary */
void BCopy(srcptr, destptr, num, order)
onebyt *srcptr, *destptr;
register int num;
BOOLEAN order;    /* true if byte ordering is important */
{
    register int i = num--;

    if (order && HiLo) {
    while (i--) {  /* copy & reverse */
        *(destptr+i) = *(srcptr + num - i);  /* dest+3 = src + 3 - 3 .. */
    }
    } else if (order) {
    while (i--) {  /* copy only */
        *(destptr+i) = *(srcptr + i);
    }
    } else {
    while (i--) {  /* byte ordering not important; just copy */
        *(destptr+i) = *(srcptr+i);
    }
    }
}


/*
 * Calculate CRC on a region
 *
 * A CRC is the result of a mathematical operation based on the
 * coefficients of a polynomial when multiplied by X^16 then divided by
 * the generator polynomial (X^16 + X^12 + X^5 + 1) using modulo two
 * arithmetic.
 *
 * This routine is a slightly modified verison of one found in:
 * _Advanced Programming Techniques for the Apple //gs Toolbox_
 * By Morgan Davis and Dan Gookin (Compute! Publications, Inc.)
 * It can either calculate the CRC bit-by-bit or use a table.
 * [ one of the few //gs books worth the money    +atm ]
 */
twobyt CalcCRC(seed, ptr, count)
twobyt seed;  /* initial value for CRC */
onebyt *ptr;  /* pointer to start of data buffer */
int count;    /* number of bytes to scan through - note 64K max */
{
    register int x;
    register twobyt CRC = seed;

    do {
#ifndef CRC_TAB
    CRC ^= *ptr++ << 8;          /* XOR hi-byte of CRC w/data     */
    for (x = 8; x; --x)          /* Then, for 8 bit shifts...     */
        if (CRC & 0x8000)          /* Test hi order bit of CRC     */
        CRC = CRC << 1 ^ 0x1021;  /* if set, shift & XOR w/$1021 */
        else
        CRC <<= 1;          /* Else, just shift left once. */
#else
    CRC = updcrc(*ptr++, CRC);      /* look up new value in table  */
#endif
    } while (--count);
    return (CRC);
}


/*
 * Test an archive's integrity.
 *
 * Reads the entire file, and checks CRCs for certain things.
 */
void NuTest(filename, options)
char *filename;
char *options;
{
    ListHdr *archive;
    onebyt *copybuf;  /* buffer for reading record */
    int partial;   /* size for partial read */
    unsigned int rec;
    RNode *RNodePtr;
    MHblock *MHptr;
    TNode *TNodePtr;
    long hdr_size, total_size, thread_size;
    int srcfd;    /* file descriptor */
    int thread;
    twobyt CRC, RecordCRC;
    long CRCsum = 0L;    /* sum of CRCs for all records */
    BOOLEAN check_thread_crc;    /* TRUE if we want to check a give thread */
    static char *procName = "NuTest";

    printf("Testing %s", filename);
    if (verbose) printf("\n");
    else       { printf("...");  fflush(stdout); }

    archive = NuRead(filename);  /* this catches most errors... */

    MHptr = archive->MHptr;
    RNodePtr = archive->RNodePtr;
    copybuf = (onebyt *) Malloc(PAKBUFSIZ);
    if ((srcfd = open(filename, O_RDONLY | O_BINARY)) < 0)
    Fatal("Unable to close archive", procName);
    if (lseek(srcfd, (long) MHsize, S_ABS) < 0)  /* seek past master block */
    Fatal("Bad seek (MH)", procName);

    for (rec = 0; rec < (unsigned int) MHptr->total_records; rec++) {
    if (verbose) printf("Record %d (%s): ", rec, RNodePtr->filename);
    hdr_size = (long) RNodePtr->RHptr->attrib_count;
    hdr_size += (long) RNodePtr->filename_length;
    total_size = hdr_size;
    TNodePtr = RNodePtr->TNodePtr;
    for (thread=0; thread < (int)RNodePtr->RHptr->total_threads; thread++){
        if (TNodePtr == (TNode *) NULL) {
        fprintf(stderr, "Internal error: Bad thread structure\n");
        Quit(-1);
        }
        hdr_size += (long) THsize;
        total_size += (long) THsize;
        total_size += TNodePtr->THptr->comp_thread_eof;
        TNodePtr = TNodePtr->TNext;
    }
    if (verbose) {
        printf("total record size = %ld (%d threads)\n", total_size,
        (int) RNodePtr->RHptr->total_threads);
        fflush(stdout);
    }

    /* read record header */
    RecordCRC = 0;
    while (hdr_size != 0L) {
        if (hdr_size > (long) PAKBUFSIZ) {
        partial = (unsigned int) PAKBUFSIZ;
        hdr_size -= (long) PAKBUFSIZ;
        } else {
        partial = (unsigned int) hdr_size;
        hdr_size = 0L;
        }

        if (read(srcfd, copybuf, partial) != partial) {
        fprintf(stderr, ">>> Read error");
        if (verbose) fprintf(stderr, "\n");
        else fprintf(stderr,
            " - record %d (%s)\n",  rec, RNodePtr->filename);
        fprintf(stderr, "Operation aborted.\n");
        Quit(-1);
        }
        if (verbose) RecordCRC = CalcCRC(CRC, (onebyt *) copybuf, partial);
    }

    TNodePtr = RNodePtr->TNodePtr;
    for (thread=0; thread < (int)RNodePtr->RHptr->total_threads; thread++){
        if (lseek(srcfd, (long) TNodePtr->fileposn, S_ABS) < 0)
        Fatal("whoops!", procName);
        thread_size = TNodePtr->THptr->comp_thread_eof;

        /* decide whether or not to check thread CRCs */
        check_thread_crc = FALSE;
        if (RNodePtr->RHptr->version_number >= 2)    /* valid CRCs */
        if (TNodePtr->THptr->thread_class == 2)    /* data_thread */
            check_thread_crc = TRUE;

        if (check_thread_crc) CRC = 0xffff;
        while (thread_size != 0L) {
        if (thread_size > (long) PAKBUFSIZ) {
            partial = (unsigned int) PAKBUFSIZ;
            thread_size -= (long) PAKBUFSIZ;
        } else {
            partial = (unsigned int) thread_size;
            thread_size = 0L;
        }

        if (read(srcfd, copybuf, partial) != partial) {
            fprintf(stderr, ">>> Read error in thread");
            if (verbose) fprintf(stderr, " %d\n", thread);
            else fprintf(stderr, " - record %d (%s), thread %d\n",
                rec, RNodePtr->filename, thread);
            fprintf(stderr, "Operation aborted.\n");
            Quit(-1);
        }

        if (verbose)
            RecordCRC = CalcCRC(RecordCRC, (onebyt *)copybuf, partial);

        /* calculate CRC for thread, and compare with thread_crc */
        if (check_thread_crc)
            CRC = CalcCRC(CRC, (onebyt *) copybuf, partial);
#ifdef DEBUG
        printf(
"At posn %ld: rec %d/thread %d (%ld bytes)  CalcCRC = 0x%.4x (0x%.4x)\n",
TNodePtr->fileposn, rec, thread, thread_size, CRC, TNodePtr->THptr->thread_crc
        );
#endif
        }

        /* check and see if CRC matches */
        if (check_thread_crc) {
        if (CRC != TNodePtr->THptr->thread_crc) {
            fprintf(stderr, ">>> Bad CRC for thread %d",
            thread);
            if (verbose) fprintf(stderr, "\n");
            else fprintf(stderr, " in record %d\n", rec);
        }
        }
        TNodePtr = TNodePtr->TNext;
    }

    if (verbose) {
        printf("--- CRC for entire record was $%.4x\n", RecordCRC);
        CRCsum += RecordCRC;
    }
    RNodePtr = RNodePtr->RNext;
    }
    if (close(srcfd) < 0)
    Fatal("Unable to close archive", procName);

    free(copybuf);
    if (verbose) printf("Sum of CRCs = $%.8lx\n", CRCsum);
    printf("done.\n");
}


/*
 * Read thread header data, and skip data fields
 */

static TNode *ReadThreads(fd, RHptr, RNodePtr, CRC_ptr)
int fd;
RHblock *RHptr;
RNode *RNodePtr;
twobyt *CRC_ptr;  /* CRC seed; result is returned thru this */
{
    int i;
    unsigned int size;
    BOOLEAN first;
    TNode *TNodePtr, *THeadPtr = (TNode *) NULL;
    THblock *THptr;
    char filebuf[THsize];
    twobyt CRC = *CRC_ptr;
    static char *procName = "ReadThreads";

    RNodePtr->unc_len = 0L;
    RNodePtr->comp_len = 0L;
    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;

    /* Create the thread header block, and read it in */
    TNodePtr->THptr = (THblock *) Malloc(sizeof(THblock));
    THptr = TNodePtr->THptr;

    if (size = read(fd, filebuf, THsize) < THsize) {  /* should be 16 */
        printf("read size = %d, THsize = %d\n", size, THsize);
        Fatal("ReadThread (THblock)", procName);
    }
    CRC = CalcCRC(CRC, (onebyt *) filebuf, 16);  /* hdr CRC part(s) 5/5 */

    /* copy all fields... */
    BCopy(filebuf+0, (onebyt *) &THptr->thread_class, 2, TRUE);
    BCopy(filebuf+2, (onebyt *) &THptr->thread_format, 2, TRUE);
    BCopy(filebuf+4, (onebyt *) &THptr->thread_kind, 2, TRUE);
    BCopy(filebuf+6, (onebyt *) &THptr->thread_crc, 2, TRUE);
    BCopy(filebuf+8, (onebyt *) &THptr->thread_eof, 4, TRUE);
    BCopy(filebuf+12, (onebyt *) &THptr->comp_thread_eof, 4, TRUE);

    RNodePtr->unc_len += THptr->thread_eof;
    RNodePtr->comp_len += THptr->comp_thread_eof;
    if (THptr->comp_thread_eof > 2097152L)    /* SANITY CHECK */
        fprintf(stderr, "Sanity check: found comp_thread_eof > 2MB\n");
    }

    /* skip the actual data */
    TNodePtr = THeadPtr;
    for (i = 0; i < RHptr->total_threads; i++) {
    THptr = TNodePtr->THptr;
    if ((TNodePtr->fileposn = lseek(fd, 0L, S_REL)) < 0)
        Fatal("Bad thread posn lseek()", procName);

    /* pull filenames out of threads, if present */
    if (THptr->thread_class == 0x0003) {    /* filename thread */
        RNodePtr->filename = (char *) Malloc(THptr->thread_eof +1);
        if (read(fd, RNodePtr->filename, THptr->thread_eof) < 0) {
        fprintf(stderr, "Error on thread %d\n", i);
        Fatal("Unable to read filename", procName);
        }
        RNodePtr->filename[THptr->thread_eof] = '\0';
        RNodePtr->real_fn_length = THptr->thread_eof;

        {        /* patch to fix bug in ShrinkIt v3.0.0 */
        int j, name_len = strlen(RNodePtr->filename);

        for (j = 0; j < name_len; j++) {
            RNodePtr->filename[j] &= 0x7f;    /* clear hi bit */
        }
        }

        if (lseek(fd, TNodePtr->fileposn, S_ABS) < 0)
        Fatal("Unable to seek back after fn", procName);
    }
    if (lseek(fd, THptr->comp_thread_eof, S_REL) < 0)
        Fatal("Bad skip-thread seek", procName);
    TNodePtr = TNodePtr->TNext;
    }
    *CRC_ptr = CRC;
    return (THeadPtr);
}


/*
 * Read header data from a NuFX archive into memory
 */
ListHdr *NuRead(filename)
char *filename;
{
    int fd;  /* archive file descriptor */
    char namebuf[MAXFILENAME];
    int rec, num;
    BOOLEAN first;
    twobyt namelen;
    twobyt CRC;
    ListHdr *ListPtr;  /* List Header struct */
    MHblock *MHptr;  /* Master Header block */
    RNode *RNodePtr;  /* Record Node */
    RHblock *RHptr;  /* Record Header block */
    onebyt filebuf[RECBUFSIZ];    /* must be > RH, MH, or atts-RH size */
    static char *procName = "NuRead";

    ListPtr = (ListHdr *) Malloc(sizeof(ListHdr));  /* create head of list */
    ListPtr->arc_name = (char *) Malloc(strlen(filename)+1); /* archive fnam */
    strcpy(ListPtr->arc_name, filename);
    ListPtr->MHptr = (MHblock *) Malloc(sizeof(MHblock));  /* master block */

    if ((fd = open(filename, O_RDONLY | O_BINARY)) < 0) {
    if (errno == ENOENT) {
        fprintf(stderr, "%s: can't find file '%s'\n", prgName, filename);
        Quit (-1);
    } else
        Fatal("Unable to open archive", procName);
    }

    /* create and read the master header block */
    MHptr = ListPtr->MHptr;
    if (read(fd, filebuf, MHsize) < MHsize) {
    fprintf(stderr, "File '%s' may not be a NuFX archive\n", filename);
    Fatal("Unable to read Master Header Block", procName);
    }

    CRC = CalcCRC(0, (onebyt *) filebuf+8, MHsize-8);  /* master header CRC */

    /* Copy data to structs, correcting byte ordering if necessary */
    BCopy(filebuf+0, (onebyt *) MHptr->ID, 6, FALSE);
    BCopy(filebuf+6, (onebyt *) &MHptr->master_crc, 2, TRUE);
    BCopy(filebuf+8, (onebyt *) &MHptr->total_records, 4, TRUE);
    BCopy(filebuf+12, (onebyt *) &MHptr->arc_create_when, sizeof(Time), FALSE);
    BCopy(filebuf+20, (onebyt *) &MHptr->arc_mod_when, sizeof(Time), FALSE);
    BCopy(filebuf+28, (onebyt *) &MHptr->master_version, 2, TRUE);
    BCopy(filebuf+30, (onebyt *) MHptr->reserved1, 8, FALSE);
    BCopy(filebuf+38, (onebyt *) &MHptr->master_eof, 4, TRUE);  /* m_v $0001 */
    BCopy(filebuf+42, (onebyt *) MHptr->reserved2, 6, FALSE);

    if (strncmp(MHptr->ID, MasterID, 6)) {
    fprintf(stderr, "\nFile '%s' is not a NuFX archive\n", filename);
    if ((filebuf[0] == 10) && (filebuf[1] == 71) &&
        (filebuf[2] == 76) && (filebuf[18] == 2))
#ifdef NO_BLU
        OtherArc("Binary II", "unblu");
#else
        fprintf(stderr, "File may be Binary II; try 'B' option\n");
#endif
    if ((filebuf[0] == '\037') && (filebuf[1] == '\036'))
        OtherArc("packed", "unpack");
    if ((filebuf[0] == (onebyt)'\377') && (filebuf[1] == '\037'))
        OtherArc("compacted", "uncompact");
    if ((filebuf[0] == '\037') && (filebuf[1] == (onebyt)'\235'))
        OtherArc("compressed", "uncompress");
    if ((filebuf[0] == 0x76) && (filebuf[1] == 0xff))
        OtherArc("SQueezed", "usq");
    if ((filebuf[0] == 0x04) && (filebuf[1] == 0x03) &&
        (filebuf[2] == 0x4b) && (filebuf[3] == 0x50))
        OtherArc("a ZIP archive", "UnZip");
    if (!strncmp((char *) filebuf, "SIT!", 4))  /* StuffIt */
        OtherArc("a StuffIt archive", "StuffIt (Macintosh)");
    if (!strncmp((char *) filebuf, "<ar>", 4))  /* system V arch */
        OtherArc("a library archive (Sys V)", "ar");
    if (!strncmp((char *) filebuf, "!<arch>", 7))
        OtherArc("a library archive", "ar");
    if (!strncmp((char *) filebuf, "#! /bin/sh", 10) ||
        !strncmp((char *) filebuf, "#!/bin/sh", 9))
        OtherArc("a shar archive", "/bin/sh");
    if (!strncmp((char *) filebuf, "GIF87a", 6))
        OtherArc("a GIF picture", "?!?");
    /* still need ARC and ZOO */

    Quit (-1);
    }

    if (CRC != MHptr->master_crc)
    printf("WARNING: Master Header block may be corrupted (bad CRC)\n");

    if (MHptr->master_version > MAXMVERS)
    printf("WARNING: unknown Master Header version, trying to continue\n");

    /* main record read loop */
    first = TRUE;
    for (rec = 0; rec < (unsigned int) MHptr->total_records; rec++) {
    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 */
    /* expansion here */
    RHptr = RNodePtr->RHptr;
    if (read(fd, filebuf, RHsize) < RHsize) {  /* get known stuff */
        fprintf(stderr,"%s: error in record %d (at EOF?)\n", prgName, rec);
        Fatal("Bad RHblock read", procName);
    }

    /* rec hdr CRC part 1/5 */
    CRC = CalcCRC(0, (onebyt *) filebuf+6, RHsize-6);

    BCopy(filebuf+0, (onebyt *) RHptr->ID, 4, FALSE);
    BCopy(filebuf+4, (onebyt *) &RHptr->header_crc, 2, TRUE);
    BCopy(filebuf+6, (onebyt *) &RHptr->attrib_count, 2, TRUE);
    BCopy(filebuf+8, (onebyt *) &RHptr->version_number, 2, TRUE);
    BCopy(filebuf+10, (onebyt *) &RHptr->total_threads, 2, TRUE);
    BCopy(filebuf+12, (onebyt *) &RHptr->reserved1, 2, TRUE);
    BCopy(filebuf+14, (onebyt *) &RHptr->file_sys_id, 2, TRUE);
    BCopy(filebuf+16, (onebyt *) &RHptr->file_sys_info, 1, TRUE);
    BCopy(filebuf+17, (onebyt *) &RHptr->reserved2, 1, TRUE);
    BCopy(filebuf+18, (onebyt *) &RHptr->access, 4, TRUE);
    BCopy(filebuf+22, (onebyt *) &RHptr->file_type, 4, TRUE);
    BCopy(filebuf+26, (onebyt *) &RHptr->extra_type, 4, TRUE);
    BCopy(filebuf+30, (onebyt *) &RHptr->storage_type, 2, TRUE);
    BCopy(filebuf+32, (onebyt *) &RHptr->create_when, sizeof(Time), FALSE);
    BCopy(filebuf+40, (onebyt *) &RHptr->mod_when, sizeof(Time), FALSE);
    BCopy(filebuf+48, (onebyt *) &RHptr->archive_when, sizeof(Time), FALSE);
    BCopy(filebuf+56, (onebyt *) &RHptr->option_size, 2, TRUE);
    /* expansion here */

    if (strncmp(RHptr->ID, RecordID, 4)) {
        fprintf(stderr, "%s: Found bad record ID (#%d) -- exiting\n",
                                prgName, rec);
        Quit (-1);
    }

    /* read remaining (unknown) attributes into buffer, if any */
    num = RHptr->attrib_count - RHsize - 2;
    if (num > RECBUFSIZ) {
        fprintf(stderr, "ERROR: attrib_count > RECBUFSIZ\n");
        Quit (-1);
    }
    if (num > 0) {
        if (read(fd, filebuf, num) < num)
        Fatal("Bad xtra attr read", procName);
        CRC = CalcCRC(CRC, (onebyt *) filebuf, num);  /* hdr CRC part 2/5 */
    }

    if (read(fd, (char *) &namelen, 2) < 2)  /* read filename len */
        Fatal("Bad namelen read", procName);
    CRC = CalcCRC(CRC, (onebyt *) &namelen, 2);  /* rec hdr CRC part 3/5 */

    HiSwap((onebyt *) &namelen, 0, 1);
    /* read filename, and store in struct */
    if (namelen > MAXFILENAME) {
        fprintf(stderr, "ERROR: namelen > MAXFILENAME\n");
        Quit (-1);
    }
    RNodePtr->filename_length = namelen;

    if (namelen > 0) {
        RNodePtr->real_fn_length = namelen;
        if (read(fd, namebuf, namelen) < namelen)
        Fatal("Bad namebuf read", procName);
        /* rec hdr CRC part 4/5 */
        CRC = CalcCRC(CRC, (onebyt *) namebuf, namelen); 

        RNodePtr->filename = (char *) Malloc(namelen+1);  /* store fname */
        BCopy(namebuf, (onebyt *) RNodePtr->filename, namelen, FALSE);
        RNodePtr->filename[namelen] = '\0';
    } else {
        RNodePtr->filename = UNKNOWN_FN;
        RNodePtr->real_fn_length = strlen(UNKNOWN_FN);
    }

    RNodePtr->TNodePtr = ReadThreads(fd, RHptr, RNodePtr, &CRC);
    /* rec hdr CRC part 5/5 calculated by ReadThreads */

    if (CRC != RHptr->header_crc) {
        printf("WARNING: Detected a bad record header CRC\n");
        printf("         Rec %d in file '%.60s'\n",rec,RNodePtr->filename);
    }
    }

    /* begin adding new files at  this point */
    if ((ListPtr->nextposn = lseek(fd, 0L, S_REL)) < 0)
    Fatal("Bad final lseek()", procName);

    if (close(fd) < 0) {
    Fatal("Bad close", procName);
    }

    if (MHptr->master_version > 0x0000) {
    if (MHptr->master_eof != ListPtr->nextposn) {
        printf("WARNING: master_eof (stored)=%ld, nextposn (actual)=%ld\n",
        MHptr->master_eof, ListPtr->nextposn);
        printf(
        "         (master_eof will be fixed if archive is changed)\n");
    }
    }
    
    return (ListPtr);
}

