/* Copyright (c) 1997-2004 Miller Puckette, krzYszcz, and others. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ /* LATER verify endianness transparency */ #include #include #include #include #include #define BINPORT_MAXSTRING 256 #define BINPORT_SYMGROW 64 #ifndef BINPORT_STANDALONE /* load a max binary file into a Pd binbuf */ #include "m_pd.h" #else /* make a max-textual listing from a max binary file */ /* This is a standalone version of a ``max binary to binbuf'' module. It uses certain Pd calls and structs, which are duplicated below. LATER should be linked to the Pd API library. */ #define BINPORT_VERBOSE //#define BINPORT_DEBUG #endif #include "binport.h" static void binport_error(char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "ERROR (binport): "); vfprintf(stderr, fmt, ap); putc('\n', stderr); va_end(ap); } static void binport_warning(char *fmt, ...) { #if defined (BINPORT_STANDALONE) || defined(BINPORT_VERBOSE) va_list ap; va_start(ap, fmt); fprintf(stderr, "warning (binport): "); vfprintf(stderr, fmt, ap); putc('\n', stderr); va_end(ap); #endif } static void binport_bug(char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "BUG (binport): "); vfprintf(stderr, fmt, ap); putc('\n', stderr); va_end(ap); } static void binport_failure(char *filename) { binport_error("\"%s\" doesn't look like a patch file", filename); } static void binpold_failure(char *filename) { binport_error("tried reading \"%s\" as an old format file, but failed", filename); } #ifdef BINPORT_STANDALONE typedef int t_int; typedef float t_float; typedef struct _symbol { char *s_name; void *s_thing; struct _symbol *s_next; } t_symbol; typedef union word { t_float w_float; t_symbol *w_symbol; int w_index; } t_word; typedef enum { A_NULL, A_FLOAT, A_SYMBOL, A_POINTER, A_SEMI, A_COMMA, A_DEFFLOAT, A_DEFSYM, A_DOLLAR, A_DOLLSYM, A_GIMME, A_CANT } t_atomtype; typedef struct _atom { t_atomtype a_type; union word a_w; } t_atom; void *getbytes(size_t nbytes) { void *ret; if (nbytes < 1) nbytes = 1; ret = (void *)calloc(nbytes, 1); if (!ret) binport_error("getbytes() failed -- out of memory"); return (ret); } void *resizebytes(void *old, size_t oldsize, size_t newsize) { void *ret; if (newsize < 1) newsize = 1; if (oldsize < 1) oldsize = 1; ret = (void *)realloc((char *)old, newsize); if (newsize > oldsize && ret) memset(((char *)ret) + oldsize, 0, newsize - oldsize); if (!ret) binport_error("resizebytes() failed -- out of memory"); return (ret); } void freebytes(void *fatso, size_t nbytes) { free(fatso); } #define HASHSIZE 1024 static t_symbol *symhash[HASHSIZE]; t_symbol *dogensym(char *s, t_symbol *oldsym) { t_symbol **sym1, *sym2; unsigned int hash1 = 0, hash2 = 0; int length = 0; char *s2 = s; while (*s2) { hash1 += *s2; hash2 += hash1; length++; s2++; } sym1 = symhash + (hash2 & (HASHSIZE-1)); while (sym2 = *sym1) { if (!strcmp(sym2->s_name, s)) return(sym2); sym1 = &sym2->s_next; } if (oldsym) sym2 = oldsym; else { sym2 = (t_symbol *)getbytes(sizeof(*sym2)); sym2->s_name = getbytes(length+1); sym2->s_next = 0; sym2->s_thing = 0; strcpy(sym2->s_name, s); } *sym1 = sym2; return (sym2); } t_symbol *gensym(char *s) { return(dogensym(s, 0)); } #endif /* end of Pd API */ enum { BINPORT_NULLTYPE, BINPORT_INTTYPE = 1, BINPORT_FLOATTYPE, BINPORT_SYMTYPE, BINPORT_DEFINTTYPE = 5, BINPORT_DEFFLOATTYPE, BINPORT_DEFSYMTYPE, BINPORT_SEMITYPE = 10, BINPORT_COMMATYPE, BINPORT_DOLLARTYPE, BINPORT_DOLLSYMTYPE }; /* We use A_INT atom type not only for listing, but for import too -- the parser passes ints to individual token handlers, so that any required conversion has to be done during Pd message generation. */ #define A_INT A_DEFFLOAT static int binport_readbuf(FILE *fp, char *buf, size_t sz) { return (fread(buf, 1, sz, fp) == sz ? sz : 0); } static int binport_readbyte(FILE *fp, unsigned char *buf) { int c; if ((c = fgetc(fp)) == EOF) return (0); *buf = (unsigned char)c; return (1); } static int binport_readint(FILE *fp, int *iptr) { unsigned char word[4]; if (fread(word, 1, 4, fp) == 4) { *iptr = ((word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]); return (4); } else return (0); } /* LATER more testing */ /* make it binpold_readfloat() */ static int binport_readfloat(FILE *fp, float *fptr) { unsigned char word[10]; if (fread(word, 1, 10, fp) == 10) { int ex; unsigned hi, lo; ex = ((word[0] & 0x7F) << 8) | word[1]; hi = ((unsigned)word[2] << 24) | ((unsigned)word[3] << 16) | ((unsigned)word[4] << 8) | (unsigned)word[5]; lo = ((unsigned)word[6] << 24) | ((unsigned)word[7] << 16) | ((unsigned)word[8] << 8) | (unsigned)word[9]; if (ex == 0x7FFF) { binport_warning("NaN atom bashed to zero"); *fptr = 0.; } else if (ex || hi || lo) { double d; ex -= 0x401e; hi = ((hi - 0x7fffffff) - 1) + ((float)0x7fffffff + 1.); lo = ((lo - 0x7fffffff) - 1) + ((float)0x7fffffff + 1.); d = ldexp(hi, ex) + ldexp(lo, ex - 32); *fptr = ((word[0] & 0x80) ? -(float)d : (float)d); } else *fptr = 0.; #ifdef BINPORT_DEBUG fprintf(stderr, "%02x%02x", (int)word[0], (int)word[1]); fprintf(stderr, " %02x%02x%02x%02x", (int)word[2], (int)word[3], (int)word[4], (int)word[5]); fprintf(stderr, " %02x%02x%02x%02x", (int)word[6], (int)word[7], (int)word[8], (int)word[9]); fprintf(stderr, " == %g\n", *fptr); #endif return (10); } else return (0); } static int binport_readstring(FILE *fp, char *buf) { int c, i = 1; while (c = fgetc(fp)) { if (c == EOF) return (0); if (++i < BINPORT_MAXSTRING) *buf++ = (unsigned char)c; } *buf = '\0'; if (i >= BINPORT_MAXSTRING) binport_warning("symbol string too long, skipped"); return (i); } typedef struct _binpold { FILE *o_fp; int o_natoms; int o_bodysize; int o_nsymbols; int o_symbolid; int o_ndx; t_atom *o_atombuf; } t_binpold; #define BINPOLD_NATOMTYPES 16 #define BINPOLD_MAXATOMS 1000000 /* LATER rethink */ static t_atomtype binpold_atomtypes[BINPOLD_NATOMTYPES] = { A_NULL, A_INT, A_FLOAT, A_SYMBOL, A_CANT, A_CANT, A_CANT, A_CANT, A_CANT, A_CANT, A_SEMI, A_COMMA, A_DOLLAR, A_CANT, A_CANT, A_CANT }; static int binpold_gettype(t_binpold *old, t_atom *ap) { int typecode; if ((typecode = fgetc(old->o_fp)) != EOF) { if (typecode > 0 && typecode < BINPOLD_NATOMTYPES) { ap->a_type = binpold_atomtypes[typecode]; if (ap->a_type != A_CANT) return (1); else binport_warning("unsupported type of atom %d: %d", old->o_ndx, typecode); } else binport_warning("bad type of atom %d: %d", old->o_ndx, typecode); } else binport_warning("failed reading type of atom %d", old->o_ndx); return (0); } static int binpold_getvalue(t_binpold *old, t_atom *ap, int *countp) { int ival; float fval; *countp = 0; switch (ap->a_type) { case A_INT: case A_SYMBOL: if (*countp = binport_readint(old->o_fp, &ival)) ap->a_w.w_index = ival; else goto valuefailed; if (ap->a_type == A_SYMBOL) { if (ival >= old->o_nsymbols) old->o_nsymbols = ival + 1; ap->a_type = A_DEFSYM; /* invalidate, until w_symbol is known */ } break; case A_FLOAT: if (*countp = binport_readfloat(old->o_fp, &fval)) ap->a_w.w_float = fval; else goto valuefailed; break; case A_SEMI: case A_COMMA: break; case A_DOLLAR: if (*countp = binport_readint(old->o_fp, &ival)) ap->a_w.w_index = ival; else goto valuefailed; break; default: goto valuefailed; } return (1); valuefailed: binport_warning("failed reading value of atom %d (type %d)", old->o_ndx, ap->a_type); return (0); } static int binpold_load(t_binpold *old) { char buf[BINPORT_MAXSTRING]; t_atom *ap; int total; #ifdef BINPORT_DEBUG fprintf(stderr, "old format: %d atoms, %d-byte chunk of atom values\n", old->o_natoms, old->o_bodysize); #endif for (old->o_ndx = 0, ap = old->o_atombuf; old->o_ndx < old->o_natoms; old->o_ndx++, ap++) if (!binpold_gettype(old, ap)) return (0); old->o_nsymbols = 0; total = 0; for (old->o_ndx = 0, ap = old->o_atombuf; old->o_ndx < old->o_natoms; old->o_ndx++, ap++) { int count; if (!binpold_getvalue(old, ap, &count)) return (0); total += count; } if (total != old->o_bodysize) { binport_warning("actual chunk size %d inconsistent with declared %d", total, old->o_bodysize); return (0); } for (old->o_symbolid = 0; old->o_symbolid < old->o_nsymbols; old->o_symbolid++) { if (binport_readstring(old->o_fp, buf)) { t_symbol *s = gensym(buf); for (old->o_ndx = 0, ap = old->o_atombuf; old->o_ndx < old->o_natoms; old->o_ndx++, ap++) { if (ap->a_type == A_DEFSYM && ap->a_w.w_index == old->o_symbolid) { ap->a_type = A_SYMBOL; ap->a_w.w_symbol = s; } } } else { binport_warning("failed reading string for symbol %d", old->o_symbolid); return (0); } } for (old->o_ndx = 0, ap = old->o_atombuf; old->o_ndx < old->o_natoms; old->o_ndx++, ap++) { if (ap->a_type == A_DEFSYM) { binport_warning("unknown string for symbol %d", ap->a_w.w_index); return (0); } else if (ap->a_type == A_DOLLAR) { sprintf(buf, "#%d", ap->a_w.w_index); ap->a_type = A_SYMBOL; ap->a_w.w_symbol = gensym(buf); } /* CHECKME A_DOLLSYM */ } return (1); } static void binpold_free(t_binpold *old) { if (old->o_fp) fclose(old->o_fp); if (old->o_atombuf) freebytes(old->o_atombuf, old->o_natoms * sizeof(*old->o_atombuf)); freebytes(old, sizeof(*old)); } static t_binpold *binpold_new(FILE *fp) { int natoms, bodysize; if (binport_readint(fp, &natoms)) { if (natoms < 0 || natoms > BINPOLD_MAXATOMS) binport_warning("bad number of atoms: %d", natoms); else if (binport_readint(fp, &bodysize)) { if (bodysize < 0) binport_warning("negative chunk size: %d", bodysize); else { t_binpold *old = getbytes(sizeof(*old)); old->o_fp = fp; old->o_natoms = natoms; old->o_bodysize = bodysize; if (!(old->o_atombuf = getbytes(old->o_natoms * sizeof(*old->o_atombuf)))) { binport_error("could not allocate %d atoms", old->o_natoms); freebytes(old, sizeof(*old)); fclose(fp); return (0); } return (old); } } } else binport_warning("file too short"); fclose(fp); return (0); } typedef struct _binport { FILE *b_fp; int b_nsymbols; int b_symsize; t_symbol **b_symtable; t_binpold *b_old; } t_binport; static void binport_setint(t_atom *ap, int i) { ap->a_type = A_INT; ap->a_w.w_index = i; } static void binport_setfloat(t_atom *ap, float f) { ap->a_type = A_FLOAT; ap->a_w.w_float = f; } static t_symbol *binport_makesymbol(t_binport *bp, int id) { char s[BINPORT_MAXSTRING]; if (id < bp->b_nsymbols) binport_bug("symbol id mismatch"); else if (id > bp->b_nsymbols) binport_error("unexpected symbol id"); else if (binport_readstring(bp->b_fp, s)) { int reqsize = ++bp->b_nsymbols; if (reqsize > bp->b_symsize) { reqsize += (BINPORT_SYMGROW - 1); #ifdef BINPORT_DEBUG binport_warning("resizing symbol table to %d elements", reqsize); #endif if (bp->b_symtable = resizebytes(bp->b_symtable, bp->b_symsize * sizeof(*bp->b_symtable), reqsize * sizeof(*bp->b_symtable))) bp->b_symsize = reqsize; else { bp->b_nsymbols = bp->b_symsize = 0; return (0); } } return (bp->b_symtable[id] = gensym(s)); } return (0); } static t_symbol *binport_getsymbol(t_binport *bp, int id) { if (id < bp->b_nsymbols) return (bp->b_symtable[id]); else return (binport_makesymbol(bp, id)); } static int binport_setsymbol(t_binport *bp, t_atom *ap, int id) { t_symbol *s = binport_getsymbol(bp, id); if (s) { ap->a_type = A_SYMBOL; ap->a_w.w_symbol = s; } return (s != 0); } static int binport_nextatom(t_binport *bp, t_atom *ap) { unsigned char opcode; int opval; char buf[64]; t_binpold *old = bp->b_old; if (old) { if (old->o_ndx < old->o_natoms) { *ap = old->o_atombuf[old->o_ndx++]; return (1); } else return (0); } if (!binport_readbyte(bp->b_fp, &opcode)) goto bad; opval = opcode & 0x0f; switch (opcode >> 4) { case BINPORT_INTTYPE: /* variable length int, opval: length (number of bytes that follow) */ if (!binport_readbuf(bp->b_fp, buf, opval)) goto bad; else { unsigned char *p = (unsigned char *)buf + opval; int i = 0; while (opval--) i = (i << 8) | *--p; if (opcode == 0x12) /* FIXME */ i = (short)i; binport_setint(ap, i); } break; case BINPORT_FLOATTYPE: /* variable length float, opval: length (number of bytes that follow) */ if (!binport_readbuf(bp->b_fp, buf, opval)) goto bad; else { unsigned char *p = (unsigned char *)buf + opval; int i = 0; while (opval--) i = (i << 8) | *--p; binport_setfloat(ap, *(t_float *)&i); } break; case BINPORT_SYMTYPE: /* variable length symbol id, opval: length (number of bytes that follow) */ if (!binport_readbuf(bp->b_fp, buf, opval)) goto bad; else { unsigned char *p = (unsigned char *)buf + opval; int i = 0; while (opval--) i = (i << 8) | *--p; if (!binport_setsymbol(bp, ap, i)) goto bad; } break; case BINPORT_DEFINTTYPE: /* half-byte int */ binport_setint(ap, opval); break; case BINPORT_DEFSYMTYPE: /* half-byte symbol id */ if (!binport_setsymbol(bp, ap, opval)) goto bad; break; case BINPORT_SEMITYPE: /* LATER warn about nonzero opval */ ap->a_type = A_SEMI; break; case BINPORT_COMMATYPE: /* CHECKME apparently never used? */ binport_warning("found the comma type in max binary..."); /* LATER warn about nonzero opval */ ap->a_type = A_COMMA; break; case BINPORT_DOLLARTYPE: /* #number */ sprintf(buf, "#%d", opval); ap->a_type = A_SYMBOL; ap->a_w.w_symbol = gensym(buf); break; case BINPORT_DOLLSYMTYPE: /* #symbol id, opval: length (number of bytes that follow) */ if (!binport_readbuf(bp->b_fp, buf, opval)) goto bad; else { unsigned char *p = (unsigned char *)buf + opval; int i = 0; while (opval--) i = (i << 8) | *--p; if (!binport_setsymbol(bp, ap, i)) goto bad; } sprintf(buf, "#%s", ap->a_w.w_symbol->s_name); #ifdef BINPORT_DEBUG binport_warning(buf); #endif ap->a_w.w_symbol = gensym(buf); break; default: binport_error("unknown opcode %x", (int)opcode); goto bad; } return (1); bad: return (0); } static int binport_binalike(char *header) { static char binport_header[4] = { 2, 0, 0, 0 }; /* CHECKME any others? */ return (memcmp(header, binport_header, 4) == 0); } static int binport_oldalike(char *header) { static char binpold_header[4] = { 0, 0, 0, 1 }; /* CHECKME any others? */ return (memcmp(header, binpold_header, 4) == 0); } static void binport_free(t_binport *bp) { fclose(bp->b_fp); if (bp->b_symtable) freebytes(bp->b_symtable, bp->b_symsize * sizeof(*bp->b_symtable)); if (bp->b_old) { bp->b_old->o_fp = 0; binpold_free(bp->b_old); } freebytes(bp, sizeof(*bp)); } static t_binport *binport_new(FILE *fp, int *ftypep) { char header[4]; *ftypep = BINPORT_INVALID; if (fread(header, 1, 4, fp) == 4) { if (binport_binalike(header)) { t_binport *bp = getbytes(sizeof(*bp)); bp->b_fp = fp; bp->b_nsymbols = 0; bp->b_symsize = BINPORT_SYMGROW; bp->b_symtable = getbytes(bp->b_symsize * sizeof(*bp->b_symtable)); bp->b_old = 0; *ftypep = BINPORT_OK; return (bp); } else if (memcmp(header, "max", 3) == 0) *ftypep = BINPORT_MAXTEXT; else if (binport_oldalike(header)) { t_binport *bp = getbytes(sizeof(*bp)); bp->b_fp = fp; bp->b_nsymbols = 0; bp->b_symsize = 0; bp->b_symtable = 0; bp->b_old = 0; *ftypep = BINPORT_MAXOLD; return (bp); } else { if (header[0] == '#') /* LATER rethink */ *ftypep = BINPORT_PDFILE; else binport_warning("unknown header: %x %x %x %x", (int)header[0], (int)header[1], (int)header[2], (int)header[3]); } } else binport_warning("file too short"); fclose(fp); return (0); } #ifndef BINPORT_STANDALONE static int binport_tobinbuf(t_binport *bp, t_binbuf *bb) { t_atom at; if (bp->b_old) bp->b_old->o_ndx = 0; while (binport_nextatom(bp, &at)) if (at.a_type != A_NULL) binbuf_add(bb, 1, &at); return (1); } /* LATER deal with corrupt binary files? */ int binport_read(t_binbuf *bb, char *filename, char *dirname) { FILE *fp; char namebuf[MAXPDSTRING]; namebuf[0] = 0; if (*dirname) strcat(namebuf, dirname), strcat(namebuf, "/"); strcat(namebuf, filename); sys_bashfilename(namebuf, namebuf); if (fp = fopen(namebuf, "rb")) { int ftype; t_binport *bp = binport_new(fp, &ftype); if (bp) { int result; if (ftype == BINPORT_OK) result = (binport_tobinbuf(bp, bb) ? BINPORT_OK : BINPORT_CORRUPT); else if (ftype == BINPORT_MAXOLD) { t_binpold *old = binpold_new(fp); result = BINPORT_FAILED; if (old) { bp->b_old = old; if (binpold_load(old) && binport_tobinbuf(bp, bb)) result = BINPORT_OK; } else binpold_failure(filename); } else result = BINPORT_FAILED; binport_free(bp); return (result); } else if (ftype == BINPORT_MAXTEXT || ftype == BINPORT_PDFILE) return (ftype); else binport_failure(filename); } else binport_bug("cannot open file"); return (BINPORT_INVALID); } #else static void binport_atomstring(t_atom *ap, char *buf, int bufsize) { char *sp, *bp, *ep; switch(ap->a_type) { case A_SEMI: strcpy(buf, ";"); break; case A_COMMA: strcpy(buf, ","); break; case A_INT: sprintf(buf, "%d", ap->a_w.w_index); break; case A_FLOAT: sprintf(buf, "%#f", ap->a_w.w_float); ep = buf + strlen(buf) - 1; while (ep > buf && *ep == '0') *ep-- = 0; break; case A_SYMBOL: sp = ap->a_w.w_symbol->s_name; bp = buf; ep = buf + (bufsize-5); while (bp < ep && *sp) { if (*sp == ';' || *sp == ',' || *sp == '\\' || (*sp == '$' && bp == buf && sp[1] >= '0' && sp[1] <= '9')) *bp++ = '\\'; if ((unsigned char)*sp < 127) *bp++ = *sp++; else /* FIXME this is temporary -- codepage horror */ sprintf(bp, "\\%.3o", (unsigned char)*sp++), bp += 4; } if (*sp) *bp++ = '*'; *bp = 0; break; case A_DOLLAR: sprintf(buf, "$%d", ap->a_w.w_index); break; case A_DOLLSYM: sprintf(buf, "$%s", ap->a_w.w_symbol->s_name); break; default: binport_bug("bad atom type"); strcpy(buf, "???"); } } static void binport_print(t_binport *bp) { char buf[BINPORT_MAXSTRING]; t_atom at; int ac = 0; if (bp->b_old) bp->b_old->o_ndx = 0; while (binport_nextatom(bp, &at)) { if (at.a_type == A_SEMI) { fputs(";\n", stdout); ac = 0; } else if (at.a_type != A_NULL) { if (ac++) fputc(' ', stdout); binport_atomstring(&at, buf, BINPORT_MAXSTRING); fputs(buf, stdout); } } } int main(int ac, char **av) { if (ac > 1) { FILE *fp = fopen(av[1], "rb"); if (fp) { int ftype; t_binport *bp = binport_new(fp, &ftype); if (bp) { if (ftype == BINPORT_OK) binport_print(bp); else if (ftype == BINPORT_MAXOLD) { t_binpold *old = binpold_new(fp); if (old) { bp->b_old = old; if (binpold_load(old)) binport_print(bp); else ftype = BINPORT_FAILED; } else ftype = BINPORT_FAILED; if (ftype == BINPORT_FAILED) binpold_failure(av[1]); } binport_free(bp); } else if (ftype == BINPORT_MAXTEXT) binport_warning("\"%s\" looks like a Max text file", av[1]); else if (ftype == BINPORT_PDFILE) binport_warning("\"%s\" looks like a Pd patch file", av[1]); else binport_failure(av[1]); } else binport_error("cannot open file \"%s\"", av[1]); } else binport_error("what file?"); return (0); } #endif