augustss@chalmers.UUCP (Lennart Augustsson) (01/03/85)
[] Here is a resource disassembler for MacIntosh. It's written with SUMacC, but it may be possible to use with other compilers. It takes a resource file (file.rsrc) and produces a file that may be used as input to the resource compiler (rmaker). This is useful when you have edited your resource on the Mac (with the resource editor, menu editor, or whatever) and want to have these new resources as input to the rmaker. Just 'macget' the file and apply rekamr. Please mail me any bugfxes and extensions you make. Any resemblance of the opinions above to real opinions, living or dead, is purely coincidence. Lennart Augustsson {seismo,philabs,decvax}!mcvax!enea!chalmers!augustss #--------CUT---------CUT---------CUT---------CUT--------# ######################################################### # # # This is a shell archive file. To extract files: # # # # 1) Make a directory for the files. # # 2) Write a file, such as "file.shar", containing # # this archive file into the directory. # # 3) Type "sh file.shar". Do not use csh. # # # ######################################################### # # echo Extracting Makefile: sed 's/^Z//' >Makefile <<\STUNKYFLUFF Zrekamr: rekamr.c Z cc -I/usr/sun/include rekamr.c -o rekamr Zinstall: rekamr Z install -s rekamr /usr/local/rekamr Z cp rekamr.man /usr/man/manl/rekamr.manl STUNKYFLUFF set `sum Makefile` if test 10934 != $1 then echo Makefile: Checksum error. Is: $1, should be: 10934. fi # # echo Extracting rekamr.c: sed 's/^Z//' >rekamr.c <<\STUNKYFLUFF Z#include <stdio.h> Z#include <mac/res.h> Z#include <mac/quickdraw.h> Z#include <mac/toolintf.h> Z Z#define void int /* The stupid compiler is handling voidfun wrong. */ Z Z#define CODEMAX 1024 Z#define MAXMAP 100 Z Zchar *malloc(); Zchar *index(); Zchar *rindex(); Zshort htons(); /* host to "net" byte order, short. */ Zlong htonl(); Z#define HTONL(x) x = htonl(x) Z#define HTONS(x) x = htons(x) Z Zint lookup(); Zvoid dumpdata(), handstr(), handalrt(), handditl(), handdlog(), handwind(), Z handmenu(), handcntl(), handcode(), handfref(), handbndl(); Z Zint codeflag = 0; /* Put code in a file */ Zint debug = 0; Zchar filename[1024]; Zint ncode = 0; Z Zstruct { Z char th_type[4]; Z char th_suff[4]; Z void (*th_fun)(); Z} typehand[MAXMAP] = { Z#define IHEXA 0 Z"HEXA", "HEXA", dumpdata, Z#define ICODE 1 Z"CODE", " ", handcode, Z"STR ", " ", handstr, Z"ALRT", " ", handalrt, Z"DITL", " ", handditl, Z"DLOG", " ", handdlog, Z"WIND", " ", handwind, Z"MENU", " ", handmenu, Z"CNTL", " ", handcntl, Z#define FIRSTNEW "FREF" /* First entry not in old rmaker */ Z"FREF", " ", handfref, Z"BNDL", " ", handbndl, Z0, 0 Z}; Z Z Zmain(argc, argv) Zchar *argv[]; Z{ Z char *cmdname = *argv; Z int i, j; Z long typeoffs; Z int funi; Z FILE *inf, *outf; Z struct resfile resh; Z struct resmap rmaph; Z struct restype typeh; Z struct resid idh; Z numtypes_t ntypes; Z lendata_t ndata; Z char oname[1024]; Z char *p; Z char *stringtype = ""; Z Z argc--, argv++; Z while (argc && argv[0][0] == '-') { Z while (*++*argv) Z switch(**argv) { Z case 'c': codeflag = 1; break; Z case 'd': debug = 1; break; Z case 'r': oldmap(); break; Z case 't': Z if (strlen(*argv+1) != 9 || (*argv)[5] != '=') Z goto usage; Z addmap(*argv+1, *argv+6); Z goto nextarg; Z default: Z goto usage; Z } Znextarg: Z argc--, argv++; Z } Z if (argc != 1) { Z goto usage; Z } Z if ((inf = fopen(argv[0], "r")) == NULL) { Z perror(argv[0]); Z exit(1); Z } Z strcpy(filename, argv[0]); Z if ((p = rindex(filename, '.')) == 0) goto usage; Z *p = 0; Z sprintf(oname, "%s.rc", filename); Z if ((outf = fopen(oname, "w")) == NULL) { Z perror(oname); Z exit(1); Z } Z fprintf(outf, "*\n"); Z fprintf(outf, "%s.new\n", argv[0]); Z fread((char *)&resh, sizeof resh, 1, inf); Z HTONL(resh.rf_offdata); Z HTONL(resh.rf_offmap); Z HTONL(resh.rf_lendata); Z HTONL(resh.rf_lenmap); Z if (debug) printf("offset to data = %5d\n", resh.rf_offdata); Z if (debug) printf("offset to map = %5d\n", resh.rf_offmap); Z if (debug) printf("length of data = %5d\n", resh.rf_lendata); Z if (debug) printf("length of map = %5d\n", resh.rf_lenmap); Z fseek(inf, resh.rf_offmap, 0); Z fread((char *)&rmaph, sizeof rmaph, 1, inf); Z HTONS(rmaph.rm_fileatt); Z HTONS(rmaph.rm_offtype); Z HTONS(rmaph.rm_offname); Z if (debug) printf("file att = %x\n", rmaph.rm_fileatt); Z if (debug) printf("offset to type = %5d\n", rmaph.rm_offtype); Z if (debug) printf("offset to name = %5d\n", rmaph.rm_offname); Z typeoffs = resh.rf_offmap+rmaph.rm_offtype; Z fseek(inf, typeoffs, 0); Z fread((char *)&ntypes, sizeof ntypes, 1, inf); Z HTONS(ntypes); Z if (debug) printf("ntypes = %d\n", ntypes); Z for(i = 0; i <= ntypes; i++) { Z long where, offname; Z Z fread((char *)&typeh, sizeof typeh, 1, inf); Z HTONS(typeh.rt_numids); Z HTONS(typeh.rt_offids); Z if (debug) printf("type = %.4s\n", typeh.rt_type); Z if (debug) printf("number of ids = %d\n", typeh.rt_numids); Z if (debug) printf("offset to ids = %d\n", typeh.rt_offids); Z where = ftell(inf); Z offname = typeoffs+typeh.rt_offids; Z fseek(inf, offname, 0); Z for(j = 0; j <= typeh.rt_numids; j++) { Z long swhere, offdata; Z char rname[256]; Z Z fread((char *)&idh, sizeof idh, 1, inf); Z HTONS(idh.ri_id); Z HTONS(idh.ri_offname); Z HTONS(idh.ri_offdata); Z HTONS(idh.ri_sysid); Z HTONS(idh.ri_sysoffname); Z offdata = (idh.ri_offdatahi << 16) + idh.ri_offdata; Z if (idh.ri_offname != -1) { Z long oldpos; Z unsigned char len; Z Z oldpos = fseek(inf); Z fseek(inf, offname+idh.ri_offname, 0); Z len = getc(inf); Z fread(rname, 1, len, inf); Z fseek(inf, oldpos, inf); Z rname[len] = 0; Z } Z funi = lookup(typeh.rt_type); Z fprintf(outf, "*\n"); Z fprintf(outf, "Type %.4s", typeh.rt_type); Z if (typehand[funi].th_suff[0] != ' ') Z fprintf(outf, " = %.4s", typehand[funi].th_suff); Z fprintf(outf, "\n"); Z fprintf(outf, " "); Z if (funi == ICODE && codeflag) Z fprintf(outf, "%s.code.%d", filename, ++ncode); Z if (idh.ri_offname != -1) Z fprintf(outf, "/%s", rname); Z fprintf(outf, ",%d(%d)\n", idh.ri_id, idh.ri_att); Z swhere = ftell(inf); Z fseek(inf, resh.rf_offdata+offdata, 0); Z fread((char *)&ndata, sizeof ndata, 1, inf); HTONL(ndata); Z (*typehand[funi].th_fun)(inf, outf, ndata); Z fprintf(outf, "\n"); Z fseek(inf, swhere, 0); Z if (debug) printf("rsrc id = %d\n", idh.ri_id); Z if (debug) printf("rsrc name offs = %d\n", idh.ri_offname); Z if (debug) printf("rsrc attr = %x\n", idh.ri_att); Z if (debug) printf("rsrc data offs = %d\n", offdata); Z if (debug) printf("rsrc sysid = %d\n", idh.ri_sysid); Z if (debug) printf("rsrc sysoffname = %d\n", idh.ri_sysoffname); Z if (debug) printf("rsrc data len = %d\n", ndata); Z } Z fseek(inf, where, 0); Z } Z fclose(inf); Z fclose(outf); Z exit(0); Z Zusage: Z fprintf(stderr, "Usage: %s [-cdr] [-tTYPE=TYPE] file.rsrc\n", cmdname); Z exit(1); Z} Z Zvoid Zdumpdata(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z int c, k; Z Z for(k = 1; k <= n; k++) { Z c = getc(i); Z fprintf(o, "%02x", c & 0xff); Z if (k % 32 == 0) Z fprintf(o, "\n"); Z else if (k % 4 == 0) Z fprintf(o, " "); Z } Z if (k % 32 != 1) Z fprintf(o, "\n"); Z} Z Zchar * Zgetstr255(i) ZFILE *i; Z{ Z static char data[256]; Z unsigned char len; Z Z len = getc(i); Z fread(data, 1, len, i); Z data[len] = 0; Z return data; Z} Z Z Zvoid Z/*ARGSUSED*/ Zhandstr(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z int n; Z Z n = getc(i); Z while(n--) Z putc(getc(i), o); Z putc('\n', o); Z} Z Zvoid Z/*ARGSUSED*/ Zhandalrt(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z AlertTemplate a; Z Z fread((char *)&a, sizeof a, 1, i); Z HTONS(a.boundsRect.top); Z HTONS(a.boundsRect.left); Z HTONS(a.boundsRect.bottom); Z HTONS(a.boundsRect.right); Z HTONS(a.itemsID); Z HTONS(a.stages); Z fprintf(o, " %d %d %d %d\n", a.boundsRect.top, a.boundsRect.left, Z a.boundsRect.bottom, a.boundsRect.right); Z fprintf(o, " %d\n", a.itemsID); Z fprintf(o, " %04x\n", a.stages); Z} Z Zvoid Z/*ARGSUSED*/ Zhandditl(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z struct item { Z long dummy; Z Rect i_Rect; Z unsigned char i_type; Z unsigned char i_len; Z } it; Z short n, s; Z int k; Z char data[1024]; Z int t; Z Z fread((char *)&n, sizeof n, 1, i); Z HTONS(n); Z fprintf(o, " %d\n", n+1); Z for(k = 0; k <= n; k++) { Z fread((char *)&it, /*sizeof it*/ 14, 1, i); Z if (debug) printf("ditl len = %d\n", it.i_len); Z fread(data, 1, (it.i_len+1) & ~1, i); Z HTONS(it.i_Rect.top); Z HTONS(it.i_Rect.left); Z HTONS(it.i_Rect.bottom); Z HTONS(it.i_Rect.right); Z t = it.i_type; Z fprintf(o, " "); Z switch(t & 3) { Z case BtnCtrl: fprintf(o, "BtnCtrl "); break; Z case ChkCtrl: fprintf(o, "ChkCtrl "); break; Z case RadCtrl: fprintf(o, "RadCtrl "); break; Z case ResCtrl: fprintf(o, "ResCtrl "); break; Z } Z if (t & CtrlItem) fprintf(o, "CtrlItem "); Z else fprintf(o, "UserItem "); Z if (t & StatText) fprintf(o, "StatText "); Z if (t & EditText) fprintf(o, "EditText "); Z if (t & IconItem) fprintf(o, "IconItem "); Z if (t & PicItem) fprintf(o, "PicItem "); Z if (t & ItemDisable) fprintf(o, "Disabled\n"); Z else fprintf(o, "Enabled\n"); Z fprintf(o, " %d %d %d %d\n", it.i_Rect.top, it.i_Rect.left, Z it.i_Rect.bottom, it.i_Rect.right); Z switch(t & ~ItemDisable) { Z case IconItem: /* 2 byte resource ID */ Z case PicItem: Z case CtrlItem+ResCtrl: Z fread((char *)&s, sizeof s, 1, i); Z HTONS(s); Z fprintf(o, " %d\n", s); Z break; Z case CtrlItem+BtnCtrl: Z case CtrlItem+ChkCtrl: Z case CtrlItem+RadCtrl: Z case StatText: Z case EditText: Z fprintf(o, "%.*s\n", it.i_len, data); Z break; Z default: Z fprintf(stderr, "Unknown DITL data\n"); Z fprintf(o, "* Unknown DITL type. Hexdump:\n"); Z dumpdata(i, o, (lendata_t)it.i_len); Z break; Z } Z fprintf(o, "\n"); Z } Z} Z Z/* Z * This structure is defined in toolintf.h, but to avoid byte swap Z * and alignment problems, we read it "by hand". Z * Z * typedef struct { Z * Rect boundsRect; Z * short procID; Z * char visible; Z * char filler1; Z * char goAwayFlag; Z * char filler2; Z * long refCon; Z * short itemsID; Z * Str255 title; Z * } DialogTemplate; Z */ Zvoid Z/*ARGSUSED*/ Zhanddlog(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z struct { Z Rect boundsRect; Z short procID; Z char visible; Z char filler1; Z char goAwayFlag; Z char filler2; Z } dt; Z long refCon; Z short itemsID; Z unsigned char len; Z char data[256]; Z Z fread((char *)&dt, /*sizeof dt*/14, 1, i); Z HTONS(dt.boundsRect.top); Z HTONS(dt.boundsRect.left); Z HTONS(dt.boundsRect.bottom); Z HTONS(dt.boundsRect.right); Z HTONS(dt.procID); Z fread((char *)&refCon, sizeof refCon, 1, i); HTONL(refCon); Z fread((char *)&itemsID, sizeof itemsID, 1, i); HTONS(itemsID); Z fprintf(o, " %d %d %d %d\n", dt.boundsRect.top, dt.boundsRect.left, Z dt.boundsRect.bottom, dt.boundsRect.right); Z fprintf(o, " %s %d %s %d\n", dt.visible?"Visible":"Invisible", Z dt.procID, dt.goAwayFlag?"GoAway":"NoGoAway", refCon); Z fprintf(o, " %d\n", itemsID); Z fread((char *)&len, sizeof len, 1, i); Z if (len > 0) { Z fread(data, 1, len, i); Z fprintf(o, "%.*s\n", len, data); Z } Z} Z Z/* Z * typedef struct { Z * Rect boundsRect; Z * short procID; Z * char visible; Z * char filler1; Z * char goAwayFlag; Z * char filler2; Z * long refCon; Z * Str255 title; Z * } WindowTemplate; Z */ Zvoid Z/*ARGSUSED*/ Zhandwind(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z struct { Z Rect boundsRect; Z short procID; Z char visible; Z char filler1; Z char goAwayFlag; Z char filler2; Z } dt; Z long refCon; Z short itemsID; Z Z fread((char *)&dt, /*sizeof dt*/14, 1, i); Z HTONS(dt.boundsRect.top); Z HTONS(dt.boundsRect.left); Z HTONS(dt.boundsRect.bottom); Z HTONS(dt.boundsRect.right); Z HTONS(dt.procID); Z fread((char *)&refCon, sizeof refCon, 1, i); HTONL(refCon); Z Z fprintf(o, " %s\n", getstr255(i)); Z fprintf(o, " %d %d %d %d\n", dt.boundsRect.top, dt.boundsRect.left, Z dt.boundsRect.bottom, dt.boundsRect.right); Z fprintf(o, " %s %s\n", dt.visible?"Visible":"Invisible", Z dt.goAwayFlag?"GoAway":"NoGoAway"); Z fprintf(o, " %d\n", dt.procID); Z fprintf(o, " %d\n", refCon); Z} Z Z/* Z * typedef struct { Z * short menuID; Z * long fill1,fill2; placeholder Z * long enableFlags; Z * Str255 title; Z * for each menu item: Z * Str255 text; Z * char icon#; Z * char keyboardequiv; Z * char mark; Z * char textstyle; Z * finally: Z * char zero; end of items. Z * } Z */ Zvoid Z/*ARGSUSED*/ Zhandmenu(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z short menuid; Z long flags; Z char c; Z char *t; Z int item, k; Z struct { unsigned char icon, keyb, mark, style} info; Z Z fread((char *)&menuid, sizeof menuid, 1, i); HTONS(menuid); Z fseek(i, (long)(2 * sizeof(long)), 1); Z fread((char *)&flags, sizeof flags, 1, i); HTONL(flags); Z fprintf(o, " %s\n", getstr255(i)); Z for(item = 1; c = getc(i); item++) { Z ungetc(c, i); Z t = getstr255(i); Z fread((char *)&info, sizeof info, 1, i); Z fprintf(o, " "); Z if ((flags & (1<<item)) == 0) Z fprintf(o, "("); Z fprintf(o, "%s", t); Z if (info.icon != 0) Z fprintf(o, "^%c", info.icon); Z if (info.keyb != 0) Z fprintf(o, "/%c", info.keyb); Z if (info.mark != 0) Z fprintf(o, "!%c", info.mark); Z for(k = 0; k < 5; k++) { Z if ((info.style & (1<<k)) != 0) Z fprintf(o, "<%c", "BIOUS"[k]); Z } Z fprintf(o, "\n"); Z } Z} Z Z/* Z * typedef struct { Z * Rect boundsRect; Z * short value; Z * char visible; Z * char filler1; Z * short max; Z * short min; Z * short procID; Z * long refCon; Z * Str255 title; Z * } ControlTemplate; Z */ Z Zvoid Z/*ARGSUSED*/ Zhandcntl(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z struct { Z Rect boundsRect; Z short value; Z char visible; Z char filler1; Z short max; Z short min; Z short procID; Z } ct; Z long refCon; Z Z fread((char *)&ct, /*sizeof ct*/18, 1, i); Z HTONS(ct.boundsRect.top); Z HTONS(ct.boundsRect.left); Z HTONS(ct.boundsRect.bottom); Z HTONS(ct.boundsRect.right); Z HTONS(ct.value); Z HTONS(ct.max); Z HTONS(ct.min); Z HTONS(ct.procID); Z fread((char *)&refCon, sizeof refCon, 1, i); HTONL(refCon); Z Z fprintf(o, " %s\n", getstr255(i)); Z fprintf(o, " %d %d %d %d\n", ct.boundsRect.top, ct.boundsRect.left, Z ct.boundsRect.bottom, ct.boundsRect.right); Z fprintf(o, " %s\n", ct.visible?"Visible":"Invisible"); Z fprintf(o, " %d\n", ct.procID); Z fprintf(o, " %d\n", refCon); Z fprintf(o, " %d %d %d\n", ct.value, ct.max, ct.min); Z} Z Zvoid Zhandcode(i, o, ndata) ZFILE *i, *o; Zlendata_t ndata; Z{ Z FILE *of; Z char name[1024]; Z char buff[1024]; Z Z if (codeflag) { Z sprintf(name, "%s.code.%d", filename, ncode); Z if ((of = fopen(name, "w")) == NULL) { Z perror(name); Z } else { Z while(ndata > sizeof buff) { Z fread(buff, 1, sizeof buff, i); Z fwrite(buff, 1, sizeof buff, of); Z ndata -= sizeof buff; Z } Z fread(buff, 1, (unsigned)ndata, i); Z fwrite(buff, 1, (unsigned)ndata, of); Z fclose(of); Z } Z } else { Z if (ndata < CODEMAX) { Z dumpdata(i, o, ndata); Z } else { Z fprintf(o, "* Very long data (%d bytes)\n", ndata); Z } Z } Z} Z Zvoid Z/*ARGSUSED*/ Zhandfref(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z char type[4]; Z short idno; Z Z fread(type, sizeof type, 1, i); Z fread((char *)&idno, sizeof idno, 1, i); HTONS(idno); Z fprintf(o, " %.4s %d %s\n", type, idno, getstr255(i)); Z} Z Zvoid Z/*ARGSUSED*/ Zhandbndl(i, o, n) ZFILE *i, *o; Zlendata_t n; Z{ Z char type[4]; Z short n, own, nmap, loc, glob; Z int k, j; Z Z fread(type, sizeof type, 1, i); Z fread((char *)&own, sizeof own, 1, i); HTONS(own); Z fread((char *)&n, sizeof n, 1, i); HTONS(n); Z fprintf(o, " %.4s %d\n", type, own); Z for(k = 0; k <= n; k++) { Z fread((char *)type, sizeof type, 1, i); Z fread((char *)&nmap, sizeof nmap, 1, i); HTONS(nmap); Z fprintf(o, " %.4s\n", type); Z for(j = 0; j <= nmap; j++) { Z fread((char *)&glob, sizeof glob, 1, i); HTONS(glob); Z fread((char *)&loc, sizeof loc, 1, i); HTONS(loc); Z fprintf(o, " %d %d\n", glob, loc); Z } Z } Z} Z Zint Zfind(type) Zchar *type; Z{ Z int i; Z Z for(i = 0; typehand[i].th_fun != 0; i++) Z if (strncmp(type, typehand[i].th_type, 4) == 0) Z break; Z return i; Z} Z Zint Zlookup(type) Zchar *type; Z{ Z int i; Z Z i = find(type); Z if (typehand[i].th_fun == 0) Z return IHEXA; /* HEXA entry */ Z return i; Z} Z Zaddmap(t1, t2) Zchar *t1, *t2; Z{ Z int i1, i2; Z Z i1 = find(t1); Z if (i1 == MAXMAP-1) { Z fprintf(stderr, "Map table full\n"); Z exit(1); Z } Z if (typehand[i1].th_fun == 0) Z typehand[i1+1].th_fun = 0; /* Clear next entry */ Z i2 = find(t2); Z strncpy(typehand[i1].th_type, t1, 4); Z strncpy(typehand[i1].th_suff, t2, 4); Z typehand[i1].th_fun = typehand[i2].th_fun; Z} Z Zoldmap() Z{ Z typehand[find(FIRSTNEW)].th_fun = 0; Z} STUNKYFLUFF set `sum rekamr.c` if test 22141 != $1 then echo rekamr.c: Checksum error. Is: $1, should be: 22141. fi # # echo Extracting rekamr.man: sed 's/^Z//' >rekamr.man <<\STUNKYFLUFF Z.TH Rekamr 1 LOCAL Z.UC 4 Z.SH NAME Zrekamr \- resource disassembler Z.SH SYNOPSIS Z.B Z[ Z.B \-cdr Z] [ Z.BI \-t TYP1=TYP2 Z] file.rsrc Z.SH DESCRIPTION Z.I Rekamr Zis an inverse of rmaker, Zi.e. it takes a resource file and produces an input file for the resource Zcompiler to recreate the resource. ZIt is useful when you have edited a resource with the resource editor, Zbut then want to have the edited resources as input to the resource compiler. Z.PP ZA number of types are recognized and disassemled in a readable way, Zunknown types are hexdumped (HEXA). Z.PP ZThe following flags may be given Z.TP 12 Z.B \-c ZHandle the CODE type in a special way. ZNormally CODE is hexdumped if it is small (<1024 bytes), otherwise omitted, Zbut if this flag is given the code will be placed in a file (one for each Zcode segment). Z.TP Z.B \-d ZCauses a lot of debug output to be produced. Z.TP Z.B \-r ZUse type HEXA for FREF and BNDL as rmaker doesn't understand them. Z.TP Z.BI \-t "TYP1=TYP2" ZMake TYP1 behave as TYP2. ZIf you for example want the type MYWN to be Zdisassembled as WIND give the flag -tMYWN=WIND Z.SH "SEE ALSO" Zrmaker, macput, macget Z.SH FILES Zfile.rsrc, file.rc, file.code.n Z.SH BUGS ZThe CODE type could be handled better. ZThere a a lot of types missing. Z.SH DIAGNOSTICS ZSelfexplanatory(?). STUNKYFLUFF set `sum rekamr.man` if test 17697 != $1 then echo rekamr.man: Checksum error. Is: $1, should be: 17697. fi echo All done exit 0 -- Any resemblance of the opinions above to real opinions, living or dead, is purely coincidence. Lennart Augustsson {seismo,philabs,decvax}!mcvax!enea!chalmers!augustss