/* tasc (version 3):  ASC-encoder/decoder for HP48 files
Copyright 1992 by Jonathan T. Higa
Credits:
- ASC:  Bill Wickes (billw@hpcvdw.cv.hp.com).
- Cyclic Redundancy Check:
da Cruz, Frank.  _Kermit:  A File Transfer Protocol._
Bedford, MA:  Digital Press, 1987.
- Object structure:
HP48 Tools Manual;
"HP48SX Internals," by Derek S. Nickel.
- Guidance:  Joe Horn (akcs.joehorn@hpcvbbs.cv.hp.com)
- Original inspiration:
miscellaneous asc2bin programs on seq.uncwil.edu
*/

#include <stdio.h>
#include <string.h>

struct hexbuf {
   unsigned long bits;
   long nibs;
   int bsize;
   unsigned short crc;
};

enum hpdatatype { HPBIN, HPASC, UNKNOWN = -1 };

int verbose = 1;

void pushhex(struct hexbuf *b, int h)
/* Add a hex digit to the bit buffer. */
{
   b->bits |= (h & 0xfuL) << b->bsize;
   b->bsize += 4;
}

int pophex(struct hexbuf *b)
/* Remove and return a hex digit from the bit buffer, and
   include it in the CRC. */
{
   int h = b->bits & 0xf;
   b->crc = (b->crc >> 4) ^ (((b->crc ^ h) & 0xf) * 0x1081);
   b->nibs++;
   b->bits >>= 4;
   b->bsize -= 4;
   return h;
}

enum hpdatatype hptype(FILE *f)
/* Determine the type of HP file by scanning the beginning of the file.
   If the type is known, put the file pointer at the start of data. */
{
   char c;
   if (fscanf(f, "HPHP48-%c", &c) == 1)
      return HPBIN;
   else
      for (; ; )
          switch (fscanf(f, "%*[%]HP:%*[^;]%c", &c)) {
          case 1:
             while (fscanf(f, " @%c", &c) == 1) {
                if (c != '\n') {
                   fscanf(f, "%*[^\n]");
                   fscanf(f, "%*1[\n]");
                }
             }
             return fscanf(f, "%c", &c) == 1 && c == '"' ? HPASC : UNKNOWN;
          case 0:
             fscanf(f, "%*[^\n]");
             fscanf(f, "%*1[\n]");
             break;
          case EOF:
             return UNKNOWN;
          }
}

char *newext(char *new, const char *old, const char *ext)
/* Create a new filename from the old filename and extension. */
{
   char *p = strrchr(strcpy(new, old), ext[0]);
   if (p) strcpy(p, ext);
   else strcat(new, ext);
   if (verbose) fprintf(stderr, "tasc: inventing filename \"%s\"\n", new);
   return new;
}

int asctobin(FILE *fasc, FILE *fbin)
{
   struct hexbuf hb = {0, 0, 0, 0};
   int d;

   /* write header into binary file */
   fputs("HPHP48-E", fbin);

   /* translate data */
   while (fscanf(fasc, "%1x", &d) == 1) {
      pushhex(&hb, d);
      if (hb.bsize >= 24) {
          d = pophex(&hb);
          d |= pophex(&hb) << 4;
          putc(d, fbin);
      }
   }
   if (hb.bsize > 16) {
      d = pophex(&hb);
      putc(d, fbin);
   }

   /* check CRC */
   if (hb.crc != hb.bits || hb.bsize != 16) {
      fprintf(stderr, "tasc: ASC->bin: CRC is incorrect\n");
      return 1;
   }
   if (verbose)
      fprintf(stderr, "tasc: ASC->bin: BYTES: #%hXh %ld%s\n",
              hb.crc, hb.nibs/2, hb.nibs & 1 ? ".5" : "");
   return 0;
}

