/* Tasc: HP48-binary <-> HP48-ASC. Copyright 1992 by Jonathan T. Higa. Distribute freely. You may make modifications to suit your needs; simply document the changes. Dec 1991 to Mar 1992: parses hp48 binary correctly, determines type of source file, handles stdin & stdout 12 June 1992: v2.50 -- auto name completion 19 June 1992: v2.51 -- bug fix for names < 4 chars; added @ comments 22 June 1992: v2.52 -- generalized report syntax */ #include #include #include #include #include #define ATOB 1 #define BTOA 2 typedef unsigned long ulong; const char ASCSUF[]=".asc"; /* ASC file suffix */ const char VERSION[]="2.52"; /* TASC version */ const char *ROMFMT="ROM Revision: %c\n"; /* ROM Revision report format: use %c */ const char *BYTESFMT="BYTES: #%lXh %ld%s\n"; const char *HALFFMT=".5"; /* BYTES report: use long for checksum, long for integral bytes, and string for half-byte format string */ int verb=1; /* verbosity flag */ char rom='E'; /* default rom revision letter */ void *alloc(size_t n); int stricmp(const char *s1, const char *s2); ulong popbitq(ulong *buf, int *bsize, int nbits); ulong pushbitq(ulong *buf, int *bsize, int nbits, ulong bits); int bintoasc(FILE *fbin, FILE *fasc); int translate(const char *fsrc, const char *fdest, int mode); #define calc_crc(crc, hex) (crc=(crc>>4)^(((crc^(hex))&0xF)*0x1081)) /* Recalculates the Cyclic Redundancy Check value based on the old CRC value crc and a new nibble hex. Note: crc should be an unsigned lvalue initialized to 0. */ #define lowbits(n) ((1uL << (n)) - 1uL) /* Creates an unsigned long with only the low n bits set. */ void *alloc(size_t n) /* Safe memory allocation. Exits on error. */ { void *p; if (n <= 0) { fputs("mem: Invalid block size\n", stderr); exit(1); } p = malloc(n); if (!p) { perror("mem"); exit(1); } return p; } int stricmp(const char *s1, const char *s2) /* Case-insensitive string comparison via conversion to upper case. Return -1 if s1 is alphabetically before s2, 1 if after, 0 if equal. */ { int i, c1, c2; i = 0; do { c1 = toupper(s1[i]); c2 = toupper(s2[i]); i++; if (c1 < c2) return -1; if (c1 > c2) return 1; } while (c1); return 0; } ulong popbitq(ulong *buf, int *bsize, int nbits) /* Returns the lowest nbits bits of *buf. Removes those bits from *buf and adjusts *bsize appropriately. */ { ulong b; b = *buf & lowbits(nbits); *buf >>= nbits; if ((*bsize -= nbits) < 0) { fputs("tasc: Bit buffer underflow\n", stderr); exit(1); } return b; } ulong pushbitq(ulong *buf, int *bsize, int nbits, ulong bits) /* Pushes the low nbits of bits onto the high end of *buf. Adjusts *bsize appropriately. Returns the bits actually pushed. */ { bits &= lowbits(nbits); *buf |= bits << *bsize; if ((*bsize += nbits) > sizeof *buf * CHAR_BIT) { fputs("tasc: Bit buffer overflow\n", stderr); exit(1); } return bits; } int asctobin(FILE *fasc, FILE *fbin) /* Translate ASC to HP48 binary. Returns 0 if ok, 1 on error. */ { ulong buf=0, crc=0; long nibs; int bsize=0, c, d; if (verb) fputs("Mode: ASC->bin\n", stderr); /* scan input file to find line beginning with '"' */ c = '\n'; do { d = c; c = getc(fasc); } while (c != EOF && (d != '\n' || c != '"')); if (c == EOF) { fputs("ASC->bin: Invalid ASC file\n", stderr); return 1; } /* write header into binary file */ fputs("HPHP48-", fbin); putc(rom, fbin); if (verb) fprintf(stderr, ROMFMT, rom); /* translate data */ nibs = -4; while (fscanf(fasc, "%1x", &d) == 1) { pushbitq(&buf, &bsize, 4, d); calc_crc(crc, d); nibs++; if (bsize >= 16 + CHAR_BIT) fputc((int) popbitq(&buf, &bsize, CHAR_BIT), fbin); } /* check CRC */ if (bsize > 16) fputc((int) popbitq(&buf, &bsize, bsize - 16), fbin); if (crc || bsize != 16) { fputs("ASC->bin: CRC failed\n", stderr); return 1; } if (verb) fprintf(stderr, BYTESFMT, buf, nibs/2, nibs&1?HALFFMT:""); return 0; } int bintoasc(FILE *fbin, FILE *fasc) /* Translates HP48 binary to ASC format. Return 0 if ok, 1 on error. */ { ulong buf=0, crc=0, skip=0; long nibs; int bsize=0, c, width=0; enum { NONE, SIZE, ASCIC, ASCIX, DIR } state=NONE; const int MAXWIDTH=64; char str[7]; if (verb) fputs("Mode: bin->ASC\n", stderr); /* check input for "HPHP48-" header */ if (fread(str, 1, 7, fbin) != 7 || strncmp(str, "HPHP48-", 7) || (c = getc(fbin)) == EOF) { fputs("bin->ASC: Invalid source file header\n", stderr); return 1; } if (verb) fprintf(stderr, ROMFMT, c); /* write header into ASC file */ fprintf(fasc, "%%%%HP: T(1)A(R)F(.); @ tasc v%s file\n\"", VERSION); nibs = 0; while ((c = getc(fbin)) != EOF) { pushbitq(&buf, &bsize, CHAR_BIT, c); /* parse input HP objects */ if (!skip) switch (state) { case NONE: if (bsize >= 20) { ulong pro = buf & lowbits(20); skip = 5; if (pro == 0x29e8uL || pro == 0x2a0auL || pro == 0x2a2cuL || pro == 0x2a4euL || pro == 0x2b1euL || pro == 0x2b40uL || pro == 0x2b62uL || pro == 0x2b88uL || pro == 0x2dccuL) state = SIZE; else if (pro == 0x2e48uL || pro == 0x2e6duL || pro == 0x2afcuL) state = ASCIC; else if (pro == 0x2a96uL) state = DIR, skip = 8; else if (pro == 0x2911uL) skip = 10; else if (pro == 0x2933uL) skip = 21; else if (pro == 0x2955uL) skip = 26; else if (pro == 0x2977uL) skip = 37; else if (pro == 0x299duL) skip = 47; else if (pro == 0x29bfuL) skip = 7; else if (pro == 0x2e92uL) skip = 11; } break; case SIZE: if (bsize >= 20) state = NONE, skip = buf & lowbits(20); break; case ASCIC: if (bsize >= 8) state = NONE, skip = 2 + 2 * (buf & lowbits(8)); break; case ASCIX: if (bsize >= 8) state = NONE, skip = 4 + 2 * (buf & lowbits(8)); break; case DIR: if (bsize >= 20) state = ASCIX, skip = buf & lowbits(20); break; } /* write already interpreted binary data */ while (skip && bsize >= 4) { c = (int) popbitq(&buf, &bsize, 4); if (width == MAXWIDTH) { putc('\n', fasc); width = 0; } fprintf(fasc, "%1.1X", c); width++; calc_crc(crc, c); skip--; nibs++; } } if (buf) { fprintf(stderr, "bin->ASC: Binary parsed incorrectly\n"); return 1; } /* append CRC */ buf = crc; bsize = 16; while (bsize) { if (width == MAXWIDTH) { putc('\n', fasc); width = 0; } fprintf(fasc, "%1.1X", (int) popbitq(&buf, &bsize, 4)); width++; } fputs("\"\n@ ", fasc); fprintf(fasc, BYTESFMT, crc, nibs/2, nibs&1?HALFFMT:""); if (verb) fprintf(stderr, BYTESFMT, crc, nibs/2, nibs&1?HALFFMT:""); return 0; } int translate(const char *fsrc, const char *fdest, int mode) /* Translate file named fsrc to file named fdest, using mode mode. fsrc == NULL means use stdin; fdest == NULL means use stdout. Return 0 if ok, 1 on error. */ { FILE *in, *out; int i; switch (mode) { case ATOB: if (fsrc) { in = fopen(fsrc, "r"); if (!in) { perror(fsrc); return 1; } } else in = stdin; if (fdest) { out = fopen(fdest, "wb"); if (!out) { perror(fdest); return 1; } } else { fputs("ASC->bin: Case not implemented\n",stderr); exit(1); } i = asctobin(in, out); break; case BTOA: if (fsrc) { in = fopen(fsrc, "rb"); if (!in) { perror(fsrc); return 1; } } else { fputs("bin->ASC: Case not implemented\n",stderr); exit(1); } if (fdest) { out = fopen(fdest, "w"); if (!out) { perror(fdest); return 1; } } else out = stdout; i = bintoasc(in, out); break; default: fputs("tasc: Unimplemented translation mode\n", stderr); exit(1); } fclose(in); fclose(out); return i; } int main(int argc, char **argv, char **envp) /* Uses: tasc options files options: -d Force ASC->bin translation (decode ASC) -e Force bin->ASC translation (encode ASC) -i Use stdin for -a mode / stdout for -b mode: ignored in auto-translation -q Suppress printouts (quiet) -r Set rom revision : ignored in bin->ASC translation files: names of input/output files return code: 0 ok, 1 error */ { int e, mode=0, stdio=0, argi; char *p, *q; /* interpret options as described above */ e = 0; for (argi = 1; argv[argi] && argv[argi][0] == '-'; argi++) for (p = argv[argi] + 1; *p; p++) { switch (*p) { case 'd': if (mode) e++; else mode = ATOB; break; case 'e': if (mode) e++; else mode = BTOA; break; case 'i': if (stdio) e++; else stdio = 1; break; case 'q': if (verb) verb = 0; else e++; break; case 'r': if (p[1]) { p++; if (!strchr("ABCDEF", rom = toupper(*p))) e++; } else e++; break; default: e++; } } if (e) { fprintf(stderr, "Use: %s [-deiqr] file [file]\n", argv[0]); return 1; } if (verb) fprintf(stderr, "TASC version %s\nCompiled on %s at %s\n", VERSION, __DATE__, __TIME__); /* translate files by method specified */ switch (mode) { case ATOB: if (stdio) { if (argc - argi == 1) e = translate(0, argv[argi], ATOB); else { fputs("ASC->bin: Specify output filename\n", stderr); return 1; } } else { if (argc - argi == 2) e = translate(argv[argi], argv[argi+1], ATOB); else { fputs("ASC->bin: Specify input and output filenames\n", stderr); return 1; } } break; case BTOA: if (stdio) { if (argc - argi == 1) e = translate(argv[argi], 0, BTOA); else { fputs("bin->ASC: Specify input filename\n", stderr); return 1; } } else { if (argc - argi == 2) e = translate(argv[argi], argv[argi+1], BTOA); else { fputs("bin->ASC: Specify input and output filenames\n", stderr); return 1; } } break; case 0: if (argc - argi == 1) { int n; n = strlen(argv[argi]); q = alloc(n + sizeof ASCSUF); strcpy(q, argv[argi]); n -= sizeof ASCSUF - 1; if (n > 0 && !stricmp(q+n, ASCSUF)) { q[n] = 0; e = translate(argv[argi], q, ATOB); } else { p = strrchr(q, ASCSUF[0]); if (p) strcpy(p, ASCSUF); else strcat(q, ASCSUF); e = translate(argv[argi], q, BTOA); } free(q); } else if (argc - argi == 2) { e = translate(argv[argi], argv[argi+1], stricmp(argv[argi]+strlen(argv[argi])-(sizeof ASCSUF -1), ASCSUF) ? BTOA : ATOB); } else { fputs("tasc: Specify source [and target] filename[s]\n", stderr); return 1; } break; } return e; }