int bintoasc(FILE *fbin, FILE *fasc)
{
   struct hexbuf hb = {0, 0, 0, 0};
   unsigned long fldlen = 0;
   enum { SIZE, ASCIC, ASCIX, DIR, ANY = -1 } fldnxt = ANY;
   int c, width = 0;

   /* write header into ASC file */
   fprintf(fasc, "%%%%HP: T(1);\n\"");

   /* parse binary */
   while ((c = getc(fbin)) != EOF) {
      pushhex(&hb, c);
      pushhex(&hb, c >> 4);
      if (!fldlen) /* done with previous field */
          switch (fldnxt) { /* now for the current field */
          case SIZE:
             if (hb.bsize >= 20) {
                /* this object's length is known by the size field */
                fldlen = hb.bits & 0xfffff;
                fldnxt = ANY;
             }
             break;
          case ASCIC:
             if (hb.bsize >= 8) {
                /* ASCII-char identifier */
                fldlen = 2 + 2 * (hb.bits & 0xff);
                fldnxt = ANY;
             }
             break;
          case ASCIX:
             if (hb.bsize >= 8) {
                /* ASCII-extended identifier */
                fldlen = 4 + 2 * (hb.bits & 0xff);
                fldnxt = ANY;
             }
             break;
          case DIR:
             if (hb.bsize >= 20) {
                /* first object pointer in a directory, to an ASCIX name */
                fldlen = hb.bits & 0xfffff;
                fldnxt = ASCIX;
             }
             break;
          default:
             if (hb.bsize >= 20) {
                unsigned long pro = hb.bits & 0xfffff;
                fldlen = 5; /* the prolog field is 5 nibbles long */
                if (pro == 0x29e8uL || pro == 0x2a0auL || pro == 0x2a2cuL
                    || pro == 0x2a4euL || pro == 0x2b1euL || pro == 0x2b40uL
                    || pro == 0x2b62uL || pro == 0x2b88uL || pro == 0x2dccuL)
                   fldnxt = SIZE; /* expect a size field */
                else if (pro == 0x2e48uL || pro == 0x2e6duL || pro == 0x2afcuL)
                   fldnxt = ASCIC; /* expect an ASCIC object */
                else if (pro == 0x2a96uL) {
                   /* expect the directory pointer after the first 8 nibbles */
                   fldlen = 8;
                   fldnxt = DIR;
                }
                else if (pro == 0x2911uL) fldlen = 10; /* is system binary */
                else if (pro == 0x2933uL) fldlen = 21; /* is real */
                else if (pro == 0x2955uL) fldlen = 26; /* is long real */
                else if (pro == 0x2977uL) fldlen = 37; /* is complex */
                else if (pro == 0x299duL) fldlen = 47; /* is long complex */
                else if (pro == 0x29bfuL) fldlen = 7; /* is char */
                else if (pro == 0x2e92uL) fldlen = 11; /* is XLIB name */
             }
             break;
          }
      
      /* write out the current field */
      while (fldlen && hb.bsize) {
          c = pophex(&hb);
          if (width == 64) {
             putc('\n', fasc);
             width = 0;
          }
          fprintf(fasc, "%X", c);
          width++;
          fldlen--;
      }
   }
   if (hb.bits) {
      fprintf(stderr, "tasc: bin->ASC: end of last object not found\n");
      return 1;
   }

   /* append CRC */
   if (verbose)
      fprintf(stderr, "tasc: bin->ASC: BYTES: #%hXh %ld%s\n",
              hb.crc, hb.nibs / 2, hb.nibs & 1 ? ".5" : "");
   hb.bits = hb.crc;
   hb.bsize = 16;
   while (hb.bsize) {
      if (width == 64) {
          putc('\n', fasc);
          width = 0;
      }
      fprintf(fasc, "%X", pophex(&hb));
      width++;
   }
   fprintf(fasc, "\"\n");
   return 0;
}

int main(int argc, char **argv)
{
   const char *STDIO = "-";
   enum hpdatatype coding = UNKNOWN;
   int i = 1;
   char *iname, *oname, temp[256];
   if (argc > i && argv[i][0] == '-') {
      switch (argv[i][1]) {
      case 'd':
          coding = HPASC;
          i++;
          break;
      case 'e':
          coding = HPBIN;
          i++;
          break;
      case 'q':
         verbose = 0;
         i++;
         break;
      }
   }
   argc -= i;
   if (argc < 1 || argc > 2) {
      fprintf(stderr, "Use: %s [opt ...] source [target]\n"
              "opt\taction\n"
              " -d\tForce ASC->bin (ASC decode)\n"
              " -e\tForce bin->ASC (ASC encode)\n"
              " -q\tSuppress non-error messages (quiet)\n"
              "The filename \"-\" represents the terminal.\n",
              argv[0]);
      return 1;
   }
   iname = argv[i];
   if (strcmp(iname, STDIO)) {
      if (!freopen(iname, "rb", stdin)) {
          fprintf(stderr, "tasc: ");
          perror(iname);
          return 1;
      }
   } else if (argc == 1) {
      fprintf(stderr, "tasc: cannot invent output filename for stdin\n");
      return 1;
   }
   switch (hptype(stdin)) {
   case HPASC:
      if (coding == HPBIN) {
          fprintf(stderr, "tasc: ASC->bin mode was disallowed\n");
          return 1;
      }
      if (verbose) fprintf(stderr, "tasc: entering ASC->bin mode\n");
      oname = argc == 1 ? newext(temp, iname, ".bin") : argv[i+1];
      if (strcmp(oname, STDIO) && !freopen(oname, "wb", stdout)) {
          fprintf(stderr, "tasc: ");
          perror(oname);
          return 1;
      }
      if (asctobin(stdin, stdout))
          return 1;
      break;
   case HPBIN:
      if (coding == HPASC) {
          fprintf(stderr, "tasc: bin->ASC mode was disallowed\n");
          return 1;
      }
      if (verbose) fprintf(stderr, "tasc: entering bin->ASC mode\n");
      oname = argc == 1 ? newext(temp, iname, ".asc") : argv[i+1];
      if (strcmp(oname, STDIO) && !freopen(oname, "w", stdout)) {
          fprintf(stderr, "tasc: ");
          perror(oname);
          return 1;
      }
      if (bintoasc(stdin, stdout))
          return 1;
      break;
   default:
      fprintf(stderr, "tasc: unknown input type\n");
      return 1;
   }
   if (fclose(stdin)) {
      fprintf(stderr, "tasc: ");
      perror(iname);
      return 1;
   }
   if (fclose(stdout)) {
      fprintf(stderr, "tasc: ");
      perror(oname);
      return 1;
   }
   return 0;
}
